@cybedefend/vibedefend 1.5.0 → 1.6.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/README.md +67 -2
- package/dist/api/gateway-request.js +49 -0
- package/dist/api/gateway-request.js.map +1 -0
- package/dist/api/projects.js +67 -0
- package/dist/api/projects.js.map +1 -1
- package/dist/api/repository-link.js +157 -0
- package/dist/api/repository-link.js.map +1 -0
- package/dist/auth/auth-store.js +107 -25
- package/dist/auth/auth-store.js.map +1 -1
- package/dist/auth/auth.js +21 -0
- package/dist/auth/auth.js.map +1 -1
- package/dist/clients/cursor.js +5 -1
- package/dist/clients/cursor.js.map +1 -1
- package/dist/clients/vscode.js +38 -29
- package/dist/clients/vscode.js.map +1 -1
- package/dist/commands/mcp/allow.js +219 -0
- package/dist/commands/mcp/allow.js.map +1 -0
- package/dist/commands/mcp/connect.js +337 -0
- package/dist/commands/mcp/connect.js.map +1 -0
- package/dist/commands/mcp/index.js +170 -0
- package/dist/commands/mcp/index.js.map +1 -0
- package/dist/commands/mcp/proxy.js +264 -0
- package/dist/commands/mcp/proxy.js.map +1 -0
- package/dist/commands/mcp/scan.js +349 -0
- package/dist/commands/mcp/scan.js.map +1 -0
- package/dist/commands/mcp/sync.js +215 -0
- package/dist/commands/mcp/sync.js.map +1 -0
- package/dist/config.js +6 -0
- package/dist/config.js.map +1 -1
- package/dist/diagnostics.js +78 -12
- package/dist/diagnostics.js.map +1 -1
- package/dist/doctor.js +19 -0
- package/dist/doctor.js.map +1 -1
- package/dist/guards-evaluator/analysis-types.js +27 -0
- package/dist/guards-evaluator/analysis-types.js.map +1 -0
- package/dist/guards-evaluator/command-analysis.js +572 -0
- package/dist/guards-evaluator/command-analysis.js.map +1 -0
- package/dist/guards-evaluator/command-view.js +586 -0
- package/dist/guards-evaluator/command-view.js.map +1 -0
- package/dist/guards-evaluator/env.js +11 -0
- package/dist/guards-evaluator/env.js.map +1 -0
- package/dist/guards-evaluator/evaluate.js +91 -41
- package/dist/guards-evaluator/evaluate.js.map +1 -1
- package/dist/guards-evaluator/index.js +2 -1
- package/dist/guards-evaluator/index.js.map +1 -1
- package/dist/guards-evaluator/inline-code.js +259 -0
- package/dist/guards-evaluator/inline-code.js.map +1 -0
- package/dist/guards-evaluator/normalize.js +716 -0
- package/dist/guards-evaluator/normalize.js.map +1 -0
- package/dist/guards-evaluator/preprocess.js +13 -5
- package/dist/guards-evaluator/preprocess.js.map +1 -1
- package/dist/guards-evaluator/redact.js +19 -0
- package/dist/guards-evaluator/redact.js.map +1 -1
- package/dist/guards-evaluator/regex.js +25 -3
- package/dist/guards-evaluator/regex.js.map +1 -1
- package/dist/guards-evaluator/safe-commands.js +155 -0
- package/dist/guards-evaluator/safe-commands.js.map +1 -0
- package/dist/guards-evaluator/triage.js +315 -0
- package/dist/guards-evaluator/triage.js.map +1 -0
- package/dist/hook-runner.js +24679 -5079
- package/dist/hooks/install.js +38 -0
- package/dist/hooks/install.js.map +1 -1
- package/dist/hooks/runtime/config.js +38 -0
- package/dist/hooks/runtime/config.js.map +1 -1
- package/dist/hooks/runtime/emit.js +94 -61
- package/dist/hooks/runtime/emit.js.map +1 -1
- package/dist/hooks/runtime/fetch-rules.js +22 -7
- package/dist/hooks/runtime/fetch-rules.js.map +1 -1
- package/dist/hooks/runtime/gateway-http-error.js +34 -0
- package/dist/hooks/runtime/gateway-http-error.js.map +1 -0
- package/dist/hooks/runtime/guard-rules-cache.js +139 -17
- package/dist/hooks/runtime/guard-rules-cache.js.map +1 -1
- package/dist/hooks/runtime/guard-violations-buffer.js +321 -48
- package/dist/hooks/runtime/guard-violations-buffer.js.map +1 -1
- package/dist/hooks/runtime/judge-cache.js +6 -0
- package/dist/hooks/runtime/judge-cache.js.map +1 -1
- package/dist/hooks/runtime/judge-triage.js +193 -0
- package/dist/hooks/runtime/judge-triage.js.map +1 -0
- package/dist/hooks/runtime/judge.js +7 -1
- package/dist/hooks/runtime/judge.js.map +1 -1
- package/dist/hooks/runtime/pre-compact.js +12 -6
- package/dist/hooks/runtime/pre-compact.js.map +1 -1
- package/dist/hooks/runtime/resolve.js +90 -8
- package/dist/hooks/runtime/resolve.js.map +1 -1
- package/dist/hooks/runtime/retry-policy.js +57 -0
- package/dist/hooks/runtime/retry-policy.js.map +1 -0
- package/dist/hooks/runtime/review-sessions.js +74 -0
- package/dist/hooks/runtime/review-sessions.js.map +1 -0
- package/dist/hooks/runtime/script-resolve.js +695 -0
- package/dist/hooks/runtime/script-resolve.js.map +1 -0
- package/dist/hooks/runtime/session-activation.js +5 -1
- package/dist/hooks/runtime/session-activation.js.map +1 -1
- package/dist/hooks/runtime/session-review.js +163 -45
- package/dist/hooks/runtime/session-review.js.map +1 -1
- package/dist/hooks/runtime/session-start.js +2 -1
- package/dist/hooks/runtime/session-start.js.map +1 -1
- package/dist/hooks/runtime/session-writes.js +232 -0
- package/dist/hooks/runtime/session-writes.js.map +1 -0
- package/dist/hooks/runtime/sniff.js +26 -7
- package/dist/hooks/runtime/sniff.js.map +1 -1
- package/dist/hooks/runtime/target-resolve.js +336 -0
- package/dist/hooks/runtime/target-resolve.js.map +1 -0
- package/dist/hooks/runtime/unmapped-tools.js +93 -0
- package/dist/hooks/runtime/unmapped-tools.js.map +1 -0
- package/dist/hooks/runtime/usage-block.js +4 -0
- package/dist/hooks/runtime/usage-block.js.map +1 -1
- package/dist/hooks/runtime/user-prompt-submit.js +2 -1
- package/dist/hooks/runtime/user-prompt-submit.js.map +1 -1
- package/dist/hooks/runtime/wire-text.js +41 -0
- package/dist/hooks/runtime/wire-text.js.map +1 -0
- package/dist/hooks/runtime/work-dir.js +84 -0
- package/dist/hooks/runtime/work-dir.js.map +1 -0
- package/dist/index.js +45 -12
- package/dist/index.js.map +1 -1
- package/dist/install.js +174 -11
- package/dist/install.js.map +1 -1
- package/dist/link.js +74 -48
- package/dist/link.js.map +1 -1
- package/dist/login.js +77 -11
- package/dist/login.js.map +1 -1
- package/dist/mcp/auth/agent-sign-ins.js +174 -0
- package/dist/mcp/auth/agent-sign-ins.js.map +1 -0
- package/dist/mcp/auth/callback.js +200 -0
- package/dist/mcp/auth/callback.js.map +1 -0
- package/dist/mcp/auth/connect.js +392 -0
- package/dist/mcp/auth/connect.js.map +1 -0
- package/dist/mcp/auth/discovery.js +399 -0
- package/dist/mcp/auth/discovery.js.map +1 -0
- package/dist/mcp/auth/oauth.js +326 -0
- package/dist/mcp/auth/oauth.js.map +1 -0
- package/dist/mcp/auth/probe.js +128 -0
- package/dist/mcp/auth/probe.js.map +1 -0
- package/dist/mcp/auth/secrets.js +446 -0
- package/dist/mcp/auth/secrets.js.map +1 -0
- package/dist/mcp/auth/store.js +260 -0
- package/dist/mcp/auth/store.js.map +1 -0
- package/dist/mcp/auth/tokenSource.js +55 -0
- package/dist/mcp/auth/tokenSource.js.map +1 -0
- package/dist/mcp/config/atomicWrite.js +98 -0
- package/dist/mcp/config/atomicWrite.js.map +1 -0
- package/dist/mcp/config/clients.js +171 -0
- package/dist/mcp/config/clients.js.map +1 -0
- package/dist/mcp/config/cybedefend-servers.js +48 -0
- package/dist/mcp/config/cybedefend-servers.js.map +1 -0
- package/dist/mcp/config/discover.js +114 -0
- package/dist/mcp/config/discover.js.map +1 -0
- package/dist/mcp/config/hook.js +123 -0
- package/dist/mcp/config/hook.js.map +1 -0
- package/dist/mcp/config/index.js +11 -0
- package/dist/mcp/config/index.js.map +1 -0
- package/dist/mcp/config/install.js +132 -0
- package/dist/mcp/config/install.js.map +1 -0
- package/dist/mcp/config/installed.js +37 -0
- package/dist/mcp/config/installed.js.map +1 -0
- package/dist/mcp/config/jsonEdit.js +119 -0
- package/dist/mcp/config/jsonEdit.js.map +1 -0
- package/dist/mcp/config/lock.js +102 -0
- package/dist/mcp/config/lock.js.map +1 -0
- package/dist/mcp/config/proxyBinary.js +211 -0
- package/dist/mcp/config/proxyBinary.js.map +1 -0
- package/dist/mcp/config/rewrite.js +388 -0
- package/dist/mcp/config/rewrite.js.map +1 -0
- package/dist/mcp/config/types.js +34 -0
- package/dist/mcp/config/types.js.map +1 -0
- package/dist/mcp/proxy/audit.js +83 -0
- package/dist/mcp/proxy/audit.js.map +1 -0
- package/dist/mcp/proxy/authInterceptor.js +119 -0
- package/dist/mcp/proxy/authInterceptor.js.map +1 -0
- package/dist/mcp/proxy/expand.js +93 -0
- package/dist/mcp/proxy/expand.js.map +1 -0
- package/dist/mcp/proxy/inject.js +397 -0
- package/dist/mcp/proxy/inject.js.map +1 -0
- package/dist/mcp/proxy/intercept.js +37 -0
- package/dist/mcp/proxy/intercept.js.map +1 -0
- package/dist/mcp/proxy/jsonrpc.js +153 -0
- package/dist/mcp/proxy/jsonrpc.js.map +1 -0
- package/dist/mcp/proxy/log.js +109 -0
- package/dist/mcp/proxy/log.js.map +1 -0
- package/dist/mcp/proxy/paths.js +53 -0
- package/dist/mcp/proxy/paths.js.map +1 -0
- package/dist/mcp/proxy/proxy.js +510 -0
- package/dist/mcp/proxy/proxy.js.map +1 -0
- package/dist/mcp/proxy/spec.js +203 -0
- package/dist/mcp/proxy/spec.js.map +1 -0
- package/dist/mcp/proxy/sse.js +77 -0
- package/dist/mcp/proxy/sse.js.map +1 -0
- package/dist/mcp/proxy/upstream.js +2 -0
- package/dist/mcp/proxy/upstream.js.map +1 -0
- package/dist/mcp/proxy/upstreamHttp.js +499 -0
- package/dist/mcp/proxy/upstreamHttp.js.map +1 -0
- package/dist/mcp/proxy/upstreamStdio.js +188 -0
- package/dist/mcp/proxy/upstreamStdio.js.map +1 -0
- package/dist/mcp/proxy/upstreams.js +143 -0
- package/dist/mcp/proxy/upstreams.js.map +1 -0
- package/dist/mcp/proxy/verifyInterceptor.js +559 -0
- package/dist/mcp/proxy/verifyInterceptor.js.map +1 -0
- package/dist/mcp/proxy/windowsCommand.js +99 -0
- package/dist/mcp/proxy/windowsCommand.js.map +1 -0
- package/dist/mcp/scan/args.js +228 -0
- package/dist/mcp/scan/args.js.map +1 -0
- package/dist/mcp/scan/results.js +253 -0
- package/dist/mcp/scan/results.js.map +1 -0
- package/dist/mcp/scan/types.js +40 -0
- package/dist/mcp/scan/types.js.map +1 -0
- package/dist/mcp/verify/api.js +475 -0
- package/dist/mcp/verify/api.js.map +1 -0
- package/dist/mcp/verify/backend.js +143 -0
- package/dist/mcp/verify/backend.js.map +1 -0
- package/dist/mcp/verify/cache.js +182 -0
- package/dist/mcp/verify/cache.js.map +1 -0
- package/dist/mcp/verify/contract-errors.js +198 -0
- package/dist/mcp/verify/contract-errors.js.map +1 -0
- package/dist/mcp/verify/digest.js +91 -0
- package/dist/mcp/verify/digest.js.map +1 -0
- package/dist/mcp/verify/factory.js +56 -0
- package/dist/mcp/verify/factory.js.map +1 -0
- package/dist/mcp/verify/identity.js +173 -0
- package/dist/mcp/verify/identity.js.map +1 -0
- package/dist/mcp/verify/policy-store.js +63 -0
- package/dist/mcp/verify/policy-store.js.map +1 -0
- package/dist/mcp/verify/policy.js +158 -0
- package/dist/mcp/verify/policy.js.map +1 -0
- package/dist/mcp/verify/pool.js +27 -0
- package/dist/mcp/verify/pool.js.map +1 -0
- package/dist/mcp/verify/token-refresh.js +44 -0
- package/dist/mcp/verify/token-refresh.js.map +1 -0
- package/dist/mcp/verify/token.js +67 -0
- package/dist/mcp/verify/token.js.map +1 -0
- package/dist/mcp/verify/verifier.js +1009 -0
- package/dist/mcp/verify/verifier.js.map +1 -0
- package/dist/org-gate.js +1 -1
- package/dist/org-gate.js.map +1 -1
- package/dist/org.js +146 -0
- package/dist/org.js.map +1 -0
- package/dist/project-create.js +129 -0
- package/dist/project-create.js.map +1 -0
- package/dist/prompts.js +21 -0
- package/dist/prompts.js.map +1 -1
- package/dist/re2.wasm +0 -0
- package/dist/status.js +61 -0
- package/dist/status.js.map +1 -1
- package/dist/uninstall.js +49 -4
- package/dist/uninstall.js.map +1 -1
- package/dist/utils.js +62 -6
- package/dist/utils.js.map +1 -1
- package/package.json +10 -3
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Putting the proxy in front of the servers a user has configured, and taking
|
|
3
|
+
* it back out.
|
|
4
|
+
*
|
|
5
|
+
* After a wrap, a client's config no longer holds the URL, the command or the
|
|
6
|
+
* headers of a server: it holds `{"command":"vibedefend","args":["mcp",
|
|
7
|
+
* "--name","<id>"]}`, and the original entry moves to
|
|
8
|
+
* `~/.cybedefend/mcp/upstreams.json`. That is what lets `uninstall` restore
|
|
9
|
+
* the original from a record of OURS rather than from a backup file the user
|
|
10
|
+
* may have edited, moved or cleaned up in the meantime.
|
|
11
|
+
*
|
|
12
|
+
* ─── What is deliberately left alone ────────────────────────────────────────
|
|
13
|
+
*
|
|
14
|
+
* - a `projects.<path>.mcpServers` section: project-scoped, out of bounds per
|
|
15
|
+
* section 0, even though the file it lives in is the user's own;
|
|
16
|
+
* - an entry already wrapped: wrapping twice would bury the original;
|
|
17
|
+
* - an entry whose transport we could not read: wrapping it would replace a
|
|
18
|
+
* working server with a proxy that cannot reach it;
|
|
19
|
+
* - a JSONC file: `jsonEdit.ts` refuses to round-trip a document with
|
|
20
|
+
* comments rather than silently dropping them.
|
|
21
|
+
*
|
|
22
|
+
* ─── The limit of byte-for-byte ─────────────────────────────────────────────
|
|
23
|
+
*
|
|
24
|
+
* `install` then `uninstall` reproduces the file exactly: the indentation,
|
|
25
|
+
* the newline style, the trailing newline and the key order all survive,
|
|
26
|
+
* because the shape is measured on the way in and the untouched parts
|
|
27
|
+
* round-trip through `JSON.parse`/`JSON.stringify` unchanged. The one thing
|
|
28
|
+
* that does not survive is an escape sequence spelled differently from what
|
|
29
|
+
* `JSON.stringify` would emit: `"\u00e9"` comes back as the raw letter. Both are the
|
|
30
|
+
* same string; only the bytes differ. Fixing that needs a full concrete
|
|
31
|
+
* syntax tree, which is not worth it for a case no config in the fixtures
|
|
32
|
+
* exhibits.
|
|
33
|
+
*/
|
|
34
|
+
import { copyFileSync, readFileSync } from 'node:fs';
|
|
35
|
+
import { writeFileAtomic } from './atomicWrite.js';
|
|
36
|
+
import { readUpstreams, writeUpstreams, } from '../proxy/upstreams.js';
|
|
37
|
+
import { mcpAdapterFor } from './clients.js';
|
|
38
|
+
import { isCybeDefendMcpUrl } from './cybedefend-servers.js';
|
|
39
|
+
import { discoverMcpConfigs } from './discover.js';
|
|
40
|
+
import { interactivePlaceholders } from '../proxy/expand.js';
|
|
41
|
+
import { readJsonDocument, renderJson } from './jsonEdit.js';
|
|
42
|
+
import { wrappedEntryFor } from './types.js';
|
|
43
|
+
/**
|
|
44
|
+
* What `install` and `sync` both do: wrap what is not wrapped, and forget
|
|
45
|
+
* what the client no longer lists.
|
|
46
|
+
*
|
|
47
|
+
* They are the same operation on purpose. `install` is the first `sync`, and
|
|
48
|
+
* treating them as one thing is what makes running either of them twice
|
|
49
|
+
* harmless.
|
|
50
|
+
*/
|
|
51
|
+
export function planSync(options = {}) {
|
|
52
|
+
const env = options.env ?? process.env;
|
|
53
|
+
const discovery = discoverMcpConfigs(options);
|
|
54
|
+
const upstreams = readUpstreams(env);
|
|
55
|
+
const changes = [];
|
|
56
|
+
const skipped = [];
|
|
57
|
+
const taken = new Set(Object.keys(upstreams.servers));
|
|
58
|
+
for (const entry of discovery.entries) {
|
|
59
|
+
if (entry.wrapped) {
|
|
60
|
+
// An earlier version wrapped CybeDefend's own server: put it back.
|
|
61
|
+
const record = entry.wrappedName ? upstreams.servers[entry.wrappedName] : undefined;
|
|
62
|
+
if (entry.wrappedName &&
|
|
63
|
+
record?.original &&
|
|
64
|
+
record.transport === 'http' &&
|
|
65
|
+
record.url &&
|
|
66
|
+
isCybeDefendMcpUrl(record.url, env)) {
|
|
67
|
+
changes.push({
|
|
68
|
+
kind: 'unwrap',
|
|
69
|
+
location: entry.location,
|
|
70
|
+
name: entry.name,
|
|
71
|
+
upstreamName: entry.wrappedName,
|
|
72
|
+
original: record.original,
|
|
73
|
+
});
|
|
74
|
+
changes.push({ kind: 'forget', upstreamName: entry.wrappedName });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
skipped.push({ entry, reason: 'already-wrapped' });
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (entry.spec?.transport === 'http' && isCybeDefendMcpUrl(entry.spec.url, env)) {
|
|
81
|
+
skipped.push({ entry, reason: 'cybedefend-server' });
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (entry.kind === 'unknown') {
|
|
85
|
+
skipped.push({ entry, reason: 'unreadable-transport' });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (interactiveInputsOf(entry).length > 0) {
|
|
89
|
+
skipped.push({ entry, reason: 'needs-interactive-input' });
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const upstreamName = allocateName(entry, taken);
|
|
93
|
+
taken.add(upstreamName);
|
|
94
|
+
changes.push({ kind: 'wrap', entry, upstreamName });
|
|
95
|
+
}
|
|
96
|
+
// Anything we recorded that no client mentions any more. Only entries the
|
|
97
|
+
// proxy itself installed are forgotten: an entry added by hand to
|
|
98
|
+
// `upstreams.json` is the operator's, not ours.
|
|
99
|
+
const live = new Set(discovery.entries
|
|
100
|
+
.filter((entry) => entry.wrapped && entry.wrappedName)
|
|
101
|
+
.map((entry) => entry.wrappedName));
|
|
102
|
+
for (const [name, record] of Object.entries(upstreams.servers)) {
|
|
103
|
+
if (!record.source)
|
|
104
|
+
continue;
|
|
105
|
+
if (live.has(name))
|
|
106
|
+
continue;
|
|
107
|
+
if (changes.some((change) => change.kind !== 'unwrap' && change.upstreamName === name))
|
|
108
|
+
continue;
|
|
109
|
+
changes.push({ kind: 'forget', upstreamName: name });
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
changes,
|
|
113
|
+
skipped,
|
|
114
|
+
problems: discovery.problems,
|
|
115
|
+
files: [...new Set(changes.flatMap((change) => filesOf(change)))],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/** The inverse: put every original back where it came from. */
|
|
119
|
+
export function planUninstall(options = {}) {
|
|
120
|
+
const discovery = discoverMcpConfigs(options);
|
|
121
|
+
const upstreams = readUpstreams(options.env ?? process.env);
|
|
122
|
+
const changes = [];
|
|
123
|
+
const skipped = [];
|
|
124
|
+
for (const entry of discovery.entries) {
|
|
125
|
+
if (!entry.wrapped || !entry.wrappedName)
|
|
126
|
+
continue;
|
|
127
|
+
const record = upstreams.servers[entry.wrappedName];
|
|
128
|
+
if (!record?.original) {
|
|
129
|
+
// Wrapped, but we have no record of what it was. Leaving it is the only
|
|
130
|
+
// safe move: guessing an original would hand the user a server that
|
|
131
|
+
// never existed.
|
|
132
|
+
skipped.push({ entry, reason: 'not-ours' });
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
changes.push({
|
|
136
|
+
kind: 'unwrap',
|
|
137
|
+
location: entry.location,
|
|
138
|
+
name: entry.name,
|
|
139
|
+
upstreamName: entry.wrappedName,
|
|
140
|
+
original: record.original,
|
|
141
|
+
});
|
|
142
|
+
changes.push({ kind: 'forget', upstreamName: entry.wrappedName });
|
|
143
|
+
}
|
|
144
|
+
// Records the proxy installed that no client wraps any more (the entry was
|
|
145
|
+
// rewritten in clear by an installer or by hand): nothing to restore, but
|
|
146
|
+
// left behind they show up as phantom servers in `scan`.
|
|
147
|
+
for (const [name, record] of Object.entries(upstreams.servers)) {
|
|
148
|
+
if (!record.source)
|
|
149
|
+
continue;
|
|
150
|
+
if (changes.some((change) => change.kind !== 'unwrap' && change.upstreamName === name))
|
|
151
|
+
continue;
|
|
152
|
+
changes.push({ kind: 'forget', upstreamName: name });
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
changes,
|
|
156
|
+
skipped,
|
|
157
|
+
problems: discovery.problems,
|
|
158
|
+
files: [...new Set(changes.flatMap((change) => filesOf(change)))],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function filesOf(change) {
|
|
162
|
+
if (change.kind === 'wrap')
|
|
163
|
+
return [change.entry.location.file];
|
|
164
|
+
if (change.kind === 'unwrap')
|
|
165
|
+
return [change.location.file];
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The id a wrapped entry points at.
|
|
170
|
+
*
|
|
171
|
+
* The entry's own name, so `upstreams.json` reads like the config it came
|
|
172
|
+
* from. Two clients configuring the same server under the same name share one
|
|
173
|
+
* id — and therefore one credential, which is the point. A collision between
|
|
174
|
+
* genuinely different servers is broken by the client name, then by a counter.
|
|
175
|
+
*/
|
|
176
|
+
/**
|
|
177
|
+
* The `${input:…}` ids anywhere in an entry's own fields.
|
|
178
|
+
*
|
|
179
|
+
* Scanned on the RAW entry rather than the parsed spec: a placeholder can sit
|
|
180
|
+
* in a field the parser does not model, and a value we did not look at is
|
|
181
|
+
* exactly the one that would come back to bite.
|
|
182
|
+
*/
|
|
183
|
+
export function interactiveInputsOf(entry) {
|
|
184
|
+
return interactivePlaceholders(JSON.stringify(entry.raw));
|
|
185
|
+
}
|
|
186
|
+
export function allocateName(entry, taken) {
|
|
187
|
+
const base = sanitize(entry.name);
|
|
188
|
+
if (!taken.has(base))
|
|
189
|
+
return base;
|
|
190
|
+
const scoped = `${base}-${entry.location.client}`;
|
|
191
|
+
if (!taken.has(scoped))
|
|
192
|
+
return scoped;
|
|
193
|
+
for (let counter = 2;; counter += 1) {
|
|
194
|
+
const candidate = `${scoped}-${counter}`;
|
|
195
|
+
if (!taken.has(candidate))
|
|
196
|
+
return candidate;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** An id travels on a command line and into a filename; keep it boring. */
|
|
200
|
+
function sanitize(name) {
|
|
201
|
+
const cleaned = name.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '');
|
|
202
|
+
return cleaned === '' ? 'server' : cleaned;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Applies a plan.
|
|
206
|
+
*
|
|
207
|
+
* Every file is read again here rather than reusing the document discovery
|
|
208
|
+
* parsed: between the two, a client may have written to it — `claude mcp add`
|
|
209
|
+
* takes a second — and rewriting from a stale copy would delete whatever it
|
|
210
|
+
* added.
|
|
211
|
+
*/
|
|
212
|
+
export function applySyncPlan(plan, options = {}) {
|
|
213
|
+
const env = options.env ?? process.env;
|
|
214
|
+
const readFileFn = options.readFileFn ?? defaultRead;
|
|
215
|
+
const writeFileFn = options.writeFileFn ?? defaultWrite;
|
|
216
|
+
const copyFileFn = options.copyFileFn ?? copyFileSync;
|
|
217
|
+
const now = options.now ?? (() => new Date());
|
|
218
|
+
const upstreams = readUpstreams(env);
|
|
219
|
+
const contents = new Map();
|
|
220
|
+
const written = [];
|
|
221
|
+
const backups = [];
|
|
222
|
+
const failed = [];
|
|
223
|
+
// Group by file so one document is read, edited and written once, however
|
|
224
|
+
// many entries it holds.
|
|
225
|
+
const byFile = new Map();
|
|
226
|
+
for (const change of plan.changes) {
|
|
227
|
+
for (const file of filesOf(change)) {
|
|
228
|
+
const list = byFile.get(file) ?? [];
|
|
229
|
+
list.push(change);
|
|
230
|
+
byFile.set(file, list);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
for (const [file, changes] of byFile) {
|
|
234
|
+
let text;
|
|
235
|
+
try {
|
|
236
|
+
text = readFileFn(file);
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
for (const change of changes) {
|
|
240
|
+
failed.push({ change, reason: message(error) });
|
|
241
|
+
}
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const parsed = readJsonDocument(text);
|
|
245
|
+
if (!parsed.ok) {
|
|
246
|
+
for (const change of changes)
|
|
247
|
+
failed.push({ change, reason: parsed.problem });
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const document = parsed.document;
|
|
251
|
+
for (const change of changes) {
|
|
252
|
+
if (change.kind === 'wrap') {
|
|
253
|
+
const adapter = mcpAdapterFor(change.entry.location.client);
|
|
254
|
+
const map = mapKeysFor(adapter, document, change.entry.location);
|
|
255
|
+
const sourceMap = readMap(document.value, map);
|
|
256
|
+
const original = sourceMap && Object.hasOwn(sourceMap, change.entry.name)
|
|
257
|
+
? Object.getOwnPropertyDescriptor(sourceMap, change.entry.name)?.value
|
|
258
|
+
: undefined;
|
|
259
|
+
if (typeof original !== 'object' || original === null) {
|
|
260
|
+
failed.push({ change, reason: 'the entry disappeared before it could be wrapped' });
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
setEntry(document.value, map, change.entry.name, adapter.wrappedEntry(change.upstreamName, original));
|
|
264
|
+
Object.defineProperty(upstreams.servers, change.upstreamName, {
|
|
265
|
+
value: recordFor(change.upstreamName, change.entry, original, now()),
|
|
266
|
+
writable: true,
|
|
267
|
+
enumerable: true,
|
|
268
|
+
configurable: true,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
else if (change.kind === 'unwrap') {
|
|
272
|
+
const adapter = mcpAdapterFor(change.location.client);
|
|
273
|
+
const map = mapKeysFor(adapter, document, change.location);
|
|
274
|
+
setEntry(document.value, map, change.name, change.original);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const next = renderJson(document.value, document.format);
|
|
278
|
+
contents.set(file, next);
|
|
279
|
+
if (next === document.original)
|
|
280
|
+
continue;
|
|
281
|
+
written.push(file);
|
|
282
|
+
if (options.dryRun)
|
|
283
|
+
continue;
|
|
284
|
+
if (options.backup) {
|
|
285
|
+
const target = `${file}.vibedefend-${stamp(now())}.bak`;
|
|
286
|
+
try {
|
|
287
|
+
copyFileFn(file, target);
|
|
288
|
+
backups.push(target);
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
// A backup we could not take is not a reason to refuse the change:
|
|
292
|
+
// the original is recorded in upstreams.json, which is what
|
|
293
|
+
// `uninstall` restores from.
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
writeFileFn(file, next);
|
|
297
|
+
}
|
|
298
|
+
for (const change of plan.changes) {
|
|
299
|
+
if (change.kind === 'forget')
|
|
300
|
+
delete upstreams.servers[change.upstreamName];
|
|
301
|
+
}
|
|
302
|
+
if (!options.dryRun)
|
|
303
|
+
writeUpstreams(upstreams, env);
|
|
304
|
+
return { written, backups, contents, upstreams, failed };
|
|
305
|
+
}
|
|
306
|
+
function recordFor(name, entry, original, now) {
|
|
307
|
+
const spec = entry.spec;
|
|
308
|
+
const base = {
|
|
309
|
+
name,
|
|
310
|
+
transport: spec?.transport ?? 'stdio',
|
|
311
|
+
source: {
|
|
312
|
+
client: entry.location.client,
|
|
313
|
+
file: entry.location.file,
|
|
314
|
+
scope: entry.location.scope,
|
|
315
|
+
},
|
|
316
|
+
// The entry exactly as the client had it. This, not the backup file, is
|
|
317
|
+
// what `uninstall` puts back.
|
|
318
|
+
original,
|
|
319
|
+
addedAt: now.toISOString(),
|
|
320
|
+
};
|
|
321
|
+
if (spec?.transport === 'http') {
|
|
322
|
+
return { ...base, url: spec.url, headers: spec.headers };
|
|
323
|
+
}
|
|
324
|
+
if (spec?.transport === 'stdio') {
|
|
325
|
+
return { ...base, command: spec.command, args: spec.args, env: spec.env, cwd: spec.cwd };
|
|
326
|
+
}
|
|
327
|
+
return base;
|
|
328
|
+
}
|
|
329
|
+
function mapKeysFor(adapter, document, location) {
|
|
330
|
+
if (location.scope === 'project-in-user-file' && location.projectPath) {
|
|
331
|
+
return ['projects', location.projectPath, 'mcpServers'];
|
|
332
|
+
}
|
|
333
|
+
const maps = adapter.serverMaps(document, location);
|
|
334
|
+
return maps[0]?.keys ?? ['mcpServers'];
|
|
335
|
+
}
|
|
336
|
+
function readMap(value, keys) {
|
|
337
|
+
let current = value;
|
|
338
|
+
for (const key of keys) {
|
|
339
|
+
if (typeof current !== 'object' || current === null)
|
|
340
|
+
return undefined;
|
|
341
|
+
if (!Object.hasOwn(current, key))
|
|
342
|
+
return undefined;
|
|
343
|
+
current = Object.getOwnPropertyDescriptor(current, key)?.value;
|
|
344
|
+
}
|
|
345
|
+
return typeof current === 'object' && current !== null && !Array.isArray(current)
|
|
346
|
+
? current
|
|
347
|
+
: undefined;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Replaces one entry in place.
|
|
351
|
+
*
|
|
352
|
+
* In place matters: assigning to an existing key keeps its position, so the
|
|
353
|
+
* server stays where the user put it in the file instead of moving to the
|
|
354
|
+
* end.
|
|
355
|
+
*/
|
|
356
|
+
function setEntry(value, keys, name, entry) {
|
|
357
|
+
const map = readMap(value, keys);
|
|
358
|
+
if (!map)
|
|
359
|
+
return;
|
|
360
|
+
// `name` comes from user-owned configuration. Defining an own data
|
|
361
|
+
// property keeps names such as `__proto__` from mutating the map's
|
|
362
|
+
// prototype while preserving the original key position on replacement.
|
|
363
|
+
Object.defineProperty(map, name, {
|
|
364
|
+
value: entry,
|
|
365
|
+
writable: true,
|
|
366
|
+
enumerable: true,
|
|
367
|
+
configurable: true,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
function stamp(date) {
|
|
371
|
+
return date.toISOString().replace(/[:.]/g, '-');
|
|
372
|
+
}
|
|
373
|
+
function message(error) {
|
|
374
|
+
return error instanceof Error ? error.message : String(error);
|
|
375
|
+
}
|
|
376
|
+
function defaultRead(path) {
|
|
377
|
+
return readFileSync(path, 'utf8');
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* A client's own config, so the mode is left alone: these files are the
|
|
381
|
+
* user's, and tightening them to 0600 behind their back would break a setup
|
|
382
|
+
* where a second account reads the same dotfiles.
|
|
383
|
+
*/
|
|
384
|
+
function defaultWrite(path, content) {
|
|
385
|
+
writeFileAtomic(path, content, { mode: 0o644, directoryMode: 0o755 });
|
|
386
|
+
}
|
|
387
|
+
export { wrappedEntryFor };
|
|
388
|
+
//# sourceMappingURL=rewrite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rewrite.js","sourceRoot":"","sources":["../../../src/mcp/config/rewrite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAErD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EACL,aAAa,EACb,cAAc,GAGf,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAA4C,MAAM,eAAe,CAAC;AAC7F,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAgD,MAAM,YAAY,CAAC;AAuC3F;;;;;;;GAOG;AACH,MAAM,UAAU,QAAQ,CAAC,UAAyD,EAAE;IAClF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACvC,MAAM,SAAS,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC9C,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IAErC,MAAM,OAAO,GAAoB,EAAE,CAAC;IACpC,MAAM,OAAO,GAAwB,EAAE,CAAC;IACxC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IAEtD,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;QACtC,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YAClB,mEAAmE;YACnE,MAAM,MAAM,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YACpF,IACE,KAAK,CAAC,WAAW;gBACjB,MAAM,EAAE,QAAQ;gBAChB,MAAM,CAAC,SAAS,KAAK,MAAM;gBAC3B,MAAM,CAAC,GAAG;gBACV,kBAAkB,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,EACnC,CAAC;gBACD,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI,EAAE,QAAQ;oBACd,QAAQ,EAAE,KAAK,CAAC,QAAQ;oBACxB,IAAI,EAAE,KAAK,CAAC,IAAI;oBAChB,YAAY,EAAE,KAAK,CAAC,WAAW;oBAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ;iBAC1B,CAAC,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;gBAClE,SAAS;YACX,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;YACnD,SAAS;QACX,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,EAAE,SAAS,KAAK,MAAM,IAAI,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC;YAChF,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC,CAAC;YACrD,SAAS;QACX,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC7B,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC,CAAC;YACxD,SAAS;QACX,CAAC;QACD,IAAI,mBAAmB,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1C,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC,CAAC;YAC3D,SAAS;QACX,CAAC;QACD,MAAM,YAAY,GAAG,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAChD,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,0EAA0E;IAC1E,kEAAkE;IAClE,gDAAgD;IAChD,MAAM,IAAI,GAAG,IAAI,GAAG,CAClB,SAAS,CAAC,OAAO;SACd,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,WAAW,CAAC;SACrD,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAqB,CAAC,CAC/C,CAAC;IACF,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/D,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,SAAS;QAC7B,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAC7B,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,YAAY,KAAK,IAAI,CAAC;YAAE,SAAS;QACjG,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,OAAO;QACL,OAAO;QACP,OAAO;QACP,QAAQ,EAAE,SAAS,CAAC,QAAQ;QAC5B,KAAK,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;KAClE,CAAC;AACJ,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,aAAa,CAAC,UAAyD,EAAE;IACvF,MAAM,SAAS,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IAC9C,MAAM,SAAS,GAAG,aAAa,CAAC,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC;IAE5D,MAAM,OAAO,GAAoB,EAAE,CAAC;IACpC,MAAM,OAAO,GAAwB,EAAE,CAAC;IAExC,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;QACtC,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW;YAAE,SAAS;QACnD,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QACpD,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC;YACtB,wEAAwE;YACxE,oEAAoE;YACpE,iBAAiB;YACjB,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC,CAAC;YAC5C,SAAS;QACX,CAAC;QACD,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,YAAY,EAAE,KAAK,CAAC,WAAW;YAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ;SAC1B,CAAC,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,2EAA2E;IAC3E,0EAA0E;IAC1E,yDAAyD;IACzD,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/D,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,SAAS;QAC7B,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,MAAM,CAAC,YAAY,KAAK,IAAI,CAAC;YAAE,SAAS;QACjG,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,OAAO;QACL,OAAO;QACP,OAAO;QACP,QAAQ,EAAE,SAAS,CAAC,QAAQ;QAC5B,KAAK,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;KAClE,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,MAAqB;IACpC,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAChE,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC5D,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;;;;GAOG;AACH;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAsB;IACxD,OAAO,uBAAuB,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAsB,EAAE,KAA0B;IAC7E,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAElC,MAAM,MAAM,GAAG,GAAG,IAAI,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;IAClD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IAEtC,KAAK,IAAI,OAAO,GAAG,CAAC,GAAI,OAAO,IAAI,CAAC,EAAE,CAAC;QACrC,MAAM,SAAS,GAAG,GAAG,MAAM,IAAI,OAAO,EAAE,CAAC;QACzC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;IAC9C,CAAC;AACH,CAAC;AAED,2EAA2E;AAC3E,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC/E,OAAO,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC;AAC7C,CAAC;AAyBD;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,IAAc,EAAE,UAAwB,EAAE;IACtE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACvC,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,WAAW,CAAC;IACrD,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,YAAY,CAAC;IACxD,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,YAAY,CAAC;IACtD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAE9C,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC3C,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,MAAM,GAA0B,EAAE,CAAC;IAEzC,0EAA0E;IAC1E,yBAAyB;IACzB,MAAM,MAAM,GAAG,IAAI,GAAG,EAA2B,CAAC;IAClD,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACpC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAClB,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,EAAE,CAAC;QACrC,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;QAC1B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAClD,CAAC;YACD,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;YACf,KAAK,MAAM,MAAM,IAAI,OAAO;gBAAE,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;YAC9E,SAAS;QACX,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QACjC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBAC3B,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;gBAC5D,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;gBACjE,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;gBAC/C,MAAM,QAAQ,GAAG,SAAS,IAAI,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;oBACvE,CAAC,CAAC,MAAM,CAAC,wBAAwB,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK;oBACtE,CAAC,CAAC,SAAS,CAAC;gBACd,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;oBACtD,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,kDAAkD,EAAE,CAAC,CAAC;oBACpF,SAAS;gBACX,CAAC;gBACD,QAAQ,CACN,QAAQ,CAAC,KAAK,EACd,GAAG,EACH,MAAM,CAAC,KAAK,CAAC,IAAI,EACjB,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,YAAY,EAAE,QAAmC,CAAC,CAC/E,CAAC;gBACF,MAAM,CAAC,cAAc,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,YAAY,EAAE;oBAC5D,KAAK,EAAE,SAAS,CACd,MAAM,CAAC,YAAY,EACnB,MAAM,CAAC,KAAK,EACZ,QAAmC,EACnC,GAAG,EAAE,CACN;oBACD,QAAQ,EAAE,IAAI;oBACd,UAAU,EAAE,IAAI;oBAChB,YAAY,EAAE,IAAI;iBACnB,CAAC,CAAC;YACL,CAAC;iBAAM,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACpC,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;gBACtD,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAC3D,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC;QAED,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;QACzD,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACzB,IAAI,IAAI,KAAK,QAAQ,CAAC,QAAQ;YAAE,SAAS;QAEzC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,IAAI,OAAO,CAAC,MAAM;YAAE,SAAS;QAE7B,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,MAAM,GAAG,GAAG,IAAI,eAAe,KAAK,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC;YACxD,IAAI,CAAC;gBACH,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;gBACzB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACvB,CAAC;YAAC,MAAM,CAAC;gBACP,mEAAmE;gBACnE,4DAA4D;gBAC5D,6BAA6B;YAC/B,CAAC;QACH,CAAC;QACD,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;IAC9E,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,MAAM;QAAE,cAAc,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IACpD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;AAC3D,CAAC;AAED,SAAS,SAAS,CAChB,IAAY,EACZ,KAAsB,EACtB,QAAiC,EACjC,GAAS;IAET,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACxB,MAAM,IAAI,GAAkB;QAC1B,IAAI;QACJ,SAAS,EAAE,IAAI,EAAE,SAAS,IAAI,OAAO;QACrC,MAAM,EAAE;YACN,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,MAAM;YAC7B,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,IAAI;YACzB,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK;SAC5B;QACD,wEAAwE;QACxE,8BAA8B;QAC9B,QAAQ;QACR,OAAO,EAAE,GAAG,CAAC,WAAW,EAAE;KAC3B,CAAC;IACF,IAAI,IAAI,EAAE,SAAS,KAAK,MAAM,EAAE,CAAC;QAC/B,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;IAC3D,CAAC;IACD,IAAI,IAAI,EAAE,SAAS,KAAK,OAAO,EAAE,CAAC;QAChC,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3F,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,UAAU,CACjB,OAAyC,EACzC,QAA+E,EAC/E,QAA2B;IAE3B,IAAI,QAAQ,CAAC,KAAK,KAAK,sBAAsB,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;QACtE,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;IAC1D,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,QAAiB,EAAE,QAAQ,CAAC,CAAC;IAC7D,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,OAAO,CAAC,KAA8B,EAAE,IAAuB;IACtE,IAAI,OAAO,GAAY,KAAK,CAAC;IAC7B,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,SAAS,CAAC;QACtE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC;YAAE,OAAO,SAAS,CAAC;QACnD,OAAO,GAAG,MAAM,CAAC,wBAAwB,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC;IACjE,CAAC;IACD,OAAO,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAC/E,CAAC,CAAE,OAAmC;QACtC,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,SAAS,QAAQ,CACf,KAA8B,EAC9B,IAAuB,EACvB,IAAY,EACZ,KAA8B;IAE9B,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACjC,IAAI,CAAC,GAAG;QAAE,OAAO;IACjB,mEAAmE;IACnE,mEAAmE;IACnE,uEAAuE;IACvE,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE;QAC/B,KAAK,EAAE,KAAK;QACZ,QAAQ,EAAE,IAAI;QACd,UAAU,EAAE,IAAI;QAChB,YAAY,EAAE,IAAI;KACnB,CAAC,CAAC;AACL,CAAC;AAED,SAAS,KAAK,CAAC,IAAU;IACvB,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,OAAO,CAAC,KAAc;IAC7B,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,SAAS,YAAY,CAAC,IAAY,EAAE,OAAe;IACjD,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,CAAC;AACxE,CAAC;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** The wrapped shape, and how to recognise it again. */
|
|
2
|
+
export const PROXY_COMMAND = 'vibedefend';
|
|
3
|
+
export function wrappedEntryFor(upstreamName) {
|
|
4
|
+
return { command: PROXY_COMMAND, args: ['mcp', '--name', upstreamName] };
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Reads the upstream id back out of a wrapped entry, or `null`.
|
|
8
|
+
*
|
|
9
|
+
* Used to keep `install` idempotent: an entry already pointing at the proxy
|
|
10
|
+
* is left exactly as it is, never wrapped a second time.
|
|
11
|
+
*/
|
|
12
|
+
export function wrappedUpstreamName(raw) {
|
|
13
|
+
const command = typeof raw.command === 'string' ? raw.command : '';
|
|
14
|
+
if (!isProxyCommand(command))
|
|
15
|
+
return null;
|
|
16
|
+
const args = Array.isArray(raw.args) ? raw.args.map((arg) => String(arg)) : [];
|
|
17
|
+
if (args[0] !== 'mcp')
|
|
18
|
+
return null;
|
|
19
|
+
const index = args.indexOf('--name');
|
|
20
|
+
if (index === -1 || index + 1 >= args.length)
|
|
21
|
+
return null;
|
|
22
|
+
return args[index + 1];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The command may be `vibedefend`, an absolute path to it, or `vibedefend.cmd`
|
|
26
|
+
* on Windows — depending on how it was installed and on who wrote the entry.
|
|
27
|
+
*/
|
|
28
|
+
function isProxyCommand(command) {
|
|
29
|
+
if (command === '')
|
|
30
|
+
return false;
|
|
31
|
+
const base = command.replace(/\\/g, '/').split('/').pop() ?? command;
|
|
32
|
+
return base === PROXY_COMMAND || base === `${PROXY_COMMAND}.cmd` || base === `${PROXY_COMMAND}.exe`;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/mcp/config/types.ts"],"names":[],"mappings":"AAsGA,wDAAwD;AACxD,MAAM,CAAC,MAAM,aAAa,GAAG,YAAY,CAAC;AAE1C,MAAM,UAAU,eAAe,CAAC,YAAoB;IAClD,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,YAAY,CAAC,EAAE,CAAC;AAC3E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAA4B;IAC9D,MAAM,OAAO,GAAG,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IACnE,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/E,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAC1D,OAAO,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;AACzB,CAAC;AAED;;;GAGG;AACH,SAAS,cAAc,CAAC,OAAe;IACrC,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IACjC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,OAAO,CAAC;IACrE,OAAO,IAAI,KAAK,aAAa,IAAI,IAAI,KAAK,GAAG,aAAa,MAAM,IAAI,IAAI,KAAK,GAAG,aAAa,MAAM,CAAC;AACtG,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The local audit trail.
|
|
3
|
+
*
|
|
4
|
+
* One JSONL line per `tools/call`: which server, which tool, its digest, the
|
|
5
|
+
* verdict, and how big the arguments and the result were. Never the
|
|
6
|
+
* arguments, never the result — the plan's rule is that nothing from a
|
|
7
|
+
* `tools/call` leaves the machine, and a file on disk that quotes the
|
|
8
|
+
* arguments of every call is a second copy of everything the user has done,
|
|
9
|
+
* sitting in their home directory waiting to be read by the next thing that
|
|
10
|
+
* gets a foothold.
|
|
11
|
+
*
|
|
12
|
+
* Sizes are kept because they are the one thing that is useful and harmless:
|
|
13
|
+
* "the result of `read_file` was 4 MB" is worth seeing after the fact, and it
|
|
14
|
+
* says nothing about what was in it.
|
|
15
|
+
*
|
|
16
|
+
* Rotation at 50 MB, by renaming rather than truncating: a reader holding the
|
|
17
|
+
* file keeps reading the one it opened instead of finding it emptied
|
|
18
|
+
* underneath.
|
|
19
|
+
*/
|
|
20
|
+
import { appendFileSync, mkdirSync, renameSync, statSync, unlinkSync } from 'node:fs';
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
import { mcpAuditDir } from './paths.js';
|
|
23
|
+
export const ROTATE_AT_BYTES = 50 * 1024 * 1024;
|
|
24
|
+
/** How many rotated files to keep. Older ones are dropped. */
|
|
25
|
+
export const KEEP_ROTATIONS = 3;
|
|
26
|
+
export function createAudit(options = {}) {
|
|
27
|
+
const dir = options.dir ?? mcpAuditDir(options.env ?? process.env);
|
|
28
|
+
const now = options.now ?? (() => new Date());
|
|
29
|
+
const rotateAt = options.rotateAtBytes ?? ROTATE_AT_BYTES;
|
|
30
|
+
const file = join(dir, 'calls.jsonl');
|
|
31
|
+
let ready = false;
|
|
32
|
+
return {
|
|
33
|
+
file,
|
|
34
|
+
record(entry) {
|
|
35
|
+
const line = `${JSON.stringify({ ts: now().toISOString(), ...entry })}\n`;
|
|
36
|
+
try {
|
|
37
|
+
if (!ready) {
|
|
38
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
39
|
+
ready = true;
|
|
40
|
+
}
|
|
41
|
+
rotateIfNeeded(file, rotateAt);
|
|
42
|
+
appendFileSync(file, line, { mode: 0o600 });
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// An audit that throws would take the session down with it, which is
|
|
46
|
+
// a worse outcome than a missing line.
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function rotateIfNeeded(file, rotateAt) {
|
|
52
|
+
let size;
|
|
53
|
+
try {
|
|
54
|
+
size = statSync(file).size;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return; // not there yet
|
|
58
|
+
}
|
|
59
|
+
if (size < rotateAt)
|
|
60
|
+
return;
|
|
61
|
+
// Shift the numbered files up, drop the oldest.
|
|
62
|
+
try {
|
|
63
|
+
unlinkSync(`${file}.${KEEP_ROTATIONS}`);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
/* not there */
|
|
67
|
+
}
|
|
68
|
+
for (let index = KEEP_ROTATIONS - 1; index >= 1; index -= 1) {
|
|
69
|
+
try {
|
|
70
|
+
renameSync(`${file}.${index}`, `${file}.${index + 1}`);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
/* not there */
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
renameSync(file, `${file}.1`);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
/* another process got there first */
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/mcp/proxy/audit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACtF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAChD,8DAA8D;AAC9D,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AA6BhC,MAAM,UAAU,WAAW,CAAC,UAAwB,EAAE;IACpD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC;IACnE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,OAAO,CAAC,aAAa,IAAI,eAAe,CAAC;IAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;IAEtC,IAAI,KAAK,GAAG,KAAK,CAAC;IAElB,OAAO;QACL,IAAI;QACJ,MAAM,CAAC,KAAK;YACV,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,WAAW,EAAE,EAAE,GAAG,KAAK,EAAE,CAAC,IAAI,CAAC;YAC1E,IAAI,CAAC;gBACH,IAAI,CAAC,KAAK,EAAE,CAAC;oBACX,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;oBACjD,KAAK,GAAG,IAAI,CAAC;gBACf,CAAC;gBACD,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;gBAC/B,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YAC9C,CAAC;YAAC,MAAM,CAAC;gBACP,qEAAqE;gBACrE,uCAAuC;YACzC,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,IAAY,EAAE,QAAgB;IACpD,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,gBAAgB;IAC1B,CAAC;IACD,IAAI,IAAI,GAAG,QAAQ;QAAE,OAAO;IAE5B,gDAAgD;IAChD,IAAI,CAAC;QACH,UAAU,CAAC,GAAG,IAAI,IAAI,cAAc,EAAE,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,eAAe;IACjB,CAAC;IACD,KAAK,IAAI,KAAK,GAAG,cAAc,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QAC5D,IAAI,CAAC;YACH,UAAU,CAAC,GAAG,IAAI,IAAI,KAAK,EAAE,EAAE,GAAG,IAAI,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC;QACzD,CAAC;QAAC,MAAM,CAAC;YACP,eAAe;QACjB,CAAC;IACH,CAAC;IACD,IAAI,CAAC;QACH,UAAU,CAAC,IAAI,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,qCAAqC;IACvC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the agent sees when the upstream cannot be authenticated.
|
|
3
|
+
*
|
|
4
|
+
* The transport has already done what it could (`upstreamHttp.ts`: the
|
|
5
|
+
* stored token, then a forced renewal) and the upstream still answers 401.
|
|
6
|
+
* Section 0 says `initialize` is always answered, so this interceptor
|
|
7
|
+
* answers it — with one tool, `vibedefend_connect_required`, whose
|
|
8
|
+
* description tells the agent which server needs `vibedefend connect`. The
|
|
9
|
+
* alternative, an `initialize` error, is a server the client files as broken
|
|
10
|
+
* and never mentions to the user.
|
|
11
|
+
*
|
|
12
|
+
* The same door serves a server that was never connected: no token, 401,
|
|
13
|
+
* same tool. Once in that state the interceptor answers `tools/list` and a
|
|
14
|
+
* call to its own tool; everything else is forwarded, and fails upstream as
|
|
15
|
+
* honestly as it did before.
|
|
16
|
+
*/
|
|
17
|
+
import { CONNECT_REQUIRED_TOOL, connectRequiredResult, connectRequiredToolsList, } from './inject.js';
|
|
18
|
+
import { FORWARD } from './intercept.js';
|
|
19
|
+
import { idKey, isRequest, isResponse, methodOf } from './jsonrpc.js';
|
|
20
|
+
import { KNOWN_PROTOCOL_VERSIONS } from './proxy.js';
|
|
21
|
+
import { UPSTREAM_UNAUTHORIZED } from './upstreamHttp.js';
|
|
22
|
+
/** The command the user has to type for this upstream. */
|
|
23
|
+
export function connectCommandFor(target) {
|
|
24
|
+
if (target.name)
|
|
25
|
+
return `vibedefend connect ${target.name}`;
|
|
26
|
+
return target.spec.transport === 'http'
|
|
27
|
+
? `vibedefend connect --url ${target.spec.url}`
|
|
28
|
+
: 'vibedefend connect';
|
|
29
|
+
}
|
|
30
|
+
export function createAuthInterceptor(options) {
|
|
31
|
+
const params = {
|
|
32
|
+
serverName: options.serverName,
|
|
33
|
+
connectCommand: options.connectCommand,
|
|
34
|
+
};
|
|
35
|
+
let requestedProtocolVersion;
|
|
36
|
+
/** The agent's `initialize` id, until it has been answered. */
|
|
37
|
+
let initializeKey;
|
|
38
|
+
let authRequired = false;
|
|
39
|
+
return {
|
|
40
|
+
name: 'auth',
|
|
41
|
+
fromAgent(message) {
|
|
42
|
+
const method = methodOf(message);
|
|
43
|
+
if (isRequest(message) && method === 'initialize') {
|
|
44
|
+
initializeKey = idKey(message.id);
|
|
45
|
+
const params = message.params;
|
|
46
|
+
if (typeof params === 'object' && params !== null) {
|
|
47
|
+
const requested = params.protocolVersion;
|
|
48
|
+
if (typeof requested === 'string')
|
|
49
|
+
requestedProtocolVersion = requested;
|
|
50
|
+
}
|
|
51
|
+
return FORWARD;
|
|
52
|
+
}
|
|
53
|
+
if (!authRequired || !isRequest(message))
|
|
54
|
+
return FORWARD;
|
|
55
|
+
if (method === 'tools/list') {
|
|
56
|
+
return {
|
|
57
|
+
action: 'respond',
|
|
58
|
+
message: { jsonrpc: '2.0', id: message.id, result: connectRequiredToolsList(params) },
|
|
59
|
+
reason: 'the upstream cannot be authenticated',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (method === 'tools/call' && calledTool(message) === CONNECT_REQUIRED_TOOL) {
|
|
63
|
+
return {
|
|
64
|
+
action: 'respond',
|
|
65
|
+
message: { jsonrpc: '2.0', id: message.id, result: connectRequiredResult(params) },
|
|
66
|
+
reason: 'a VibeDefend tool is answered by VibeDefend',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return FORWARD;
|
|
70
|
+
},
|
|
71
|
+
fromUpstream(message) {
|
|
72
|
+
if (!isResponse(message) || !isUnauthorized(message))
|
|
73
|
+
return FORWARD;
|
|
74
|
+
if (!authRequired) {
|
|
75
|
+
authRequired = true;
|
|
76
|
+
options.logger.warn('upstream cannot be authenticated; offering vibedefend_connect_required', {
|
|
77
|
+
connectCommand: options.connectCommand,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const key = idKey(message.id);
|
|
81
|
+
if (initializeKey === undefined || key !== initializeKey)
|
|
82
|
+
return FORWARD;
|
|
83
|
+
initializeKey = undefined;
|
|
84
|
+
const protocolVersion = requestedProtocolVersion &&
|
|
85
|
+
KNOWN_PROTOCOL_VERSIONS.includes(requestedProtocolVersion)
|
|
86
|
+
? requestedProtocolVersion
|
|
87
|
+
: KNOWN_PROTOCOL_VERSIONS[0];
|
|
88
|
+
return {
|
|
89
|
+
action: 'replace',
|
|
90
|
+
message: {
|
|
91
|
+
jsonrpc: '2.0',
|
|
92
|
+
id: message.id,
|
|
93
|
+
result: {
|
|
94
|
+
protocolVersion,
|
|
95
|
+
capabilities: { tools: {} },
|
|
96
|
+
serverInfo: { name: 'vibedefend-proxy', version: options.proxyVersion ?? '0.0.0' },
|
|
97
|
+
instructions: `The MCP server ${params.serverName} could not be authenticated by the VibeDefend proxy. ` +
|
|
98
|
+
`Its tools are unavailable until the user runs \`${params.connectCommand}\`; ` +
|
|
99
|
+
'list the tools to see what to tell them.',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function isUnauthorized(message) {
|
|
107
|
+
const error = message.error;
|
|
108
|
+
return (typeof error === 'object' &&
|
|
109
|
+
error !== null &&
|
|
110
|
+
error.code === UPSTREAM_UNAUTHORIZED);
|
|
111
|
+
}
|
|
112
|
+
function calledTool(message) {
|
|
113
|
+
const params = message.params;
|
|
114
|
+
if (typeof params !== 'object' || params === null)
|
|
115
|
+
return undefined;
|
|
116
|
+
const name = params.name;
|
|
117
|
+
return typeof name === 'string' ? name : undefined;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=authInterceptor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authInterceptor.js","sourceRoot":"","sources":["../../../src/mcp/proxy/authInterceptor.ts"],"names":[],"mappings":"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"}
|