chore: update stale docs

This commit is contained in:
can1357
2026-08-03 16:37:05 +02:00
parent fc04aa6fa7
commit ebd5e3f86f
120 changed files with 5246 additions and 4691 deletions
+44 -33
View File
@@ -13,8 +13,8 @@ This document describes how MCP servers are discovered, connected, exposed as to
- or cached `DeferredMCPTool`s for still-pending servers.
5. **SDK wiring** merges MCP tools into runtime tool registry for the session.
6. **Post-connect enrichment** best-effort loads resources, resource templates, prompts, and optional resource subscriptions.
7. **Live session** can refresh MCP tools via `/mcp` flows (`disconnectAll` + rediscover + `session.refreshMCPTools`) and can reconnect individual servers on transport close or `/mcp reconnect`.
8. **Teardown** happens when callers invoke `disconnectServer`/`disconnectAll`; manager also clears MCP tool/resource/prompt registrations for disconnected servers.
7. **Live session** receives late tool changes through the manager callback; `/mcp reload` does `disconnectAll` + rediscovery + `session.refreshMCPTools`, while transport close and `/mcp reconnect` use the per-server reconnect path.
8. **Teardown** happens on explicit manager disconnects and automatically when an owning `AgentSession` is disposed; borrowed parent managers are not disconnected by subagents.
## Discovery and load phase
@@ -41,7 +41,7 @@ If `enableMCP` is false, MCP discovery is skipped entirely.
Filtering behavior:
- `enableProjectConfig: false` removes project-level entries (`_source.level === "project"`).
- `enabled: false` servers are skipped before connect attempts.
- `enabled: false` entries are suppressed unless the active-profile user `enabledServers` allowlist names them; the user `disabledServers` denylist always suppresses a same-named entry.
- Exa servers are filtered out by default and API keys are extracted for native Exa tool integration; browser automation MCP servers are filtered when `filterBrowser` is true.
Result includes both `configs` and `sources` (metadata used later for provider labeling).
@@ -61,11 +61,13 @@ So startup does not fail the whole agent session when individual MCP servers fai
- `#connections: Map<string, MCPServerConnection>` — fully connected servers.
- `#pendingConnections: Map<string, Promise<MCPServerConnection>>` — handshake in progress.
- `#pendingToolLoads: Map<string, Promise<{ connection, serverTools }>>` — connected but tools still loading.
- `#tools: CustomTool[]` — current MCP tool view exposed to callers.
- `#pendingToolLoads: Map<string, Promise<{ connection, serverTools }>>` — initialized connections whose `tools/list` is still in flight.
- `#tools: CustomTool[]` — current MCP tool view exposed to callers, kept in stable name order.
- `#sources: Map<string, SourceMeta>` — provider/source metadata even before connect completes.
- `#pendingReconnections: Map<string, Promise<MCPServerConnection | null>>` — reconnects in progress after a dropped transport or explicit reconnect.
- `#serverConfigs: Map<string, MCPServerConfig>` — original unresolved configs preserved so reconnect can re-resolve credentials without leaking resolved tokens.
- `#reconnectHistory: Map<string, number[]>` plus `#epoch` — per-server crash-window accounting and invalidation of reconnect attempts that outlive a global disconnect.
- listener/callback state, including a bounded pending-notification FIFO and tracked resource subscriptions/refreshes.
`getConnectionStatus(name)` derives status from these maps:
@@ -75,27 +77,29 @@ So startup does not fail the whole agent session when individual MCP servers fai
## Connection establishment and startup timing
## Per-server connect pipeline
### Per-server connect pipeline
For each discovered server in `connectServers()`:
1. store/update source metadata,
2. skip if already connected/pending/reconnecting,
3. validate transport fields (`validateServerConfig`),
4. resolve auth/shell substitutions (`#resolveAuthConfig`),
5. call `connectToServer(name, resolvedConfig)` with manager notification/request handlers,
6. wire HTTP OAuth refresh and transport `onClose` reconnect handling,
7. call `listTools(connection)`,
8. cache tool definitions (`MCPToolCache.set`) best-effort,
9. best-effort load resources, resource templates, prompts, and subscriptions after tools load.
4. save the unresolved config for possible reconnect,
5. resolve managed OAuth credentials and env/header shell substitutions (`#resolveAuthConfig`),
6. call `connectToServer(name, resolvedConfig)` with manager notification/request handlers,
7. wire HTTP OAuth refresh and transport `onClose` reconnect handling,
8. call `listTools(connection)`,
9. cache tool definitions (`MCPToolCache.set`) best-effort,
10. best-effort load resources, resource templates, prompts, and subscriptions after tools load.
`connectToServer()` behavior (`src/mcp/client.ts`):
- creates stdio or HTTP/SSE transport,
- performs MCP `initialize`,
- for HTTP/SSE, starts the optional background SSE listener before `notifications/initialized`,
- performs MCP `initialize` using protocol version `2025-03-26` and advertises the `roots` capability,
- answers server-to-client `ping` and `roots/list` requests; unsupported request methods return JSON-RPC `-32601`,
- for HTTP/SSE, starts the background SSE listener before `notifications/initialized`,
- sends `notifications/initialized`,
- uses timeout (`OMP_MCP_TIMEOUT_MS`, `config.timeout`, or 30s default; `0` disables the client-side timeout),
- uses timeout precedence `OMP_MCP_TIMEOUT_MS`, then `config.timeout`, then 30s; `0` disables the client-side timeout,
- closes transport on init failure.
### Fast startup gate + deferred fallback
@@ -119,7 +123,8 @@ This is a hybrid startup model: fast return with deferred handles when cache is
Each pending `toolsPromise` also has a background continuation that eventually:
- replaces that server’s tool slice in manager state via `#replaceServerTools`,
- replaces that server's tool slice in manager state and restores stable name ordering,
- invokes `#onToolsChanged` so a live session can rebind the late tools,
- writes cache,
- logs late failures only after startup (`allowBackgroundLogging`).
@@ -131,11 +136,14 @@ Each pending `toolsPromise` also has a background continuation that eventually:
`createAgentSession()` then pushes these tools into `customTools`, which are wrapped and added to the runtime tool registry with names like `mcp__<server>_<tool>`.
Server and tool name components are lowercased and sanitized to letters/underscores. If two distinct origins mint the same runtime name, OMP logs the collision and keeps a deterministic winner based on the original server/tool identity, so reconnect ordering cannot change ownership.
### Tool calls
- `MCPTool` calls tools through an already connected `MCPServerConnection`.
- `DeferredMCPTool` waits for `waitForConnection(server)` before calling; this allows cached tools to exist before connection is ready.
- Both attempt a reconnect + single retry for retriable connection failures.
- A structured tool-result auth challenge can trigger the configured auth handler, reconnect, and one retry. Interactive mode wires this to the `/mcp` OAuth controller; without a handler the challenge remains an MCP error.
Both return structured tool output and convert remaining transport/tool errors into `MCP error: ...` tool content (abort remains abort).
@@ -146,18 +154,16 @@ Both return structured tool output and convert remaining transport/tool errors i
- one-time discovery/load in `sdk.ts`,
- tools are registered in initial session tool registry.
### Interactive reload path
### Interactive reload and live-change paths
`/mcp reload` path (`src/modes/controllers/mcp-command-controller.ts`) does:
`/mcp reload` (`src/modes/controllers/mcp-command-controller.ts`) does:
1. `mcpManager.disconnectAll()`,
2. `mcpManager.discoverAndConnect()`,
3. `session.refreshMCPTools(mcpManager.getTools())`.
`session.refreshMCPTools()` (`src/session/agent-session.ts`) removes all `mcp__` tools, re-wraps latest MCP tools, and re-activates tool set so MCP changes apply without restarting session.
There is also a follow-up path for late connections: after waiting for a specific server, if status becomes `connected`, it re-runs `session.refreshMCPTools(...)` so newly available tools are rebound in-session.
2. clears stale MCP prompt commands,
3. calls `mcpManager.discoverAndConnect()` with the same project/Exa/browser filters as startup,
4. calls `session.refreshMCPTools(mcpManager.getTools())`.
`session.refreshMCPTools()` (`src/session/agent-session.ts`) removes all `mcp__` tools, re-wraps the latest MCP tools, and re-activates the tool set so changes apply without restarting. The owning SDK session also installs `setOnToolsChanged`, so late initial connections, server `tools/list_changed` notifications, reconnects, and disconnects can trigger the same rebinding. Explicit `/mcp reconnect <name>` performs one final refresh after the manager reconnect completes.
## Server-initiated notifications
@@ -168,9 +174,10 @@ MCP servers may push JSON-RPC notification frames at any point after `initialize
- `notifications/resources/list_changed` → `refreshServerResources`
- `notifications/resources/updated` → `#onResourcesChanged` (only for currently subscribed URIs)
- `notifications/prompts/list_changed` → `refreshServerPrompts`
2. **Listener fanout**: every notification (including the known ones AND server-custom methods) is delivered to registered listeners AFTER the internal refresh runs. Registered via `MCPManager.addNotificationListener(listener)`, which returns an unsubscribe function. Multiple listeners are supported; each is invoked with independent error isolation — a synchronous throw in one listener does not prevent others from firing (thrown errors are logged at `debug`).
2. **Listener fanout**: every notification (known and server-custom) is delivered after any internal refresh. `MCPManager.addNotificationListener(listener)` returns an unsubscribe function; multiple listeners have independent error isolation.
If no listener is attached, the manager buffers up to 100 frames, dropping the oldest on overflow, then drains the FIFO into the first listener that attaches. `sdk.ts` registers a per-session listener that bridges to the extension runner's `mcp_notification` event with `{ server, method, params }`; the extension runner has its own bounded startup buffer. The listener and debounce timers are released through session postmortem cleanup.
`sdk.ts` registers one listener that bridges to the extension runner's `mcp_notification` event, so extensions receive every server-initiated frame with `{ server, method, params }`. The listener is captured with `postmortem` so it is released on session teardown.
## Health, reconnect, and partial failure behavior
Current runtime behavior is connection-event driven:
@@ -194,19 +201,21 @@ Operationally:
`disconnectServer(name)`:
- removes pending entries, source metadata, saved config, resource refresh/subscription state,
- removes pending connect/tool-load/reconnect entries, source metadata, saved config, reconnect history, and resource refresh/subscription state,
- detaches `onClose` so explicit close does not trigger reconnect,
- closes transport if connected,
- removes manager tool entries using the current raw-name prefix filter (`mcp__${name}_`); generated tool names are sanitized by `tool-bridge.ts`.
- closes the transport if connected,
- removes tools by their exact `mcpServerName` owner (not by a sanitized name prefix) and notifies tool consumers,
- notifies prompt consumers when stale prompt commands need removal.
### Global teardown
### Global teardown and ownership
`disconnectAll()`:
- increments a lifecycle epoch so reconnect attempts that finish later cannot resurrect old connections,
- detaches `onClose` for all active transports, then closes them with `Promise.allSettled`,
- clears pending maps, sources, saved configs, connections, subscriptions, resource refreshes, and manager tool list.
- clears pending maps, sources, saved configs, connections, subscriptions, resource refreshes, reconnect history, and manager tools.
In current wiring, explicit teardown is used in MCP command flows (for reload/remove/disable). Startup stores the manager on the session; callers that need deterministic MCP shutdown should invoke manager disconnect methods.
Top-level sessions own managers they create. `AgentSession.dispose()` disconnects that owned manager with a 3-second cleanup timeout and logs cleanup failure; a subagent/session given `options.mcpManager` borrows the parent manager and does not disconnect it. `/mcp reload` deliberately reuses the manager object after `disconnectAll`, so installed callbacks/listeners remain available for the next discovery cycle.
## Failure modes and guarantees
@@ -219,10 +228,12 @@ In current wiring, explicit teardown is used in MCP command flows (for reload/re
| `tools/list` still pending at startup without cache | No tools at startup; background continuation registers them via `#onToolsChanged` when ready | Best-effort late registration |
| Late background tool-load failure | Logged after startup gate | Best-effort logging |
| Runtime dropped transport | Manager attempts reconnect; stale tools remain while reconnecting and future calls may retry once or fail with MCP errors | Best-effort automatic recovery |
| More than 5 reconnect invocations within 30s | Circuit breaker closes/removes the stale connection but leaves tools registered; manual reconnect resets the history | Automatic reconnect suspended |
| Owning session disposal | Owned manager disconnect is awaited for up to 3s; failure is logged | Bounded best-effort cleanup |
## Public API surface
`src/mcp/index.ts` re-exports loader/manager/client APIs for external callers. `src/sdk.ts` exposes `discoverMCPServers()` as a convenience wrapper returning the same loader result shape.
`src/mcp/index.ts` re-exports client operations, config loader/writer APIs, loader and manager APIs, OAuth discovery, tool bridges/cache, HTTP and stdio transports, protocol types, plus `callMCP`/`parseSSE`. `src/sdk.ts` exposes `discoverMCPServers()` as a convenience wrapper over `discoverAndLoadMCPTools`; it returns `{ manager, tools, errors, connectedServers, exaApiKeys }`.
## Implementation files