mcp-loader v2: streamable-HTTP transport for remote MCP servers (context7)
- New RemoteMcpClient implementing MCP streamable-HTTP per spec 2025-03-26:
POST JSON-RPC, parse application/json or text/event-stream responses,
round-trip optional Mcp-Session-Id, optional auth via 'headers' config.
- Refactor StdioMcpClient to share an IMcpClient interface with the remote
client; extension entry dispatches on cfg.type. Drops the v1 'remote
skipped with warning' code path.
- Bump MCP_PROTOCOL_VERSION to 2025-11-25 (single constant, both clients).
- 404 self-heal: when a remote returns 404 to a request carrying our
Mcp-Session-Id, drop the id, re-initialize, retry the request once
(per spec 2025-11-25 \u00a72.2). allowReinitOn404=false on the retry path
prevents recursion. Verified via mock-server smoke test.
- Sanitize pi-facing tool names to ^[A-Za-z][A-Za-z0-9_]{0,63}$. Anthropic
allows hyphens but Bedrock's Anthropic shim rejects them, causing entire
turns to 4xx silently when context7's hyphenated tools (resolve-library-id,
query-docs) were registered. Original MCP-side names are preserved in the
tool-execute closure, so sanitization is purely pi-facing.
- /mcp slash command: drop 'remote (skipped)' status label.
- Docs: README and AGENTS updated for transports, headers config, 404
self-heal, tool-name sanitization rationale, OAuth limitation.
End-to-end verified: context7 connects through pi, returns useful docs
(Bun streaming/SSE example fetched successfully).
This commit is contained in:
@@ -205,7 +205,7 @@ This is a verbatim copy of the upstream `examples/extensions/todo.ts` shipped wi
|
||||
|
||||
### `mcp-loader.ts`
|
||||
|
||||
Generic MCP server loader. Reads an `mcp` block from `~/.pi/agent/settings.json` (same shape as opencode and Claude Desktop) and connects to each declared server, exposing all of their tools to pi as native tools — namespaced as `<server-name>_<tool-name>` to avoid collisions.
|
||||
Generic MCP server loader. Reads an `mcp` block from `~/.pi/agent/settings.json` (same shape as opencode and Claude Desktop) and connects to each declared server, exposing all of their tools to pi as native tools — namespaced as `<server-name>_<tool-name>` to avoid collisions, with non-`[A-Za-z0-9_]` characters replaced by `_` so the names pass the strictest provider tool-name regex (e.g. AWS Bedrock).
|
||||
|
||||
**Settings.json shape:**
|
||||
|
||||
@@ -239,13 +239,20 @@ Generic MCP server loader. Reads an `mcp` block from `~/.pi/agent/settings.json`
|
||||
| `type` | `"local"` (stdio subprocess) or `"remote"` (streamable-http). Default `"local"`. |
|
||||
| `command` | Argv array. First element is the executable, rest are args. Local servers only. |
|
||||
| `url` | Remote MCP endpoint URL. Remote servers only. |
|
||||
| `headers` | Optional object of HTTP headers (e.g. `Authorization`, `X-API-Key`) sent with every request. Remote servers only. |
|
||||
| `enabled` | Default `true`. Set `false` to disable a server without removing the entry. |
|
||||
| `env` | Optional object of env vars injected into the subprocess. Inherits parent env first, then overlays these keys. Local servers only. |
|
||||
|
||||
**Limitations (v1):**
|
||||
**Transports:**
|
||||
|
||||
- **Stdio only.** Remote/streamable-HTTP transport is detected and skipped with a warning. Server like `context7` configured as `"type": "remote"` will not load until v2.
|
||||
- **No reconnect** if a subprocess dies mid-session — those tools become unavailable until `/reload` (same as `mempalace.ts`).
|
||||
- `local` — stdio JSON-RPC subprocess (mcp-searxng, gitea-mcp, mcp-server-time…).
|
||||
- `remote` — streamable-HTTP per MCP spec 2025-03-26: POST JSON-RPC, server replies either `application/json` or `text/event-stream`. Optional `Mcp-Session-Id` round-trip if the server issues one. No GET subscription stream (server-initiated notifications are not consumed).
|
||||
|
||||
**Limitations:**
|
||||
|
||||
- **No stdio reconnect** if a subprocess dies mid-session — those tools become unavailable until `/reload` (same as `mempalace.ts`).
|
||||
- **Remote sessions self-heal on 404.** If a streamable-HTTP server forgets our session id (e.g. server restart), the client transparently re-initializes and retries the request once.
|
||||
- **No OAuth flow.** Remote servers requiring OAuth must be accessed with a pre-issued bearer token via `headers`.
|
||||
- **Coexists with `mempalace.ts`** but does not replace it. The mempalace bridge has bespoke handling (agent identity injection) that's worth keeping. Don't list `mempalace` in the `mcp` block too — you'd get duplicate tool registrations.
|
||||
|
||||
**Debug:** set `PI_MCP_LOADER_DEBUG=1` in the environment to surface per-server stderr and connection logs.
|
||||
@@ -255,7 +262,6 @@ Generic MCP server loader. Reads an `mcp` block from `~/.pi/agent/settings.json`
|
||||
- `running · N tools` — connected, tools registered
|
||||
- `failed: <message>` — start handshake threw
|
||||
- `disabled in settings` — `enabled: false`
|
||||
- `remote (skipped — v1 stdio only)` — type `remote`, awaiting v2
|
||||
- `invalid: <message>` — malformed config (read-only row)
|
||||
|
||||
UX matches `/ext`: **space** stages a toggle, **enter** writes back to `settings.json` and reloads pi, **esc** cancels. Toggling re-enables a previously-disabled server by removing the explicit `enabled` key (the default is `true`).
|
||||
|
||||
Reference in New Issue
Block a user