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

@@ -107,6 +107,10 @@ tasks by push, subscribe or poll, persists them in `.volcanic/a2a/pending.json`
- Tests replay recorded CLI output from `tests/Volcanic.AgenticCore.Extensions.Tests/ClaudeCode/Fixtures/*.jsonl`.
- `Mcp`: the tool catalogue is discovered once per host process (`McpCatalogCache.Shared`), each session starts its own
server process on the first call, and tests run `tests/Volcanic.AgenticCore.Extensions.Tests.FakeMcpServer`.
`Mcp/Remote/RemoteMcpExtension` is separate: remote servers (Streamable HTTP/SSE) per chat from the workspace's
`.volcanic/mcp/servers.json`, headers sealed (AES-GCM, chat + visible fields as associated data) with a host secret
so only the owner's `/mcp_add|remove|tools|list` change them; tools `<server>__<tool>` are registered and
(de)activated in the live session; tests use an in-memory ASP.NET MCP server (`Mcp/Remote/RemoteMcpTestHost`).
### Telegram host (`hosts/Volcanic.AgenticCore.Telegram`)
@@ -134,6 +138,8 @@ writes there. Each private chat's `trigger` tool manages its topic triggers (`Tr
`noul` question per trigger, 3 retries) about every message and logs one line per message; matches are batched for 10 s
by `TriggerDispatcher` and delivered as follow-up prompts (`TelegramTriggerDelivery`); snoozed triggers queue messages
(`SnoozeQueue`) and check them as one batch when the snooze ends. `/triggers` lists them.
`RemoteMcpSetup` adds `RemoteMcpExtension` to chat sessions (secret `MCP_ENCRYPTION_SECRET`, else the bot token); the
router deletes a `/mcp_add` carrying a key and any answer to a `UiRequestOptions.Secret` question.
`ADMIN_USER_ID` (`Telegram:AdminUserId`) may `/impersonate <user_id>` (`Chats/Impersonation`, in memory, `0` stops): the
router then handles the admin's updates as the target's, and the target chat's renderer delivers to the admin's chat
(`ChatRenderer.DeliveryChatId`); the target's own updates get `Impersonation.BlockedNotice` instead. Answers go out complete, not streamed (`Rendering/`). Tests drive the host through `Support/FakeBotClient` and `ChatHost`.