Code: services/ (protocol), services/
(settings → clients → tools), services/ (the
tool class), services/ (HTTP), services/
(built-in servers). Standard library only.
Configuration (settings section mcp)#
{"servers": [
{"id": "files", "name": "Files", "transport": "stdio",
"command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "C:/Users/me/notes"],
"env": {"SOME_KEY": "${env:SOME_KEY}"}, "cwd": "",
"enabled": true, "auto_approve": ["read_file", "list_directory"], "timeout": 60},
{"id": "remote", "name": "Remote", "transport": "http",
"url": "https://example.com/mcp", "headers": {"Authorization": "Bearer ${env:REMOTE_TOKEN}"},
"enabled": false}
]}| Field | Meaning |
|---|---|
id |
1–32 chars a-z 0-9 - _; part of every tool name |
transport |
stdio (spawn a process) or http (Streamable HTTP) |
command, args, env, cwd |
stdio only; cwd defaults to the imurun folder |
url, headers |
http only; https:/, or http:/ on 127.0.0.1/localhost/::1 only |
enabled |
connect at start-up and offer the tools |
auto_approve |
MCP tool names (as the server names them) that run without a card |
timeout |
seconds per tool call (1–600, default 60); connect has 20 s |
Placeholders in command/args/cwd/env/headers/url: ${python} (this
interpreter), ${repo} (the imurun folder), ${home} (~/),
${env:NAME} (an environment variable, so a key need not sit in the
settings file). The settings file is 0600 but plain text; GET views
replace every env and header value with ********, and posting
******** back keeps the stored value.
The child process gets the parent environment minus credential-looking
names (*TOKEN*, *SECRET*, *KEY…, same rule as the shell tool) plus its
own env.
Protocol#
- JSON-RPC 2.0. stdio: newline-delimited JSON on stdin/stdout; a stray
non-JSON stdout line is ignored; stderr is kept (last 20 lines) for error
messages only. Streamable HTTP: each message is POSTed with
Accept: application/; a JSON or SSE answer is read until the matching response;json, text/ event-stream Mcp-Session-Idis kept frominitializeand sent afterwards withMCP-Protocol-Version;DELETEon close. initializeasks for2025-06-18;2025-03-26and2024-11-05are accepted when the server answers with them; anything else is refused. Thennotifications/.initialized tools/followslist nextCursor(20 pages, 500 tools max).tools/: text kept; image/audio →call [image: type, N bytes, not shown]; embedded text resources inlined; binary resources and links summarised;structuredContentused when there is no text; capped at 12 000 chars.isError: true→ a failed tool result with the server's text.- The server's own
pingrequests are answered; other server requests get-32601(no sampling / roots / elicitation).notifications/re-lists the tools.tools/ list_changed - Timeouts everywhere; on a timeout
notifications/is sent. A dead process fails the pending calls with its exit code and stderr tail. Nothing raises out of the tool loop: every failure is a failed tool result.cancelled - Close: stdin closed, 2 s grace, then the process tree is killed
(
taskkill /on Windows). Servers are also closed at interpreter exit.T
Lifecycle#
- Enabled servers connect in a background thread when the server starts.
- A dropped connection is re-established lazily on the next tool call.
- Saving a server (
POST /) disconnects and re-discovers it.api/ mcp/ servers - A disabled or removed server's tools are retired: they report unavailable
and are not offered (the registry has no
unregisteryet, REQUESTS.md).
Tool names and permissions#
mcp_<server id>_<tool>, lower-cased, non [a-z0-9_] → _ (e.g.
mcp_files_read_file); a clash gets _2. Schema: the server's inputSchema
passed through (the executor coerces types). Description: [MCP <name>] ….
Category mcp (connectors/):
| MCP tool | ask |
allow-reads |
allow-all |
|---|---|---|---|
annotations.destructiveHint: true |
ask | ask | ask — auto_approve does not override it, "remember" is ignored |
listed in auto_approve |
allow | allow | allow |
annotations.readOnlyHint: true |
ask | allow | allow |
| anything else | ask | ask | ask ("remember" holds for the session) |
Hints are the server's own claims about third-party code, hence the default
of asking even in allow-all. HTTP servers follow the network toggle.
Built-in servers (off by default)#
| Entry | Script | Tools |
|---|---|---|
gmail |
services/ |
the 7 Gmail tools |
connectors |
services/ |
the 33 tools of the other seven connectors |
They read the same token store and settings as IMURUN, so accounts are
connected once, in Settings. Annotations follow IMURUN's own levels: reads
are readOnlyHint; every "always ask" write (send, trash, post, deploy, DNS,
insert…) is destructiveHint, so the bridge always asks. The scripts make no
permission decisions themselves: the MCP host that runs them must ask the
user. Any MCP host (not only IMURUN) can run them the same way.
Enable one:
Invoke-RestMethod "$b/api/mcp/servers" -Method Post -Headers $h -ContentType application/json -Body '{"id": "gmail", "enabled": true}'
Invoke-RestMethod "$b/api/mcp/servers/gmail/connect" -Method Post -Headers $h
Invoke-RestMethod "$b/api/mcp/servers/gmail/tools" -Headers $hUsing both the direct gmail_* tools and mcp_gmail_gmail_* duplicates the
same capability for the model; enable the MCP entry only to test the MCP path
or to share the tools with another MCP host.
Third-party servers (Node / npx)#
This machine has no Node.js. Once Node ≥ 18 is installed (winget install OpenJS.NodeJS.LTS), npx-based servers work as stdio entries — on Windows
npx resolves to npx.cmd through PATH:
{"id": "fs", "name": "Filesystem", "transport": "stdio", "command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "C:/Users/me/Documents"], "enabled": true}Python servers: "command": "${python}", "args": ["-m", "some_mcp_server"]
(installed into the same venv) or uvx. Remote servers: "transport": "http" with their URL; servers that need their own OAuth (MCP authorization)
are not supported yet — pass a static header via ${env:...} if the
server allows one.
Routes and events#
GET /, GET /,
GET /, POST / (add/update),
POST / (waits; 409 when disabled, 502 with the
reason on failure), POST /,
DELETE /. Events: mcp.connected {id, name, tools, protocolVersion, server}, mcp.disconnected {id, name, reason},
mcp.error {id, name, error}. Contract: services/.
Limits#
- Tools only: MCP resources, prompts, sampling, roots and elicitation are not used.
- No MCP OAuth authorization for remote servers.
- No SSE-only legacy transport (
2024-11-05HTTP+SSE); stdio and Streamable HTTP only. - Tool calls to one stdio server are concurrent on the wire; a single-threaded server answers them in order.
Tests#
tests/ (fake stdio server tests/:
initialize, paginated list, call, content kinds, isError, remote error,
timeout, crash, silence, protocol versions; in-process Streamable HTTP server
with JSON and SSE answers and sessions), tests/ (manager,
names, permission levels, re-discovery, lazy reconnect, redaction, the two
built-in servers over real stdio), tests/ (routes and
events).