Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
63 changes: 47 additions & 16 deletions docs/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
This repo registers MCP servers with the agent clients running on each
machine: Claude Code via `claude mcp add` calls in install scripts,
VS Code (Copilot) via `mcp.servers` in the shared VS Code settings
template. Currently provisioned on Windows and Android only — Linux
and macOS would need install scripts added.
template, and GitHub Copilot CLI via a chezmoi-managed
`~/.copilot/mcp-config.json`. Currently provisioned on Windows and
Android only — Linux and macOS would need install scripts added.

## File Map

Expand All @@ -16,6 +17,8 @@ and macOS would need install scripts added.
| [`home/.chezmoiscripts/windows/run_onchange_after_mcp-readonly-install.ps1.tmpl`](../home/.chezmoiscripts/windows/run_onchange_after_mcp-readonly-install.ps1.tmpl) | Windows: install + register `readonly-mcp` (stdio) |
| [`home/.chezmoitemplates/vscode_settings.json`](../home/.chezmoitemplates/vscode_settings.json) | VS Code `mcp.servers` config (shared across OSes) |
| [`home/dot_claude/settings.json.tmpl`](../home/dot_claude/settings.json.tmpl) | Claude permissions `allow` list — explicitly enumerates every MCP tool |
| [`home/dot_config/powershell/profile.d/10-functions.ps1.tmpl`](../home/dot_config/powershell/profile.d/10-functions.ps1.tmpl) | `copilot` wrapper re-passing each server as `--allow-tool` (Windows) |
| [`home/dot_copilot/mcp-config.json`](../home/dot_copilot/mcp-config.json) | Copilot CLI `mcpServers` config (shared across OSes) |
| [`package.json`](../package.json) | `@readonly-mcp/core` pin (GitHub-spec) |

## Servers
Expand All @@ -27,27 +30,38 @@ exposes allowlisted read-only access to common CLI tools so agents can
inspect state without `Bash` permission prompts. See the upstream repo
for the current tool allowlist.

Installed via the pnpm-globals template, then registered with both
Claude and VS Code:
Installed via the pnpm-globals template, then registered per client:

- **Claude**: each OS's `mcp-readonly-install` script registers the
binary as a user-scope stdio MCP.
- **VS Code**: a `mcp.servers.readonly` entry in the shared
[`vscode_settings.json`](../home/.chezmoitemplates/vscode_settings.json) template, applied to every OS that deploys
VS Code settings.
- **Copilot CLI**: an `mcpServers.readonly` entry in
[`home/dot_copilot/mcp-config.json`](../home/dot_copilot/mcp-config.json), deployed to
`~/.copilot/mcp-config.json` on every OS.

The package is pinned in [`package.json`](../package.json) as a GitHub-spec dep. See
[`pnpm-globals.md`](pnpm-globals.md) for how GitHub-spec pins flow
through the install template.

### `microsoft-learn` (HTTP)

Microsoft's hosted HTTP MCP. Registered with Claude only by each OS's
`claude-configure` script; not added to VS Code.
Microsoft's hosted HTTP MCP. Registered per client:

- **Claude**: each OS's `claude-configure` script registers the
endpoint as a user-scope HTTP MCP.
- **VS Code**: an `mcp.servers.microsoft-learn` entry in the shared
[`vscode_settings.json`](../home/.chezmoitemplates/vscode_settings.json) template.
- **Copilot CLI**: an `mcpServers.microsoft-learn` entry in
[`home/dot_copilot/mcp-config.json`](../home/dot_copilot/mcp-config.json).

Being hosted, it needs no local install — the declarative clients pick
it up from a chezmoi apply with no binary to provision first.

## Cross-Tool Divergences

The two clients have meaningfully different registration mechanisms:
The clients have meaningfully different registration mechanisms:

