@x-code-cli/core 0.2.10 → 0.3.1
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/agent/compression.d.ts +12 -2
- package/dist/agent/compression.d.ts.map +1 -1
- package/dist/agent/compression.js +51 -2
- package/dist/agent/compression.js.map +1 -1
- package/dist/agent/file-ingest.js +2 -2
- package/dist/agent/file-ingest.js.map +1 -1
- package/dist/agent/loop-state.d.ts +3 -2
- package/dist/agent/loop-state.d.ts.map +1 -1
- package/dist/agent/loop-state.js.map +1 -1
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +134 -5
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/memory-extractor.js +5 -5
- package/dist/agent/memory-extractor.js.map +1 -1
- package/dist/agent/plan-storage.js +1 -1
- package/dist/agent/plan-storage.js.map +1 -1
- package/dist/agent/sub-agents/index.d.ts +2 -1
- package/dist/agent/sub-agents/index.d.ts.map +1 -1
- package/dist/agent/sub-agents/index.js +1 -1
- package/dist/agent/sub-agents/index.js.map +1 -1
- package/dist/agent/sub-agents/loader.d.ts +13 -3
- package/dist/agent/sub-agents/loader.d.ts.map +1 -1
- package/dist/agent/sub-agents/loader.js +36 -9
- package/dist/agent/sub-agents/loader.js.map +1 -1
- package/dist/agent/sub-agents/registry.d.ts +18 -1
- package/dist/agent/sub-agents/registry.d.ts.map +1 -1
- package/dist/agent/sub-agents/registry.js +38 -5
- package/dist/agent/sub-agents/registry.js.map +1 -1
- package/dist/agent/sub-agents/runner.d.ts.map +1 -1
- package/dist/agent/sub-agents/runner.js +45 -1
- package/dist/agent/sub-agents/runner.js.map +1 -1
- package/dist/agent/sub-agents/types.d.ts +4 -1
- package/dist/agent/sub-agents/types.d.ts.map +1 -1
- package/dist/agent/system-prompt.d.ts +21 -0
- package/dist/agent/system-prompt.d.ts.map +1 -1
- package/dist/agent/system-prompt.js +68 -2
- package/dist/agent/system-prompt.js.map +1 -1
- package/dist/agent/tool-execution.d.ts.map +1 -1
- package/dist/agent/tool-execution.js +220 -1
- package/dist/agent/tool-execution.js.map +1 -1
- package/dist/commands/index.d.ts +6 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +3 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/loader.d.ts +19 -0
- package/dist/commands/loader.d.ts.map +1 -0
- package/dist/commands/loader.js +109 -0
- package/dist/commands/loader.js.map +1 -0
- package/dist/commands/registry.d.ts +44 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +102 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/commands/types.d.ts +23 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +26 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +12 -10
- package/dist/config/index.js.map +1 -1
- package/dist/hooks/bus.d.ts +54 -0
- package/dist/hooks/bus.d.ts.map +1 -0
- package/dist/hooks/bus.js +165 -0
- package/dist/hooks/bus.js.map +1 -0
- package/dist/hooks/config-schema.d.ts +854 -0
- package/dist/hooks/config-schema.d.ts.map +1 -0
- package/dist/hooks/config-schema.js +79 -0
- package/dist/hooks/config-schema.js.map +1 -0
- package/dist/hooks/executor.d.ts +16 -0
- package/dist/hooks/executor.d.ts.map +1 -0
- package/dist/hooks/executor.js +183 -0
- package/dist/hooks/executor.js.map +1 -0
- package/dist/hooks/index.d.ts +10 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/registry.d.ts +23 -0
- package/dist/hooks/registry.d.ts.map +1 -0
- package/dist/hooks/registry.js +49 -0
- package/dist/hooks/registry.js.map +1 -0
- package/dist/hooks/types.d.ts +165 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +25 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/hooks/variables.d.ts +22 -0
- package/dist/hooks/variables.d.ts.map +1 -0
- package/dist/hooks/variables.js +80 -0
- package/dist/hooks/variables.js.map +1 -0
- package/dist/index.d.ts +57 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -2
- package/dist/index.js.map +1 -1
- package/dist/knowledge/auto-memory.d.ts +1 -1
- package/dist/knowledge/auto-memory.d.ts.map +1 -1
- package/dist/knowledge/auto-memory.js +10 -10
- package/dist/knowledge/auto-memory.js.map +1 -1
- package/dist/knowledge/loader.js +12 -12
- package/dist/knowledge/loader.js.map +1 -1
- package/dist/mcp/arg-parser.d.ts +49 -0
- package/dist/mcp/arg-parser.d.ts.map +1 -0
- package/dist/mcp/arg-parser.js +357 -0
- package/dist/mcp/arg-parser.js.map +1 -0
- package/dist/mcp/client.d.ts +73 -0
- package/dist/mcp/client.d.ts.map +1 -0
- package/dist/mcp/client.js +376 -0
- package/dist/mcp/client.js.map +1 -0
- package/dist/mcp/config-schema.d.ts +64 -0
- package/dist/mcp/config-schema.d.ts.map +1 -0
- package/dist/mcp/config-schema.js +86 -0
- package/dist/mcp/config-schema.js.map +1 -0
- package/dist/mcp/config-writer.d.ts +41 -0
- package/dist/mcp/config-writer.d.ts.map +1 -0
- package/dist/mcp/config-writer.js +138 -0
- package/dist/mcp/config-writer.js.map +1 -0
- package/dist/mcp/env-safety.d.ts +12 -0
- package/dist/mcp/env-safety.d.ts.map +1 -0
- package/dist/mcp/env-safety.js +80 -0
- package/dist/mcp/env-safety.js.map +1 -0
- package/dist/mcp/expand-env.d.ts +14 -0
- package/dist/mcp/expand-env.d.ts.map +1 -0
- package/dist/mcp/expand-env.js +52 -0
- package/dist/mcp/expand-env.js.map +1 -0
- package/dist/mcp/loader.d.ts +81 -0
- package/dist/mcp/loader.d.ts.map +1 -0
- package/dist/mcp/loader.js +223 -0
- package/dist/mcp/loader.js.map +1 -0
- package/dist/mcp/name-mangling.d.ts +11 -0
- package/dist/mcp/name-mangling.d.ts.map +1 -0
- package/dist/mcp/name-mangling.js +82 -0
- package/dist/mcp/name-mangling.js.map +1 -0
- package/dist/mcp/oauth/callback-server.d.ts +25 -0
- package/dist/mcp/oauth/callback-server.d.ts.map +1 -0
- package/dist/mcp/oauth/callback-server.js +118 -0
- package/dist/mcp/oauth/callback-server.js.map +1 -0
- package/dist/mcp/oauth/provider.d.ts +80 -0
- package/dist/mcp/oauth/provider.d.ts.map +1 -0
- package/dist/mcp/oauth/provider.js +292 -0
- package/dist/mcp/oauth/provider.js.map +1 -0
- package/dist/mcp/oauth/token-storage.d.ts +42 -0
- package/dist/mcp/oauth/token-storage.d.ts.map +1 -0
- package/dist/mcp/oauth/token-storage.js +121 -0
- package/dist/mcp/oauth/token-storage.js.map +1 -0
- package/dist/mcp/permissions.d.ts +28 -0
- package/dist/mcp/permissions.d.ts.map +1 -0
- package/dist/mcp/permissions.js +105 -0
- package/dist/mcp/permissions.js.map +1 -0
- package/dist/mcp/registry.d.ts +150 -0
- package/dist/mcp/registry.d.ts.map +1 -0
- package/dist/mcp/registry.js +334 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/mcp/resources.d.ts +7 -0
- package/dist/mcp/resources.d.ts.map +1 -0
- package/dist/mcp/resources.js +40 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/tool-bridge.d.ts +16 -0
- package/dist/mcp/tool-bridge.d.ts.map +1 -0
- package/dist/mcp/tool-bridge.js +56 -0
- package/dist/mcp/tool-bridge.js.map +1 -0
- package/dist/mcp/trust.d.ts +31 -0
- package/dist/mcp/trust.d.ts.map +1 -0
- package/dist/mcp/trust.js +103 -0
- package/dist/mcp/trust.js.map +1 -0
- package/dist/mcp/types.d.ts +73 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +13 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/permissions/index.d.ts +1 -1
- package/dist/permissions/index.d.ts.map +1 -1
- package/dist/permissions/index.js +10 -4
- package/dist/permissions/index.js.map +1 -1
- package/dist/permissions/session-store.d.ts +79 -17
- package/dist/permissions/session-store.d.ts.map +1 -1
- package/dist/permissions/session-store.js +298 -38
- package/dist/permissions/session-store.js.map +1 -1
- package/dist/plugins/consent.d.ts +87 -0
- package/dist/plugins/consent.d.ts.map +1 -0
- package/dist/plugins/consent.js +181 -0
- package/dist/plugins/consent.js.map +1 -0
- package/dist/plugins/enable-state.d.ts +34 -0
- package/dist/plugins/enable-state.d.ts.map +1 -0
- package/dist/plugins/enable-state.js +159 -0
- package/dist/plugins/enable-state.js.map +1 -0
- package/dist/plugins/installer.d.ts +64 -0
- package/dist/plugins/installer.d.ts.map +1 -0
- package/dist/plugins/installer.js +416 -0
- package/dist/plugins/installer.js.map +1 -0
- package/dist/plugins/integration.d.ts +91 -0
- package/dist/plugins/integration.d.ts.map +1 -0
- package/dist/plugins/integration.js +233 -0
- package/dist/plugins/integration.js.map +1 -0
- package/dist/plugins/loader.d.ts +69 -0
- package/dist/plugins/loader.d.ts.map +1 -0
- package/dist/plugins/loader.js +243 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/plugins/manifest.d.ts +23 -0
- package/dist/plugins/manifest.d.ts.map +1 -0
- package/dist/plugins/manifest.js +143 -0
- package/dist/plugins/manifest.js.map +1 -0
- package/dist/plugins/marketplace.d.ts +103 -0
- package/dist/plugins/marketplace.d.ts.map +1 -0
- package/dist/plugins/marketplace.js +532 -0
- package/dist/plugins/marketplace.js.map +1 -0
- package/dist/plugins/paths.d.ts +44 -0
- package/dist/plugins/paths.d.ts.map +1 -0
- package/dist/plugins/paths.js +89 -0
- package/dist/plugins/paths.js.map +1 -0
- package/dist/plugins/refresh.d.ts +61 -0
- package/dist/plugins/refresh.d.ts.map +1 -0
- package/dist/plugins/refresh.js +98 -0
- package/dist/plugins/refresh.js.map +1 -0
- package/dist/plugins/registry.d.ts +40 -0
- package/dist/plugins/registry.d.ts.map +1 -0
- package/dist/plugins/registry.js +80 -0
- package/dist/plugins/registry.js.map +1 -0
- package/dist/plugins/types.d.ts +225 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/plugins/types.js +16 -0
- package/dist/plugins/types.js.map +1 -0
- package/dist/plugins/user-config.d.ts +22 -0
- package/dist/plugins/user-config.d.ts.map +1 -0
- package/dist/plugins/user-config.js +96 -0
- package/dist/plugins/user-config.js.map +1 -0
- package/dist/skills/loader.d.ts +19 -0
- package/dist/skills/loader.d.ts.map +1 -0
- package/dist/skills/loader.js +197 -0
- package/dist/skills/loader.js.map +1 -0
- package/dist/skills/registry.d.ts +74 -0
- package/dist/skills/registry.d.ts.map +1 -0
- package/dist/skills/registry.js +136 -0
- package/dist/skills/registry.js.map +1 -0
- package/dist/skills/settings.d.ts +13 -0
- package/dist/skills/settings.d.ts.map +1 -0
- package/dist/skills/settings.js +100 -0
- package/dist/skills/settings.js.map +1 -0
- package/dist/tools/activate-skill.d.ts +5 -0
- package/dist/tools/activate-skill.d.ts.map +1 -0
- package/dist/tools/activate-skill.js +33 -0
- package/dist/tools/activate-skill.js.map +1 -0
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/shell-utils.d.ts.map +1 -1
- package/dist/tools/shell-utils.js +157 -6
- package/dist/tools/shell-utils.js.map +1 -1
- package/dist/tools/todo-write.d.ts +1 -1
- package/dist/tools/web-fetch.d.ts.map +1 -1
- package/dist/tools/web-fetch.js +2 -1
- package/dist/tools/web-fetch.js.map +1 -1
- package/dist/types/index.d.ts +46 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils.d.ts +23 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +76 -20
- package/dist/utils.js.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +47 -0
- package/dist/version.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { debugLog } from '../../utils.js';
|
|
3
|
+
import { startCallbackServer } from './callback-server.js';
|
|
4
|
+
const CLIENT_METADATA_BASE = {
|
|
5
|
+
client_name: 'X-Code CLI',
|
|
6
|
+
client_uri: 'https://github.com/woai3c/x-code-cli',
|
|
7
|
+
grant_types: ['authorization_code', 'refresh_token'],
|
|
8
|
+
response_types: ['code'],
|
|
9
|
+
token_endpoint_auth_method: 'none',
|
|
10
|
+
};
|
|
11
|
+
/** Concrete provider, wired up to fetched persisted state + a callback
|
|
12
|
+
* server that gets started on demand. Reused across multiple connect /
|
|
13
|
+
* refresh attempts for the same server. */
|
|
14
|
+
export class McpOAuthProvider {
|
|
15
|
+
opts;
|
|
16
|
+
/** Currently-running callback server. We keep a handle so a second
|
|
17
|
+
* call to redirectToAuthorization (after a failed first attempt)
|
|
18
|
+
* reuses the same port instead of opening another listener. */
|
|
19
|
+
callbackServer = null;
|
|
20
|
+
/** PKCE verifier — kept in memory only, replaced on each new flow. */
|
|
21
|
+
memoryCodeVerifier = null;
|
|
22
|
+
/** Pending callback that the SDK will consume via `finishAuth` on
|
|
23
|
+
* the transport. Caller of `waitForAuthCode()` retrieves it. */
|
|
24
|
+
pendingCode = null;
|
|
25
|
+
/** Whether `redirectToAuthorization` should actually launch a browser.
|
|
26
|
+
* Default false — booting the CLI with an HTTP MCP server that has
|
|
27
|
+
* no stored token must NOT silently open a browser window. The flag
|
|
28
|
+
* is flipped on for the duration of `connectWithOAuth` (driven by
|
|
29
|
+
* `/mcp auth <name>`) and back off in `finally`. */
|
|
30
|
+
interactive = false;
|
|
31
|
+
constructor(opts) {
|
|
32
|
+
this.opts = opts;
|
|
33
|
+
}
|
|
34
|
+
/** Caller (client.ts:connectWithOAuth) toggles this around an
|
|
35
|
+
* authenticated dance. Outside that window we stay passive. */
|
|
36
|
+
setInteractive(value) {
|
|
37
|
+
this.interactive = value;
|
|
38
|
+
}
|
|
39
|
+
/** Eagerly start the callback server, so the real loopback port is
|
|
40
|
+
* available to `redirectUrl` and `clientMetadata.redirect_uris`
|
|
41
|
+
* BEFORE the SDK constructs the dynamic-registration request.
|
|
42
|
+
*
|
|
43
|
+
* Why this matters: Sentry (and any auth server that doesn't follow
|
|
44
|
+
* RFC 8252 §7.3 strictly) validates the auth-URL `redirect_uri` against
|
|
45
|
+
* the value the client registered with. If we register with the
|
|
46
|
+
* port-less placeholder and then redirect to a concrete port, the
|
|
47
|
+
* server replies "Invalid redirect URI" and the whole flow dies.
|
|
48
|
+
* Pre-starting the server ensures registration and authorization use
|
|
49
|
+
* the SAME concrete `http://127.0.0.1:<port>/callback`. */
|
|
50
|
+
async prepareForAuth() {
|
|
51
|
+
this.interactive = true;
|
|
52
|
+
await this.ensureCallbackServer();
|
|
53
|
+
}
|
|
54
|
+
// ── OAuthClientProvider ────────────────────────────────────────────────
|
|
55
|
+
get redirectUrl() {
|
|
56
|
+
// The SDK actually reads `redirectUrl` BEFORE `redirectToAuthorization`
|
|
57
|
+
// fires (e.g. while constructing the authorize URL during the very
|
|
58
|
+
// first connect attempt with no stored token). An earlier version
|
|
59
|
+
// threw here, which surfaced HTTP servers as `failed` instead of the
|
|
60
|
+
// intended `needs_auth` on the first launch after `/mcp add`.
|
|
61
|
+
//
|
|
62
|
+
// We return the same loopback placeholder `clientMetadata.redirect_uris`
|
|
63
|
+
// already uses. RFC 8252 §7.3 says authorisation servers MUST accept any
|
|
64
|
+
// port on a registered loopback redirect_uri, so the placeholder being
|
|
65
|
+
// port-less is fine for the registration roundtrip; `redirectToAuthorization`
|
|
66
|
+
// rewrites the actual `redirect_uri` query param with the real port
|
|
67
|
+
// right before launching the browser.
|
|
68
|
+
return this.callbackServer?.url ?? 'http://127.0.0.1/callback';
|
|
69
|
+
}
|
|
70
|
+
get clientMetadata() {
|
|
71
|
+
return {
|
|
72
|
+
...CLIENT_METADATA_BASE,
|
|
73
|
+
// Filled in by redirectToAuthorization once the server is up.
|
|
74
|
+
// Until then the SDK may inspect this object during dynamic
|
|
75
|
+
// registration — we use a placeholder; the SDK will overwrite
|
|
76
|
+
// the registration response anyway.
|
|
77
|
+
redirect_uris: [this.callbackServer?.url ?? 'http://127.0.0.1/callback'],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async clientInformation() {
|
|
81
|
+
const stored = await this.opts.storage.get(this.opts.serverName);
|
|
82
|
+
return stored?.clientInformation;
|
|
83
|
+
}
|
|
84
|
+
async saveClientInformation(info) {
|
|
85
|
+
await this.opts.storage.setClientInformation(this.opts.serverName, this.opts.serverUrl, info);
|
|
86
|
+
}
|
|
87
|
+
async tokens() {
|
|
88
|
+
const stored = await this.opts.storage.get(this.opts.serverName);
|
|
89
|
+
return stored?.tokens;
|
|
90
|
+
}
|
|
91
|
+
async saveTokens(tokens) {
|
|
92
|
+
await this.opts.storage.setTokens(this.opts.serverName, this.opts.serverUrl, tokens);
|
|
93
|
+
}
|
|
94
|
+
saveCodeVerifier(codeVerifier) {
|
|
95
|
+
this.memoryCodeVerifier = codeVerifier;
|
|
96
|
+
}
|
|
97
|
+
codeVerifier() {
|
|
98
|
+
if (!this.memoryCodeVerifier) {
|
|
99
|
+
throw new Error('No PKCE verifier set — auth flow not in progress');
|
|
100
|
+
}
|
|
101
|
+
return this.memoryCodeVerifier;
|
|
102
|
+
}
|
|
103
|
+
async redirectToAuthorization(authorizationUrl) {
|
|
104
|
+
// Passive (boot) mode: the SDK is in the middle of a "lazy" first
|
|
105
|
+
// connect with no stored token. We must NOT open a browser window
|
|
106
|
+
// unprompted — every other MCP-aware CLI (Claude Code, Gemini,
|
|
107
|
+
// OpenCode) waits for explicit user action before doing that, and
|
|
108
|
+
// a CLI start-up that hijacks the user's browser is a hostile
|
|
109
|
+
// surprise. Returning here is enough: the SDK will throw
|
|
110
|
+
// UnauthorizedError next, the registry classifies it as
|
|
111
|
+
// `needs_auth`, and `/mcp auth <name>` can drive the real flow
|
|
112
|
+
// (after setInteractive(true) flips us into the interactive path
|
|
113
|
+
// below).
|
|
114
|
+
if (!this.interactive) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Lazy-start the callback server right before we hand the auth URL
|
|
118
|
+
// to the browser, so the URL we advertise (via `redirectUrl`)
|
|
119
|
+
// matches what we'll listen on. We rebuild the auth URL with the
|
|
120
|
+
// updated redirect_uri reflecting our actual port.
|
|
121
|
+
await this.ensureCallbackServer();
|
|
122
|
+
authorizationUrl.searchParams.set('redirect_uri', this.callbackServer.url);
|
|
123
|
+
this.opts.onOpenBrowser?.(authorizationUrl.toString());
|
|
124
|
+
await openInBrowser(authorizationUrl.toString());
|
|
125
|
+
// Stash the pending callback so the caller can `await` it through
|
|
126
|
+
// `waitForAuthCode()` while the transport machinery handles the
|
|
127
|
+
// token-exchange step.
|
|
128
|
+
this.pendingCode = this.callbackServer.waitForCallback();
|
|
129
|
+
}
|
|
130
|
+
// ── Helpers used by /mcp auth handler ─────────────────────────────────
|
|
131
|
+
/** Block until the auth server has redirected back. Resolves with the
|
|
132
|
+
* captured code; the caller then calls `transport.finishAuth(code)`
|
|
133
|
+
* on the SDK's StreamableHTTPClientTransport.
|
|
134
|
+
*
|
|
135
|
+
* We close the callback server here because we already have the code
|
|
136
|
+
* — Sentry won't call us back again on this flow. But we leave
|
|
137
|
+
* `memoryCodeVerifier` alive: the SDK reads it during
|
|
138
|
+
* `transport.finishAuth(code)`, which the caller runs AFTER this
|
|
139
|
+
* promise resolves. Nulling the verifier in this finally block was
|
|
140
|
+
* the cause of "No PKCE verifier set — auth flow not in progress".
|
|
141
|
+
* Cleanup of the verifier happens either via `cancel()` (abort
|
|
142
|
+
* path) or naturally on the next `saveCodeVerifier(...)` call. */
|
|
143
|
+
async waitForAuthCode() {
|
|
144
|
+
if (!this.pendingCode) {
|
|
145
|
+
throw new Error('Auth flow not started — redirectToAuthorization was never invoked');
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
return await this.pendingCode;
|
|
149
|
+
}
|
|
150
|
+
finally {
|
|
151
|
+
this.pendingCode = null;
|
|
152
|
+
this.callbackServer?.close();
|
|
153
|
+
this.callbackServer = null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/** Drop any in-progress flow without saving. Safe to call any time. */
|
|
157
|
+
cancel() {
|
|
158
|
+
this.callbackServer?.close();
|
|
159
|
+
this.callbackServer = null;
|
|
160
|
+
this.pendingCode = null;
|
|
161
|
+
this.memoryCodeVerifier = null;
|
|
162
|
+
}
|
|
163
|
+
// ── internals ──────────────────────────────────────────────────────────
|
|
164
|
+
async ensureCallbackServer() {
|
|
165
|
+
if (this.callbackServer)
|
|
166
|
+
return;
|
|
167
|
+
this.callbackServer = await startCallbackServer();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/** Best-effort cross-platform `open <url>`. Detached so the CLI doesn't
|
|
171
|
+
* block on the browser process; stdio piped to /dev/null so output
|
|
172
|
+
* doesn't smear into our terminal UI. Failures are logged but never
|
|
173
|
+
* thrown — the user can still copy/paste the URL by hand. */
|
|
174
|
+
async function openInBrowser(url) {
|
|
175
|
+
try {
|
|
176
|
+
if (process.platform === 'win32') {
|
|
177
|
+
// We deliberately AVOID `cmd /c start` here. cmd.exe treats `&`
|
|
178
|
+
// as a command separator, so an OAuth URL like
|
|
179
|
+
// https://x.com/auth?response_type=code&client_id=abc&code_challenge=...
|
|
180
|
+
// got silently truncated to `https://x.com/auth?response_type=code`
|
|
181
|
+
// — the user's browser landed on a URL with no client_id /
|
|
182
|
+
// redirect_uri / PKCE challenge and Sentry replied "Invalid
|
|
183
|
+
// redirect URI". Node's argv quoting doesn't quote `&` (it's not
|
|
184
|
+
// a Windows-native special char, only a cmd-builtin special char)
|
|
185
|
+
// so even passing the URL as a separate arg didn't save us.
|
|
186
|
+
//
|
|
187
|
+
// `rundll32 url.dll,FileProtocolHandler <url>` is the documented
|
|
188
|
+
// Win32 way to invoke the default browser's protocol handler.
|
|
189
|
+
// It bypasses cmd entirely, so `&` passes through verbatim.
|
|
190
|
+
spawnDetached('rundll32', ['url.dll,FileProtocolHandler', url]);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (process.platform === 'darwin') {
|
|
194
|
+
// macOS `open` is rock-solid for URLs, no quirks.
|
|
195
|
+
spawnDetached('open', [url]);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
// Linux / *BSD: no single command works everywhere. xdg-utils
|
|
199
|
+
// (`xdg-open`) is the de-facto standard but missing on minimal
|
|
200
|
+
// containers and many server distros; `gio open` covers newer
|
|
201
|
+
// GNOME stacks; `wslview` covers WSL → Windows browser (when
|
|
202
|
+
// xdg-open inside WSL doesn't reach the host); `kde-open` and
|
|
203
|
+
// `gnome-open` cover their respective legacy desktops.
|
|
204
|
+
//
|
|
205
|
+
// We try each in turn, falling through on ENOENT or non-zero exit.
|
|
206
|
+
// Failing silently with no opener would leave the user staring at
|
|
207
|
+
// the CLI scrollback wondering why nothing happened — we surface a
|
|
208
|
+
// `mcp.browser-open-no-opener` debug entry so the situation is at
|
|
209
|
+
// least diagnosable, and the CLI's "Opened …" line already gave
|
|
210
|
+
// them the URL to copy/paste by hand.
|
|
211
|
+
const candidates = [
|
|
212
|
+
['xdg-open', [url]],
|
|
213
|
+
['gio', ['open', url]],
|
|
214
|
+
['wslview', [url]],
|
|
215
|
+
['kde-open', [url]],
|
|
216
|
+
['gnome-open', [url]],
|
|
217
|
+
];
|
|
218
|
+
for (const [cmd, args] of candidates) {
|
|
219
|
+
if (await trySpawnOpener(cmd, args))
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
debugLog('mcp.browser-open-no-opener', `no working URL opener found; advised user to copy/paste manually`);
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
debugLog('mcp.browser-open-threw', String(err));
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/** Fire a child process, detach, walk away. Used on Windows/macOS where
|
|
229
|
+
* the command is known-good — failure-detection is just a debug log. */
|
|
230
|
+
function spawnDetached(cmd, args) {
|
|
231
|
+
const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
|
|
232
|
+
child.unref();
|
|
233
|
+
child.on('error', (err) => debugLog('mcp.browser-open-failed', String(err)));
|
|
234
|
+
}
|
|
235
|
+
/** Try one Linux URL opener candidate. Resolves true if the binary
|
|
236
|
+
* exists and either exited cleanly OR is still alive after a brief
|
|
237
|
+
* grace window (most openers exec into a browser and exit ~immediately,
|
|
238
|
+
* but a few — notably wslview on cold start — fork and stay running for
|
|
239
|
+
* a moment). Resolves false on ENOENT or non-zero exit, signalling the
|
|
240
|
+
* caller to try the next candidate. */
|
|
241
|
+
function trySpawnOpener(cmd, args) {
|
|
242
|
+
return new Promise((resolve) => {
|
|
243
|
+
let settled = false;
|
|
244
|
+
const settle = (ok) => {
|
|
245
|
+
if (settled)
|
|
246
|
+
return;
|
|
247
|
+
settled = true;
|
|
248
|
+
resolve(ok);
|
|
249
|
+
};
|
|
250
|
+
let child;
|
|
251
|
+
try {
|
|
252
|
+
child = spawn(cmd, args, { stdio: 'ignore', detached: true });
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
settle(false);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
child.on('error', () => settle(false));
|
|
259
|
+
child.on('exit', (code) => {
|
|
260
|
+
if (code === 0) {
|
|
261
|
+
child.unref();
|
|
262
|
+
settle(true);
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
settle(false);
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
// Grace window for openers that fork-and-stay-alive. 500 ms is well
|
|
269
|
+
// under any user-perceptible delay yet covers the slowest reasonable
|
|
270
|
+
// launch path; anything still alive at this point is almost certainly
|
|
271
|
+
// the real browser-launching process.
|
|
272
|
+
setTimeout(() => {
|
|
273
|
+
if (!settled) {
|
|
274
|
+
child.unref();
|
|
275
|
+
settle(true);
|
|
276
|
+
}
|
|
277
|
+
}, 500);
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
/** Factory used by loader.ts. Returns undefined for stdio servers — the
|
|
281
|
+
* loader skips OAuth construction for those. */
|
|
282
|
+
export function createOAuthProviderFactory(storage, onOpenBrowser) {
|
|
283
|
+
return (serverName, serverUrl) => {
|
|
284
|
+
return new McpOAuthProvider({
|
|
285
|
+
serverName,
|
|
286
|
+
serverUrl,
|
|
287
|
+
storage,
|
|
288
|
+
onOpenBrowser: onOpenBrowser ? (url) => onOpenBrowser(serverName, url) : undefined,
|
|
289
|
+
});
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../../src/mcp/oauth/provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { OAuthClientInformationFull, OAuthClientInformationMixed, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
|
|
2
|
+
export interface StoredServerAuth {
|
|
3
|
+
/** Server URL — recorded so we can detect "this stored token belongs
|
|
4
|
+
* to a different deployment" if the user repoints config later. */
|
|
5
|
+
url: string;
|
|
6
|
+
clientInformation?: OAuthClientInformationMixed;
|
|
7
|
+
tokens?: OAuthTokens;
|
|
8
|
+
/** UTC ISO timestamp when the most recent tokens were obtained. Used
|
|
9
|
+
* to compute expiry locally because OAuth `expires_in` is relative
|
|
10
|
+
* to issuance, not absolute. */
|
|
11
|
+
tokensIssuedAt?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare class McpTokenStorage {
|
|
14
|
+
private cache;
|
|
15
|
+
get(serverName: string): Promise<StoredServerAuth | undefined>;
|
|
16
|
+
setClientInformation(serverName: string, url: string, info: OAuthClientInformationMixed): Promise<void>;
|
|
17
|
+
setTokens(serverName: string, url: string, tokens: OAuthTokens): Promise<void>;
|
|
18
|
+
clear(serverName: string): Promise<void>;
|
|
19
|
+
listServers(): Promise<Array<{
|
|
20
|
+
name: string;
|
|
21
|
+
url: string;
|
|
22
|
+
hasTokens: boolean;
|
|
23
|
+
}>>;
|
|
24
|
+
/** Compute the absolute expiry timestamp from issuedAt + expires_in.
|
|
25
|
+
* Returns undefined when either is missing (some servers omit expiry —
|
|
26
|
+
* in that case callers should optimistically use the token and let a
|
|
27
|
+
* 401 trigger refresh). */
|
|
28
|
+
static expiresAt(stored: StoredServerAuth | undefined): number | undefined;
|
|
29
|
+
/** True iff stored tokens exist AND look fresh enough to use
|
|
30
|
+
* (i.e. won't expire in the next `skewMs` window). When expiry
|
|
31
|
+
* isn't known we return true and let the next 401 drive a refresh. */
|
|
32
|
+
static isAccessTokenLikelyValid(stored: StoredServerAuth | undefined, skewMs?: number): boolean;
|
|
33
|
+
private ensureLoaded;
|
|
34
|
+
private flush;
|
|
35
|
+
}
|
|
36
|
+
export declare function getTokenStorage(): McpTokenStorage;
|
|
37
|
+
/** Test hook — replace the singleton so unit tests don't touch
|
|
38
|
+
* ~/.x-code/. Note that X_CODE_HOME also reroutes the file, so most
|
|
39
|
+
* tests can just set that env var and avoid this hook. */
|
|
40
|
+
export declare function setTokenStorageForTesting(s: McpTokenStorage | null): void;
|
|
41
|
+
export type { OAuthClientInformationFull, OAuthClientInformationMixed, OAuthTokens };
|
|
42
|
+
//# sourceMappingURL=token-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-storage.d.ts","sourceRoot":"","sources":["../../../src/mcp/oauth/token-storage.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EACV,0BAA0B,EAC1B,2BAA2B,EAC3B,WAAW,EACZ,MAAM,0CAA0C,CAAA;AAWjD,MAAM,WAAW,gBAAgB;IAC/B;wEACoE;IACpE,GAAG,EAAE,MAAM,CAAA;IACX,iBAAiB,CAAC,EAAE,2BAA2B,CAAA;IAC/C,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB;;qCAEiC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAID,qBAAa,eAAe;IAC1B,OAAO,CAAC,KAAK,CAAyB;IAEhC,GAAG,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC;IAK9D,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,2BAA2B,GAAG,OAAO,CAAC,IAAI,CAAC;IAQvG,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAS9E,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQxC,WAAW,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAWtF;;;gCAG4B;IAC5B,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS;IAS1E;;2EAEuE;IACvE,MAAM,CAAC,wBAAwB,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,EAAE,MAAM,SAAS,GAAG,OAAO;YASjF,YAAY;YAKZ,KAAK;CAcpB;AAmBD,wBAAgB,eAAe,IAAI,eAAe,CAGjD;AAED;;2DAE2D;AAC3D,wBAAgB,yBAAyB,CAAC,CAAC,EAAE,eAAe,GAAG,IAAI,GAAG,IAAI,CAEzE;AAED,YAAY,EAAE,0BAA0B,EAAE,2BAA2B,EAAE,WAAW,EAAE,CAAA"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { debugLog, userXcodeDir } from '../../utils.js';
|
|
4
|
+
function authFile() {
|
|
5
|
+
return path.join(userXcodeDir(), 'mcp-auth.json');
|
|
6
|
+
}
|
|
7
|
+
export class McpTokenStorage {
|
|
8
|
+
cache = null;
|
|
9
|
+
async get(serverName) {
|
|
10
|
+
await this.ensureLoaded();
|
|
11
|
+
return this.cache[serverName];
|
|
12
|
+
}
|
|
13
|
+
async setClientInformation(serverName, url, info) {
|
|
14
|
+
await this.ensureLoaded();
|
|
15
|
+
const entry = (this.cache[serverName] ??= { url });
|
|
16
|
+
entry.url = url;
|
|
17
|
+
entry.clientInformation = info;
|
|
18
|
+
await this.flush();
|
|
19
|
+
}
|
|
20
|
+
async setTokens(serverName, url, tokens) {
|
|
21
|
+
await this.ensureLoaded();
|
|
22
|
+
const entry = (this.cache[serverName] ??= { url });
|
|
23
|
+
entry.url = url;
|
|
24
|
+
entry.tokens = tokens;
|
|
25
|
+
entry.tokensIssuedAt = new Date().toISOString();
|
|
26
|
+
await this.flush();
|
|
27
|
+
}
|
|
28
|
+
async clear(serverName) {
|
|
29
|
+
await this.ensureLoaded();
|
|
30
|
+
if (this.cache[serverName]) {
|
|
31
|
+
delete this.cache[serverName];
|
|
32
|
+
await this.flush();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async listServers() {
|
|
36
|
+
await this.ensureLoaded();
|
|
37
|
+
return Object.entries(this.cache).map(([name, entry]) => ({
|
|
38
|
+
name,
|
|
39
|
+
url: entry.url,
|
|
40
|
+
hasTokens: !!entry.tokens,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
// ── helpers ────────────────────────────────────────────────────────────
|
|
44
|
+
/** Compute the absolute expiry timestamp from issuedAt + expires_in.
|
|
45
|
+
* Returns undefined when either is missing (some servers omit expiry —
|
|
46
|
+
* in that case callers should optimistically use the token and let a
|
|
47
|
+
* 401 trigger refresh). */
|
|
48
|
+
static expiresAt(stored) {
|
|
49
|
+
const t = stored?.tokens;
|
|
50
|
+
if (!t)
|
|
51
|
+
return undefined;
|
|
52
|
+
if (typeof t.expires_in !== 'number')
|
|
53
|
+
return undefined;
|
|
54
|
+
const issued = stored.tokensIssuedAt ? Date.parse(stored.tokensIssuedAt) : NaN;
|
|
55
|
+
if (Number.isNaN(issued))
|
|
56
|
+
return undefined;
|
|
57
|
+
return issued + t.expires_in * 1000;
|
|
58
|
+
}
|
|
59
|
+
/** True iff stored tokens exist AND look fresh enough to use
|
|
60
|
+
* (i.e. won't expire in the next `skewMs` window). When expiry
|
|
61
|
+
* isn't known we return true and let the next 401 drive a refresh. */
|
|
62
|
+
static isAccessTokenLikelyValid(stored, skewMs = 60_000) {
|
|
63
|
+
if (!stored?.tokens?.access_token)
|
|
64
|
+
return false;
|
|
65
|
+
const expiresAt = McpTokenStorage.expiresAt(stored);
|
|
66
|
+
if (expiresAt === undefined)
|
|
67
|
+
return true;
|
|
68
|
+
return Date.now() + skewMs < expiresAt;
|
|
69
|
+
}
|
|
70
|
+
// ── internals ──────────────────────────────────────────────────────────
|
|
71
|
+
async ensureLoaded() {
|
|
72
|
+
if (this.cache !== null)
|
|
73
|
+
return;
|
|
74
|
+
this.cache = await readFile();
|
|
75
|
+
}
|
|
76
|
+
async flush() {
|
|
77
|
+
if (!this.cache)
|
|
78
|
+
return;
|
|
79
|
+
try {
|
|
80
|
+
await fs.mkdir(userXcodeDir(), { recursive: true });
|
|
81
|
+
const tmp = authFile() + '.tmp';
|
|
82
|
+
await fs.writeFile(tmp, JSON.stringify(this.cache, null, 2) + '\n', {
|
|
83
|
+
encoding: 'utf-8',
|
|
84
|
+
mode: 0o600,
|
|
85
|
+
});
|
|
86
|
+
await fs.rename(tmp, authFile());
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
debugLog('mcp.token-write-failed', String(err));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function readFile() {
|
|
94
|
+
try {
|
|
95
|
+
const raw = await fs.readFile(authFile(), 'utf-8');
|
|
96
|
+
const parsed = JSON.parse(raw);
|
|
97
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
98
|
+
return parsed;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// missing / malformed — start clean
|
|
103
|
+
}
|
|
104
|
+
return {};
|
|
105
|
+
}
|
|
106
|
+
/** Singleton instance. Wiring is simple: CLI startup constructs it once,
|
|
107
|
+
* passes it to loadMcpServers (which threads it into per-server OAuth
|
|
108
|
+
* providers) and to /mcp auth / /mcp logout handlers. */
|
|
109
|
+
let globalInstance = null;
|
|
110
|
+
export function getTokenStorage() {
|
|
111
|
+
if (!globalInstance)
|
|
112
|
+
globalInstance = new McpTokenStorage();
|
|
113
|
+
return globalInstance;
|
|
114
|
+
}
|
|
115
|
+
/** Test hook — replace the singleton so unit tests don't touch
|
|
116
|
+
* ~/.x-code/. Note that X_CODE_HOME also reroutes the file, so most
|
|
117
|
+
* tests can just set that env var and avoid this hook. */
|
|
118
|
+
export function setTokenStorageForTesting(s) {
|
|
119
|
+
globalInstance = s;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=token-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-storage.js","sourceRoot":"","sources":["../../../src/mcp/oauth/token-storage.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAE,MAAM,kBAAkB,CAAA;AACjC,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAEvD,SAAS,QAAQ;IACf,OAAO,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,eAAe,CAAC,CAAA;AACnD,CAAC;AAgBD,MAAM,OAAO,eAAe;IAClB,KAAK,GAAqB,IAAI,CAAA;IAEtC,KAAK,CAAC,GAAG,CAAC,UAAkB;QAC1B,MAAM,IAAI,CAAC,YAAY,EAAE,CAAA;QACzB,OAAO,IAAI,CAAC,KAAM,CAAC,UAAU,CAAC,CAAA;IAChC,CAAC;IAED,KAAK,CAAC,oBAAoB,CAAC,UAAkB,EAAE,GAAW,EAAE,IAAiC;QAC3F,MAAM,IAAI,CAAC,YAAY,EAAE,CAAA;QACzB,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,KAAM,CAAC,UAAU,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;QACnD,KAAK,CAAC,GAAG,GAAG,GAAG,CAAA;QACf,KAAK,CAAC,iBAAiB,GAAG,IAAI,CAAA;QAC9B,MAAM,IAAI,CAAC,KAAK,EAAE,CAAA;IACpB,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,UAAkB,EAAE,GAAW,EAAE,MAAmB;QAClE,MAAM,IAAI,CAAC,YAAY,EAAE,CAAA;QACzB,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,KAAM,CAAC,UAAU,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;QACnD,KAAK,CAAC,GAAG,GAAG,GAAG,CAAA;QACf,KAAK,CAAC,MAAM,GAAG,MAAM,CAAA;QACrB,KAAK,CAAC,cAAc,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;QAC/C,MAAM,IAAI,CAAC,KAAK,EAAE,CAAA;IACpB,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,UAAkB;QAC5B,MAAM,IAAI,CAAC,YAAY,EAAE,CAAA;QACzB,IAAI,IAAI,CAAC,KAAM,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAC,KAAM,CAAC,UAAU,CAAC,CAAA;YAC9B,MAAM,IAAI,CAAC,KAAK,EAAE,CAAA;QACpB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,WAAW;QACf,MAAM,IAAI,CAAC,YAAY,EAAE,CAAA;QACzB,OAAO,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;YACzD,IAAI;YACJ,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM;SAC1B,CAAC,CAAC,CAAA;IACL,CAAC;IAED,0EAA0E;IAE1E;;;gCAG4B;IAC5B,MAAM,CAAC,SAAS,CAAC,MAAoC;QACnD,MAAM,CAAC,GAAG,MAAM,EAAE,MAAM,CAAA;QACxB,IAAI,CAAC,CAAC;YAAE,OAAO,SAAS,CAAA;QACxB,IAAI,OAAO,CAAC,CAAC,UAAU,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAA;QACtD,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;QAC9E,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC;YAAE,OAAO,SAAS,CAAA;QAC1C,OAAO,MAAM,GAAG,CAAC,CAAC,UAAU,GAAG,IAAI,CAAA;IACrC,CAAC;IAED;;2EAEuE;IACvE,MAAM,CAAC,wBAAwB,CAAC,MAAoC,EAAE,MAAM,GAAG,MAAM;QACnF,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY;YAAE,OAAO,KAAK,CAAA;QAC/C,MAAM,SAAS,GAAG,eAAe,CAAC,SAAS,CAAC,MAAM,CAAC,CAAA;QACnD,IAAI,SAAS,KAAK,SAAS;YAAE,OAAO,IAAI,CAAA;QACxC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,GAAG,SAAS,CAAA;IACxC,CAAC;IAED,0EAA0E;IAElE,KAAK,CAAC,YAAY;QACxB,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI;YAAE,OAAM;QAC/B,IAAI,CAAC,KAAK,GAAG,MAAM,QAAQ,EAAE,CAAA;IAC/B,CAAC;IAEO,KAAK,CAAC,KAAK;QACjB,IAAI,CAAC,IAAI,CAAC,KAAK;YAAE,OAAM;QACvB,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;YACnD,MAAM,GAAG,GAAG,QAAQ,EAAE,GAAG,MAAM,CAAA;YAC/B,MAAM,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE;gBAClE,QAAQ,EAAE,OAAO;gBACjB,IAAI,EAAE,KAAK;aACZ,CAAC,CAAA;YACF,MAAM,EAAE,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAA;QAClC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;QACjD,CAAC;IACH,CAAC;CACF;AAED,KAAK,UAAU,QAAQ;IACrB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAAE,OAAO,CAAC,CAAA;QAClD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,CAAA;QACzC,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACnE,OAAO,MAAmB,CAAA;QAC5B,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,oCAAoC;IACtC,CAAC;IACD,OAAO,EAAE,CAAA;AACX,CAAC;AAED;;0DAE0D;AAC1D,IAAI,cAAc,GAA2B,IAAI,CAAA;AACjD,MAAM,UAAU,eAAe;IAC7B,IAAI,CAAC,cAAc;QAAE,cAAc,GAAG,IAAI,eAAe,EAAE,CAAA;IAC3D,OAAO,cAAc,CAAA;AACvB,CAAC;AAED;;2DAE2D;AAC3D,MAAM,UAAU,yBAAyB,CAAC,CAAyB;IACjE,cAAc,GAAG,CAAC,CAAA;AACpB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** In-memory mirror of the persisted file + a session-scoped set for
|
|
2
|
+
* "this session only" allows. The persisted set is loaded lazily on
|
|
3
|
+
* first check; the session set is cleared on construction and never
|
|
4
|
+
* written to disk. */
|
|
5
|
+
export declare class McpPermissionStore {
|
|
6
|
+
private persisted;
|
|
7
|
+
private session;
|
|
8
|
+
/** Pre-load the persisted file. Optional — checks lazy-load anyway. */
|
|
9
|
+
preload(): Promise<void>;
|
|
10
|
+
/** Returns true iff the user has already approved this tool (either
|
|
11
|
+
* by "always allow" persisted, or by "this session" in-memory). */
|
|
12
|
+
isApproved(callableName: string): Promise<boolean>;
|
|
13
|
+
/** Mark this tool approved for the rest of the session only.
|
|
14
|
+
* Not persisted. */
|
|
15
|
+
approveForSession(callableName: string): void;
|
|
16
|
+
/** Mark this tool approved permanently — writes to disk. Failure to
|
|
17
|
+
* write is logged but never thrown; the worst case is the user has
|
|
18
|
+
* to click "always allow" again next session. */
|
|
19
|
+
approvePermanently(callableName: string): Promise<void>;
|
|
20
|
+
private ensurePersistedLoaded;
|
|
21
|
+
private writePersisted;
|
|
22
|
+
}
|
|
23
|
+
/** Pull "yes" / "always" / "no" out of the existing askPermission
|
|
24
|
+
* callback. The callback's contract returns one of those three strings;
|
|
25
|
+
* we map them to a structured choice for our own callers. */
|
|
26
|
+
export type McpPermissionDecision = 'allow-once' | 'allow-always' | 'deny';
|
|
27
|
+
export declare function classifyDecision(raw: 'yes' | 'always' | 'no'): McpPermissionDecision;
|
|
28
|
+
//# sourceMappingURL=permissions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/mcp/permissions.ts"],"names":[],"mappings":"AA4BA;;;uBAGuB;AACvB,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,SAAS,CAA2B;IAC5C,OAAO,CAAC,OAAO,CAAoB;IAEnC,uEAAuE;IACjE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B;wEACoE;IAC9D,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAMxD;yBACqB;IACrB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAI7C;;sDAEkD;IAC5C,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAgB/C,qBAAqB;YAKrB,cAAc;CAY7B;AAeD;;8DAE8D;AAC9D,MAAM,MAAM,qBAAqB,GAAG,YAAY,GAAG,cAAc,GAAG,MAAM,CAAA;AAE1E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,KAAK,GAAG,QAAQ,GAAG,IAAI,GAAG,qBAAqB,CAIpF"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// @x-code-cli/core — MCP tool permission gate
|
|
2
|
+
//
|
|
3
|
+
// Sits parallel to packages/core/src/permissions/index.ts (which gates
|
|
4
|
+
// built-in writeFile / edit / shell). MCP tools live in their own pool
|
|
5
|
+
// because:
|
|
6
|
+
// - their names are runtime-discovered, can't be enumerated in a
|
|
7
|
+
// static rules table;
|
|
8
|
+
// - the user's "this MCP tool is fine, don't ask again" decision is
|
|
9
|
+
// persisted per-tool to ~/.x-code/mcp-permissions.json, separate
|
|
10
|
+
// from any per-shell-prefix allow rules.
|
|
11
|
+
//
|
|
12
|
+
// Default policy: every MCP tool starts at "ask" and stays there until
|
|
13
|
+
// the user picks "always allow". No name-based heuristics — MCP tools
|
|
14
|
+
// are too varied for `list_/read_/search_` style classification to be
|
|
15
|
+
// safe (some "list_*" tools mutate, some "create_*" tools are no-ops).
|
|
16
|
+
import fs from 'node:fs/promises';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { debugLog, userXcodeDir } from '../utils.js';
|
|
19
|
+
function permissionsFile() {
|
|
20
|
+
return path.join(userXcodeDir(), 'mcp-permissions.json');
|
|
21
|
+
}
|
|
22
|
+
/** In-memory mirror of the persisted file + a session-scoped set for
|
|
23
|
+
* "this session only" allows. The persisted set is loaded lazily on
|
|
24
|
+
* first check; the session set is cleared on construction and never
|
|
25
|
+
* written to disk. */
|
|
26
|
+
export class McpPermissionStore {
|
|
27
|
+
persisted = null;
|
|
28
|
+
session = new Set();
|
|
29
|
+
/** Pre-load the persisted file. Optional — checks lazy-load anyway. */
|
|
30
|
+
async preload() {
|
|
31
|
+
await this.ensurePersistedLoaded();
|
|
32
|
+
}
|
|
33
|
+
/** Returns true iff the user has already approved this tool (either
|
|
34
|
+
* by "always allow" persisted, or by "this session" in-memory). */
|
|
35
|
+
async isApproved(callableName) {
|
|
36
|
+
if (this.session.has(callableName))
|
|
37
|
+
return true;
|
|
38
|
+
await this.ensurePersistedLoaded();
|
|
39
|
+
return this.persisted.has(callableName);
|
|
40
|
+
}
|
|
41
|
+
/** Mark this tool approved for the rest of the session only.
|
|
42
|
+
* Not persisted. */
|
|
43
|
+
approveForSession(callableName) {
|
|
44
|
+
this.session.add(callableName);
|
|
45
|
+
}
|
|
46
|
+
/** Mark this tool approved permanently — writes to disk. Failure to
|
|
47
|
+
* write is logged but never thrown; the worst case is the user has
|
|
48
|
+
* to click "always allow" again next session. */
|
|
49
|
+
async approvePermanently(callableName) {
|
|
50
|
+
await this.ensurePersistedLoaded();
|
|
51
|
+
if (this.persisted.has(callableName))
|
|
52
|
+
return;
|
|
53
|
+
this.persisted.add(callableName);
|
|
54
|
+
// Also reflect in the session set so the very next call doesn't
|
|
55
|
+
// race the disk write.
|
|
56
|
+
this.session.add(callableName);
|
|
57
|
+
try {
|
|
58
|
+
await this.writePersisted();
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
debugLog('mcp.perm-write-failed', String(err));
|
|
62
|
+
// Best-effort: do NOT remove from in-memory set on failure —
|
|
63
|
+
// the user explicitly said yes, honour that for the session.
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async ensurePersistedLoaded() {
|
|
67
|
+
if (this.persisted !== null)
|
|
68
|
+
return;
|
|
69
|
+
this.persisted = await readPersisted();
|
|
70
|
+
}
|
|
71
|
+
async writePersisted() {
|
|
72
|
+
if (!this.persisted)
|
|
73
|
+
return;
|
|
74
|
+
await fs.mkdir(userXcodeDir(), { recursive: true });
|
|
75
|
+
const tmp = permissionsFile() + '.tmp';
|
|
76
|
+
const payload = { alwaysAllow: [...this.persisted].sort() };
|
|
77
|
+
// 0600 — readable only by the user. Same posture as mcp-auth.json
|
|
78
|
+
// (and same caveat: Windows ignores the mode bits but file is in
|
|
79
|
+
// ~/.x-code so practical leakage is limited to other apps running
|
|
80
|
+
// as the same user).
|
|
81
|
+
await fs.writeFile(tmp, JSON.stringify(payload, null, 2) + '\n', { encoding: 'utf-8', mode: 0o600 });
|
|
82
|
+
await fs.rename(tmp, permissionsFile());
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function readPersisted() {
|
|
86
|
+
try {
|
|
87
|
+
const raw = await fs.readFile(permissionsFile(), 'utf-8');
|
|
88
|
+
const parsed = JSON.parse(raw);
|
|
89
|
+
if (parsed && Array.isArray(parsed.alwaysAllow)) {
|
|
90
|
+
return new Set(parsed.alwaysAllow.filter((s) => typeof s === 'string'));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// missing / malformed — start with empty allow list, degrade to all-ask
|
|
95
|
+
}
|
|
96
|
+
return new Set();
|
|
97
|
+
}
|
|
98
|
+
export function classifyDecision(raw) {
|
|
99
|
+
if (raw === 'always')
|
|
100
|
+
return 'allow-always';
|
|
101
|
+
if (raw === 'yes')
|
|
102
|
+
return 'allow-once';
|
|
103
|
+
return 'deny';
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=permissions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/mcp/permissions.ts"],"names":[],"mappings":"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"}
|