feat(mcp): server launch plumbing and tool naming

Spec: the complete environment goes to the SDK with InheritEnvironmentVariables=false instead of nulls; the cache key also covers cwd and prefix.
This commit is contained in:
EugeneTes
2026-09-26 20:17:37 +02:00
parent 5b7d593427
commit b30c71d30a
8 changed files with 319 additions and 5 deletions

View File

@@ -111,10 +111,11 @@ two meet only in tool naming (see "Names").
the value is explicit when it reaches the policy.
- The environment a server process gets is `IProcessSpawnPolicy`'s result for a request with `Purpose = McpServer`,
`Origin` = the server name, the expanded command, args and cwd, and the expanded `env` entries as the explicit
environment. It's passed to `StdioClientTransport.EnvironmentVariables` in full, with a null for every inherited
name the policy removed, so the SDK's merge over the host environment can't reintroduce it.
- The catalogue cache key is the server name plus a digest of the **unexpanded** command, args and env, so every chat
shares one catalogue even though their expanded args differ. Secrets from `${env:…}` never enter the key.
environment. It's passed to `StdioClientTransportOptions.EnvironmentVariables` in full with
`InheritEnvironmentVariables = false` (SDK 2.x), so the SDK doesn't merge the host environment back in.
- The catalogue cache key is the server name plus a digest of the **unexpanded** command, args, env, cwd and tool
prefix (the catalogue holds exposed names, which depend on the prefix), so every chat shares one catalogue even
though their expanded args differ. Secrets from `${env:…}` never enter the key.
- `McpOptions.ApplyEnvironment(getEnv)` works like `TelegramOptions.ApplyEnvironment` and fills in the preset from
`PLAYWRIGHT_ENABLED`, `PLAYWRIGHT_HEADLESS` (default true), `PLAYWRIGHT_BROWSER`, `PLAYWRIGHT_EXTRA_ARGS`
(shell-style split honouring quotes, so `--device "iPhone 15"` is two argv entries) and `PLAYWRIGHT_COMMAND`.

View File

@@ -227,7 +227,7 @@ Process-wide spawners without a session (the scheduler, MCP catalogue discovery)
| `BashTool` | `BashTool`, `Command` = the model's command, `Cwd` = `ctx.Cwd` | none | Through `ctx.PrepareProcessAsync`. Then it runs the shell as today with the returned command and cwd, and a cleared-then-copied environment. |
| `ExtensionApi.ExecAsync` | `ExtensionExec`, `Origin` = extension name | `ExecOptions.Environment` | Session-bound. `ExecOptions.Environment` keeps its meaning (set, or null to remove), now on top of the filtered base. |
| Scheduler `ScriptRunner` | `ScheduledScript`, `Scope` = target, `Origin` = job id, `TimeZoneId` = the target's zone | none | Through the DI `IProcessSpawnPolicy`: jobs fire for chats with no live session. Replaces `SchedulerOptions.ScrubbedEnvironment`. |
| MCP `McpServerConnection` | `McpServer`, `Origin` = server name, `Arguments` = expanded args | the server's `env` entries | Through `ctx.PrepareProcessAsync` from the first tool call. The result's env goes to `StdioClientTransport.EnvironmentVariables`, with a null for every inherited name it doesn't contain. |
| MCP `McpServerConnection` | `McpServer`, `Origin` = server name, `Arguments` = expanded args | the server's `env` entries | Through `ctx.PrepareProcessAsync` from the first tool call. The result's env goes to `StdioClientTransportOptions.EnvironmentVariables`, with `InheritEnvironmentVariables = false`. |
| MCP `McpCatalogCache` discovery | as above, `Cwd` = the temp dir | the server's `env` entries | Through `api.Processes.Prepare` (activation, no session). |
A spawner whose `Prepare` throws reports it the way it reports a failed start: `bash` returns an error result