- **Claude** registers imperatively — the install scripts shell out
to `claude mcp add`. Permissions are enumerated explicitly in
Expand All @@ -57,17 +71,34 @@ The two clients have meaningfully different registration mechanisms:
- **VS Code** reads `mcp.servers` declaratively from user settings.
The shared template includes the registration, so any platform that
deploys VS Code settings picks it up.
- **Copilot CLI** reads `mcpServers` declaratively from
`~/.copilot/mcp-config.json`. `copilot mcp add` exists but only
writes that same file, so chezmoi owns it directly and no install
script is needed. The file is checked in verbatim rather than
templated — nothing in it varies by host or OS.

This means the Claude install scripts need to run any time the set of
MCP servers changes, while VS Code follows immediately from a chezmoi
apply. The Claude scripts are gated by `run_onchange_*` hashing, so
they re-execute when the rendered registration command changes.
MCP servers changes, while VS Code and Copilot CLI follow immediately
from a chezmoi apply. The Claude scripts are gated by `run_onchange_*`
hashing, so they re-execute when the rendered registration command
changes.

Tool permissions diverge too. Claude auto-allows each MCP tool by name
in `permissions.allow`; Copilot CLI has no persistent equivalent, so a
`copilot` function in
[`10-functions.ps1.tmpl`](../home/dot_config/powershell/profile.d/10-functions.ps1.tmpl)
shadows the binary and re-passes the servers as launch flags, deriving
their names from `mcp-config.json` so registering one stays a one-file
change. The reasoning and the flag's variadic footgun are commented
there. It's PowerShell-only because Copilot CLI comes from winget, so
Windows is the only platform that has it.

## Gaps

- **Linux/macOS** have the VS Code `mcp.servers` entry deployed but no
`readonly-mcp` binary installed and no Claude registration script.
If Claude or Copilot is used there, add an install script modeled on
the Windows or Android version.
- **`microsoft-learn`** is currently Claude-only; whether VS Code
should also register it hasn't been decided.
- **Linux/macOS** have the VS Code and Copilot CLI registrations
deployed but no `readonly-mcp` binary installed and no Claude
registration script. If Claude or Copilot is used there, add an
install script modeled on the Windows or Android version.
- **Codex** is configured by this repo (see
[`agent-config.md`](agent-config.md)) but gets no MCP registration
at all — it reads `~/.codex/config.toml`, which isn't managed here.
4 changes: 4 additions & 0 deletions home/.chezmoitemplates/vscode_settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
"markdown-preview-enhanced.previewTheme": "github-dark.css",
"mcp": {
"servers": {
"microsoft-learn": {
"type": "http",
"url": "https://learn.microsoft.com/api/mcp"
},
"readonly": {
"command": "readonly-mcp"
}
Expand Down
17 changes: 17 additions & 0 deletions home/dot_config/powershell/profile.d/10-functions.ps1.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ Function ccc { clear; claude @args }
Function cmcd { Set-Location $ChezmoiSourcePath }

{{- if eq .chezmoi.os "windows" }}
{{- $allowMcp := list -}}
{{- range $name, $_ := (include "dot_copilot/mcp-config.json" | fromJson).mcpServers -}}
{{- $allowMcp = append $allowMcp (printf "--allow-tool=%s" $name) -}}
{{- end }}

# Pre-approve the MCP servers registered in dot_copilot/mcp-config.json — the
# same read-only set Claude auto-allows via permissions.allow. Copilot CLI has
# no persistent equivalent: its config exposes only allowedUrls, deniedUrls,
# and trustedFolders, and an approval granted in-session dies with it, so the
# grant has to be re-passed at every launch. Shell and file-write tools are
# untouched and still prompt.
#
# The `=` form is load-bearing: --allow-tool is variadic, so the space form
# swallows whatever follows it — `copilot --allow-tool readonly mcp list`
# reads `mcp` and `list` as tool names and never runs the subcommand.
Function copilot { copilot.exe {{ join " " $allowMcp }} @args }

Function ls { eza --icons=auto @args }

Function su { sudo pwsh -NoLogo }
Expand Down
12 changes: 12 additions & 0 deletions home/dot_copilot/mcp-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"mcpServers": {
"microsoft-learn": {
"type": "http",
"url": "https://learn.microsoft.com/api/mcp"
},
"readonly": {
"command": "readonly-mcp",
"type": "local"
}
}
}