Skip to content

Commit d5a1683

Browse files
committed
Document Desktop Extensions in Claude Desktop scope
1 parent fd8186c commit d5a1683

2 files changed

Lines changed: 34 additions & 4 deletions

File tree

cli/src/detect/claude-desktop.ts

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
// therefore install agentlock as an MCP server entry — that's the only
99
// honest write we can do here.
1010

11-
import { existsSync } from "node:fs";
11+
import { existsSync, readFileSync } from "node:fs";
1212
import { join } from "node:path";
1313
import { appSupport } from "../util/paths.ts";
1414
import { claudeDesktopAgentlockState } from "./agentlock-state.ts";
@@ -31,13 +31,25 @@ export const claudeDesktop: Detector = {
3131
async detect(): Promise<Detection> {
3232
const configPath = claudeDesktopConfigPath();
3333
const dir = join(configPath, "..");
34+
const extensionsRegistry = join(dir, "extensions-installations.json");
3435

3536
const evidence: string[] = [];
3637
const dirExists = existsSync(dir);
3738
const configExists = existsSync(configPath);
3839
if (dirExists) evidence.push(`found ${dir}`);
3940
if (configExists) evidence.push(`found ${configPath}`);
4041

42+
// Count Desktop Extensions installed via Settings → Extensions UI.
43+
// This is the registry agentlock now wraps in addition to the
44+
// manual mcpServers path; surfacing the count tells the user up
45+
// front how much surface area they're hardening.
46+
const extensionCount = countInstalledExtensions(extensionsRegistry);
47+
if (extensionCount > 0) {
48+
evidence.push(
49+
`found ${extensionCount} Desktop Extension${extensionCount === 1 ? "" : "s"} (${extensionsRegistry})`,
50+
);
51+
}
52+
4153
const scopes: DetectedScope[] = [
4254
{ kind: "global", path: configPath, exists: configExists },
4355
];
@@ -57,15 +69,33 @@ export const claudeDesktop: Detector = {
5769
surfaces: ["mcp-stdio"],
5870
notes: dirExists
5971
? [
60-
"Install wraps every MCP server entry with `agentlock mcp-proxy` so each tools/call is gated by daemon policy. Originals preserved under _agentlock_original for clean uninstall.",
72+
"Install wraps every MCP server entry (manual mcpServers + Desktop Extensions installed via Settings → Extensions UI) with `agentlock mcp-proxy` so each tools/call is gated by daemon policy. Manual mcpServers entries preserve originals under _agentlock_original; Desktop Extension bundle manifests stash originals under _meta.agentlock (MCPB v0.3+ schema slot).",
73+
"Anthropic auto-updates may overwrite the wrap on extension version bumps — re-run `agentlock install` after extension updates.",
6174
"Coverage is the MCP slice only: not gated are Computer Use, integrated terminal, native connectors (Slack/GCal), Cowork's non-MCP paths, and Anthropic cloud features. For full local enforcement, use Claude Code.",
6275
]
6376
: [
6477
"Claude Desktop not detected. Selecting it will create the config dir on install.",
65-
"When Claude Desktop is in use, install wraps each MCP server — coverage is MCP-slice only (not Computer Use, terminal, connectors, or cloud features).",
78+
"When Claude Desktop is in use, install wraps each MCP server (mcpServers + Desktop Extensions) — coverage is MCP-slice only (not Computer Use, terminal, connectors, or cloud features).",
6679
],
6780
agentlockInstalled: al.installed,
6881
agentlockDaemonURL: al.daemonURL,
6982
};
7083
},
7184
};
85+
86+
// countInstalledExtensions parses extensions-installations.json and
87+
// returns the number of installed Desktop Extensions. Returns 0 on any
88+
// parse error or missing file — the count is informational; we don't
89+
// want detection to fail loud on a malformed registry that the install
90+
// pipeline will gracefully no-op past anyway.
91+
function countInstalledExtensions(registryPath: string): number {
92+
if (!existsSync(registryPath)) return 0;
93+
try {
94+
const parsed = JSON.parse(readFileSync(registryPath, "utf8")) as {
95+
extensions?: Record<string, unknown>;
96+
};
97+
return Object.keys(parsed.extensions ?? {}).length;
98+
} catch {
99+
return 0;
100+
}
101+
}

docs/status.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Live status of every component shipped to the public repo. <span class="md-statu
88
|---|---|
99
| `agentlock detect` | <span class="md-status-pill shipped">Shipped</span> |
1010
| `agentlock install` (Claude Code, Codex CLI, Cursor, Gemini CLI) | <span class="md-status-pill shipped">Shipped</span> |
11-
| `agentlock install` (Claude Desktop) | <span class="md-status-pill shipped">Shipped</span> — **MCP-slice enforcement** via `agentlock mcp-proxy`. Wraps every user-installed MCP server and `.mcpb` Desktop Extension; each `tools/call` goes through daemon policy. **Not gated:** Computer Use (direct mouse/keyboard), integrated terminal, native connectors (Slack/GCal), and server-side features (web search, code interpreter). **Cowork coverage uncertain:** any MCP-mediated tool call Cowork makes IS gated; whether Cowork has separate non-MCP code paths is unverified — verify in your environment by running a Cowork task and checking the agentlock ledger. For full local enforcement of an agent harness, use Claude Code. Tracks [anthropics/claude-code#45514](https://github.com/anthropics/claude-code/issues/45514) for native PreToolUse parity. |
11+
| `agentlock install` (Claude Desktop) | <span class="md-status-pill shipped">Shipped</span> — wraps every MCP server entry through `agentlock mcp-proxy` so each `tools/call` goes through daemon policy. Both install paths covered: (a) manual `mcpServers` entries in `~/Library/Application Support/Claude/claude_desktop_config.json` (originals preserved under `_agentlock_original`); (b) Desktop Extensions installed via *Settings → Extensions* UI — each per-extension bundle manifest at `Claude Extensions/<ext-id>/manifest.json` is rewritten in place using the schema-blessed `_meta.agentlock` slot (MCPB v0.3+), with `manifest_version` bumped from 0.1/0.2 → 0.3 when needed so the slot validates. Originals stashed under `_meta.agentlock.original_*` for byte-clean restore. **Caveat:** Anthropic auto-updates overwrite the wrap on extension version bumps — re-run `agentlock install` after extension updates (a watcher closes this gap; tracked separately). Other surfaces remain out of scope: Computer Use, integrated terminal, native connectors (Slack/GCal), Cowork's non-MCP paths, server-side cloud features. For full local enforcement of an agent harness, use Claude Code. Tracks [anthropics/claude-code#45514](https://github.com/anthropics/claude-code/issues/45514) for native PreToolUse parity. |
1212
| `agentlock install` (OpenCode, Cline, Continue, VS Code Copilot) | <span class="md-status-pill not-yet">Not yet implemented</span> — detected but disabled in selector |
1313
| `agentlock install` (Codex Desktop, Openclaw, Nemoclaw, Hermesagent, Pi) | <span class="md-status-pill not-yet">Not yet implemented</span> — roadmap; awaiting per-app hook/config investigation |
1414
| `agentlock install --tier {unattested,software,totp}` | <span class="md-status-pill shipped">Shipped</span> |

0 commit comments

Comments
 (0)