chore: update stale docs
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user