Extending
Skills, MCP servers, hooks, CLIs, and plugins.
You extend summonr outside the process, with files and child processes. This keeps summonr's memory small (27 MB in our benchmark runs) and its first-round prefix short. There is no in-process package loader and no marketplace.
After the first model round, the advertised tool list and the skill catalog stay frozen until the process exits. A module switch in /settings still writes the config for the next process.
Add a skill
Write a SKILL.md under ~/.summonr/skills/<name>/ or .summonr/skills/<name>/. Claude Code skills work as they are: summonr also reads ~/.claude/skills/, .claude/skills/ in the workspace, and ~/.agents/skills/. The model loads it with the skill tool. The body arrives as a later tool result.
Add an MCP server
1. Run summonr mcp add NAME -- COMMAND [ARGS...] for a local server, or summonr mcp add NAME --url URL for a remote one. This writes a [mcp.NAME] table to ~/.summonr/config.toml; you can also write it yourself. mcp add connects once, caches the tool list, and prints NAME: N tools. When the server does not start or answer, mcp add exits 1 and names the config file; the table stays, and summonr mcp list shows failed: <error>. 2. Restart summonr so the new tool aliases join the next prefix.
summonr mcp list shows each server's state, tool count, and how many bytes of tool schemas it adds to every request. /mcp shows the same in the terminal UI, with the live state of the current session. summonr mcp remove NAME deletes the table.
The mcp module is on by default. summonr lists each server's tools once and keeps the list in ~/.summonr/mcp/. A server starts on its first tool call and stops when it is idle, so a configured server costs no memory until the model uses it. Optional keys on [mcp.NAME]:
tools = ["name", ...]advertises only these tools.idle_secs = Nstops the server after N idle seconds. The default is 120.keep_alive = truekeeps the server running once it starts.timeout_secs = Ncancels a call with no reply after N seconds. The default is 300. Starting a server waits at most 60 seconds, ortimeout_secswhen that is lower.
idle_secs and timeout_secs must be at least 1. These keys also work on a table that only names a server from .mcp.json or an imported config, so you can narrow a server you did not write; a .mcp.json server still needs enabled = true to run. policy.toml deny lists match tool aliases exactly or by a trailing *, for example github__*, and a denied tool is not advertised. Under --strict, a tool that is not marked read-only needs an allow entry. Images from a tool reach the model only when it takes images and the data is PNG, JPEG, GIF, or WebP.
summonr speaks MCP 2025-11-25 and accepts servers on 2025-06-18, 2025-03-26, and 2024-11-05. A local server can ask for the workspace root. Progress from a tool shows in the terminal UI and in --json, and it keeps a slow call alive for up to four times timeout_secs. A server that changes its tool list gets it fetched again; the new list applies from the next session. Server log messages go to ~/.summonr/logs/mcp.log. Stopping a turn cancels the MCP call it was waiting on.
A project .mcp.json server runs only after you set enabled = true under its [mcp.NAME] table. summonr names the servers that wait. Claude Code and Codex MCP configs load only when import_mcp = true is set in ~/.summonr/config.toml. The model sees each tool as a server__tool alias. A server with more than 8 KB of tool schemas is listed by tool name under one mcp tool instead: the model looks up schemas with query and calls a tool with tool and its arguments: an arguments object, an arguments JSON string, or fields beside tool. Policy and hooks see the tool behind mcp.
Type /server:prompt followed by its arguments to run a server prompt; the arguments fill the prompt's declared arguments in order, and the last one takes the rest of the line. @server:uri attaches a server resource to your prompt the way @path attaches a file. The composer suggests both from the cached lists. Both work with -p.
A server can ask you to fill a form during a tool call. summonr shows which server asks, takes one field at a time, checks each answer against the server's schema, and lets you review before y sends, n declines, or esc cancels. With no one at the keyboard (-p, ACP) the form is declined. Form requests work over stdio; remote servers do not get the form capability.
Add a hook
Set pre_tool, post_tool, compact, or stop under [hooks] in ~/.summonr/config.toml. Each value is a shell command, a table { command = "...", dialect = "claude", matcher = "Edit|Write" }, or a list of either; the list runs in order. summonr writes JSON to each command's stdin. A pre_tool hook that exits nonzero blocks the call, and the rest of the list does not run. With dialect = "claude", a hook gets Claude Code's stdin JSON and verdict rules. matcher takes tool names separated by |, or a regex, as in a Claude Code hooks.json, and is checked against the tool name in the hook's dialect. The reference lists the stdin fields, verdicts, and environment variables.
Add a CLI
Put the binary on PATH or under ~/.summonr/bin/<name>/. The model runs it with run_command. Policy and the danger prompt still apply. summonr doesn't put a workspace .summonr/bin on PATH until you approve it, the same way as .mcp.json.
Install a plugin
A plugin is a directory in the Claude Code plugin layout. summonr install PATH copies it to ~/.summonr/plugins/<name>/; the name comes from .claude-plugin/plugin.json, or from the directory name. Copying does not run anything inside summonr. After the copy, install connects to each MCP server the plugin adds. Restart summonr to load the plugin.
| Part | What summonr does with it |
|---|---|
skills/<name>/SKILL.md, manifest skills | Added to the skill catalog; the summary is the frontmatter description |
.mcp.json, manifest mcpServers, mcp.toml | MCP servers named <plugin>-<server>; installing counts as approval |
hooks/hooks.json, manifest hooks, hooks.toml | Hooks added after your own |
bin/ | On PATH for run_command |
commands/<name>.md | Slash commands: /<plugin>:<name> args, or /<name> args when no other plugin or built-in command has the name. The body is the prompt; $ARGUMENTS is everything after the name and $1..$9 are its words. They show in completion. |
agents/<name>.md | Agents the model can hand a task to by name. The frontmatter description is listed to the model; the body opens the child's first message; tools (Claude or summonr names) limits what the child may call. model is ignored, so the child keeps the parent's prompt cache. |
.lsp.json | Language servers for the listed extensions, started on first use like built-in ones. Your own [lsp] settings come first. |
${CLAUDE_PLUGIN_ROOT} and ${SUMMONR_PLUGIN_ROOT} in a server's command, args, env, and URL become the plugin directory, ${CLAUDE_PLUGIN_DATA} becomes ~/.summonr/plugin-data/<name>/ (deleted on uninstall), and ${CLAUDE_PROJECT_DIR} becomes the workspace; hook commands get the same values as environment variables. summonr does not prompt for a plugin's userConfig. install names it as skipped and names each ${user_config.KEY}. Set the key under [mcp.<plugin>-<server>.user_config], or set env on [mcp.<plugin>-<server>] without repeating command or url. A missing key is empty. enabled = false on that table turns that one server off. Hooks from hooks.json run the way Claude Code runs them: their stdin carries tool_name (Bash for run_command, Grep for search, or Glob for a name search, mcp__<server>__<tool> for MCP tools), tool_input, and hook_event_name; matcher selects tools; exit 2, or permissionDecision: "deny" on stdout, blocks a PreToolUse call; any other nonzero exit is a note, not a block. summonr maps PreToolUse, PostToolUse, PreCompact, and Stop, and runs command hooks only. monitors/, output-styles/, workflows/, and themes/ are not loaded; install names the ones it skipped.
Nothing a plugin ships runs inside summonr. Skills, commands, and agents are files. Hooks and bin/ tools run once per call. An MCP server starts once to list its tools, then on first use; a language server starts on first use. Both stop on their own when idle. Beyond servers_max live servers, the least recently used idle server stops. /mcp shows each running server's memory on macOS and Linux.
summonr install --list lists plugins and what each adds. summonr uninstall NAME deletes the directory and ~/.summonr/plugin-data/<name>/, and it removes that name from [plugins] off. [plugins] off = ["NAME"] in ~/.summonr/config.toml keeps one installed but unloaded. A name in off that is not installed is an error when a session starts and when you run summonr install --list.
install refuses a directory that adds nothing summonr loads, and it refuses plugin.json, .mcp.json, hooks.json, .lsp.json, mcp.toml, or hooks.toml when the file is present, not empty, and does not parse. The same parse check applies to a JSON file the manifest points at. It connects to each new MCP server. If a server does not answer, install exits 1 and keeps the copy. Installing the same name again replaces the directory and keeps ~/.summonr/plugin-data/<name>/. A hook whose type is not command is skipped and does not run.
Related
- The reference has the table of what the model sees for each kind of extra.
- The module API states the rule for a new compiled module.