@softspark/ai-toolkit 4.27.0 → 4.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,103 @@ Versioning follows [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ---
9
9
 
10
+ ## v4.29.0 — Stale plugin uploads stop hiding (2026-08-21)
11
+
12
+ ### Added
13
+
14
+ - **`doctor` reports Claude app plugin version drift.** Check 11 now compares the
15
+ `version` recorded for every `ai-toolkit@…` entry in
16
+ `~/.claude/plugins/installed_plugins.json` against the installed toolkit's
17
+ `package.json` and warns when they differ. The upload is a point-in-time ZIP:
18
+ once the toolkit moves on, Chat and Cowork keep running the skills, agents,
19
+ hooks, and rules from whenever the export was made, with no signal anywhere
20
+ that they are behind. `--fix` deliberately cannot clear this warning — the
21
+ export can be regenerated but the upload itself happens by hand in the app's
22
+ Customize > Plugins panel, so the check names the two steps instead of
23
+ pretending to do them.
24
+
25
+ ### Changed
26
+
27
+ - **Version drift is checked before the enabled/disabled branch.** A plugin
28
+ disabled for Claude Code still feeds Chat and Cowork, which have no other
29
+ channel for toolkit content. The old flow returned `OK: registered but
30
+ disabled` and never looked at versions, so the state a user reaches by running
31
+ `doctor --fix` was also the state where a stale upload passed silently. That
32
+ was the wrong place to stop looking.
33
+ - **`ai-toolkit install` re-asserts the plugin as disabled for Claude Code.**
34
+ Uploading the ZIP re-enables the plugin every time, which put the double-load
35
+ fix back on whoever remembered to run `doctor --fix` afterwards. Global
36
+ installs and updates now flip enabled toolkit plugins to `false` in
37
+ `~/.claude/settings.json` and print each key they touched. `--local` and
38
+ `--dry-run` are untouched, and non-toolkit plugins are never modified. The
39
+ disable logic moved into `disable_toolkit_plugins_for_claude_code()` in
40
+ `scripts/doctor.py`, shared by both call sites.
41
+ - Test count: 1659 → 1666.
42
+ - Skill body budget threshold unchanged. The largest body (`medplum-rules`, 16760
43
+ bytes) sits 1240 bytes under the 18000-byte warn line, short of the 2000-byte
44
+ margin the ratchet requires, so the threshold stays where it is this release.
45
+
46
+ ---
47
+
48
+ ## v4.28.0 — MCP servers reach Chat and Cowork (2026-08-21)
49
+
50
+ ### Added
51
+
52
+ - **`claude-app` MCP adapter.** `ai-toolkit mcp install --editor claude-app
53
+ --scope global <template>` writes `claude_desktop_config.json` — macOS
54
+ `~/Library/Application Support/Claude/`, Windows `%APPDATA%/Claude/`, Linux
55
+ `~/.config/Claude/`, with `CLAUDE_USER_DATA_DIR` overriding the root the same
56
+ way the app itself honors it. Until now the Claude app was reachable only
57
+ through an uploaded plugin ZIP, so a user with seven MCP servers configured for
58
+ Claude Code still had an empty `mcpServers` block in Chat and Cowork. The app
59
+ parses this file on startup and reports invalid entries in its own warning
60
+ dialog, so it was always a supported surface — ai-toolkit just never wrote to
61
+ it. Note that `--editor claude` targets Claude Code (`.mcp.json`,
62
+ `~/.claude.json`); the two are different runtimes and different files.
63
+ - **HTTP and SSE templates are bridged through `mcp-remote`.** The app validates
64
+ each entry as `{command, args, env}`; remote endpoints live in a separate
65
+ `remoteMcpServers` surface managed from its Connectors UI and are not
66
+ file-configurable. Writing a portable `{"type":"http","url":…}` entry verbatim
67
+ would produce a config the app silently skips, so the adapter wraps remote
68
+ servers as `npx -y mcp-remote <url>`, forwarding `headers` as `--header`
69
+ arguments. `mcp-remote` negotiates the transport itself, so one bridge shape
70
+ covers both the `http` and `sse` spellings. This also applies to
71
+ `ai-toolkit inject-mcp`, which propagates to every editor exposing a global
72
+ path and therefore now reaches the Claude app too.
73
+
74
+ ### Changed
75
+
76
+ - `_resolve_global_config_root` gained a `_validate_configured_config_root`
77
+ helper, shared with the new adapter. `COPILOT_HOME` and `CODEX_HOME` keep
78
+ their existing semantics; the Claude app root is validated only when set
79
+ explicitly, because its platform default need not exist yet.
80
+
81
+ ### Fixed
82
+
83
+ - **Two docs described the `claude` adapter's config path wrongly.**
84
+ `kb/reference/mcp-editor-compatibility.md` and `kb/reference/mcp-templates.md`
85
+ both claimed it writes `.claude/settings.local.json` and
86
+ `~/.claude/settings.json`. It writes `.mcp.json` and `~/.claude.json`, and has
87
+ for as long as the adapter has existed. Both scopes were wrong in both files.
88
+
89
+ ### Ecosystem
90
+
91
+ - `claude-app` gains three `config_paths`, the `scripts/mcp_editors.py`
92
+ generator, and an `MCP` capability marker. Re-baseline with
93
+ `python3 scripts/ecosystem_doctor.py --update --tool claude-app`.
94
+
95
+ ### Tests
96
+
97
+ - 1645 → 1659. Thirteen cover the adapter (path resolution, parent-tree
98
+ creation, preservation of `preferences`/`coworkUserFilesPath`/user-owned
99
+ servers, byte-identical re-install, the bridge shape, rejection of entries
100
+ with neither or both transports, four `CLAUDE_USER_DATA_DIR` validation cases,
101
+ removal, and transactional rollback). One covers `inject-mcp` propagation,
102
+ where per-editor failures are downgraded to warnings and a regression would
103
+ otherwise be silent.
104
+
105
+ ---
106
+
10
107
  ## v4.27.0 — Doctor sees the Claude app plugin (2026-08-21)
11
108
 
12
109
  ### Added
package/README.md CHANGED
@@ -6,25 +6,24 @@
6
6
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
7
7
  [![Skills](https://img.shields.io/badge/skills-109-brightgreen)](app/skills/)
8
8
  [![Agents](https://img.shields.io/badge/agents-44-blue)](app/agents/)
9
- [![Tests](https://img.shields.io/badge/tests-1645%20passing-success)](tests/)
9
+ [![Tests](https://img.shields.io/badge/tests-1666%20passing-success)](tests/)
10
10
 
11
- ## What's New in v4.27.0
11
+ ## What's New in v4.29.0
12
12
 
13
- **v4.27.0** teaches `doctor` about a collision it used to report as healthy:
13
+ **v4.29.0** stops a stale Claude app upload from passing as healthy:
14
14
 
15
- - Uploading the Claude app plugin ZIP registers it under `~/.claude/plugins`,
16
- which Claude Code reads too. The plugin then carries the same skills, agents,
17
- and hooks as the global install.
18
- - Claude Code merges plugin hooks with user hooks without deduplication, so
19
- every toolkit hook fired twice per event and skills and agents loaded twice.
20
- `doctor` printed `HEALTH CHECK PASSED` through all of it.
21
- - New check 11 reports the collision with the hook count; `doctor --fix`
22
- disables the plugin for Claude Code and leaves the global install
23
- authoritative.
24
- - `kb/troubleshooting/plugin-double-load.md` documents the debug-log evidence
25
- and the verification steps.
26
-
27
- See [CHANGELOG.md](CHANGELOG.md) for full history.
15
+ - The plugin ZIP is a point-in-time snapshot. Once the toolkit moves on, Chat and
16
+ Cowork keep running the skills, agents, hooks, and rules from whenever you
17
+ exported it, with nothing anywhere saying so.
18
+ - `ai-toolkit doctor` now compares the version recorded in
19
+ `~/.claude/plugins/installed_plugins.json` against the installed toolkit and
20
+ warns when they differ -- including when the plugin is disabled for Claude
21
+ Code, since Chat and Cowork still load it.
22
+ - `--fix` cannot clear that warning. It can regenerate the export; the upload
23
+ itself is manual, so the check names both steps instead of pretending.
24
+ - `ai-toolkit install` now re-asserts the plugin as disabled for Claude Code.
25
+ Uploading the ZIP re-enables it every time, which had left the double-load fix
26
+ to whoever remembered to re-run `doctor --fix`.
28
27
 
29
28
  ## Table of Contents
30
29
 
@@ -143,7 +142,7 @@ See [CLI Reference](kb/reference/cli-reference.md) for all commands and options.
143
142
  | Platform | Config Files | Hooks | Scope |
144
143
  |----------|-------------|:-----:|-------|
145
144
  | Claude Code | `~/.claude/agents`, `~/.claude/skills`, `~/.claude/rules/*.md`, `~/.claude/settings.json` | ✅ | global |
146
- | Claude Chat / Cowork | uploaded plugin ZIP + UI global/folder instructions | Cowork only | account/app |
145
+ | Claude Chat / Cowork | uploaded plugin ZIP + UI global/folder instructions + `claude_desktop_config.json` (MCP) | Cowork only | account/app |
147
146
  | Cursor | `.cursor/rules/*.mdc` + `.cursor/mcp.json` + `.cursor/skills/*` | ✅ | project (`~/.cursor/mcp.json` for MCP only) |
148
147
  | Windsurf (Devin Desktop) | `~/.config/devin/AGENTS.md` + `.devin/rules/*.md` + `.devin/hooks.v1.json` + `.windsurf/skills/*` | ✅ | global + project |
149
148
  | Gemini CLI | `~/.gemini/GEMINI.md` + `.gemini/settings.json` + `.gemini/{commands,skills,agents}/` | ✅ | project + user |
@@ -156,7 +155,7 @@ See [CLI Reference](kb/reference/cli-reference.md) for all commands and options.
156
155
  | Codex CLI | Project: `AGENTS.md` + `.agents/skills/*` + `.codex/{agents,hooks}/` + `.codex/{hooks.json,config.toml}`; user: `$CODEX_HOME/{AGENTS.md,agents,hooks.json,config.toml}` + `$HOME/.agents/skills/*` | ✅ | project + user |
157
156
  | opencode | `AGENTS.md` + `.opencode/{agents,commands,plugins,skills}/*` + `opencode.{json,jsonc}` | ✅ | project + global (`~/.config/opencode/`) |
158
157
 
159
- > Claude Code is always installed (primary platform). Other editors are selected with `--editors`; the Claude app uses the separate `claude-app export` flow because its customization store is UI/plugin-managed. The **Hooks** column marks platforms with lifecycle enforcement. Platforms marked — receive guidance without blocking hooks.
158
+ > Claude Code is always installed (primary platform). Other editors are selected with `--editors`; the Claude app uses the separate `claude-app export` flow because its customization store is UI/plugin-managed, except for MCP servers, which `ai-toolkit mcp install --editor claude-app --scope global` writes straight to `claude_desktop_config.json`. The **Hooks** column marks platforms with lifecycle enforcement. Platforms marked — receive guidance without blocking hooks.
160
159
 
161
160
  ---
162
161
 
@@ -192,7 +191,7 @@ ai-toolkit/
192
191
  │ └── ARCHITECTURE.md # Full system design
193
192
  ├── kb/ # Reference docs, procedures, plans
194
193
  ├── scripts/ # Validation, install, evaluation scripts
195
- ├── tests/ # Bats and Python test suite (1645 tests)
194
+ ├── tests/ # Bats and Python test suite (1659 tests)
196
195
  └── CHANGELOG.md
197
196
  ```
198
197
 
@@ -3,7 +3,7 @@
3
3
  "name": "ai-toolkit",
4
4
  "displayName": "AI Toolkit",
5
5
  "description": "Professional-grade engineering skills, agents, rules, and lifecycle guardrails for Claude Code, Claude Chat, and Cowork.",
6
- "version": "4.27.0",
6
+ "version": "4.29.0",
7
7
  "author": {
8
8
  "name": "SoftSpark",
9
9
  "url": "https://github.com/softspark"
@@ -1,5 +1,5 @@
1
1
  {
2
- "last_run": "2026-08-21T15:52:27Z",
2
+ "last_run": "2026-08-21T18:54:11Z",
3
3
  "schema_version": 1,
4
4
  "tools": {
5
5
  "aider": {
@@ -24,7 +24,7 @@
24
24
  }
25
25
  },
26
26
  "augment": {
27
- "docs_hash": "516d8db28cb3ec63",
27
+ "docs_hash": "fda8f5ca2c114b30",
28
28
  "headings": [
29
29
  "Admin",
30
30
  "Auggie CLI",
@@ -66,7 +66,7 @@
66
66
  }
67
67
  },
68
68
  "claude-app": {
69
- "docs_hash": "cacc8b579baa5e86",
69
+ "docs_hash": "73b44d89c7b771d9",
70
70
  "headings": [
71
71
  "Add global and folder instructions",
72
72
  "Availability",
@@ -101,13 +101,14 @@
101
101
  "markers": {
102
102
  "Folder instructions": true,
103
103
  "Global instructions": true,
104
+ "MCP": true,
104
105
  "plugins": true,
105
106
  "skills": true,
106
107
  "sub-agents": true
107
108
  }
108
109
  },
109
110
  "claude-code": {
110
- "docs_hash": "2b3d6b11c4be766f",
111
+ "docs_hash": "1666675fbb24aaad",
111
112
  "headings": [
112
113
  "Core concepts",
113
114
  "Documentation Index",
@@ -176,7 +177,7 @@
176
177
  "version": "2.1.238 (Claude Code)"
177
178
  },
178
179
  "cline": {
179
- "docs_hash": "f93d3a8437c1165f",
180
+ "docs_hash": "a0753bb220f610ba",
180
181
  "headings": [
181
182
  "API Reference",
182
183
  "Best Practices",
@@ -223,7 +224,7 @@
223
224
  }
224
225
  },
225
226
  "codex-cli": {
226
- "docs_hash": "df3b133739abfa24",
227
+ "docs_hash": "61d85bae5c0cf885",
227
228
  "headings": [
228
229
  "API",
229
230
  "API Reference",
@@ -365,7 +366,7 @@
365
366
  "version": "codex-cli 0.148.0"
366
367
  },
367
368
  "cursor": {
368
- "docs_hash": "2abfadb030c12d07",
369
+ "docs_hash": "189e666436c73107",
369
370
  "headings": [
370
371
  "Agent",
371
372
  "CLI",
@@ -397,7 +398,7 @@
397
398
  }
398
399
  },
399
400
  "gemini-cli": {
400
- "docs_hash": "c66bb0647b70c7cf",
401
+ "docs_hash": "547969f433375d0c",
401
402
  "headings": [
402
403
  "Breadcrumbs",
403
404
  "Directory actions",
@@ -438,7 +439,7 @@
438
439
  "version": "0.55.1"
439
440
  },
440
441
  "github-copilot": {
441
- "docs_hash": "2e595a058e906644",
442
+ "docs_hash": "a313606be316fada",
442
443
  "headings": [
443
444
  "About Copilot auto model selection",
444
445
  "About Copilot automations",
@@ -564,7 +565,7 @@
564
565
  }
565
566
  },
566
567
  "windsurf": {
567
- "docs_hash": "b08c4f883c276129",
568
+ "docs_hash": "8bf9d2e8b6d36350",
568
569
  "headings": [
569
570
  "Accounts",
570
571
  "Advanced",
@@ -26,8 +26,11 @@ active `CODEX_HOME` (default `~/.codex`) and shared user skills use
26
26
  Codex user surface.
27
27
 
28
28
  Claude Chat/Desktop/Cowork is a separate runtime. It does not read the
29
- filesystem surfaces under `~/.claude`; it receives ai-toolkit through an
30
- uploaded plugin plus app-managed global/folder instructions.
29
+ filesystem surfaces under `~/.claude`; it receives ai-toolkit skills, agents,
30
+ hooks, and rules through an uploaded plugin plus app-managed global/folder
31
+ instructions. Its one file-based surface is `claude_desktop_config.json`, which
32
+ the app parses for local MCP servers -- `ai-toolkit mcp install --editor
33
+ claude-app --scope global` targets it directly, without a plugin.
31
34
 
32
35
  ## Command Responsibilities
33
36
 
@@ -3,9 +3,9 @@ title: "AI Toolkit - MCP Editor Compatibility"
3
3
  category: reference
4
4
  service: ai-toolkit
5
5
  tags: [mcp, editors, compatibility, codex, cursor, antigravity]
6
- version: "1.3.1"
6
+ version: "1.4.0"
7
7
  created: "2026-04-12"
8
- last_updated: "2026-08-19"
8
+ last_updated: "2026-08-21"
9
9
  description: "Official MCP support matrix and native config targets for editors supported by ai-toolkit."
10
10
  ---
11
11
 
@@ -19,7 +19,8 @@ ai-toolkit keeps `.mcp.json` as the project-level canonical template format and
19
19
 
20
20
  | Editor | Scope | Native Config Path | Adapter Behavior |
21
21
  |--------|-------|--------------------|------------------|
22
- | Claude Code | project + global | `.claude/settings.local.json`, `~/.claude/settings.json` | Merges `mcpServers` while preserving other settings keys |
22
+ | Claude Code | project + global | `.mcp.json`, `~/.claude.json` | Merges `mcpServers` while preserving other top-level keys |
23
+ | Claude Chat / Cowork | global | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS), `%APPDATA%/Claude/claude_desktop_config.json` (Windows), `~/.config/Claude/claude_desktop_config.json` (Linux); `CLAUDE_USER_DATA_DIR` overrides the root | Emits the app's stdio-only `{command, args, env}` schema and bridges HTTP/SSE servers through `mcp-remote`; preserves `preferences`, `coworkUserFilesPath`, and user-owned servers |
23
24
  | Cursor | project + global | `.cursor/mcp.json`, `~/.cursor/mcp.json` | Mirrors `mcpServers` directly |
24
25
  | GitHub Copilot | project + global | `.github/mcp.json`, `$COPILOT_HOME/mcp-config.json` (default `~/.copilot/mcp-config.json`) | Adds Copilot-required `type` and `tools` fields |
25
26
  | Gemini CLI | project + global | `.gemini/settings.json`, `~/.gemini/settings.json` | Merges `mcpServers` into settings JSON |
@@ -46,9 +47,32 @@ ai-toolkit mcp install --editor cursor --scope project github --target .
46
47
  ai-toolkit mcp install --editor antigravity --scope project context7 --target .
47
48
  ai-toolkit mcp install --editor codex --scope project context7 --target .
48
49
  ai-toolkit mcp install --editor codex context7
50
+ ai-toolkit mcp install --editor claude-app --scope global context7
49
51
  ai-toolkit mcp remove github --editor cursor --scope project --target .
50
52
  ```
51
53
 
54
+ ## Claude Chat / Cowork
55
+
56
+ The Claude app is the one target whose MCP surface is file-based while everything
57
+ else about it is not. Skills, agents, hooks, and rules reach Chat/Cowork only
58
+ through the uploaded plugin ZIP (`ai-toolkit claude-app export`), because the app
59
+ scans no filesystem location for them. Local MCP servers are different: the app
60
+ parses `claude_desktop_config.json` on startup and reports invalid entries in its
61
+ own warning dialog.
62
+
63
+ Two constraints shape the adapter:
64
+
65
+ - **stdio only.** Each entry is validated as `{command, args, env}`. Remote
66
+ endpoints live in a separate `remoteMcpServers` surface that the app manages
67
+ through its Connectors UI and does not read from this file. Portable HTTP/SSE
68
+ templates are therefore wrapped in `mcp-remote`, which negotiates the transport
69
+ itself, so one bridge shape covers both spellings.
70
+ - **The file is not ours.** It also holds `preferences` and `coworkUserFilesPath`,
71
+ which the app rewrites on its own. The adapter merges only `mcpServers` and
72
+ leaves every other key untouched.
73
+
74
+ The app must be restarted for a config change to take effect.
75
+
52
76
  ## Install Flow Integration
53
77
 
54
78
  When `.mcp.json` exists in a project, `ai-toolkit install --local` mirrors its servers into:
@@ -3,9 +3,9 @@ title: "MCP Server Templates"
3
3
  category: reference
4
4
  service: ai-toolkit
5
5
  tags: [mcp, templates, servers, configuration, editors, inject-mcp, external-templates]
6
- version: "1.4.1"
6
+ version: "1.5.0"
7
7
  created: "2026-04-07"
8
- last_updated: "2026-08-19"
8
+ last_updated: "2026-08-21"
9
9
  description: "Reference for 26 built-in MCP server templates, external template injection via inject-mcp, and native editor MCP installation support."
10
10
  ---
11
11
 
@@ -39,6 +39,7 @@ The `add` command merges the `mcpServers` block from the template into `.mcp.jso
39
39
  The `install` command renders the same canonical template into an editor-native config format:
40
40
  - JSON clients with `mcpServers` blocks: Claude Code, Cursor, Gemini CLI, Google Antigravity, Roo Code, Windsurf, Cline, Augment
41
41
  - JSON clients with additional transport metadata: GitHub Copilot
42
+ - JSON clients restricted to stdio: Claude Chat/Cowork (`claude-app`) -- remote servers are bridged through `mcp-remote`
42
43
  - TOML clients: Codex CLI (`[mcp_servers.<name>]`)
43
44
 
44
45
  When `install` runs with `--scope project`, ai-toolkit also updates the project's `.mcp.json` so it remains the source of truth for later syncs.
@@ -47,7 +48,8 @@ When `install` runs with `--scope project`, ai-toolkit also updates the project'
47
48
 
48
49
  | Editor | Scope | Native Config Path | Notes |
49
50
  |--------|-------|--------------------|-------|
50
- | `claude` | project + global | `.claude/settings.local.json`, `~/.claude/settings.json` | Preserves existing hooks and env keys |
51
+ | `claude` | project + global | `.mcp.json`, `~/.claude.json` | Preserves unrelated top-level keys |
52
+ | `claude-app` | global | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS), `%APPDATA%/Claude/claude_desktop_config.json` (Windows), `~/.config/Claude/claude_desktop_config.json` (Linux) | Claude Chat/Cowork; stdio-only schema, bridges HTTP/SSE through `mcp-remote`, preserves `preferences` and `coworkUserFilesPath`; `CLAUDE_USER_DATA_DIR` overrides the root |
51
53
  | `cursor` | project + global | `.cursor/mcp.json`, `~/.cursor/mcp.json` | Mirrors canonical `mcpServers` |
52
54
  | `copilot` | project + global | `.github/mcp.json`, `$COPILOT_HOME/mcp-config.json` (default `~/.copilot/mcp-config.json`) | Adds `type` and `tools: ["*"]` automatically |
53
55
  | `gemini` | project + global | `.gemini/settings.json`, `~/.gemini/settings.json` | Uses Gemini CLI `mcpServers` format |
@@ -49,11 +49,12 @@ The canonical data lives in **`scripts/ecosystem_tools.json`** and is consumed b
49
49
  | ID | `claude-app` |
50
50
  | Docs | https://support.claude.com/en/articles/13345190-get-started-with-claude-cowork |
51
51
  | Plugin docs | https://support.claude.com/en/articles/13837440-use-plugins-in-claude |
52
- | Config surfaces | `Settings > Cowork > Global instructions`, Cowork folder instructions, `Customize > Skills`, and `Customize > Plugins` |
52
+ | Config surfaces | `Settings > Cowork > Global instructions`, Cowork folder instructions, `Customize > Skills`, `Customize > Plugins`, plus the one file-based surface: `claude_desktop_config.json` (macOS `~/Library/Application Support/Claude/`, Windows `%APPDATA%/Claude/`, Linux `~/.config/Claude/`; `CLAUDE_USER_DATA_DIR` overrides the root) |
53
53
  | Plugin layout | `.claude-plugin/plugin.json`, `skills/*/SKILL.md`, `agents/*.md`, `hooks/hooks.json`; ai-toolkit uses manifest paths under `claude-app/` for its generated app-only rules and hooks |
54
- | Our generator | `scripts/claude_app.py` (`ai-toolkit claude-app export`) |
54
+ | Our generator | `scripts/claude_app.py` (`ai-toolkit claude-app export`); `scripts/mcp_editors.py` for the `claude-app` MCP adapter |
55
55
  | Runtime split | Skills work in Chat (web/Desktop) and Cowork. Hooks and sub-agents run only in Cowork. Claude app does **not** scan Claude Code's `~/.claude/rules/`, `CLAUDE.md`, or `~/.claude/settings.json`. |
56
- | Install/update | Export the ZIP, upload it from `Customize > Plugins`, then paste the generated global-instructions file into `Settings > Cowork > Global instructions`. Re-export/re-upload after toolkit updates. |
56
+ | MCP | `ai-toolkit mcp install --editor claude-app --scope global <template>` writes `claude_desktop_config.json` directly -- no plugin involved. Entries are validated by the app as `{command, args, env}`; remote endpoints belong to the separate UI-managed `remoteMcpServers` surface, so HTTP/SSE templates are bridged through `mcp-remote`. Restart the app to load a change. |
57
+ | Install/update | Export the ZIP, upload it from `Customize > Plugins`, then paste the generated global-instructions file into `Settings > Cowork > Global instructions`. Re-export/re-upload after toolkit updates. MCP config is the exception -- it updates from the CLI like any other editor. |
57
58
 
58
59
  ---
59
60
 
package/llms-full.txt CHANGED
@@ -11730,8 +11730,11 @@ active `CODEX_HOME` (default `~/.codex`) and shared user skills use
11730
11730
  Codex user surface.
11731
11731
 
11732
11732
  Claude Chat/Desktop/Cowork is a separate runtime. It does not read the
11733
- filesystem surfaces under `~/.claude`; it receives ai-toolkit through an
11734
- uploaded plugin plus app-managed global/folder instructions.
11733
+ filesystem surfaces under `~/.claude`; it receives ai-toolkit skills, agents,
11734
+ hooks, and rules through an uploaded plugin plus app-managed global/folder
11735
+ instructions. Its one file-based surface is `claude_desktop_config.json`, which
11736
+ the app parses for local MCP servers -- `ai-toolkit mcp install --editor
11737
+ claude-app --scope global` targets it directly, without a plugin.
11735
11738
 
11736
11739
  ## Command Responsibilities
11737
11740
 
@@ -13532,9 +13535,9 @@ title: "AI Toolkit - MCP Editor Compatibility"
13532
13535
  category: reference
13533
13536
  service: ai-toolkit
13534
13537
  tags: [mcp, editors, compatibility, codex, cursor, antigravity]
13535
- version: "1.3.1"
13538
+ version: "1.4.0"
13536
13539
  created: "2026-04-12"
13537
- last_updated: "2026-08-19"
13540
+ last_updated: "2026-08-21"
13538
13541
  description: "Official MCP support matrix and native config targets for editors supported by ai-toolkit."
13539
13542
  ---
13540
13543
 
@@ -13548,7 +13551,8 @@ ai-toolkit keeps `.mcp.json` as the project-level canonical template format and
13548
13551
 
13549
13552
  | Editor | Scope | Native Config Path | Adapter Behavior |
13550
13553
  |--------|-------|--------------------|------------------|
13551
- | Claude Code | project + global | `.claude/settings.local.json`, `~/.claude/settings.json` | Merges `mcpServers` while preserving other settings keys |
13554
+ | Claude Code | project + global | `.mcp.json`, `~/.claude.json` | Merges `mcpServers` while preserving other top-level keys |
13555
+ | Claude Chat / Cowork | global | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS), `%APPDATA%/Claude/claude_desktop_config.json` (Windows), `~/.config/Claude/claude_desktop_config.json` (Linux); `CLAUDE_USER_DATA_DIR` overrides the root | Emits the app's stdio-only `{command, args, env}` schema and bridges HTTP/SSE servers through `mcp-remote`; preserves `preferences`, `coworkUserFilesPath`, and user-owned servers |
13552
13556
  | Cursor | project + global | `.cursor/mcp.json`, `~/.cursor/mcp.json` | Mirrors `mcpServers` directly |
13553
13557
  | GitHub Copilot | project + global | `.github/mcp.json`, `$COPILOT_HOME/mcp-config.json` (default `~/.copilot/mcp-config.json`) | Adds Copilot-required `type` and `tools` fields |
13554
13558
  | Gemini CLI | project + global | `.gemini/settings.json`, `~/.gemini/settings.json` | Merges `mcpServers` into settings JSON |
@@ -13575,9 +13579,32 @@ ai-toolkit mcp install --editor cursor --scope project github --target .
13575
13579
  ai-toolkit mcp install --editor antigravity --scope project context7 --target .
13576
13580
  ai-toolkit mcp install --editor codex --scope project context7 --target .
13577
13581
  ai-toolkit mcp install --editor codex context7
13582
+ ai-toolkit mcp install --editor claude-app --scope global context7
13578
13583
  ai-toolkit mcp remove github --editor cursor --scope project --target .
13579
13584
  ```
13580
13585
 
13586
+ ## Claude Chat / Cowork
13587
+
13588
+ The Claude app is the one target whose MCP surface is file-based while everything
13589
+ else about it is not. Skills, agents, hooks, and rules reach Chat/Cowork only
13590
+ through the uploaded plugin ZIP (`ai-toolkit claude-app export`), because the app
13591
+ scans no filesystem location for them. Local MCP servers are different: the app
13592
+ parses `claude_desktop_config.json` on startup and reports invalid entries in its
13593
+ own warning dialog.
13594
+
13595
+ Two constraints shape the adapter:
13596
+
13597
+ - **stdio only.** Each entry is validated as `{command, args, env}`. Remote
13598
+ endpoints live in a separate `remoteMcpServers` surface that the app manages
13599
+ through its Connectors UI and does not read from this file. Portable HTTP/SSE
13600
+ templates are therefore wrapped in `mcp-remote`, which negotiates the transport
13601
+ itself, so one bridge shape covers both spellings.
13602
+ - **The file is not ours.** It also holds `preferences` and `coworkUserFilesPath`,
13603
+ which the app rewrites on its own. The adapter merges only `mcpServers` and
13604
+ leaves every other key untouched.
13605
+
13606
+ The app must be restarted for a config change to take effect.
13607
+
13581
13608
  ## Install Flow Integration
13582
13609
 
13583
13610
  When `.mcp.json` exists in a project, `ai-toolkit install --local` mirrors its servers into:
@@ -13621,9 +13648,9 @@ title: "MCP Server Templates"
13621
13648
  category: reference
13622
13649
  service: ai-toolkit
13623
13650
  tags: [mcp, templates, servers, configuration, editors, inject-mcp, external-templates]
13624
- version: "1.4.1"
13651
+ version: "1.5.0"
13625
13652
  created: "2026-04-07"
13626
- last_updated: "2026-08-19"
13653
+ last_updated: "2026-08-21"
13627
13654
  description: "Reference for 26 built-in MCP server templates, external template injection via inject-mcp, and native editor MCP installation support."
13628
13655
  ---
13629
13656
 
@@ -13657,6 +13684,7 @@ The `add` command merges the `mcpServers` block from the template into `.mcp.jso
13657
13684
  The `install` command renders the same canonical template into an editor-native config format:
13658
13685
  - JSON clients with `mcpServers` blocks: Claude Code, Cursor, Gemini CLI, Google Antigravity, Roo Code, Windsurf, Cline, Augment
13659
13686
  - JSON clients with additional transport metadata: GitHub Copilot
13687
+ - JSON clients restricted to stdio: Claude Chat/Cowork (`claude-app`) -- remote servers are bridged through `mcp-remote`
13660
13688
  - TOML clients: Codex CLI (`[mcp_servers.<name>]`)
13661
13689
 
13662
13690
  When `install` runs with `--scope project`, ai-toolkit also updates the project's `.mcp.json` so it remains the source of truth for later syncs.
@@ -13665,7 +13693,8 @@ When `install` runs with `--scope project`, ai-toolkit also updates the project'
13665
13693
 
13666
13694
  | Editor | Scope | Native Config Path | Notes |
13667
13695
  |--------|-------|--------------------|-------|
13668
- | `claude` | project + global | `.claude/settings.local.json`, `~/.claude/settings.json` | Preserves existing hooks and env keys |
13696
+ | `claude` | project + global | `.mcp.json`, `~/.claude.json` | Preserves unrelated top-level keys |
13697
+ | `claude-app` | global | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS), `%APPDATA%/Claude/claude_desktop_config.json` (Windows), `~/.config/Claude/claude_desktop_config.json` (Linux) | Claude Chat/Cowork; stdio-only schema, bridges HTTP/SSE through `mcp-remote`, preserves `preferences` and `coworkUserFilesPath`; `CLAUDE_USER_DATA_DIR` overrides the root |
13669
13698
  | `cursor` | project + global | `.cursor/mcp.json`, `~/.cursor/mcp.json` | Mirrors canonical `mcpServers` |
13670
13699
  | `copilot` | project + global | `.github/mcp.json`, `$COPILOT_HOME/mcp-config.json` (default `~/.copilot/mcp-config.json`) | Adds `type` and `tools: ["*"]` automatically |
13671
13700
  | `gemini` | project + global | `.gemini/settings.json`, `~/.gemini/settings.json` | Uses Gemini CLI `mcpServers` format |
@@ -15491,11 +15520,12 @@ The canonical data lives in **`scripts/ecosystem_tools.json`** and is consumed b
15491
15520
  | ID | `claude-app` |
15492
15521
  | Docs | https://support.claude.com/en/articles/13345190-get-started-with-claude-cowork |
15493
15522
  | Plugin docs | https://support.claude.com/en/articles/13837440-use-plugins-in-claude |
15494
- | Config surfaces | `Settings > Cowork > Global instructions`, Cowork folder instructions, `Customize > Skills`, and `Customize > Plugins` |
15523
+ | Config surfaces | `Settings > Cowork > Global instructions`, Cowork folder instructions, `Customize > Skills`, `Customize > Plugins`, plus the one file-based surface: `claude_desktop_config.json` (macOS `~/Library/Application Support/Claude/`, Windows `%APPDATA%/Claude/`, Linux `~/.config/Claude/`; `CLAUDE_USER_DATA_DIR` overrides the root) |
15495
15524
  | Plugin layout | `.claude-plugin/plugin.json`, `skills/*/SKILL.md`, `agents/*.md`, `hooks/hooks.json`; ai-toolkit uses manifest paths under `claude-app/` for its generated app-only rules and hooks |
15496
- | Our generator | `scripts/claude_app.py` (`ai-toolkit claude-app export`) |
15525
+ | Our generator | `scripts/claude_app.py` (`ai-toolkit claude-app export`); `scripts/mcp_editors.py` for the `claude-app` MCP adapter |
15497
15526
  | Runtime split | Skills work in Chat (web/Desktop) and Cowork. Hooks and sub-agents run only in Cowork. Claude app does **not** scan Claude Code's `~/.claude/rules/`, `CLAUDE.md`, or `~/.claude/settings.json`. |
15498
- | Install/update | Export the ZIP, upload it from `Customize > Plugins`, then paste the generated global-instructions file into `Settings > Cowork > Global instructions`. Re-export/re-upload after toolkit updates. |
15527
+ | MCP | `ai-toolkit mcp install --editor claude-app --scope global <template>` writes `claude_desktop_config.json` directly -- no plugin involved. Entries are validated by the app as `{command, args, env}`; remote endpoints belong to the separate UI-managed `remoteMcpServers` surface, so HTTP/SSE templates are bridged through `mcp-remote`. Restart the app to load a change. |
15528
+ | Install/update | Export the ZIP, upload it from `Customize > Plugins`, then paste the generated global-instructions file into `Settings > Cowork > Global instructions`. Re-export/re-upload after toolkit updates. MCP config is the exception -- it updates from the CLI like any other editor. |
15499
15529
 
15500
15530
  ---
15501
15531
 
package/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.27.0",
2
+ "version": "4.29.0",
3
3
  "components": {
4
4
  "agents": {
5
5
  "description": "44 specialized agents (orchestrator, backend, frontend, security, devops, etc.)",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softspark/ai-toolkit",
3
- "version": "4.27.0",
3
+ "version": "4.29.0",
4
4
  "description": "AI coding toolkit: 109 skills, 44 agents, 12 developer-tool integrations, recoverable native tool-output filtering, Claude Chat/Cowork export, safety constitution, SARIF audit, and signed npm provenance.",
5
5
  "keywords": [
6
6
  "claude",
package/scripts/doctor.py CHANGED
@@ -683,6 +683,68 @@ def _registered_toolkit_plugins(registry: dict) -> list[str]:
683
683
  return [key for key in plugins if str(key).split("@", 1)[0] == "ai-toolkit"]
684
684
 
685
685
 
686
+ def _registered_plugin_versions(registry: dict, keys: list[str]) -> dict[str, str]:
687
+ """Map plugin key to the version recorded in the Claude Code registry."""
688
+ plugins = registry.get("plugins", {})
689
+ versions: dict[str, str] = {}
690
+ for key in keys:
691
+ entries = plugins.get(key)
692
+ if not isinstance(entries, list):
693
+ continue
694
+ for entry in entries:
695
+ if isinstance(entry, dict) and isinstance(entry.get("version"), str):
696
+ versions[key] = entry["version"]
697
+ break
698
+ return versions
699
+
700
+
701
+ def _toolkit_package_version() -> str | None:
702
+ """Version of the installed toolkit package, or None when unreadable."""
703
+ try:
704
+ data = json.loads((toolkit_dir / "package.json").read_text(encoding="utf-8"))
705
+ except (OSError, json.JSONDecodeError):
706
+ return None
707
+ version = data.get("version")
708
+ return version if isinstance(version, str) else None
709
+
710
+
711
+ def disable_toolkit_plugins_for_claude_code() -> list[str]:
712
+ """Disable every enabled ai-toolkit plugin in ``~/.claude/settings.json``.
713
+
714
+ Returns the keys that were flipped. Uploading the Claude app plugin re-enables
715
+ it for Claude Code every time, so this is re-asserted rather than fixed once.
716
+ Raises OSError when settings.json cannot be written.
717
+ """
718
+ if not PLUGIN_REGISTRY.is_file():
719
+ return []
720
+ try:
721
+ registry = json.loads(PLUGIN_REGISTRY.read_text(encoding="utf-8"))
722
+ except (OSError, json.JSONDecodeError):
723
+ return []
724
+
725
+ keys = _registered_toolkit_plugins(registry)
726
+ if not keys:
727
+ return []
728
+
729
+ settings_json = CLAUDE_DIR / "settings.json"
730
+ try:
731
+ settings = json.loads(settings_json.read_text(encoding="utf-8"))
732
+ except (OSError, json.JSONDecodeError):
733
+ return []
734
+ enabled = settings.get("enabledPlugins", {})
735
+ if not isinstance(enabled, dict):
736
+ enabled = {}
737
+
738
+ active = [key for key in keys if enabled.get(key, True)]
739
+ if not active:
740
+ return []
741
+
742
+ enabled.update({key: False for key in active})
743
+ settings["enabledPlugins"] = enabled
744
+ settings_json.write_text(json.dumps(settings, indent=2) + "\n", encoding="utf-8")
745
+ return active
746
+
747
+
686
748
  def check_plugin_double_load(dr: DiagResult, fix_mode: bool) -> None:
687
749
  """Warn when the Claude app plugin and the global install both feed Claude Code.
688
750
 
@@ -709,6 +771,22 @@ def check_plugin_double_load(dr: DiagResult, fix_mode: bool) -> None:
709
771
  dr.ok("no ai-toolkit plugin registered in Claude Code")
710
772
  return
711
773
 
774
+ # Version drift is independent of whether the plugin is enabled here: a
775
+ # plugin disabled for Claude Code still feeds Chat and Cowork, and those
776
+ # runtimes have no other channel for skills, agents, hooks, or rules. Report
777
+ # it before the enabled/disabled branch, which returns early on the healthy
778
+ # path and would otherwise let a stale upload pass unnoticed.
779
+ toolkit_version = _toolkit_package_version()
780
+ if toolkit_version:
781
+ for key, plugin_version in _registered_plugin_versions(registry, keys).items():
782
+ if plugin_version != toolkit_version:
783
+ dr.warn(
784
+ f"{key} is v{plugin_version} but the toolkit is v{toolkit_version}: "
785
+ "Chat and Cowork keep running the older skills, agents, hooks, and "
786
+ "rules until you re-run `ai-toolkit claude-app export` and upload "
787
+ "the ZIP again (--fix cannot do this; the upload is manual)"
788
+ )
789
+
712
790
  settings_json = CLAUDE_DIR / "settings.json"
713
791
  try:
714
792
  settings = json.loads(settings_json.read_text(encoding="utf-8"))
@@ -738,14 +816,12 @@ def check_plugin_double_load(dr: DiagResult, fix_mode: bool) -> None:
738
816
  if not fix_mode:
739
817
  return
740
818
 
741
- enabled.update({key: False for key in active})
742
- settings["enabledPlugins"] = enabled
743
819
  try:
744
- settings_json.write_text(json.dumps(settings, indent=2) + "\n", encoding="utf-8")
820
+ disabled = disable_toolkit_plugins_for_claude_code()
745
821
  except OSError as exc:
746
822
  dr.fail(f"could not disable plugin in settings.json: {exc}")
747
823
  return
748
- for key in active:
824
+ for key in disabled:
749
825
  dr.fixed(f"disabled {key} for Claude Code (global install stays authoritative)")
750
826
 
751
827
 
@@ -110,18 +110,23 @@
110
110
  "UI: Customize > Plugins",
111
111
  ".claude-plugin/plugin.json",
112
112
  "claude-app/hooks/hooks.json",
113
- "claude-app/skills/*/SKILL.md"
113
+ "claude-app/skills/*/SKILL.md",
114
+ "~/Library/Application Support/Claude/claude_desktop_config.json",
115
+ "%APPDATA%/Claude/claude_desktop_config.json",
116
+ "~/.config/Claude/claude_desktop_config.json"
114
117
  ],
115
118
  "our_generators": [
116
- "scripts/claude_app.py"
119
+ "scripts/claude_app.py",
120
+ "scripts/mcp_editors.py"
117
121
  ],
118
- "status_note": "Claude Chat/Desktop/Cowork does not scan ~/.claude/rules, CLAUDE.md, or Claude Code's user settings. ai-toolkit exports an uploadable plugin ZIP plus Cowork global instructions. Skills work in Chat and Cowork; plugin hooks and sub-agents run only in Cowork.",
122
+ "status_note": "Claude Chat/Desktop/Cowork does not scan ~/.claude/rules, CLAUDE.md, or Claude Code's user settings, so skills, agents, hooks, and rules ship only as an uploadable plugin ZIP plus Cowork global instructions. Local MCP servers are the exception: the app reads claude_desktop_config.json directly, so `ai-toolkit mcp install --editor claude-app --scope global` configures them without a plugin. That file accepts stdio entries only; HTTP/SSE templates are bridged through mcp-remote. Skills work in Chat and Cowork; plugin hooks and sub-agents run only in Cowork.",
119
123
  "capability_markers": [
120
124
  "Global instructions",
121
125
  "Folder instructions",
122
126
  "plugins",
123
127
  "skills",
124
- "sub-agents"
128
+ "sub-agents",
129
+ "MCP"
125
130
  ],
126
131
  "version_probe": null
127
132
  },
@@ -831,6 +831,18 @@ def main() -> None:
831
831
  if is_new:
832
832
  print(f" Registered project in {TOOLKIT_DATA_DIR / 'projects.json'}")
833
833
 
834
+ # A global install is authoritative for Claude Code, so an uploaded Claude
835
+ # app plugin must not feed it in parallel. Uploading the ZIP re-enables the
836
+ # plugin every time, so this is re-asserted on every install/update rather
837
+ # than left to whoever remembers to run `doctor --fix` afterwards.
838
+ if not local and not dry_run:
839
+ from doctor import disable_toolkit_plugins_for_claude_code
840
+ try:
841
+ for key in disable_toolkit_plugins_for_claude_code():
842
+ print(f" Disabled Claude app plugin for Claude Code: {key}")
843
+ except OSError as error:
844
+ print(f" WARNING: could not disable Claude app plugin: {error}")
845
+
834
846
  print_summary(local=local)
835
847
 
836
848
 
@@ -13,6 +13,7 @@ import json
13
13
  import math
14
14
  import os
15
15
  import re
16
+ import sys
16
17
  import tempfile
17
18
  from dataclasses import dataclass
18
19
  from pathlib import Path
@@ -23,6 +24,18 @@ except ModuleNotFoundError: # pragma: no cover - Python 3.11+ should have tomll
23
24
  tomllib = None
24
25
 
25
26
 
27
+ def _claude_app_config_relpath() -> str:
28
+ """Home-relative Claude app MCP config path for the running platform."""
29
+ if sys.platform == "darwin":
30
+ return "Library/Application Support/Claude/claude_desktop_config.json"
31
+ if os.name == "nt":
32
+ return "AppData/Roaming/Claude/claude_desktop_config.json"
33
+ return ".config/Claude/claude_desktop_config.json"
34
+
35
+
36
+ CLAUDE_APP_CONFIG_RELPATH = _claude_app_config_relpath()
37
+
38
+
26
39
  EDITOR_SPECS: dict[str, dict[str, str | None]] = {
27
40
  "claude": {
28
41
  "label": "Claude Code",
@@ -94,6 +107,13 @@ EDITOR_SPECS: dict[str, dict[str, str | None]] = {
94
107
  "format": "toml",
95
108
  "doc_scope": "project + global",
96
109
  },
110
+ "claude-app": {
111
+ "label": "Claude Chat / Cowork",
112
+ "project_path": None,
113
+ "global_path": CLAUDE_APP_CONFIG_RELPATH,
114
+ "format": "json",
115
+ "doc_scope": "global",
116
+ },
97
117
  }
98
118
 
99
119
 
@@ -202,6 +222,11 @@ def _resolve_global_config_root(
202
222
  if not configured:
203
223
  return (Path.home() / default_dir).absolute()
204
224
 
225
+ return _validate_configured_config_root(env_name, configured)
226
+
227
+
228
+ def _validate_configured_config_root(env_name: str, configured: str) -> Path:
229
+ """Validate an operator-supplied config root without resolving symlinks."""
205
230
  config_root = Path(configured).expanduser()
206
231
  if not config_root.is_absolute():
207
232
  raise ValueError(f"Configured {env_name} must be absolute: {configured}")
@@ -219,6 +244,30 @@ def _resolve_global_config_root(
219
244
  return config_root
220
245
 
221
246
 
247
+ def _resolve_claude_app_config_path(*, home: Path | None) -> Path:
248
+ """Resolve ``claude_desktop_config.json`` for the Claude app.
249
+
250
+ The Claude app honors ``CLAUDE_USER_DATA_DIR`` for its user-data root, so we
251
+ honor it too. Unlike ``COPILOT_HOME``/``CODEX_HOME`` the default directory is
252
+ platform-specific and need not exist yet -- ``_atomic_write_bytes`` creates it
253
+ -- so only an explicitly configured root is validated.
254
+ """
255
+ if home is not None:
256
+ return (home / CLAUDE_APP_CONFIG_RELPATH).expanduser().absolute()
257
+
258
+ configured = os.environ.get("CLAUDE_USER_DATA_DIR", "").strip()
259
+ if configured:
260
+ root = _validate_configured_config_root("CLAUDE_USER_DATA_DIR", configured)
261
+ return root / "claude_desktop_config.json"
262
+
263
+ if os.name == "nt": # pragma: no cover - exercised on Windows only
264
+ appdata = os.environ.get("APPDATA", "").strip()
265
+ if appdata:
266
+ return (Path(appdata) / "Claude" / "claude_desktop_config.json").absolute()
267
+
268
+ return (Path.home() / CLAUDE_APP_CONFIG_RELPATH).absolute()
269
+
270
+
222
271
  def resolve_editor_path(
223
272
  editor: str,
224
273
  scope: str,
@@ -256,6 +305,8 @@ def resolve_editor_path(
256
305
  default_dir=".codex",
257
306
  )
258
307
  return codex_home / "config.toml"
308
+ if editor == "claude-app":
309
+ return _resolve_claude_app_config_path(home=home)
259
310
  return (home or Path.home()) / str(rel)
260
311
  raise ValueError(f"Unsupported scope: {scope}")
261
312
 
@@ -509,6 +560,8 @@ def _rollback_config_update(update: ConfigUpdate) -> None:
509
560
  def _normalize_server(editor: str, server: dict) -> dict:
510
561
  if editor == "antigravity":
511
562
  return _normalize_antigravity_server(server)
563
+ if editor == "claude-app":
564
+ return _normalize_claude_app_server(server)
512
565
  data = copy.deepcopy(server)
513
566
  if editor == "copilot":
514
567
  if "url" in data:
@@ -519,6 +572,65 @@ def _normalize_server(editor: str, server: dict) -> dict:
519
572
  return data
520
573
 
521
574
 
575
+ MCP_REMOTE_PACKAGE = "mcp-remote"
576
+
577
+
578
+ def _normalize_claude_app_server(server: dict) -> dict:
579
+ """Emit the Claude app's stdio-only MCP schema, bridging remote endpoints.
580
+
581
+ ``claude_desktop_config.json`` validates each entry as ``{command, args,
582
+ env}``; remote servers live in a separate app-managed ``remoteMcpServers``
583
+ surface that is not file-configurable. Portable HTTP/SSE templates are
584
+ therefore wrapped in ``mcp-remote``, which negotiates the transport itself,
585
+ so one bridge shape covers both the ``http`` and ``sse`` spellings.
586
+ """
587
+ if not isinstance(server, dict):
588
+ raise ValueError("Claude app MCP server configuration must be an object")
589
+
590
+ data = copy.deepcopy(server)
591
+ for key in ("_source", "transport", "type", "tools"):
592
+ data.pop(key, None)
593
+
594
+ url = data.pop("url", None)
595
+ server_url = data.pop("serverUrl", None)
596
+ if url is not None and server_url is not None:
597
+ raise ValueError(
598
+ "Claude app MCP server requires exactly one of 'url' or 'serverUrl'"
599
+ )
600
+ url = url if url is not None else server_url
601
+
602
+ if "command" in data and url is not None:
603
+ raise ValueError(
604
+ "Claude app MCP server requires either 'command' or a URL, not both"
605
+ )
606
+
607
+ if url is not None:
608
+ if not isinstance(url, str) or not url.strip():
609
+ raise ValueError("Claude app MCP server URL must be a non-empty string")
610
+ headers = data.pop("headers", None)
611
+ args = ["-y", MCP_REMOTE_PACKAGE, url.strip()]
612
+ if headers is not None:
613
+ if not isinstance(headers, dict):
614
+ raise ValueError("Claude app MCP 'headers' must be an object")
615
+ for name, value in headers.items():
616
+ if not isinstance(name, str) or not isinstance(value, str):
617
+ raise ValueError(
618
+ "Claude app MCP 'headers' must map strings to strings"
619
+ )
620
+ args.extend(["--header", f"{name}: {value}"])
621
+ bridged: dict = {"command": "npx", "args": args}
622
+ if "env" in data:
623
+ bridged["env"] = data["env"]
624
+ return bridged
625
+
626
+ command = data.get("command")
627
+ if not isinstance(command, str) or not command.strip():
628
+ raise ValueError(
629
+ "Claude app MCP server requires a non-empty 'command' or a URL"
630
+ )
631
+ return {key: data[key] for key in ("command", "args", "env") if key in data}
632
+
633
+
522
634
  def _normalize_antigravity_server(server: dict) -> dict:
523
635
  """Validate and emit Antigravity's current ``serverUrl`` MCP schema."""
524
636
  if not isinstance(server, dict):