@x-code-cli/core 0.2.9 → 0.3.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.
Files changed (255) hide show
  1. package/dist/agent/compression.d.ts +12 -2
  2. package/dist/agent/compression.d.ts.map +1 -1
  3. package/dist/agent/compression.js +51 -2
  4. package/dist/agent/compression.js.map +1 -1
  5. package/dist/agent/file-ingest.js +2 -2
  6. package/dist/agent/file-ingest.js.map +1 -1
  7. package/dist/agent/loop-state.d.ts +3 -2
  8. package/dist/agent/loop-state.d.ts.map +1 -1
  9. package/dist/agent/loop-state.js.map +1 -1
  10. package/dist/agent/loop.d.ts.map +1 -1
  11. package/dist/agent/loop.js +140 -9
  12. package/dist/agent/loop.js.map +1 -1
  13. package/dist/agent/memory-extractor.js +5 -5
  14. package/dist/agent/memory-extractor.js.map +1 -1
  15. package/dist/agent/plan-storage.js +1 -1
  16. package/dist/agent/plan-storage.js.map +1 -1
  17. package/dist/agent/sub-agents/index.d.ts +2 -1
  18. package/dist/agent/sub-agents/index.d.ts.map +1 -1
  19. package/dist/agent/sub-agents/index.js +1 -1
  20. package/dist/agent/sub-agents/index.js.map +1 -1
  21. package/dist/agent/sub-agents/loader.d.ts +13 -3
  22. package/dist/agent/sub-agents/loader.d.ts.map +1 -1
  23. package/dist/agent/sub-agents/loader.js +36 -9
  24. package/dist/agent/sub-agents/loader.js.map +1 -1
  25. package/dist/agent/sub-agents/registry.d.ts +18 -1
  26. package/dist/agent/sub-agents/registry.d.ts.map +1 -1
  27. package/dist/agent/sub-agents/registry.js +38 -5
  28. package/dist/agent/sub-agents/registry.js.map +1 -1
  29. package/dist/agent/sub-agents/runner.d.ts.map +1 -1
  30. package/dist/agent/sub-agents/runner.js +45 -1
  31. package/dist/agent/sub-agents/runner.js.map +1 -1
  32. package/dist/agent/sub-agents/types.d.ts +4 -1
  33. package/dist/agent/sub-agents/types.d.ts.map +1 -1
  34. package/dist/agent/system-prompt.d.ts +21 -0
  35. package/dist/agent/system-prompt.d.ts.map +1 -1
  36. package/dist/agent/system-prompt.js +68 -2
  37. package/dist/agent/system-prompt.js.map +1 -1
  38. package/dist/agent/tool-execution.d.ts.map +1 -1
  39. package/dist/agent/tool-execution.js +220 -1
  40. package/dist/agent/tool-execution.js.map +1 -1
  41. package/dist/commands/index.d.ts +6 -0
  42. package/dist/commands/index.d.ts.map +1 -0
  43. package/dist/commands/index.js +3 -0
  44. package/dist/commands/index.js.map +1 -0
  45. package/dist/commands/loader.d.ts +13 -0
  46. package/dist/commands/loader.d.ts.map +1 -0
  47. package/dist/commands/loader.js +93 -0
  48. package/dist/commands/loader.js.map +1 -0
  49. package/dist/commands/registry.d.ts +44 -0
  50. package/dist/commands/registry.d.ts.map +1 -0
  51. package/dist/commands/registry.js +102 -0
  52. package/dist/commands/registry.js.map +1 -0
  53. package/dist/commands/types.d.ts +23 -0
  54. package/dist/commands/types.d.ts.map +1 -0
  55. package/dist/commands/types.js +26 -0
  56. package/dist/commands/types.js.map +1 -0
  57. package/dist/config/index.d.ts +9 -0
  58. package/dist/config/index.d.ts.map +1 -1
  59. package/dist/config/index.js +12 -10
  60. package/dist/config/index.js.map +1 -1
  61. package/dist/hooks/bus.d.ts +54 -0
  62. package/dist/hooks/bus.d.ts.map +1 -0
  63. package/dist/hooks/bus.js +165 -0
  64. package/dist/hooks/bus.js.map +1 -0
  65. package/dist/hooks/config-schema.d.ts +854 -0
  66. package/dist/hooks/config-schema.d.ts.map +1 -0
  67. package/dist/hooks/config-schema.js +79 -0
  68. package/dist/hooks/config-schema.js.map +1 -0
  69. package/dist/hooks/executor.d.ts +16 -0
  70. package/dist/hooks/executor.d.ts.map +1 -0
  71. package/dist/hooks/executor.js +183 -0
  72. package/dist/hooks/executor.js.map +1 -0
  73. package/dist/hooks/index.d.ts +10 -0
  74. package/dist/hooks/index.d.ts.map +1 -0
  75. package/dist/hooks/index.js +6 -0
  76. package/dist/hooks/index.js.map +1 -0
  77. package/dist/hooks/registry.d.ts +23 -0
  78. package/dist/hooks/registry.d.ts.map +1 -0
  79. package/dist/hooks/registry.js +49 -0
  80. package/dist/hooks/registry.js.map +1 -0
  81. package/dist/hooks/types.d.ts +165 -0
  82. package/dist/hooks/types.d.ts.map +1 -0
  83. package/dist/hooks/types.js +25 -0
  84. package/dist/hooks/types.js.map +1 -0
  85. package/dist/hooks/variables.d.ts +22 -0
  86. package/dist/hooks/variables.d.ts.map +1 -0
  87. package/dist/hooks/variables.js +80 -0
  88. package/dist/hooks/variables.js.map +1 -0
  89. package/dist/index.d.ts +56 -1
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +37 -1
  92. package/dist/index.js.map +1 -1
  93. package/dist/knowledge/auto-memory.d.ts +1 -1
  94. package/dist/knowledge/auto-memory.d.ts.map +1 -1
  95. package/dist/knowledge/auto-memory.js +10 -10
  96. package/dist/knowledge/auto-memory.js.map +1 -1
  97. package/dist/knowledge/loader.js +12 -12
  98. package/dist/knowledge/loader.js.map +1 -1
  99. package/dist/mcp/arg-parser.d.ts +49 -0
  100. package/dist/mcp/arg-parser.d.ts.map +1 -0
  101. package/dist/mcp/arg-parser.js +357 -0
  102. package/dist/mcp/arg-parser.js.map +1 -0
  103. package/dist/mcp/client.d.ts +73 -0
  104. package/dist/mcp/client.d.ts.map +1 -0
  105. package/dist/mcp/client.js +376 -0
  106. package/dist/mcp/client.js.map +1 -0
  107. package/dist/mcp/config-schema.d.ts +64 -0
  108. package/dist/mcp/config-schema.d.ts.map +1 -0
  109. package/dist/mcp/config-schema.js +86 -0
  110. package/dist/mcp/config-schema.js.map +1 -0
  111. package/dist/mcp/config-writer.d.ts +41 -0
  112. package/dist/mcp/config-writer.d.ts.map +1 -0
  113. package/dist/mcp/config-writer.js +138 -0
  114. package/dist/mcp/config-writer.js.map +1 -0
  115. package/dist/mcp/env-safety.d.ts +12 -0
  116. package/dist/mcp/env-safety.d.ts.map +1 -0
  117. package/dist/mcp/env-safety.js +80 -0
  118. package/dist/mcp/env-safety.js.map +1 -0
  119. package/dist/mcp/expand-env.d.ts +14 -0
  120. package/dist/mcp/expand-env.d.ts.map +1 -0
  121. package/dist/mcp/expand-env.js +52 -0
  122. package/dist/mcp/expand-env.js.map +1 -0
  123. package/dist/mcp/loader.d.ts +81 -0
  124. package/dist/mcp/loader.d.ts.map +1 -0
  125. package/dist/mcp/loader.js +223 -0
  126. package/dist/mcp/loader.js.map +1 -0
  127. package/dist/mcp/name-mangling.d.ts +11 -0
  128. package/dist/mcp/name-mangling.d.ts.map +1 -0
  129. package/dist/mcp/name-mangling.js +82 -0
  130. package/dist/mcp/name-mangling.js.map +1 -0
  131. package/dist/mcp/oauth/callback-server.d.ts +25 -0
  132. package/dist/mcp/oauth/callback-server.d.ts.map +1 -0
  133. package/dist/mcp/oauth/callback-server.js +118 -0
  134. package/dist/mcp/oauth/callback-server.js.map +1 -0
  135. package/dist/mcp/oauth/provider.d.ts +80 -0
  136. package/dist/mcp/oauth/provider.d.ts.map +1 -0
  137. package/dist/mcp/oauth/provider.js +292 -0
  138. package/dist/mcp/oauth/provider.js.map +1 -0
  139. package/dist/mcp/oauth/token-storage.d.ts +42 -0
  140. package/dist/mcp/oauth/token-storage.d.ts.map +1 -0
  141. package/dist/mcp/oauth/token-storage.js +121 -0
  142. package/dist/mcp/oauth/token-storage.js.map +1 -0
  143. package/dist/mcp/permissions.d.ts +28 -0
  144. package/dist/mcp/permissions.d.ts.map +1 -0
  145. package/dist/mcp/permissions.js +105 -0
  146. package/dist/mcp/permissions.js.map +1 -0
  147. package/dist/mcp/registry.d.ts +150 -0
  148. package/dist/mcp/registry.d.ts.map +1 -0
  149. package/dist/mcp/registry.js +334 -0
  150. package/dist/mcp/registry.js.map +1 -0
  151. package/dist/mcp/resources.d.ts +7 -0
  152. package/dist/mcp/resources.d.ts.map +1 -0
  153. package/dist/mcp/resources.js +40 -0
  154. package/dist/mcp/resources.js.map +1 -0
  155. package/dist/mcp/tool-bridge.d.ts +16 -0
  156. package/dist/mcp/tool-bridge.d.ts.map +1 -0
  157. package/dist/mcp/tool-bridge.js +56 -0
  158. package/dist/mcp/tool-bridge.js.map +1 -0
  159. package/dist/mcp/trust.d.ts +31 -0
  160. package/dist/mcp/trust.d.ts.map +1 -0
  161. package/dist/mcp/trust.js +103 -0
  162. package/dist/mcp/trust.js.map +1 -0
  163. package/dist/mcp/types.d.ts +73 -0
  164. package/dist/mcp/types.d.ts.map +1 -0
  165. package/dist/mcp/types.js +13 -0
  166. package/dist/mcp/types.js.map +1 -0
  167. package/dist/permissions/session-store.d.ts +13 -2
  168. package/dist/permissions/session-store.d.ts.map +1 -1
  169. package/dist/permissions/session-store.js +264 -62
  170. package/dist/permissions/session-store.js.map +1 -1
  171. package/dist/plugins/consent.d.ts +87 -0
  172. package/dist/plugins/consent.d.ts.map +1 -0
  173. package/dist/plugins/consent.js +181 -0
  174. package/dist/plugins/consent.js.map +1 -0
  175. package/dist/plugins/enable-state.d.ts +34 -0
  176. package/dist/plugins/enable-state.d.ts.map +1 -0
  177. package/dist/plugins/enable-state.js +159 -0
  178. package/dist/plugins/enable-state.js.map +1 -0
  179. package/dist/plugins/installer.d.ts +64 -0
  180. package/dist/plugins/installer.d.ts.map +1 -0
  181. package/dist/plugins/installer.js +416 -0
  182. package/dist/plugins/installer.js.map +1 -0
  183. package/dist/plugins/integration.d.ts +91 -0
  184. package/dist/plugins/integration.d.ts.map +1 -0
  185. package/dist/plugins/integration.js +233 -0
  186. package/dist/plugins/integration.js.map +1 -0
  187. package/dist/plugins/loader.d.ts +69 -0
  188. package/dist/plugins/loader.d.ts.map +1 -0
  189. package/dist/plugins/loader.js +243 -0
  190. package/dist/plugins/loader.js.map +1 -0
  191. package/dist/plugins/manifest.d.ts +23 -0
  192. package/dist/plugins/manifest.d.ts.map +1 -0
  193. package/dist/plugins/manifest.js +143 -0
  194. package/dist/plugins/manifest.js.map +1 -0
  195. package/dist/plugins/marketplace.d.ts +100 -0
  196. package/dist/plugins/marketplace.d.ts.map +1 -0
  197. package/dist/plugins/marketplace.js +529 -0
  198. package/dist/plugins/marketplace.js.map +1 -0
  199. package/dist/plugins/paths.d.ts +44 -0
  200. package/dist/plugins/paths.d.ts.map +1 -0
  201. package/dist/plugins/paths.js +89 -0
  202. package/dist/plugins/paths.js.map +1 -0
  203. package/dist/plugins/refresh.d.ts +61 -0
  204. package/dist/plugins/refresh.d.ts.map +1 -0
  205. package/dist/plugins/refresh.js +98 -0
  206. package/dist/plugins/refresh.js.map +1 -0
  207. package/dist/plugins/registry.d.ts +40 -0
  208. package/dist/plugins/registry.d.ts.map +1 -0
  209. package/dist/plugins/registry.js +80 -0
  210. package/dist/plugins/registry.js.map +1 -0
  211. package/dist/plugins/types.d.ts +225 -0
  212. package/dist/plugins/types.d.ts.map +1 -0
  213. package/dist/plugins/types.js +16 -0
  214. package/dist/plugins/types.js.map +1 -0
  215. package/dist/plugins/user-config.d.ts +22 -0
  216. package/dist/plugins/user-config.d.ts.map +1 -0
  217. package/dist/plugins/user-config.js +96 -0
  218. package/dist/plugins/user-config.js.map +1 -0
  219. package/dist/providers/cache-control.d.ts +9 -0
  220. package/dist/providers/cache-control.d.ts.map +1 -1
  221. package/dist/providers/cache-control.js +31 -4
  222. package/dist/providers/cache-control.js.map +1 -1
  223. package/dist/skills/loader.d.ts +19 -0
  224. package/dist/skills/loader.d.ts.map +1 -0
  225. package/dist/skills/loader.js +197 -0
  226. package/dist/skills/loader.js.map +1 -0
  227. package/dist/skills/registry.d.ts +74 -0
  228. package/dist/skills/registry.d.ts.map +1 -0
  229. package/dist/skills/registry.js +136 -0
  230. package/dist/skills/registry.js.map +1 -0
  231. package/dist/skills/settings.d.ts +13 -0
  232. package/dist/skills/settings.d.ts.map +1 -0
  233. package/dist/skills/settings.js +100 -0
  234. package/dist/skills/settings.js.map +1 -0
  235. package/dist/tools/activate-skill.d.ts +5 -0
  236. package/dist/tools/activate-skill.d.ts.map +1 -0
  237. package/dist/tools/activate-skill.js +33 -0
  238. package/dist/tools/activate-skill.js.map +1 -0
  239. package/dist/tools/index.d.ts +1 -1
  240. package/dist/tools/todo-write.d.ts +1 -1
  241. package/dist/tools/web-fetch.d.ts.map +1 -1
  242. package/dist/tools/web-fetch.js +2 -1
  243. package/dist/tools/web-fetch.js.map +1 -1
  244. package/dist/types/index.d.ts +46 -1
  245. package/dist/types/index.d.ts.map +1 -1
  246. package/dist/types/index.js.map +1 -1
  247. package/dist/utils.d.ts +23 -2
  248. package/dist/utils.d.ts.map +1 -1
  249. package/dist/utils.js +76 -20
  250. package/dist/utils.js.map +1 -1
  251. package/dist/version.d.ts +2 -0
  252. package/dist/version.d.ts.map +1 -0
  253. package/dist/version.js +47 -0
  254. package/dist/version.js.map +1 -0
  255. 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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"}