forked from admin/VolcanicAgenticCore
feat(mcp): process-wide tool catalogue discovery
McpProcess starts a server through the SDK stdio client under the startup timeout and keeps a stderr tail for its errors. McpCatalogCache runs one shared discovery per server config in a temp dir, with the environment from the process spawn policy. It keeps successes for the process lifetime and failures for 60 s. Deviation from the plan: the discovery process is disposed and its temp dir deleted in the background, after the catalogue is returned. SDK 2.2 waits the whole ShutdownTimeout (5 s) with stdin still open before it kills the process, so an awaited dispose held every first activation for 5 s. A new test (Discovery_returns_without_waiting_for_the_server_shutdown) covers this. Spec section 3 gets the new GetAsync signature, the detached discovery and the background cleanup. Section 5 now describes the SDK's shutdown order correctly.
This commit is contained in:
@@ -194,12 +194,16 @@ callTimeoutSeconds: 120, startupTimeoutSeconds: 120 (the first npx run downloads
|
||||
|
||||
### 3. Catalogue — `McpCatalogCache`
|
||||
|
||||
`Task<McpCatalog> GetAsync(McpServerConfig, ILogger, CancellationToken)`, process-wide (`Shared`):
|
||||
`Task<McpCatalog> GetAsync(name, McpServerConfig, IProcessSpawnPolicy, envLookup, ILogger, CancellationToken)` (internal;
|
||||
the extension passes `api.Processes` and the host environment lookup), process-wide (`Shared`):
|
||||
|
||||
- Concurrent activations for the same key share one in-flight task, so ten chats starting at once make one spawn.
|
||||
The shared discovery runs detached from any caller's token; each caller waits with its own.
|
||||
- Discovery spawns the server in a fresh temp dir, with its environment from `api.Processes.Prepare` (activation has no
|
||||
session, so only host hooks run and `TZ` isn't set), runs `initialize` and a paginated `tools/list` under
|
||||
`startupTimeout`, captures `serverInfo`, then disposes the client and deletes the dir.
|
||||
`startupTimeout`, captures `serverInfo`, then disposes the client and deletes the dir in the background. The
|
||||
catalogue is returned without waiting for that cleanup, because SDK 2.2's dispose waits the whole `ShutdownTimeout`
|
||||
before it kills the process (see §5).
|
||||
- Success is cached for the process lifetime. Failure is cached for 60 s, then retried. The failure message
|
||||
includes the last 20 stderr lines, as in the TS bridge, because a server that dies during the handshake explains
|
||||
why only there.
|
||||
@@ -241,8 +245,9 @@ One per server per extension instance (that is, per session):
|
||||
`"<name> MCP server exited (<code>): <stderr tail>. It restarts on the next call; browser state was lost."`. The
|
||||
next call respawns it. There's no automatic retry of the failed call, because it may have had side effects.
|
||||
- The idle timer (reset per call) disposes the client after `idleTimeout` with no calls in flight.
|
||||
- `DisposeAsync` is idempotent: dispose the client (the SDK closes stdin, waits `ShutdownTimeout`, then kills), then
|
||||
kill the process tree if it's still alive. The extension calls it on every `SessionShutdownEvent` (quit, new,
|
||||
- `DisposeAsync` is idempotent: dispose the client, then kill the process tree if it's still alive. SDK 2.2 waits up to
|
||||
`ShutdownTimeout` for the process to exit with stdin still open, then kills its tree, so disposing a live server
|
||||
always takes about `ShutdownTimeout`. The extension calls it on every `SessionShutdownEvent` (quit, new,
|
||||
resume). A new session in the same chat starts from a fresh process, and the persistent profile keeps the logins.
|
||||
- If the live server's `tools/list` differs from the cached catalogue (a package upgrade in place), a warning is
|
||||
logged. Calls go by name either way.
|
||||
|
||||
Reference in New Issue
Block a user