@intutic/sync-daemon 2.1.0 → 2.2.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/dist/agentReporter.d.ts +1 -0
- package/dist/agentReporter.d.ts.map +1 -1
- package/dist/configReader.d.ts +44 -22
- package/dist/configReader.d.ts.map +1 -1
- package/dist/configReader.js +70 -44
- package/dist/configReader.js.map +1 -1
- package/dist/configWriter.d.ts +29 -41
- package/dist/configWriter.d.ts.map +1 -1
- package/dist/configWriter.js +26 -637
- package/dist/configWriter.js.map +1 -1
- package/dist/disconnect/harnesses.d.ts +31 -0
- package/dist/disconnect/harnesses.d.ts.map +1 -0
- package/dist/disconnect/harnesses.js +728 -0
- package/dist/disconnect/harnesses.js.map +1 -0
- package/dist/disconnect/index.d.ts +25 -0
- package/dist/disconnect/index.d.ts.map +1 -0
- package/dist/disconnect/index.js +82 -0
- package/dist/disconnect/index.js.map +1 -0
- package/dist/disconnect/mcp.d.ts +60 -0
- package/dist/disconnect/mcp.d.ts.map +1 -0
- package/dist/disconnect/mcp.js +283 -0
- package/dist/disconnect/mcp.js.map +1 -0
- package/dist/disconnect/originals.d.ts +88 -0
- package/dist/disconnect/originals.d.ts.map +1 -0
- package/dist/disconnect/originals.js +257 -0
- package/dist/disconnect/originals.js.map +1 -0
- package/dist/disconnect/plan.d.ts +175 -0
- package/dist/disconnect/plan.d.ts.map +1 -0
- package/dist/disconnect/plan.js +580 -0
- package/dist/disconnect/plan.js.map +1 -0
- package/dist/disconnect/recognise.d.ts +43 -0
- package/dist/disconnect/recognise.d.ts.map +1 -0
- package/dist/disconnect/recognise.js +98 -0
- package/dist/disconnect/recognise.js.map +1 -0
- package/dist/disconnect/tomlLines.d.ts +39 -0
- package/dist/disconnect/tomlLines.d.ts.map +1 -0
- package/dist/disconnect/tomlLines.js +105 -0
- package/dist/disconnect/tomlLines.js.map +1 -0
- package/dist/harness/aiderConfigMerger.d.ts +1 -0
- package/dist/harness/aiderConfigMerger.d.ts.map +1 -1
- package/dist/harness/aiderConfigMerger.js +4 -1
- package/dist/harness/aiderConfigMerger.js.map +1 -1
- package/dist/harness/antigravityHooks.d.ts.map +1 -1
- package/dist/harness/antigravityHooks.js +2 -0
- package/dist/harness/antigravityHooks.js.map +1 -1
- package/dist/harness/claudeCodeHooks.d.ts +1 -2
- package/dist/harness/claudeCodeHooks.d.ts.map +1 -1
- package/dist/harness/claudeCodeHooks.js +19 -6
- package/dist/harness/claudeCodeHooks.js.map +1 -1
- package/dist/harness/claudeDesktopHooks.d.ts +3 -0
- package/dist/harness/claudeDesktopHooks.d.ts.map +1 -1
- package/dist/harness/claudeDesktopHooks.js +3 -1
- package/dist/harness/claudeDesktopHooks.js.map +1 -1
- package/dist/harness/claudeProjectApproval.d.ts +53 -0
- package/dist/harness/claudeProjectApproval.d.ts.map +1 -0
- package/dist/harness/claudeProjectApproval.js +159 -0
- package/dist/harness/claudeProjectApproval.js.map +1 -0
- package/dist/harness/clineHooks.d.ts.map +1 -1
- package/dist/harness/clineHooks.js +3 -0
- package/dist/harness/clineHooks.js.map +1 -1
- package/dist/harness/codexConfigMerger.d.ts.map +1 -1
- package/dist/harness/codexConfigMerger.js +4 -0
- package/dist/harness/codexConfigMerger.js.map +1 -1
- package/dist/harness/codexHooks.d.ts.map +1 -1
- package/dist/harness/codexHooks.js +3 -0
- package/dist/harness/codexHooks.js.map +1 -1
- package/dist/harness/continueConfigMerger.d.ts.map +1 -1
- package/dist/harness/continueConfigMerger.js +4 -0
- package/dist/harness/continueConfigMerger.js.map +1 -1
- package/dist/harness/continueHooks.d.ts.map +1 -1
- package/dist/harness/continueHooks.js +3 -0
- package/dist/harness/continueHooks.js.map +1 -1
- package/dist/harness/cursorHooks.d.ts.map +1 -1
- package/dist/harness/cursorHooks.js +3 -0
- package/dist/harness/cursorHooks.js.map +1 -1
- package/dist/harness/dshHooks.d.ts.map +1 -1
- package/dist/harness/dshHooks.js +7 -0
- package/dist/harness/dshHooks.js.map +1 -1
- package/dist/harness/gateBody.d.ts +11 -2
- package/dist/harness/gateBody.d.ts.map +1 -1
- package/dist/harness/gateBody.js +33 -6
- package/dist/harness/gateBody.js.map +1 -1
- package/dist/harness/githubCopilotHooks.d.ts.map +1 -1
- package/dist/harness/githubCopilotHooks.js +4 -9
- package/dist/harness/githubCopilotHooks.js.map +1 -1
- package/dist/harness/gooseHooks.d.ts +15 -0
- package/dist/harness/gooseHooks.d.ts.map +1 -1
- package/dist/harness/gooseHooks.js +55 -43
- package/dist/harness/gooseHooks.js.map +1 -1
- package/dist/harness/grokHooks.d.ts.map +1 -1
- package/dist/harness/grokHooks.js +9 -12
- package/dist/harness/grokHooks.js.map +1 -1
- package/dist/harness/hermesHooks.d.ts.map +1 -1
- package/dist/harness/hermesHooks.js +7 -2
- package/dist/harness/hermesHooks.js.map +1 -1
- package/dist/harness/holdRedaction.d.ts +4 -1
- package/dist/harness/holdRedaction.d.ts.map +1 -1
- package/dist/harness/holdRedaction.js +32 -8
- package/dist/harness/holdRedaction.js.map +1 -1
- package/dist/harness/jetbrainsXmlConfig.d.ts +20 -0
- package/dist/harness/jetbrainsXmlConfig.d.ts.map +1 -1
- package/dist/harness/jetbrainsXmlConfig.js +39 -18
- package/dist/harness/jetbrainsXmlConfig.js.map +1 -1
- package/dist/harness/mcpAutoWrite.d.ts +39 -17
- package/dist/harness/mcpAutoWrite.d.ts.map +1 -1
- package/dist/harness/mcpAutoWrite.js +189 -71
- package/dist/harness/mcpAutoWrite.js.map +1 -1
- package/dist/harness/museHooks.d.ts.map +1 -1
- package/dist/harness/museHooks.js +6 -2
- package/dist/harness/museHooks.js.map +1 -1
- package/dist/harness/openCodeHooks.d.ts.map +1 -1
- package/dist/harness/openCodeHooks.js +2 -5
- package/dist/harness/openCodeHooks.js.map +1 -1
- package/dist/harness/openWebuiHooks.d.ts.map +1 -1
- package/dist/harness/openWebuiHooks.js +6 -2
- package/dist/harness/openWebuiHooks.js.map +1 -1
- package/dist/harness/openclawHooks.d.ts +5 -0
- package/dist/harness/openclawHooks.d.ts.map +1 -1
- package/dist/harness/openclawHooks.js +3 -1
- package/dist/harness/openclawHooks.js.map +1 -1
- package/dist/harness/openhandsHooks.d.ts +13 -1
- package/dist/harness/openhandsHooks.d.ts.map +1 -1
- package/dist/harness/openhandsHooks.js +106 -56
- package/dist/harness/openhandsHooks.js.map +1 -1
- package/dist/harness/piHooks.d.ts.map +1 -1
- package/dist/harness/piHooks.js +3 -0
- package/dist/harness/piHooks.js.map +1 -1
- package/dist/harness/rooCodeHooks.d.ts.map +1 -1
- package/dist/harness/rooCodeHooks.js +5 -10
- package/dist/harness/rooCodeHooks.js.map +1 -1
- package/dist/harness/windsurfHooks.d.ts +18 -2
- package/dist/harness/windsurfHooks.d.ts.map +1 -1
- package/dist/harness/windsurfHooks.js +37 -7
- package/dist/harness/windsurfHooks.js.map +1 -1
- package/dist/harness/windsurfJetBrainsProxy.d.ts +1 -1
- package/dist/harness/windsurfJetBrainsProxy.d.ts.map +1 -1
- package/dist/harness/windsurfJetBrainsProxy.js +5 -1
- package/dist/harness/windsurfJetBrainsProxy.js.map +1 -1
- package/dist/index.d.ts +9 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -10
- package/dist/index.js.map +1 -1
- package/dist/lib/decisionsDigest.d.ts +1 -1
- package/dist/lib/decisionsDigest.js +1 -1
- package/dist/lib/policySnapshot.d.ts +18 -0
- package/dist/lib/policySnapshot.d.ts.map +1 -1
- package/dist/lib/policySnapshot.js +142 -7
- package/dist/lib/policySnapshot.js.map +1 -1
- package/dist/sessionReporter.d.ts +2 -2
- package/dist/sessionReporter.js +2 -2
- package/dist/syncCycle.d.ts +142 -0
- package/dist/syncCycle.d.ts.map +1 -0
- package/dist/syncCycle.js +315 -0
- package/dist/syncCycle.js.map +1 -0
- package/dist/trajectoryMonitor.d.ts +1 -1
- package/dist/trajectoryMonitor.d.ts.map +1 -1
- package/dist/trajectoryMonitor.js +1 -1
- package/dist/trajectoryMonitor.js.map +1 -1
- package/dist/watcher/settingsGuard.d.ts +4 -1
- package/dist/watcher/settingsGuard.d.ts.map +1 -1
- package/dist/watcher/settingsGuard.js +28 -3
- package/dist/watcher/settingsGuard.js.map +1 -1
- package/package.json +5 -5
- package/dist/hashReporter.d.ts +0 -33
- package/dist/hashReporter.d.ts.map +0 -1
- package/dist/hashReporter.js +0 -69
- package/dist/hashReporter.js.map +0 -1
- package/dist/integrityStore.d.ts +0 -35
- package/dist/integrityStore.d.ts.map +0 -1
- package/dist/integrityStore.js +0 -71
- package/dist/integrityStore.js.map +0 -1
- package/dist/lib/gitWorktrees.d.ts +0 -140
- package/dist/lib/gitWorktrees.d.ts.map +0 -1
- package/dist/lib/gitWorktrees.js +0 -191
- package/dist/lib/gitWorktrees.js.map +0 -1
- package/dist/syncLoop.d.ts +0 -172
- package/dist/syncLoop.d.ts.map +0 -1
- package/dist/syncLoop.js +0 -878
- package/dist/syncLoop.js.map +0 -1
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Injects and proxy-wraps MCP server entries in:
|
|
6
6
|
* - Claude Code: ~/.claude.json (user-scope `mcpServers`, and the
|
|
7
|
-
* local-scope `projects[<workspaceRoot>].mcpServers
|
|
7
|
+
* local-scope `projects[<workspaceRoot>].mcpServers`,
|
|
8
|
+
* where the approved servers of the project's
|
|
9
|
+
* `.mcp.json` also get wrapped shadows — the shared
|
|
10
|
+
* `.mcp.json` itself is only read)
|
|
8
11
|
* - Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json
|
|
9
12
|
* - Cursor (global): ~/Library/Application Support/Cursor/User/globalSettings.json
|
|
10
13
|
* - Cursor (project): <workspaceRoot>/.cursor/mcp.json
|
|
@@ -22,8 +25,8 @@
|
|
|
22
25
|
* That is 14 config paths across 11 `HarnessType` values (Cursor, Grok
|
|
23
26
|
* Build and OpenCode each own two paths — global/project for Cursor and
|
|
24
27
|
* OpenCode, user/project for Grok Build). `discoverMcpServers` below reads
|
|
25
|
-
* all fourteen read-only, for reporting
|
|
26
|
-
* thing that writes.
|
|
28
|
+
* all fourteen read-only, for reporting, plus Claude Code's project
|
|
29
|
+
* `.mcp.json`; the injectors above are the only thing that writes.
|
|
27
30
|
*
|
|
28
31
|
* # Grok Build's compat-path overlap — dedup, not a bug
|
|
29
32
|
*
|
|
@@ -69,15 +72,13 @@
|
|
|
69
72
|
* comment used to describe as "left untouched": the entry is rewritten to
|
|
70
73
|
* spawn the SAME proxy binary in bridge mode (`--remote-url`/
|
|
71
74
|
* `--remote-transport`, headers riding in `INTUTIC_REMOTE_HEADERS` env, not
|
|
72
|
-
* argv — `ps` visibility). `__intutic_original`
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* supersedes.
|
|
75
|
+
* argv — `ps` visibility). `__intutic_original` keeps the whole pre-wrap
|
|
76
|
+
* entry, stdio or remote, so `classifyEntry` can report the TRUE transport
|
|
77
|
+
* of what got wrapped and `intutic disconnect` can put the entry back
|
|
78
|
+
* exactly.
|
|
77
79
|
*
|
|
78
|
-
* Continuous invariant, not one-shot: `
|
|
79
|
-
* `
|
|
80
|
-
* (services/sync-daemon/src/syncLoop.ts), so a server a user adds to a harness
|
|
80
|
+
* Continuous invariant, not one-shot: `intutic connect` (tools/cli) calls
|
|
81
|
+
* `injectMcpServer` on every sync cycle, so a server a user adds to a harness
|
|
81
82
|
* config after their first `connect` still gets wrapped on the next sync
|
|
82
83
|
* cycle rather than staying invisible to governance forever. Running this
|
|
83
84
|
* every ~30s only works because `writeJsonFile` below is write-if-changed —
|
|
@@ -96,6 +97,8 @@ import { createRequire } from 'node:module';
|
|
|
96
97
|
import { isDeepStrictEqual } from 'node:util';
|
|
97
98
|
import { createLogger } from '@intutic/logger';
|
|
98
99
|
import { readJsonObjectForMerge } from './jsonMergeTarget.js';
|
|
100
|
+
import { projectServerApproval } from './claudeProjectApproval.js';
|
|
101
|
+
import { keepOriginal } from '../disconnect/originals.js';
|
|
99
102
|
import { parseDocument, isMap } from 'yaml';
|
|
100
103
|
import { parse as parseToml, stringify as stringifyToml } from 'smol-toml';
|
|
101
104
|
const log = createLogger('sync-mcp-autowrite');
|
|
@@ -139,13 +142,14 @@ export function resolveProxyBin(workspaceRoot) {
|
|
|
139
142
|
// Shared by both the injectors (which write) and discoverMcpServers (which
|
|
140
143
|
// only reads), so the two can never independently drift about where a
|
|
141
144
|
// harness keeps its config.
|
|
142
|
-
/** Claude Code keeps user- and local-scope MCP servers in `~/.claude.json
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
+
/** Claude Code keeps user- and local-scope MCP servers in `~/.claude.json`.
|
|
146
|
+
* Project-scope servers live in the repo's `.mcp.json`, which is shared with
|
|
147
|
+
* the team and never rewritten here; they are governed through local-scope
|
|
148
|
+
* shadows instead (see {@link shadowProjectServers}). */
|
|
145
149
|
function claudeCodeConfigPath() {
|
|
146
150
|
return node_path.join(node_os.homedir(), '.claude.json');
|
|
147
151
|
}
|
|
148
|
-
function claudeDesktopConfigPath() {
|
|
152
|
+
export function claudeDesktopConfigPath() {
|
|
149
153
|
const home = node_os.homedir();
|
|
150
154
|
if (process.platform === 'darwin') {
|
|
151
155
|
return node_path.join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
@@ -155,7 +159,7 @@ function claudeDesktopConfigPath() {
|
|
|
155
159
|
}
|
|
156
160
|
return node_path.join(home, '.config', 'Claude', 'claude_desktop_config.json');
|
|
157
161
|
}
|
|
158
|
-
function cursorGlobalConfigPath() {
|
|
162
|
+
export function cursorGlobalConfigPath() {
|
|
159
163
|
const home = node_os.homedir();
|
|
160
164
|
if (process.platform === 'darwin') {
|
|
161
165
|
return node_path.join(home, 'Library', 'Application Support', 'Cursor', 'User', 'globalSettings.json');
|
|
@@ -171,22 +175,22 @@ function cursorProjectConfigPath(workspaceRoot) {
|
|
|
171
175
|
function clineConfigPath(workspaceRoot) {
|
|
172
176
|
return node_path.join(workspaceRoot, '.cline', 'mcp.json');
|
|
173
177
|
}
|
|
174
|
-
function windsurfConfigPath() {
|
|
178
|
+
export function windsurfConfigPath() {
|
|
175
179
|
return node_path.join(node_os.homedir(), '.codeium', 'windsurf', 'mcp_config.json');
|
|
176
180
|
}
|
|
177
|
-
function continueConfigPath() {
|
|
181
|
+
export function continueConfigPath() {
|
|
178
182
|
return node_path.join(node_os.homedir(), '.continue', 'config.json');
|
|
179
183
|
}
|
|
180
|
-
function gooseConfigPath() {
|
|
184
|
+
export function gooseConfigPath() {
|
|
181
185
|
return node_path.join(node_os.homedir(), '.config', 'goose', 'config.yaml');
|
|
182
186
|
}
|
|
183
187
|
function openHandsConfigPath(workspaceRoot) {
|
|
184
188
|
return node_path.join(workspaceRoot, '.openhands', 'mcp.json');
|
|
185
189
|
}
|
|
186
|
-
function museConfigPath() {
|
|
190
|
+
export function museConfigPath() {
|
|
187
191
|
return node_path.join(node_os.homedir(), '.config', 'muse', 'settings.json');
|
|
188
192
|
}
|
|
189
|
-
function grokUserConfigPath() {
|
|
193
|
+
export function grokUserConfigPath() {
|
|
190
194
|
return node_path.join(node_os.homedir(), '.grok', 'config.toml');
|
|
191
195
|
}
|
|
192
196
|
function grokProjectConfigPath(workspaceRoot) {
|
|
@@ -195,7 +199,7 @@ function grokProjectConfigPath(workspaceRoot) {
|
|
|
195
199
|
/** OpenCode's global config: `$XDG_CONFIG_HOME/opencode/opencode.json`, which
|
|
196
200
|
* is `~/.config/opencode/opencode.json` unless XDG_CONFIG_HOME is set (OpenCode
|
|
197
201
|
* resolves it through xdg-basedir). Only the `.json` spelling — see `injectOpenCode`. */
|
|
198
|
-
function openCodeGlobalConfigPath() {
|
|
202
|
+
export function openCodeGlobalConfigPath() {
|
|
199
203
|
const base = process.env['XDG_CONFIG_HOME'] || node_path.join(node_os.homedir(), '.config');
|
|
200
204
|
return node_path.join(base, 'opencode', 'opencode.json');
|
|
201
205
|
}
|
|
@@ -227,6 +231,7 @@ function wrapWithProxy(entry, workspaceId, workspaceRoot, serverName) {
|
|
|
227
231
|
if (entry.__intutic_wrapped)
|
|
228
232
|
return entry;
|
|
229
233
|
const proxyBin = resolveProxyBin(workspaceRoot);
|
|
234
|
+
const original = structuredClone(entry);
|
|
230
235
|
if (typeof entry.command === 'string') {
|
|
231
236
|
const originalArgs = entry.args ?? [];
|
|
232
237
|
return {
|
|
@@ -244,16 +249,12 @@ function wrapWithProxy(entry, workspaceId, workspaceRoot, serverName) {
|
|
|
244
249
|
INTUTIC_WORKSPACE_ID: workspaceId,
|
|
245
250
|
},
|
|
246
251
|
__intutic_wrapped: true,
|
|
252
|
+
__intutic_original: original,
|
|
247
253
|
};
|
|
248
254
|
}
|
|
249
255
|
if (typeof entry.url === 'string') {
|
|
250
256
|
const remoteTransport = entry.type?.toLowerCase() === 'sse' ? 'sse' : 'http';
|
|
251
257
|
const hasHeaders = entry.headers !== undefined && Object.keys(entry.headers).length > 0;
|
|
252
|
-
const original = { url: entry.url };
|
|
253
|
-
if (entry.type !== undefined)
|
|
254
|
-
original.type = entry.type;
|
|
255
|
-
if (entry.headers !== undefined)
|
|
256
|
-
original.headers = entry.headers;
|
|
257
258
|
const env = {
|
|
258
259
|
...(entry.env ?? {}),
|
|
259
260
|
INTUTIC_WORKSPACE_ID: workspaceId,
|
|
@@ -339,8 +340,8 @@ async function readJsonForWrite(filePath) {
|
|
|
339
340
|
/**
|
|
340
341
|
* Write JSON to disk, but only if the content actually changed.
|
|
341
342
|
*
|
|
342
|
-
* `injectMcpServer`
|
|
343
|
-
*
|
|
343
|
+
* `injectMcpServer` runs every sync cycle (~every 30s, from `intutic
|
|
344
|
+
* connect`) instead of only once at startup — re-running the same
|
|
344
345
|
* wrap against an already-wrapped, unchanged config must not touch the file,
|
|
345
346
|
* or every cycle would churn the file's mtime and fire a spurious inotify /
|
|
346
347
|
* FSEvents event for every harness config on every developer machine.
|
|
@@ -349,8 +350,12 @@ async function readJsonForWrite(filePath) {
|
|
|
349
350
|
* serialized string — a naive string compare would report a false "changed"
|
|
350
351
|
* on nothing more than JSON.stringify key-ordering differences between two
|
|
351
352
|
* semantically identical objects.
|
|
353
|
+
*
|
|
354
|
+
* With `workspaceRoot`, the file's original is kept for `intutic disconnect`
|
|
355
|
+
* before the first write. `~/.claude.json` goes without: it is Claude Code's
|
|
356
|
+
* live state, never restored whole.
|
|
352
357
|
*/
|
|
353
|
-
async function writeJsonFile(filePath, data) {
|
|
358
|
+
async function writeJsonFile(filePath, data, workspaceRoot) {
|
|
354
359
|
let existing;
|
|
355
360
|
let hasExisting;
|
|
356
361
|
try {
|
|
@@ -363,6 +368,8 @@ async function writeJsonFile(filePath, data) {
|
|
|
363
368
|
if (hasExisting && isDeepStrictEqual(existing, data)) {
|
|
364
369
|
return;
|
|
365
370
|
}
|
|
371
|
+
if (workspaceRoot !== undefined)
|
|
372
|
+
await keepOriginal(filePath, workspaceRoot);
|
|
366
373
|
await node_fs.mkdir(node_path.dirname(filePath), { recursive: true });
|
|
367
374
|
await node_fs.writeFile(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
368
375
|
}
|
|
@@ -378,14 +385,97 @@ async function injectClaudeCode(workspaceId, workspaceRoot) {
|
|
|
378
385
|
const current = await readJsonForWrite(configPath);
|
|
379
386
|
// User scope: available in every project.
|
|
380
387
|
current.mcpServers = wrapAllServers({ intutic: buildIntuticMcpEntry(workspaceRoot), ...(current.mcpServers ?? {}) }, workspaceId, workspaceRoot);
|
|
381
|
-
// Local scope: servers added with `claude mcp add` in this project
|
|
388
|
+
// Local scope: servers added with `claude mcp add` in this project, then
|
|
389
|
+
// the shadows that govern its approved `.mcp.json` servers. A project Claude
|
|
390
|
+
// Code has never opened has no entry, and nothing approved to shadow.
|
|
382
391
|
const project = current.projects?.[workspaceRoot];
|
|
383
|
-
if (project
|
|
384
|
-
|
|
392
|
+
if (project) {
|
|
393
|
+
const local = wrapAllServers(project.mcpServers ?? {}, workspaceId, workspaceRoot);
|
|
394
|
+
const withShadows = shadowProjectServers(local, await readProjectScopeServers(workspaceRoot), await projectServerApproval(workspaceRoot, current), workspaceId, workspaceRoot);
|
|
395
|
+
if (project.mcpServers !== undefined || Object.keys(withShadows).length > 0)
|
|
396
|
+
project.mcpServers = withShadows;
|
|
385
397
|
}
|
|
386
398
|
await writeJsonFile(configPath, current);
|
|
387
399
|
log.info({ action: 'claude_code_mcp_injected' }, 'Claude Code ~/.claude.json MCP servers updated');
|
|
388
400
|
}
|
|
401
|
+
/** Where Claude Code keeps a project's shared, committed servers. */
|
|
402
|
+
function claudeCodeProjectMcpPath(workspaceRoot) {
|
|
403
|
+
return node_path.join(workspaceRoot, '.mcp.json');
|
|
404
|
+
}
|
|
405
|
+
/** The project's `.mcp.json` servers; empty when the file is missing or not a JSON object. */
|
|
406
|
+
async function readProjectScopeServers(workspaceRoot) {
|
|
407
|
+
const parsed = await readJsonFile(claudeCodeProjectMcpPath(workspaceRoot), {});
|
|
408
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
409
|
+
return {};
|
|
410
|
+
const servers = parsed.mcpServers;
|
|
411
|
+
return servers !== null && typeof servers === 'object' && !Array.isArray(servers) ? servers : {};
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Why an approved project server cannot get a governed local-scope copy, or
|
|
415
|
+
* null when it can. Exported for discovery's reporting through
|
|
416
|
+
* {@link discoverMcpServers}.
|
|
417
|
+
*
|
|
418
|
+
* Expansion itself is not a reason: Claude Code expands `${VAR}` the same way
|
|
419
|
+
* at local scope as in `.mcp.json`, in `command`, `args`, `env`, `url` and
|
|
420
|
+
* `headers` (verified with Claude Code 2.1.233 — see the MCP governance
|
|
421
|
+
* guide), so a copy keeps references verbatim and writes no resolved value.
|
|
422
|
+
* A remote server is the exception. Its copy is a stdio entry that hands the
|
|
423
|
+
* url to the proxy as an argument and the headers through the environment,
|
|
424
|
+
* where Claude Code expands every variable; in a remote server's own `url`
|
|
425
|
+
* and `headers` it reads credential variables (its own API keys, cloud and
|
|
426
|
+
* proxy credentials) as empty, by a list it does not publish in full. A copy
|
|
427
|
+
* could therefore send a credential the original never would.
|
|
428
|
+
*/
|
|
429
|
+
function shadowBlocker(entry) {
|
|
430
|
+
if (entry === null || typeof entry !== 'object')
|
|
431
|
+
return 'an entry the proxy cannot front';
|
|
432
|
+
if (entry.__intutic_wrapped)
|
|
433
|
+
return 'the entry in .mcp.json is already wrapped';
|
|
434
|
+
if (typeof entry.command === 'string')
|
|
435
|
+
return null;
|
|
436
|
+
if (typeof entry.url === 'string') {
|
|
437
|
+
const refs = [entry.url, ...Object.values(entry.headers ?? {})].some((v) => typeof v === 'string' && v.includes('${'));
|
|
438
|
+
return refs
|
|
439
|
+
? 'its url or headers reference environment variables, which Claude Code expands with a credential filter a wrapped copy cannot keep'
|
|
440
|
+
: null;
|
|
441
|
+
}
|
|
442
|
+
return 'an entry the proxy cannot front';
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Governs a project's `.mcp.json` servers without touching the file.
|
|
446
|
+
*
|
|
447
|
+
* `.mcp.json` is committed and shared with the team: wrapping it in place
|
|
448
|
+
* would write this machine's proxy path and workspace id into everyone's
|
|
449
|
+
* checkout. Claude Code gives a local-scope server precedence over a
|
|
450
|
+
* project-scope one of the same name, so each approved project server gets a
|
|
451
|
+
* wrapped copy at local scope, in this project's `~/.claude.json` entry,
|
|
452
|
+
* marked `__intutic_shadow_of: 'project'`. The rules:
|
|
453
|
+
*
|
|
454
|
+
* - A server the user added at local scope keeps its name; it is wrapped by
|
|
455
|
+
* the local-scope pass already and never replaced by a shadow.
|
|
456
|
+
* - A shadow follows its source: rewritten when the project entry changes,
|
|
457
|
+
* removed when the project entry, or its approval, goes away. Deleting the
|
|
458
|
+
* marked entries returns the project to exactly what `.mcp.json` says.
|
|
459
|
+
* - An entry whose shape the proxy cannot wrap is left to Claude Code as is.
|
|
460
|
+
* - Re-running on unchanged input produces the same map, so the
|
|
461
|
+
* write-if-changed in `writeJsonFile` writes nothing.
|
|
462
|
+
*/
|
|
463
|
+
function shadowProjectServers(local, projectServers, approval, workspaceId, workspaceRoot) {
|
|
464
|
+
const result = {};
|
|
465
|
+
for (const [name, entry] of Object.entries(local)) {
|
|
466
|
+
if (entry.__intutic_shadow_of === 'project')
|
|
467
|
+
continue; // rebuilt below from the current source
|
|
468
|
+
result[name] = entry;
|
|
469
|
+
}
|
|
470
|
+
for (const [name, entry] of Object.entries(projectServers)) {
|
|
471
|
+
if (name === 'intutic' || name in result || !approval.approved(name))
|
|
472
|
+
continue;
|
|
473
|
+
if (shadowBlocker(entry) !== null)
|
|
474
|
+
continue;
|
|
475
|
+
result[name] = { ...wrapWithProxy(entry, workspaceId, workspaceRoot, name), __intutic_shadow_of: 'project' };
|
|
476
|
+
}
|
|
477
|
+
return result;
|
|
478
|
+
}
|
|
389
479
|
// ─── Target: Claude Desktop ───────────────────────────────────────────────────
|
|
390
480
|
async function injectClaudeDesktop(workspaceId, workspaceRoot) {
|
|
391
481
|
const configPath = claudeDesktopConfigPath();
|
|
@@ -399,7 +489,7 @@ async function injectClaudeDesktop(workspaceId, workspaceRoot) {
|
|
|
399
489
|
}
|
|
400
490
|
const current = await readJsonForWrite(configPath);
|
|
401
491
|
current.mcpServers = wrapAllServers({ intutic: buildIntuticMcpEntry(workspaceRoot), ...(current.mcpServers ?? {}) }, workspaceId, workspaceRoot);
|
|
402
|
-
await writeJsonFile(configPath, current);
|
|
492
|
+
await writeJsonFile(configPath, current, workspaceRoot);
|
|
403
493
|
log.info({ action: 'claude_desktop_mcp_injected' }, 'Claude Desktop config updated');
|
|
404
494
|
}
|
|
405
495
|
// ─── Target: Cursor (global + project) ───────────────────────────────────────
|
|
@@ -410,7 +500,7 @@ async function injectCursor(workspaceId, workspaceRoot) {
|
|
|
410
500
|
await node_fs.access(node_path.dirname(globalPath));
|
|
411
501
|
const current = await readJsonForWrite(globalPath);
|
|
412
502
|
current.mcpServers = wrapAllServers({ intutic: buildIntuticMcpEntry(workspaceRoot), ...(current.mcpServers ?? {}) }, workspaceId, workspaceRoot);
|
|
413
|
-
await writeJsonFile(globalPath, current);
|
|
503
|
+
await writeJsonFile(globalPath, current, workspaceRoot);
|
|
414
504
|
log.info({ action: 'cursor_global_mcp_injected' }, 'Cursor globalSettings.json updated');
|
|
415
505
|
}
|
|
416
506
|
catch {
|
|
@@ -421,7 +511,7 @@ async function injectCursor(workspaceId, workspaceRoot) {
|
|
|
421
511
|
try {
|
|
422
512
|
const current = await readJsonForWrite(projectPath);
|
|
423
513
|
current.mcpServers = wrapAllServers({ intutic: buildIntuticMcpEntry(workspaceRoot), ...(current.mcpServers ?? {}) }, workspaceId, workspaceRoot);
|
|
424
|
-
await writeJsonFile(projectPath, current);
|
|
514
|
+
await writeJsonFile(projectPath, current, workspaceRoot);
|
|
425
515
|
log.info({ action: 'cursor_project_mcp_injected' }, 'Cursor .cursor/mcp.json updated');
|
|
426
516
|
}
|
|
427
517
|
catch (err) {
|
|
@@ -434,7 +524,7 @@ async function injectCline(workspaceId, workspaceRoot) {
|
|
|
434
524
|
try {
|
|
435
525
|
const current = await readJsonForWrite(configPath);
|
|
436
526
|
current.mcpServers = wrapAllServers({ intutic: buildIntuticMcpEntry(workspaceRoot), ...(current.mcpServers ?? {}) }, workspaceId, workspaceRoot);
|
|
437
|
-
await writeJsonFile(configPath, current);
|
|
527
|
+
await writeJsonFile(configPath, current, workspaceRoot);
|
|
438
528
|
log.info({ action: 'cline_mcp_injected' }, 'Cline .cline/mcp.json updated');
|
|
439
529
|
}
|
|
440
530
|
catch (err) {
|
|
@@ -448,7 +538,7 @@ async function injectWindsurf(workspaceId, workspaceRoot) {
|
|
|
448
538
|
await node_fs.access(node_path.dirname(configPath));
|
|
449
539
|
const current = await readJsonForWrite(configPath);
|
|
450
540
|
current.mcpServers = wrapAllServers({ intutic: buildIntuticMcpEntry(workspaceRoot), ...(current.mcpServers ?? {}) }, workspaceId, workspaceRoot);
|
|
451
|
-
await writeJsonFile(configPath, current);
|
|
541
|
+
await writeJsonFile(configPath, current, workspaceRoot);
|
|
452
542
|
log.info({ action: 'windsurf_mcp_injected' }, 'Windsurf mcp_config.json updated');
|
|
453
543
|
}
|
|
454
544
|
catch {
|
|
@@ -469,14 +559,16 @@ async function injectContinue(workspaceId, workspaceRoot) {
|
|
|
469
559
|
// Wrap existing non-intutic servers — routed through the same
|
|
470
560
|
// wrapWithProxy used by every other harness, so the stdio-only guard and
|
|
471
561
|
// --server-name threading stay in one place instead of two.
|
|
562
|
+
// The whole entry goes in, so the wrap's `__intutic_original` holds every
|
|
563
|
+
// key `intutic disconnect` has to put back.
|
|
472
564
|
current.mcpServers = current.mcpServers.map((s) => {
|
|
473
|
-
const
|
|
474
|
-
return { name
|
|
565
|
+
const { name, ...entry } = s;
|
|
566
|
+
return { name, ...wrapWithProxy(entry, workspaceId, workspaceRoot, name) };
|
|
475
567
|
});
|
|
476
568
|
// Add Intutic MCP server
|
|
477
569
|
const entry = buildIntuticMcpEntry(workspaceRoot);
|
|
478
570
|
current.mcpServers.unshift({ name: 'intutic', ...entry });
|
|
479
|
-
await writeJsonFile(configPath, current);
|
|
571
|
+
await writeJsonFile(configPath, current, workspaceRoot);
|
|
480
572
|
log.info({ action: 'continue_mcp_injected' }, 'Continue ~/.continue/config.json updated');
|
|
481
573
|
}
|
|
482
574
|
catch {
|
|
@@ -512,6 +604,7 @@ async function injectGooseAppendOnly(configPath, existingYaml, workspaceId, prox
|
|
|
512
604
|
// (see writeJsonFile's doc comment for why: this runs every sync cycle now).
|
|
513
605
|
return;
|
|
514
606
|
}
|
|
607
|
+
await keepOriginal(configPath, node_os.homedir());
|
|
515
608
|
await node_fs.mkdir(node_path.dirname(configPath), { recursive: true });
|
|
516
609
|
await node_fs.writeFile(configPath, text, 'utf-8');
|
|
517
610
|
log.info({ action: 'goose_mcp_injected', mode: 'append_only_fallback' }, 'Goose config.yaml updated (append-only fallback)');
|
|
@@ -603,6 +696,7 @@ async function injectGoose(workspaceId, workspaceRoot) {
|
|
|
603
696
|
// (see writeJsonFile's doc comment for why: this runs every sync cycle now).
|
|
604
697
|
return;
|
|
605
698
|
}
|
|
699
|
+
await keepOriginal(configPath, workspaceRoot);
|
|
606
700
|
await node_fs.mkdir(node_path.dirname(configPath), { recursive: true });
|
|
607
701
|
await node_fs.writeFile(configPath, doc.toString(), 'utf-8');
|
|
608
702
|
log.info({ action: 'goose_mcp_injected', mode: 'yaml' }, 'Goose config.yaml updated (structural YAML edit)');
|
|
@@ -613,7 +707,7 @@ async function injectOpenHands(workspaceId, workspaceRoot) {
|
|
|
613
707
|
try {
|
|
614
708
|
const current = await readJsonForWrite(configPath);
|
|
615
709
|
current.mcpServers = wrapAllServers({ intutic: buildIntuticMcpEntry(workspaceRoot), ...(current.mcpServers ?? {}) }, workspaceId, workspaceRoot);
|
|
616
|
-
await writeJsonFile(configPath, current);
|
|
710
|
+
await writeJsonFile(configPath, current, workspaceRoot);
|
|
617
711
|
log.info({ action: 'openhands_mcp_injected' }, 'OpenHands .openhands/mcp.json updated');
|
|
618
712
|
}
|
|
619
713
|
catch (err) {
|
|
@@ -644,7 +738,7 @@ async function injectMuse(workspaceId, workspaceRoot) {
|
|
|
644
738
|
current.mcp_servers = wrapAllServers({ intutic: buildIntuticMcpEntry(workspaceRoot), ...(current.mcp_servers ?? {}) }, workspaceId, workspaceRoot);
|
|
645
739
|
if (current.schema_version === undefined)
|
|
646
740
|
current.schema_version = 1;
|
|
647
|
-
await writeJsonFile(configPath, current);
|
|
741
|
+
await writeJsonFile(configPath, current, workspaceRoot);
|
|
648
742
|
log.info({ action: 'muse_mcp_injected' }, 'Muse Code ~/.config/muse/settings.json mcp_servers updated');
|
|
649
743
|
}
|
|
650
744
|
/**
|
|
@@ -657,7 +751,7 @@ async function injectMuse(workspaceId, workspaceRoot) {
|
|
|
657
751
|
* honest limit `injectGooseAppendOnly`/`discoverGooseLineScanFallback`
|
|
658
752
|
* document for their own fallback.
|
|
659
753
|
*/
|
|
660
|
-
async function injectGrokConfigAppendOnly(configPath, existingToml, workspaceId, proxyBin) {
|
|
754
|
+
async function injectGrokConfigAppendOnly(configPath, existingToml, workspaceId, proxyBin, workspaceRoot) {
|
|
661
755
|
let text = existingToml;
|
|
662
756
|
if (!text.includes('[mcp_servers.intutic]') && !text.includes('[mcp_servers."intutic"]')) {
|
|
663
757
|
const block = [
|
|
@@ -669,6 +763,7 @@ async function injectGrokConfigAppendOnly(configPath, existingToml, workspaceId,
|
|
|
669
763
|
}
|
|
670
764
|
if (text === existingToml)
|
|
671
765
|
return;
|
|
766
|
+
await keepOriginal(configPath, workspaceRoot);
|
|
672
767
|
await node_fs.mkdir(node_path.dirname(configPath), { recursive: true });
|
|
673
768
|
await node_fs.writeFile(configPath, text, 'utf-8');
|
|
674
769
|
log.info({ action: 'grok_mcp_injected', mode: 'append_only_fallback' }, 'Grok config.toml updated (append-only fallback)');
|
|
@@ -711,7 +806,7 @@ async function injectGrokConfig(configPath, workspaceId, workspaceRoot) {
|
|
|
711
806
|
}
|
|
712
807
|
catch (err) {
|
|
713
808
|
log.warn({ action: 'grok_toml_unparseable', path: configPath, err: err.message }, 'Grok config.toml did not parse as TOML — falling back to append-only text injection');
|
|
714
|
-
await injectGrokConfigAppendOnly(configPath, existingToml, workspaceId, proxyBin);
|
|
809
|
+
await injectGrokConfigAppendOnly(configPath, existingToml, workspaceId, proxyBin, workspaceRoot);
|
|
715
810
|
return;
|
|
716
811
|
}
|
|
717
812
|
const servers = doc.mcp_servers && typeof doc.mcp_servers === 'object' ? doc.mcp_servers : {};
|
|
@@ -736,6 +831,7 @@ async function injectGrokConfig(configPath, workspaceId, workspaceRoot) {
|
|
|
736
831
|
return;
|
|
737
832
|
}
|
|
738
833
|
doc.mcp_servers = servers;
|
|
834
|
+
await keepOriginal(configPath, workspaceRoot);
|
|
739
835
|
await node_fs.mkdir(node_path.dirname(configPath), { recursive: true });
|
|
740
836
|
const tmp = configPath + '.intutic-tmp';
|
|
741
837
|
await node_fs.writeFile(tmp, stringifyToml(doc), 'utf-8');
|
|
@@ -747,7 +843,7 @@ async function injectGrok(workspaceId, workspaceRoot) {
|
|
|
747
843
|
await injectGrokConfig(grokUserConfigPath(), workspaceId, workspaceRoot);
|
|
748
844
|
}
|
|
749
845
|
/** The proxy binary path `resolveProxyBin` returns, in either of its two forms. */
|
|
750
|
-
const PROXY_BIN_PATTERN = /(?:^|[\\/])(?:@intutic[\\/]mcp-governance-proxy|packages[\\/]mcp-proxy)[\\/]dist[\\/]index\.js$/;
|
|
846
|
+
export const PROXY_BIN_PATTERN = /(?:^|[\\/])(?:@intutic[\\/]mcp-governance-proxy|packages[\\/]mcp-proxy)[\\/]dist[\\/]index\.js$/;
|
|
751
847
|
/**
|
|
752
848
|
* Whether an OpenCode entry is already fronted by the governance proxy, read
|
|
753
849
|
* from the command SHAPE — `["node", <proxy bin>, "--workspace-id", …]` —
|
|
@@ -858,6 +954,8 @@ async function injectOpenCodeConfig(configPath, workspaceId, workspaceRoot) {
|
|
|
858
954
|
return;
|
|
859
955
|
config['mcp'] = next;
|
|
860
956
|
const indent = raw.match(/^([ \t]+)\S/m)?.[1] ?? 2;
|
|
957
|
+
// OpenCode entries cannot carry a marker, so disconnect needs the file as it was.
|
|
958
|
+
await keepOriginal(configPath, workspaceRoot);
|
|
861
959
|
const tmp = configPath + '.intutic-tmp';
|
|
862
960
|
await node_fs.writeFile(tmp, JSON.stringify(config, null, indent) + '\n', 'utf-8');
|
|
863
961
|
await node_fs.rename(tmp, configPath);
|
|
@@ -910,15 +1008,31 @@ async function discoverJsonObjectHarness(harness, filePath) {
|
|
|
910
1008
|
}
|
|
911
1009
|
return out;
|
|
912
1010
|
}
|
|
913
|
-
/** Claude Code's
|
|
914
|
-
* project
|
|
1011
|
+
/** Claude Code's servers as Claude Code resolves them: user scope
|
|
1012
|
+
* (`~/.claude.json`), project scope (the repo's `.mcp.json`) and local scope
|
|
1013
|
+
* (this project's entry in `~/.claude.json`), each narrower scope winning a
|
|
1014
|
+
* name — so a project server governed by a local-scope shadow reports as
|
|
1015
|
+
* wrapped. */
|
|
915
1016
|
async function discoverClaudeCode(workspaceRoot) {
|
|
916
1017
|
const filePath = claudeCodeConfigPath();
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
const
|
|
920
|
-
const servers = { ...(current.
|
|
921
|
-
|
|
1018
|
+
const current = existsSync(filePath) ? await readJsonFile(filePath, {}) : {};
|
|
1019
|
+
const projectServers = await readProjectScopeServers(workspaceRoot);
|
|
1020
|
+
const local = current.projects?.[workspaceRoot]?.mcpServers ?? {};
|
|
1021
|
+
const servers = { ...(current.mcpServers ?? {}), ...projectServers, ...local };
|
|
1022
|
+
const approval = Object.keys(projectServers).length > 0 ? await projectServerApproval(workspaceRoot, current) : null;
|
|
1023
|
+
return Object.entries(servers).map(([name, entry]) => {
|
|
1024
|
+
const found = { server: name, harness: 'claude-code', ...classifyEntry(entry) };
|
|
1025
|
+
// A project server Claude Code will start, with no copy of ours in front of it.
|
|
1026
|
+
if (approval && name in projectServers && !(name in local) && approval.approved(name)) {
|
|
1027
|
+
const reason = shadowBlocker(projectServers[name]);
|
|
1028
|
+
if (reason)
|
|
1029
|
+
found.ungovernedReason = `Claude Code project server not governed: ${reason}`;
|
|
1030
|
+
}
|
|
1031
|
+
if (approval?.blockedReason && name in projectServers && !(name in local)) {
|
|
1032
|
+
found.ungovernedReason = `Claude Code project server not governed: ${approval.blockedReason}`;
|
|
1033
|
+
}
|
|
1034
|
+
return found;
|
|
1035
|
+
});
|
|
922
1036
|
}
|
|
923
1037
|
/** Muse Code's `~/.config/muse/settings.json` keeps servers under `mcp_servers`,
|
|
924
1038
|
* not `mcpServers` — otherwise the same JSON-map shape `discoverJsonObjectHarness`
|
|
@@ -1151,32 +1265,36 @@ export async function discoverMcpServers(workspaceRoot) {
|
|
|
1151
1265
|
*
|
|
1152
1266
|
* Non-fatal: a failure in one harness does not prevent other harnesses from being updated.
|
|
1153
1267
|
*
|
|
1154
|
-
* Called
|
|
1155
|
-
*
|
|
1156
|
-
* (services/sync-daemon/src/syncLoop.ts) so it is a continuous invariant
|
|
1157
|
-
* rather than a one-shot — a server a user adds after their first `connect`
|
|
1268
|
+
* Called by `intutic connect` (tools/cli) on every sync cycle, so it is a
|
|
1269
|
+
* continuous invariant rather than a one-shot — a server a user adds after their first `connect`
|
|
1158
1270
|
* still gets wrapped on the next cycle. Safe to call every cycle because
|
|
1159
1271
|
* `writeJsonFile` is write-if-changed: an already-wrapped, unchanged config
|
|
1160
1272
|
* writes zero bytes.
|
|
1161
1273
|
*
|
|
1162
1274
|
* @param workspaceId - The workspace ID for policy lookups and event attribution.
|
|
1163
1275
|
* @param workspaceRoot - Absolute path to the project workspace root.
|
|
1276
|
+
* @param options.skip - Harness ids to leave alone: the ones `intutic
|
|
1277
|
+
* disconnect --harness` took out.
|
|
1164
1278
|
*/
|
|
1165
|
-
export async function injectMcpServer(workspaceRoot, workspaceId = 'unknown') {
|
|
1279
|
+
export async function injectMcpServer(workspaceRoot, workspaceId = 'unknown', options = {}) {
|
|
1166
1280
|
log.info({ action: 'mcp_inject_start', workspaceRoot, workspaceId }, 'Starting MCP server injection');
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
]
|
|
1281
|
+
const targets = [
|
|
1282
|
+
['claude-code', injectClaudeCode],
|
|
1283
|
+
['claude-desktop', injectClaudeDesktop],
|
|
1284
|
+
['cursor', injectCursor],
|
|
1285
|
+
['cline', injectCline],
|
|
1286
|
+
['windsurf', injectWindsurf],
|
|
1287
|
+
['continue', injectContinue],
|
|
1288
|
+
['goose', injectGoose],
|
|
1289
|
+
['openhands', injectOpenHands],
|
|
1290
|
+
['muse-code', injectMuse],
|
|
1291
|
+
['grok', injectGrok],
|
|
1292
|
+
['opencode', injectOpenCode],
|
|
1293
|
+
];
|
|
1294
|
+
const skip = new Set(options.skip ?? []);
|
|
1295
|
+
await Promise.allSettled(targets
|
|
1296
|
+
.filter(([target]) => !skip.has(target))
|
|
1297
|
+
.map(([target, inject]) => inject(workspaceId, workspaceRoot).catch((err) => log.error({ err: err.message, target }, 'MCP injection failed'))));
|
|
1180
1298
|
log.info({ action: 'mcp_inject_complete', workspaceRoot }, 'MCP server injection complete');
|
|
1181
1299
|
}
|
|
1182
1300
|
//# sourceMappingURL=mcpAutoWrite.js.map
|