feat(mcp): remote MCP servers per chat with sealed keys and owner-only commands

Streamable HTTP/SSE servers from the workspace's .volcanic/mcp/servers.json.
Headers are sealed with AES-GCM; the chat and the visible fields are the
associated data, so only /mcp_add|remove|tools change servers. Tools
<server>__<tool> go live in the session. An unreachable server is skipped
and reported in the system prompt. Keys are redacted from tool results, and
Telegram deletes messages that carry one.
This commit is contained in:
EugeneTes
2026-10-09 21:11:01 +00:00
parent 87240238cc
commit 891a644590
29 changed files with 1813 additions and 13 deletions

View File

@@ -68,6 +68,7 @@ settings use the `GoogleCalendar` section, the scheduler the `Scheduler` section
| `PLAYWRIGHT_ENABLED` / `Mcp:Playwright:Enabled` | `false` | Browser tools through `@playwright/mcp` ([mcp.md](mcp.md)) |
| `PLAYWRIGHT_HEADLESS`, `PLAYWRIGHT_BROWSER`, `PLAYWRIGHT_EXTRA_ARGS`, `PLAYWRIGHT_COMMAND` | `true`, `chromium`, —, `npx` | See [mcp.md](mcp.md#the-playwright-preset) |
| `Mcp:Servers:<name>` | — | Other stdio MCP servers ([mcp.md](mcp.md#configuration)) |
| `MCP_ENCRYPTION_SECRET` | the bot token | Seals the keys of each chat's remote MCP servers ([mcp.md](mcp.md#remote-servers-per-chat)). Set it so a new bot token doesn't make the stored servers unreadable. Hidden from `bash` and redacted |
| `A2A__Enabled` / `A2A:Enabled` | `false` | Serve other agents over A2A (see [A2A](#a2a) and [a2a.md](a2a.md)). Opens Kestrel: port 8080 in the container, `ASPNETCORE_URLS` or Kestrel's default (`http://localhost:5000`) under `dotnet run` |
| `A2A__PublicUrl` / `A2A:PublicUrl` | — | External base URL for the agent card and push webhooks, e.g. `https://bot.example.com`. Required for push to work; see [docker.md](docker.md) |
| `A2AClient__Enabled` / `A2AClient:Enabled` | `false` | The A2A client: the `a2a` tool in every allowed chat, for calling other agents listed in the chat's `.volcanic/a2a/agents.json`, and watches for their long tasks (see [A2A](#a2a) and [a2a.md](a2a.md#client)). Independent of `A2A__Enabled` |
@@ -127,6 +128,10 @@ started a chat with the bot, Telegram refuses the message; the bot logs that and
| `/calendar_status` | The connected account and time zone, or the pending code |
| `/calendar_disconnect` | Revoke and delete this chat's calendar token, or cancel a pending connect |
| `/a2a` | This chat's A2A callers and recent tasks, remote agents and watches (see [A2A](#a2a)) |
| `/mcp_list` | This chat's remote MCP servers: status, header names (never values), allowed and active tools |
| `/mcp_add <name> <url>` | Add a remote MCP server; the bot asks for the key and deletes your answer (see [Remote MCP servers](#remote-mcp-servers)) |
| `/mcp_remove <name>` | Remove one; its tools disappear at once |
| `/mcp_tools <name> [a,b\|all]` | Show a server's tools, or limit which ones the agent gets |
Any other `/command` is passed to the session, so extension commands such as `/claude-code-doctor` and
`/claude-code-login` work. `/mcp` lists the MCP servers (see [mcp.md](mcp.md#mcp)).
@@ -299,6 +304,22 @@ chat's account, so nobody sees another chat's calendar through the agent. The bo
sending the link and code and confirms in the chat once access is approved. Setup of the Google Cloud app, token
storage and time zones are in [google-calendar.md](google-calendar.md).
### Remote MCP servers
Each chat can connect its owner's remote MCP servers (Streamable HTTP or SSE; no processes are started):
```
/mcp_add notes https://mcp.example.com/mcp
→ Key for notes: send the token (sent as "Authorization: Bearer <token>"), or one "Header-Name: value" per line, or - for none.
tok_abc123 ← your reply; the bot deletes it and the agent never sees it
→ Added notes (https://mcp.example.com/mcp): 12 tools on the server.
```
Options: `--tools a,b` (allowlist), `--transport auto|http|sse`, `--bearer <token>` or `--header "Name: value"` instead
of the question (the bot deletes that message too), `--no-key`, `--force` (save even when the server can't be reached
now). The tools appear in the same conversation as `notes__<tool>`. Only these commands change the servers: the agent
can't add one, even by editing `.volcanic/mcp/servers.json`. Details in [mcp.md](mcp.md#remote-servers-per-chat).
### A2A
With `A2A__Enabled=true` the bot also serves [A2A](a2a.md): other agents can call it over HTTP once given a key.