Skip to content
This repository was archived by the owner on Jun 12, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions docs/zeus-mcp-client.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# MCP client (built-in)

Zeus consumes MCP servers listed in `.zeus/mcp.json` and exposes their tools to the editor's AI features. This is the **client** half of the MCP-first design; the **server** half lives in `feat/mcp-server`.

## Where this lives

`src/vs/workbench/contrib/mcpClient/` — a workbench contribution split across the `common/`, `browser/`, and `node/` layers per vscode's architecture:

- `common/` — config schema, types, the workspace-side `McpToolAggregator` that hands a unified registry to the agent runtime
- `browser/` — UI surfaces (status bar entries, error notifications, the trust-prompt for newly-added stdio entries)
- `node/` — process spawning. `stdio` MCP servers must be launched from the main / node side; `browser/` cannot spawn child processes in vscode's sandbox. SSE connections can live in either layer

Behaviour:

- Reads `.zeus/mcp.json` from the workspace root
- Spawns stdio MCP servers (`node/`) as subprocesses, or opens SSE connections
- Aggregates the tool list and exposes it to the agent runtime
- Reloads on `.zeus/mcp.json` change

This is intentionally a built-in contribution rather than a VS Code extension. MCP server lifecycles are too important to let users disable accidentally; we want them tied to the workspace lifecycle.

## Trust model — RCE risk

`.zeus/mcp.json` lists *commands to execute*. Anyone with commit rights can add an arbitrary command, and a colleague who pulls and opens the workspace would silently spawn it. That's a real RCE vector.

Mitigations:

- **Trust prompt** — the first time a workspace is opened with a non-empty `mcp.json`, or whenever a new server entry is added in a subsequent pull, Zeus blocks startup of those servers and shows a per-server confirmation pane (similar to vscode's "Restricted Mode" workspace trust). Accepting writes a fingerprint into per-user (not in-git) state so the prompt doesn't re-fire on every edit.
Comment thread
kanywst marked this conversation as resolved.
Outdated
- **Inherit Workspace Trust** — if the workspace is in Restricted Mode, refuse to spawn any stdio server. SSE-only entries can be allowed because they don't execute local code.
Comment thread
kanywst marked this conversation as resolved.
Outdated
- **Hard refuse on `command: bash -c "<arbitrary>"` patterns** — block shell-form commands in `command:`; require `args:` arrays for argument vectors.
Comment thread
kanywst marked this conversation as resolved.
Outdated

## Secret storage

`mcp.json` lives in git. We only allow `${env:NAME}` and `${secret:<store>:NAME}` references in `env` blocks for credentials:

- `${env:NAME}` resolves at spawn time from the user's environment
- `${secret:keychain:NAME}` reads from `vscode.SecretStorage` (per-user, OS-keychain backed)
- Plain string values are accepted only for non-secret config; the loader warns when a field name matches a heuristic list (`*_TOKEN`, `*_KEY`, `*_SECRET`, `PASSWORD`) and the value isn't a reference
Comment thread
kanywst marked this conversation as resolved.
Outdated

Never write secret values back into the file. The loader is read-only against `mcp.json`.

## Why not [VS Code's `vscode.lm` tool API](https://code.visualstudio.com/api/extension-guides/tools)?

We want the MCP-first stance to be honest. VS Code's `vscode.lm.registerTool` is a fine API but it's vscode-specific. By going through `@modelcontextprotocol/sdk` directly, the same `.zeus/mcp.json` works in:

- Claude Code CLI (already MCP-native)
- ChatGPT desktop / Codex (MCP support shipping)
- Future agents (MCP is an open spec)

VS Code extensions can still register `lm` tools — those continue to work — but Zeus's first-class story is MCP.

## Loader

```text
.zeus/mcp.json
McpConfigLoader (watches file, validates schema)
McpClientRegistry (one McpClient per server entry)
McpToolAggregator (combined tool list, dispatches calls)
IAgentRuntime (Agent SDK PR consumes this)
```

## Sub-PRs needed before this can land

1. `feat/zeus-conventions` (`.zeus/mcp.json` schema) — PR #23
2. This PR — scaffold + design
3. Follow-up — `@modelcontextprotocol/sdk` dep + real implementation

## Acceptance criteria (real impl)

- [ ] Loads `.zeus/mcp.json` at workbench startup
- [ ] Spawns each `stdio` server as a child process
- [ ] Connects to each `sse` server with bearer auth
Comment thread
kanywst marked this conversation as resolved.
Outdated
Comment thread
kanywst marked this conversation as resolved.
Outdated
- [ ] Aggregates all tool definitions into a single registry
- [ ] Reloads on file change without restarting unaffected servers
- [ ] Surfaces server connection errors in the status bar
- [ ] Refuses servers that try to register tools with reserved name prefixes (`buffer_`, `agent_`, `editor_` — those belong to the MCP **server** half)
Comment thread
kanywst marked this conversation as resolved.
Outdated
Comment thread
kanywst marked this conversation as resolved.
Outdated
- [ ] Collisions across servers are resolved by namespacing exposed tools as `<server-name>__<tool-name>` in the aggregated registry; the underlying call still goes to the originally-named tool on the right server. UI surfaces show the short tool name with the server name as secondary text.
Comment thread
kanywst marked this conversation as resolved.
Outdated

## Status

Scaffold only. Slot reserved at `src/vs/workbench/contrib/mcpClient/`.
16 changes: 16 additions & 0 deletions src/vs/workbench/contrib/mcpClient/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# `mcpClient` contribution

Slot for the built-in MCP client. Design lives at [`docs/zeus-mcp-client.md`](../../../../../docs/zeus-mcp-client.md).

When the real implementation lands, this directory will contain (vscode layering: `common/` is platform-agnostic, `browser/` is renderer, `node/` is the Node-only half that can spawn subprocesses):
Comment thread
kanywst marked this conversation as resolved.
Outdated

- `common/mcpTypes.ts` — shared types
- `common/mcpToolAggregator.ts` — unified, namespaced tool registry
- `browser/mcpClient.contribution.ts` — workbench registration + status bar
- `browser/mcpConfigLoader.ts` — `.zeus/mcp.json` watcher + schema validation
- `browser/mcpTrustPrompt.ts` — user confirmation before spawning new stdio servers
- `node/mcpStdioRegistry.ts` — per-server stdio child processes (cannot live in `browser/`)
- `node/mcpSseRegistry.ts` — SSE connections (can also be browser; placed here for symmetry)
- `test/node/*.test.ts` — unit tests for the spawn paths

Until then, this README is the placeholder so other PRs can reference the path without merge conflicts.