@cybedefend/vibedefend 1.5.0 → 1.7.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 (264) hide show
  1. package/README.md +71 -2
  2. package/dist/api/gateway-request.js +49 -0
  3. package/dist/api/gateway-request.js.map +1 -0
  4. package/dist/api/projects.js +67 -0
  5. package/dist/api/projects.js.map +1 -1
  6. package/dist/api/repository-link.js +157 -0
  7. package/dist/api/repository-link.js.map +1 -0
  8. package/dist/auth/auth-store.js +107 -25
  9. package/dist/auth/auth-store.js.map +1 -1
  10. package/dist/auth/auth.js +21 -0
  11. package/dist/auth/auth.js.map +1 -1
  12. package/dist/clients/cursor.js +5 -1
  13. package/dist/clients/cursor.js.map +1 -1
  14. package/dist/clients/vscode.js +38 -29
  15. package/dist/clients/vscode.js.map +1 -1
  16. package/dist/commands/mcp/allow.js +219 -0
  17. package/dist/commands/mcp/allow.js.map +1 -0
  18. package/dist/commands/mcp/connect.js +337 -0
  19. package/dist/commands/mcp/connect.js.map +1 -0
  20. package/dist/commands/mcp/index.js +170 -0
  21. package/dist/commands/mcp/index.js.map +1 -0
  22. package/dist/commands/mcp/proxy.js +320 -0
  23. package/dist/commands/mcp/proxy.js.map +1 -0
  24. package/dist/commands/mcp/scan.js +349 -0
  25. package/dist/commands/mcp/scan.js.map +1 -0
  26. package/dist/commands/mcp/sync.js +215 -0
  27. package/dist/commands/mcp/sync.js.map +1 -0
  28. package/dist/config.js +6 -0
  29. package/dist/config.js.map +1 -1
  30. package/dist/diagnostics.js +78 -12
  31. package/dist/diagnostics.js.map +1 -1
  32. package/dist/doctor.js +19 -0
  33. package/dist/doctor.js.map +1 -1
  34. package/dist/guards-evaluator/analysis-types.js +27 -0
  35. package/dist/guards-evaluator/analysis-types.js.map +1 -0
  36. package/dist/guards-evaluator/command-analysis.js +572 -0
  37. package/dist/guards-evaluator/command-analysis.js.map +1 -0
  38. package/dist/guards-evaluator/command-view.js +586 -0
  39. package/dist/guards-evaluator/command-view.js.map +1 -0
  40. package/dist/guards-evaluator/env.js +11 -0
  41. package/dist/guards-evaluator/env.js.map +1 -0
  42. package/dist/guards-evaluator/evaluate.js +91 -41
  43. package/dist/guards-evaluator/evaluate.js.map +1 -1
  44. package/dist/guards-evaluator/index.js +2 -1
  45. package/dist/guards-evaluator/index.js.map +1 -1
  46. package/dist/guards-evaluator/inline-code.js +259 -0
  47. package/dist/guards-evaluator/inline-code.js.map +1 -0
  48. package/dist/guards-evaluator/normalize.js +716 -0
  49. package/dist/guards-evaluator/normalize.js.map +1 -0
  50. package/dist/guards-evaluator/preprocess.js +13 -5
  51. package/dist/guards-evaluator/preprocess.js.map +1 -1
  52. package/dist/guards-evaluator/redact.js +19 -0
  53. package/dist/guards-evaluator/redact.js.map +1 -1
  54. package/dist/guards-evaluator/regex.js +25 -3
  55. package/dist/guards-evaluator/regex.js.map +1 -1
  56. package/dist/guards-evaluator/safe-commands.js +155 -0
  57. package/dist/guards-evaluator/safe-commands.js.map +1 -0
  58. package/dist/guards-evaluator/triage.js +315 -0
  59. package/dist/guards-evaluator/triage.js.map +1 -0
  60. package/dist/hook-runner.js +24679 -5079
  61. package/dist/hooks/install.js +38 -0
  62. package/dist/hooks/install.js.map +1 -1
  63. package/dist/hooks/runtime/config.js +38 -0
  64. package/dist/hooks/runtime/config.js.map +1 -1
  65. package/dist/hooks/runtime/emit.js +94 -61
  66. package/dist/hooks/runtime/emit.js.map +1 -1
  67. package/dist/hooks/runtime/fetch-rules.js +22 -7
  68. package/dist/hooks/runtime/fetch-rules.js.map +1 -1
  69. package/dist/hooks/runtime/gateway-http-error.js +34 -0
  70. package/dist/hooks/runtime/gateway-http-error.js.map +1 -0
  71. package/dist/hooks/runtime/guard-rules-cache.js +139 -17
  72. package/dist/hooks/runtime/guard-rules-cache.js.map +1 -1
  73. package/dist/hooks/runtime/guard-violations-buffer.js +321 -48
  74. package/dist/hooks/runtime/guard-violations-buffer.js.map +1 -1
  75. package/dist/hooks/runtime/judge-cache.js +6 -0
  76. package/dist/hooks/runtime/judge-cache.js.map +1 -1
  77. package/dist/hooks/runtime/judge-triage.js +193 -0
  78. package/dist/hooks/runtime/judge-triage.js.map +1 -0
  79. package/dist/hooks/runtime/judge.js +7 -1
  80. package/dist/hooks/runtime/judge.js.map +1 -1
  81. package/dist/hooks/runtime/pre-compact.js +12 -6
  82. package/dist/hooks/runtime/pre-compact.js.map +1 -1
  83. package/dist/hooks/runtime/resolve.js +90 -8
  84. package/dist/hooks/runtime/resolve.js.map +1 -1
  85. package/dist/hooks/runtime/retry-policy.js +57 -0
  86. package/dist/hooks/runtime/retry-policy.js.map +1 -0
  87. package/dist/hooks/runtime/review-sessions.js +74 -0
  88. package/dist/hooks/runtime/review-sessions.js.map +1 -0
  89. package/dist/hooks/runtime/script-resolve.js +695 -0
  90. package/dist/hooks/runtime/script-resolve.js.map +1 -0
  91. package/dist/hooks/runtime/session-activation.js +5 -1
  92. package/dist/hooks/runtime/session-activation.js.map +1 -1
  93. package/dist/hooks/runtime/session-review.js +163 -45
  94. package/dist/hooks/runtime/session-review.js.map +1 -1
  95. package/dist/hooks/runtime/session-start.js +2 -1
  96. package/dist/hooks/runtime/session-start.js.map +1 -1
  97. package/dist/hooks/runtime/session-writes.js +232 -0
  98. package/dist/hooks/runtime/session-writes.js.map +1 -0
  99. package/dist/hooks/runtime/sniff.js +26 -7
  100. package/dist/hooks/runtime/sniff.js.map +1 -1
  101. package/dist/hooks/runtime/target-resolve.js +336 -0
  102. package/dist/hooks/runtime/target-resolve.js.map +1 -0
  103. package/dist/hooks/runtime/unmapped-tools.js +93 -0
  104. package/dist/hooks/runtime/unmapped-tools.js.map +1 -0
  105. package/dist/hooks/runtime/usage-block.js +4 -0
  106. package/dist/hooks/runtime/usage-block.js.map +1 -1
  107. package/dist/hooks/runtime/user-prompt-submit.js +2 -1
  108. package/dist/hooks/runtime/user-prompt-submit.js.map +1 -1
  109. package/dist/hooks/runtime/wire-text.js +41 -0
  110. package/dist/hooks/runtime/wire-text.js.map +1 -0
  111. package/dist/hooks/runtime/work-dir.js +84 -0
  112. package/dist/hooks/runtime/work-dir.js.map +1 -0
  113. package/dist/index.js +45 -12
  114. package/dist/index.js.map +1 -1
  115. package/dist/install.js +174 -11
  116. package/dist/install.js.map +1 -1
  117. package/dist/link.js +74 -48
  118. package/dist/link.js.map +1 -1
  119. package/dist/login.js +77 -11
  120. package/dist/login.js.map +1 -1
  121. package/dist/mcp/auth/agent-sign-ins.js +174 -0
  122. package/dist/mcp/auth/agent-sign-ins.js.map +1 -0
  123. package/dist/mcp/auth/callback.js +200 -0
  124. package/dist/mcp/auth/callback.js.map +1 -0
  125. package/dist/mcp/auth/connect.js +392 -0
  126. package/dist/mcp/auth/connect.js.map +1 -0
  127. package/dist/mcp/auth/discovery.js +399 -0
  128. package/dist/mcp/auth/discovery.js.map +1 -0
  129. package/dist/mcp/auth/oauth.js +326 -0
  130. package/dist/mcp/auth/oauth.js.map +1 -0
  131. package/dist/mcp/auth/probe.js +128 -0
  132. package/dist/mcp/auth/probe.js.map +1 -0
  133. package/dist/mcp/auth/secrets.js +446 -0
  134. package/dist/mcp/auth/secrets.js.map +1 -0
  135. package/dist/mcp/auth/store.js +260 -0
  136. package/dist/mcp/auth/store.js.map +1 -0
  137. package/dist/mcp/auth/tokenSource.js +55 -0
  138. package/dist/mcp/auth/tokenSource.js.map +1 -0
  139. package/dist/mcp/config/atomicWrite.js +98 -0
  140. package/dist/mcp/config/atomicWrite.js.map +1 -0
  141. package/dist/mcp/config/clients.js +171 -0
  142. package/dist/mcp/config/clients.js.map +1 -0
  143. package/dist/mcp/config/cybedefend-servers.js +48 -0
  144. package/dist/mcp/config/cybedefend-servers.js.map +1 -0
  145. package/dist/mcp/config/discover.js +114 -0
  146. package/dist/mcp/config/discover.js.map +1 -0
  147. package/dist/mcp/config/hook.js +123 -0
  148. package/dist/mcp/config/hook.js.map +1 -0
  149. package/dist/mcp/config/index.js +11 -0
  150. package/dist/mcp/config/index.js.map +1 -0
  151. package/dist/mcp/config/install.js +132 -0
  152. package/dist/mcp/config/install.js.map +1 -0
  153. package/dist/mcp/config/installed.js +37 -0
  154. package/dist/mcp/config/installed.js.map +1 -0
  155. package/dist/mcp/config/jsonEdit.js +119 -0
  156. package/dist/mcp/config/jsonEdit.js.map +1 -0
  157. package/dist/mcp/config/lock.js +102 -0
  158. package/dist/mcp/config/lock.js.map +1 -0
  159. package/dist/mcp/config/proxyBinary.js +211 -0
  160. package/dist/mcp/config/proxyBinary.js.map +1 -0
  161. package/dist/mcp/config/rewrite.js +388 -0
  162. package/dist/mcp/config/rewrite.js.map +1 -0
  163. package/dist/mcp/config/types.js +34 -0
  164. package/dist/mcp/config/types.js.map +1 -0
  165. package/dist/mcp/proxy/audit.js +83 -0
  166. package/dist/mcp/proxy/audit.js.map +1 -0
  167. package/dist/mcp/proxy/authInterceptor.js +119 -0
  168. package/dist/mcp/proxy/authInterceptor.js.map +1 -0
  169. package/dist/mcp/proxy/expand.js +93 -0
  170. package/dist/mcp/proxy/expand.js.map +1 -0
  171. package/dist/mcp/proxy/inject.js +412 -0
  172. package/dist/mcp/proxy/inject.js.map +1 -0
  173. package/dist/mcp/proxy/intercept.js +63 -0
  174. package/dist/mcp/proxy/intercept.js.map +1 -0
  175. package/dist/mcp/proxy/jsonrpc.js +153 -0
  176. package/dist/mcp/proxy/jsonrpc.js.map +1 -0
  177. package/dist/mcp/proxy/log.js +109 -0
  178. package/dist/mcp/proxy/log.js.map +1 -0
  179. package/dist/mcp/proxy/paths.js +57 -0
  180. package/dist/mcp/proxy/paths.js.map +1 -0
  181. package/dist/mcp/proxy/proxy.js +520 -0
  182. package/dist/mcp/proxy/proxy.js.map +1 -0
  183. package/dist/mcp/proxy/serverCheckTimer.js +75 -0
  184. package/dist/mcp/proxy/serverCheckTimer.js.map +1 -0
  185. package/dist/mcp/proxy/sessionLifecycle.js +29 -0
  186. package/dist/mcp/proxy/sessionLifecycle.js.map +1 -0
  187. package/dist/mcp/proxy/spec.js +203 -0
  188. package/dist/mcp/proxy/spec.js.map +1 -0
  189. package/dist/mcp/proxy/sse.js +77 -0
  190. package/dist/mcp/proxy/sse.js.map +1 -0
  191. package/dist/mcp/proxy/upstream.js +2 -0
  192. package/dist/mcp/proxy/upstream.js.map +1 -0
  193. package/dist/mcp/proxy/upstreamHttp.js +499 -0
  194. package/dist/mcp/proxy/upstreamHttp.js.map +1 -0
  195. package/dist/mcp/proxy/upstreamStdio.js +188 -0
  196. package/dist/mcp/proxy/upstreamStdio.js.map +1 -0
  197. package/dist/mcp/proxy/upstreams.js +143 -0
  198. package/dist/mcp/proxy/upstreams.js.map +1 -0
  199. package/dist/mcp/proxy/verifyInterceptor.js +718 -0
  200. package/dist/mcp/proxy/verifyInterceptor.js.map +1 -0
  201. package/dist/mcp/proxy/windowsCommand.js +99 -0
  202. package/dist/mcp/proxy/windowsCommand.js.map +1 -0
  203. package/dist/mcp/report/buffer.js +351 -0
  204. package/dist/mcp/report/buffer.js.map +1 -0
  205. package/dist/mcp/report/coalesce.js +165 -0
  206. package/dist/mcp/report/coalesce.js.map +1 -0
  207. package/dist/mcp/report/constants.js +51 -0
  208. package/dist/mcp/report/constants.js.map +1 -0
  209. package/dist/mcp/report/project.js +83 -0
  210. package/dist/mcp/report/project.js.map +1 -0
  211. package/dist/mcp/report/reporter.js +443 -0
  212. package/dist/mcp/report/reporter.js.map +1 -0
  213. package/dist/mcp/report/types.js +3 -0
  214. package/dist/mcp/report/types.js.map +1 -0
  215. package/dist/mcp/report/validate.js +179 -0
  216. package/dist/mcp/report/validate.js.map +1 -0
  217. package/dist/mcp/scan/args.js +228 -0
  218. package/dist/mcp/scan/args.js.map +1 -0
  219. package/dist/mcp/scan/results.js +253 -0
  220. package/dist/mcp/scan/results.js.map +1 -0
  221. package/dist/mcp/scan/types.js +40 -0
  222. package/dist/mcp/scan/types.js.map +1 -0
  223. package/dist/mcp/verify/api.js +514 -0
  224. package/dist/mcp/verify/api.js.map +1 -0
  225. package/dist/mcp/verify/backend.js +143 -0
  226. package/dist/mcp/verify/backend.js.map +1 -0
  227. package/dist/mcp/verify/cache.js +200 -0
  228. package/dist/mcp/verify/cache.js.map +1 -0
  229. package/dist/mcp/verify/contract-errors.js +198 -0
  230. package/dist/mcp/verify/contract-errors.js.map +1 -0
  231. package/dist/mcp/verify/digest.js +91 -0
  232. package/dist/mcp/verify/digest.js.map +1 -0
  233. package/dist/mcp/verify/factory.js +56 -0
  234. package/dist/mcp/verify/factory.js.map +1 -0
  235. package/dist/mcp/verify/identity.js +286 -0
  236. package/dist/mcp/verify/identity.js.map +1 -0
  237. package/dist/mcp/verify/policy-store.js +63 -0
  238. package/dist/mcp/verify/policy-store.js.map +1 -0
  239. package/dist/mcp/verify/policy.js +160 -0
  240. package/dist/mcp/verify/policy.js.map +1 -0
  241. package/dist/mcp/verify/pool.js +27 -0
  242. package/dist/mcp/verify/pool.js.map +1 -0
  243. package/dist/mcp/verify/token-refresh.js +44 -0
  244. package/dist/mcp/verify/token-refresh.js.map +1 -0
  245. package/dist/mcp/verify/token.js +67 -0
  246. package/dist/mcp/verify/token.js.map +1 -0
  247. package/dist/mcp/verify/verifier.js +1166 -0
  248. package/dist/mcp/verify/verifier.js.map +1 -0
  249. package/dist/org-gate.js +1 -1
  250. package/dist/org-gate.js.map +1 -1
  251. package/dist/org.js +146 -0
  252. package/dist/org.js.map +1 -0
  253. package/dist/project-create.js +129 -0
  254. package/dist/project-create.js.map +1 -0
  255. package/dist/prompts.js +21 -0
  256. package/dist/prompts.js.map +1 -1
  257. package/dist/re2.wasm +0 -0
  258. package/dist/status.js +61 -0
  259. package/dist/status.js.map +1 -1
  260. package/dist/uninstall.js +49 -4
  261. package/dist/uninstall.js.map +1 -1
  262. package/dist/utils.js +62 -6
  263. package/dist/utils.js.map +1 -1
  264. package/package.json +10 -3
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Credentials obtained from THIRD-PARTY MCP servers.
3
+ *
4
+ * ─── Why a store of its own, rather than `src/auth/auth-store.ts` ───────────
5
+ *
6
+ * `auth-store.ts` is cut for the single CybeDefend session: its type demands
7
+ * `logtoEndpoint` / `cliAppId` / `logtoResource`, and its file fallback holds
8
+ * one entry. Here there is one credential PER MCP SERVER — a Linear token has
9
+ * no business at Notion.
10
+ *
11
+ * Where the bytes end up is `secrets.ts`'s problem: keychain on macOS, secret
12
+ * service on Linux, DPAPI on Windows, and an encrypted file when none of
13
+ * those answers. This file only decides what is worth keeping, when a
14
+ * credential is still good, and when it has to be erased.
15
+ *
16
+ * It never throws: an unreadable credential behaves like an absent one and
17
+ * the caller asks for a new authorization.
18
+ *
19
+ * ─── Tests never touch the real store ───────────────────────────────────────
20
+ *
21
+ * Every entry point takes a `backend` in its dependencies. `pnpm test` may run
22
+ * on a developer's machine, and a test that wrote into the real keychain
23
+ * during the suite would be exactly the class of damage the plan forbids for
24
+ * `~/.cybedefend/`, extended to the system keychain.
25
+ */
26
+ import { mcpHomeDir } from '../proxy/paths.js';
27
+ import { canonicalMcpUrl, mcpAuthKey, refreshMcpToken } from './oauth.js';
28
+ import { selectSecretBackend, } from './secrets.js';
29
+ export { MCP_KEYCHAIN_SERVICE } from './secrets.js';
30
+ /**
31
+ * Choosing a backend runs a real write/read/delete round trip, so the result
32
+ * is kept. A proxy session reads its token once and refreshes it rarely;
33
+ * paying three keychain calls every time would be visible.
34
+ *
35
+ * Keyed by the directory the credentials live in, not process-wide: two calls
36
+ * with different environments are two different stores, and a cache that
37
+ * ignored that would hand the second one the first one's backend — which is
38
+ * a test suite quietly writing into the developer's real keychain.
39
+ */
40
+ const selected = new Map();
41
+ function backendFor(deps) {
42
+ if (deps.backend)
43
+ return deps.backend;
44
+ const key = deps.dir ?? mcpHomeDir(deps.env ?? process.env);
45
+ const known = selected.get(key);
46
+ if (known)
47
+ return known.backend;
48
+ const chosen = selectSecretBackend(deps);
49
+ selected.set(key, chosen);
50
+ return chosen.backend;
51
+ }
52
+ /** Forgets the cached choices. For tests, and for `vibedefend doctor`. */
53
+ export function resetSecretBackend() {
54
+ selected.clear();
55
+ }
56
+ /**
57
+ * Persists a server's credential.
58
+ *
59
+ * Returns `false` when nothing could be written anywhere. That distinction
60
+ * matters: a store that silently failed would have the caller believe the
61
+ * authorization was kept, and the next read return `null` — the user is sent
62
+ * back to the browser every session with no explanation, the worst of both
63
+ * worlds. `secrets.ts` already announced the degradation from the platform
64
+ * store to the encrypted file; this is the case where even that failed.
65
+ */
66
+ export function storeMcpAuth(serverUrl, auth, deps = {}) {
67
+ try {
68
+ backendFor(deps).set(mcpAuthKey(serverUrl), JSON.stringify(auth));
69
+ return true;
70
+ }
71
+ catch {
72
+ // The error is deliberately not propagated or interpolated: its message
73
+ // comes from a command that was handed a token.
74
+ return false;
75
+ }
76
+ }
77
+ export function mcpClientKey(serverUrl) {
78
+ return `mcp-client|${canonicalMcpUrl(serverUrl) ?? serverUrl.trim().toLowerCase()}`;
79
+ }
80
+ export function storeMcpClient(serverUrl, client, deps = {}) {
81
+ try {
82
+ backendFor(deps).set(mcpClientKey(serverUrl), JSON.stringify(client));
83
+ return true;
84
+ }
85
+ catch {
86
+ return false;
87
+ }
88
+ }
89
+ export function loadMcpClient(serverUrl, deps = {}) {
90
+ let blob;
91
+ try {
92
+ blob = backendFor(deps).get(mcpClientKey(serverUrl));
93
+ }
94
+ catch {
95
+ return null;
96
+ }
97
+ if (!blob)
98
+ return null;
99
+ try {
100
+ const parsed = JSON.parse(blob);
101
+ if (typeof parsed.clientId !== 'string' || parsed.clientId === '')
102
+ return null;
103
+ return {
104
+ clientId: parsed.clientId,
105
+ clientSecret: typeof parsed.clientSecret === 'string' ? parsed.clientSecret : null,
106
+ callbackPort: typeof parsed.callbackPort === 'number' ? parsed.callbackPort : null,
107
+ };
108
+ }
109
+ catch {
110
+ return null;
111
+ }
112
+ }
113
+ export function clearMcpClient(serverUrl, deps = {}) {
114
+ try {
115
+ backendFor(deps).delete(mcpClientKey(serverUrl));
116
+ }
117
+ catch {
118
+ /* already gone, or the store is unavailable */
119
+ }
120
+ }
121
+ /** A server's credential, or `null`. */
122
+ export function loadMcpAuth(serverUrl, deps = {}) {
123
+ let blob;
124
+ try {
125
+ blob = backendFor(deps).get(mcpAuthKey(serverUrl));
126
+ }
127
+ catch {
128
+ return null;
129
+ }
130
+ if (!blob)
131
+ return null;
132
+ let parsed;
133
+ try {
134
+ parsed = JSON.parse(blob);
135
+ }
136
+ catch {
137
+ return null;
138
+ }
139
+ if (typeof parsed.accessToken !== 'string' || parsed.accessToken === '')
140
+ return null;
141
+ if (typeof parsed.tokenEndpoint !== 'string' || typeof parsed.clientId !== 'string')
142
+ return null;
143
+ // No check on `resource` here. The partitioning between servers is already
144
+ // structural: the key IS the canonical URL of the server, so a credential
145
+ // read under it was written for it. The legacy store also indexed by HOST
146
+ // alone, which is why it needed `resource` to prove ownership; that key is
147
+ // gone. Re-adding the check would reject a legitimate credential from an
148
+ // authorization server that names its resource differently from the MCP
149
+ // endpoint — `https://host/` for `https://host/mcp` is a common shape.
150
+ return parsed;
151
+ }
152
+ /**
153
+ * Erases a server's credential.
154
+ *
155
+ * Called by `ensureMcpAccessToken` on `invalid_grant`: keeping a dead refresh
156
+ * token would reproduce the failure on every later call, for ever and
157
+ * silently. Erasing makes the missing authorization VISIBLE to the caller
158
+ * (`loadMcpAuth` returns `null`), which can then ask for a reconnection
159
+ * instead of looping.
160
+ */
161
+ export function clearMcpAuth(serverUrl, deps = {}) {
162
+ try {
163
+ backendFor(deps).delete(mcpAuthKey(serverUrl));
164
+ }
165
+ catch {
166
+ /* already gone, or the store is unavailable */
167
+ }
168
+ }
169
+ /**
170
+ * The access token if it is still good, or `null`. NEVER refreshes.
171
+ *
172
+ * 60 s of margin: a token that expires while the request is in flight is as
173
+ * useless as an expired one, and replaying it fails the probe instead of
174
+ * cleanly asking for a new authorization.
175
+ */
176
+ export function validMcpAccessToken(serverUrl, deps = {}) {
177
+ const auth = loadMcpAuth(serverUrl, deps);
178
+ if (!auth)
179
+ return null;
180
+ if (auth.expiresAt && auth.expiresAt - 60_000 <= Date.now())
181
+ return null;
182
+ return auth.accessToken;
183
+ }
184
+ /**
185
+ * Renews the credential whatever the local clock thinks, and persists it.
186
+ *
187
+ * `ensureMcpAccessToken` only refreshes a token it believes has expired, which
188
+ * is the right thing on the happy path and the wrong thing after a 401: a
189
+ * server that revoked a token, or that expires it earlier than it advertised,
190
+ * leaves us holding one that looks perfectly valid here. Re-reading the store
191
+ * would hand back the same dead token and the retry would fail for the same
192
+ * reason as the first attempt.
193
+ *
194
+ * `null` means a new human authorization is needed.
195
+ */
196
+ export async function refreshMcpAuth(serverUrl, deps = {}) {
197
+ const auth = loadMcpAuth(serverUrl, deps);
198
+ if (!auth || !auth.refreshToken)
199
+ return null;
200
+ const outcome = await (deps.refreshFn ?? refreshMcpToken)({
201
+ tokenEndpoint: auth.tokenEndpoint,
202
+ clientId: auth.clientId,
203
+ clientSecret: auth.clientSecret,
204
+ refreshToken: auth.refreshToken,
205
+ resource: auth.resource,
206
+ });
207
+ if (!outcome.ok) {
208
+ if (outcome.reason === 'invalid_grant')
209
+ clearMcpAuth(serverUrl, deps);
210
+ return null;
211
+ }
212
+ const renewed = { ...auth, ...outcome.tokens };
213
+ storeMcpAuth(serverUrl, renewed, deps);
214
+ return renewed.accessToken;
215
+ }
216
+ /**
217
+ * A usable access token, renewing if needed.
218
+ *
219
+ * `validMcpAccessToken` only reports the expiry; this repairs it. Without it
220
+ * an authorization does not survive the day and verification stops without a
221
+ * word — measured on a Linear token that expired within hours.
222
+ *
223
+ * `null` means a new human authorization is needed, whether nothing was
224
+ * stored or the renewal failed.
225
+ *
226
+ * An `invalid_grant` erases the local credential: the refresh token is dead.
227
+ * An `unavailable` (network, transient server failure) does NOT: the
228
+ * authorization is still good, only this renewal failed, and forcing a human
229
+ * reconnection for a network blip is a regression, not a protection. A
230
+ * `suspicious_redirect` is left to the caller, which must not escalate to an
231
+ * interactive flow against a server that just behaved that way.
232
+ */
233
+ export async function ensureMcpAccessToken(serverUrl, deps = {}) {
234
+ const auth = loadMcpAuth(serverUrl, deps);
235
+ if (!auth)
236
+ return null;
237
+ const stillValid = !auth.expiresAt || auth.expiresAt - 60_000 > Date.now();
238
+ if (stillValid)
239
+ return auth.accessToken;
240
+ if (!auth.refreshToken)
241
+ return null;
242
+ const outcome = await (deps.refreshFn ?? refreshMcpToken)({
243
+ tokenEndpoint: auth.tokenEndpoint,
244
+ clientId: auth.clientId,
245
+ clientSecret: auth.clientSecret,
246
+ refreshToken: auth.refreshToken,
247
+ resource: auth.resource,
248
+ });
249
+ if (!outcome.ok) {
250
+ if (outcome.reason === 'invalid_grant')
251
+ clearMcpAuth(serverUrl, deps);
252
+ return null;
253
+ }
254
+ // Persisted: otherwise every call would replay a renewal, and the NEW
255
+ // refresh token (rotation) would be lost at the next CLI restart.
256
+ const renewed = { ...auth, ...outcome.tokens };
257
+ storeMcpAuth(serverUrl, renewed, deps);
258
+ return renewed.accessToken;
259
+ }
260
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/mcp/auth/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,eAAe,EAAuB,MAAM,YAAY,CAAC;AAC/F,OAAO,EACL,mBAAmB,GAIpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAgBpD;;;;;;;;;GASG;AACH,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA2B,CAAC;AAEpD,SAAS,UAAU,CAAC,IAAuB;IACzC,IAAI,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC;IACtC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC;IAC5D,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAChC,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC,OAAO,CAAC;IAChC,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IACzC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC1B,OAAO,MAAM,CAAC,OAAO,CAAC;AACxB,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,kBAAkB;IAChC,QAAQ,CAAC,KAAK,EAAE,CAAC;AACnB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAC1B,SAAiB,EACjB,IAAmB,EACnB,OAA0B,EAAE;IAE5B,IAAI,CAAC;QACH,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;QAClE,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,wEAAwE;QACxE,gDAAgD;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAqBD,MAAM,UAAU,YAAY,CAAC,SAAiB;IAC5C,OAAO,cAAc,eAAe,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;AACtF,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,SAAiB,EACjB,MAAuB,EACvB,OAA0B,EAAE;IAE5B,IAAI,CAAC;QACH,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;QACtE,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE,OAA0B,EAAE;IAC3E,IAAI,IAAmB,CAAC;IACxB,IAAI,CAAC;QACH,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA6B,CAAC;QAC5D,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,EAAE;YAAE,OAAO,IAAI,CAAC;QAC/E,OAAO;YACL,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,YAAY,EAAE,OAAO,MAAM,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI;YAClF,YAAY,EAAE,OAAO,MAAM,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI;SACnF,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,SAAiB,EAAE,OAA0B,EAAE;IAC5E,IAAI,CAAC;QACH,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,+CAA+C;IACjD,CAAC;AACH,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,WAAW,CAAC,SAAiB,EAAE,OAA0B,EAAE;IACzE,IAAI,IAAmB,CAAC;IACxB,IAAI,CAAC;QACH,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAEvB,IAAI,MAA8B,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA2B,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ,IAAI,MAAM,CAAC,WAAW,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACrF,IAAI,OAAO,MAAM,CAAC,aAAa,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAEjG,2EAA2E;IAC3E,0EAA0E;IAC1E,0EAA0E;IAC1E,2EAA2E;IAC3E,yEAAyE;IACzE,wEAAwE;IACxE,uEAAuE;IACvE,OAAO,MAAuB,CAAC;AACjC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,SAAiB,EAAE,OAA0B,EAAE;IAC1E,IAAI,CAAC;QACH,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,+CAA+C;IACjD,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CACjC,SAAiB,EACjB,OAA0B,EAAE;IAE5B,MAAM,IAAI,GAAG,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC1C,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,SAAS,GAAG,MAAM,IAAI,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,IAAI,CAAC;IACzE,OAAO,IAAI,CAAC,WAAW,CAAC;AAC1B,CAAC;AAMD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,SAAiB,EACjB,OAAwB,EAAE;IAE1B,MAAM,IAAI,GAAG,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC1C,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IAE7C,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,IAAI,eAAe,CAAC,CAAC;QACxD,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC,CAAC;IAEH,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;QAChB,IAAI,OAAO,CAAC,MAAM,KAAK,eAAe;YAAE,YAAY,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACtE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,OAAO,GAAkB,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAC9D,YAAY,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACvC,OAAO,OAAO,CAAC,WAAW,CAAC;AAC7B,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,SAAiB,EACjB,OAAwB,EAAE;IAE1B,MAAM,IAAI,GAAG,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC1C,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAEvB,MAAM,UAAU,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,SAAS,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3E,IAAI,UAAU;QAAE,OAAO,IAAI,CAAC,WAAW,CAAC;IAExC,IAAI,CAAC,IAAI,CAAC,YAAY;QAAE,OAAO,IAAI,CAAC;IAEpC,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,IAAI,eAAe,CAAC,CAAC;QACxD,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC,CAAC;IAEH,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;QAChB,IAAI,OAAO,CAAC,MAAM,KAAK,eAAe;YAAE,YAAY,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACtE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,sEAAsE;IACtE,kEAAkE;IAClE,MAAM,OAAO,GAAkB,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAC9D,YAAY,CAAC,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACvC,OAAO,OAAO,CAAC,WAAW,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,55 @@
1
+ import { loadMcpAuth, refreshMcpAuth } from './store.js';
2
+ /** A minute of slack, the same `ensureMcpAccessToken` applies. */
3
+ const EXPIRY_SLACK_MS = 60_000;
4
+ export function createTokenSource(serverUrl, deps = {}) {
5
+ let cached = null;
6
+ let inFlight;
7
+ const resolve = async (previous) => {
8
+ let stored = null;
9
+ try {
10
+ stored = loadMcpAuth(serverUrl, deps);
11
+ }
12
+ catch {
13
+ stored = null;
14
+ }
15
+ if (!stored) {
16
+ deps.onLookup?.('absent');
17
+ return { token: null, renewed: false };
18
+ }
19
+ // A read, and nothing else: the token is good as far as the local clock
20
+ // knows and the server has not refused it. The first 401 of every session
21
+ // ends here, since nothing was sent before it.
22
+ const stillValid = !stored.expiresAt || stored.expiresAt - EXPIRY_SLACK_MS > Date.now();
23
+ if (stillValid && stored.accessToken !== previous) {
24
+ deps.onLookup?.('found');
25
+ return { token: stored.accessToken, renewed: false };
26
+ }
27
+ // Either the local clock says it expired, or the store handed back the
28
+ // very token the server just refused — a server-side revocation, or an
29
+ // expiry earlier than advertised. Both are repaired the same way, and
30
+ // both are renewals worth the name.
31
+ const refreshed = await refreshMcpAuth(serverUrl, deps).catch(() => null);
32
+ deps.onLookup?.(refreshed ? 'refreshed' : 'absent');
33
+ return { token: refreshed, renewed: refreshed !== null };
34
+ };
35
+ return {
36
+ async token() {
37
+ return cached;
38
+ },
39
+ async reauthorize(previous) {
40
+ // Several requests can hit a 401 at once; one lookup is enough for all.
41
+ if (!inFlight) {
42
+ inFlight = resolve(previous)
43
+ .then((outcome) => {
44
+ cached = outcome.token;
45
+ return outcome;
46
+ })
47
+ .finally(() => {
48
+ inFlight = undefined;
49
+ });
50
+ }
51
+ return inFlight;
52
+ },
53
+ };
54
+ }
55
+ //# sourceMappingURL=tokenSource.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenSource.js","sourceRoot":"","sources":["../../../src/mcp/auth/tokenSource.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,WAAW,EAAE,cAAc,EAAwB,MAAM,YAAY,CAAC;AAoB/E,kEAAkE;AAClE,MAAM,eAAe,GAAG,MAAM,CAAC;AAE/B,MAAM,UAAU,iBAAiB,CAAC,SAAiB,EAAE,OAAwB,EAAE;IAC7E,IAAI,MAAM,GAAkB,IAAI,CAAC;IACjC,IAAI,QAA8C,CAAC;IAEnD,MAAM,OAAO,GAAG,KAAK,EAAE,QAAuB,EAA4B,EAAE;QAC1E,IAAI,MAAM,GAAmC,IAAI,CAAC;QAClD,IAAI,CAAC;YACH,MAAM,GAAG,WAAW,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;QACD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,IAAI,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC;YAC1B,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QACzC,CAAC;QAED,wEAAwE;QACxE,0EAA0E;QAC1E,+CAA+C;QAC/C,MAAM,UAAU,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,SAAS,GAAG,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACxF,IAAI,UAAU,IAAI,MAAM,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;YAClD,IAAI,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,CAAC;YACzB,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;QACvD,CAAC;QAED,uEAAuE;QACvE,uEAAuE;QACvE,sEAAsE;QACtE,oCAAoC;QACpC,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QAC1E,IAAI,CAAC,QAAQ,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACpD,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,KAAK,IAAI,EAAE,CAAC;IAC3D,CAAC,CAAC;IAEF,OAAO;QACL,KAAK,CAAC,KAAK;YACT,OAAO,MAAM,CAAC;QAChB,CAAC;QACD,KAAK,CAAC,WAAW,CAAC,QAAuB;YACvC,wEAAwE;YACxE,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;qBACzB,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE;oBAChB,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;oBACvB,OAAO,OAAO,CAAC;gBACjB,CAAC,CAAC;qBACD,OAAO,CAAC,GAAG,EAAE;oBACZ,QAAQ,GAAG,SAAS,CAAC;gBACvB,CAAC,CAAC,CAAC;YACP,CAAC;YACD,OAAO,QAAQ,CAAC;QAClB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Replacing a file without ever letting a reader see half of it.
3
+ *
4
+ * Write to a sibling, then `rename`. The rename is atomic on every filesystem
5
+ * we ship to, so a client reading `~/.claude.json` at the moment we replace it
6
+ * sees the old document or the new one, never a truncated one — and a crash
7
+ * mid-write leaves the original intact.
8
+ *
9
+ * Two things the naive version gets wrong:
10
+ *
11
+ * - **symlinks.** A `~/.claude.json` that is a link into a dotfiles
12
+ * repository is a common setup, and `rename` onto the link REPLACES THE
13
+ * LINK with a regular file: the repository stops being the source of
14
+ * truth, silently, and the next `git pull` in it does nothing visible.
15
+ * Resolving the target first means we replace what the link points at,
16
+ * which is what the user meant.
17
+ * - **Windows.** `rename` fails with `EBUSY` or `EPERM` when an antivirus or
18
+ * an editor has the target open for a few milliseconds. It is transient
19
+ * and common enough that giving up on the first one loses writes on
20
+ * exactly one platform.
21
+ */
22
+ import { lstatSync, mkdirSync, readlinkSync, realpathSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
23
+ import { dirname, resolve } from 'node:path';
24
+ /** Attempts, and the pause between them, for the Windows `EBUSY` window. */
25
+ export const RENAME_ATTEMPTS = 5;
26
+ const RENAME_PAUSE_MS = 20;
27
+ /**
28
+ * Resolves what `path` really points at.
29
+ *
30
+ * Three cases. An ordinary file resolves to itself. A link resolves to its
31
+ * target. And a DANGLING link — a dotfiles repository that has not been
32
+ * cloned yet — resolves to where it points, which is what `> link` does in a
33
+ * shell: the target gets created and the link starts working. Replacing the
34
+ * link with a regular file instead would quietly take the path out of the
35
+ * repository for good.
36
+ */
37
+ export function resolveWriteTarget(path) {
38
+ try {
39
+ return realpathSync(path);
40
+ }
41
+ catch {
42
+ /* it does not exist, or it is a dangling link; both are handled below */
43
+ }
44
+ try {
45
+ if (lstatSync(path).isSymbolicLink()) {
46
+ return resolve(dirname(path), readlinkSync(path));
47
+ }
48
+ }
49
+ catch {
50
+ /* not a link either: the caller is creating the file */
51
+ }
52
+ return path;
53
+ }
54
+ export function writeFileAtomic(path, content, options = {}) {
55
+ const target = resolveWriteTarget(path);
56
+ const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;
57
+ mkdirSync(dirname(target), { recursive: true, mode: options.directoryMode ?? 0o700 });
58
+ writeFileSync(temporary, content, { encoding: 'utf8', mode: options.mode ?? 0o600 });
59
+ try {
60
+ renameWithRetry(temporary, target, options.sleep ?? busyWait, options.renameFn ?? renameSync);
61
+ }
62
+ catch (error) {
63
+ try {
64
+ unlinkSync(temporary);
65
+ }
66
+ catch {
67
+ /* nothing to clean up */
68
+ }
69
+ throw error;
70
+ }
71
+ }
72
+ function renameWithRetry(from, to, sleep, rename) {
73
+ for (let attempt = 1;; attempt += 1) {
74
+ try {
75
+ rename(from, to);
76
+ return;
77
+ }
78
+ catch (error) {
79
+ const code = error.code;
80
+ const transient = code === 'EBUSY' || code === 'EPERM' || code === 'EACCES';
81
+ if (!transient || attempt >= RENAME_ATTEMPTS)
82
+ throw error;
83
+ sleep(RENAME_PAUSE_MS * attempt);
84
+ }
85
+ }
86
+ }
87
+ /**
88
+ * Synchronous on purpose: every caller here is synchronous, and making them
89
+ * async to sleep properly would turn a config write into something a caller
90
+ * could forget to await. The total wait is bounded at 300 ms.
91
+ */
92
+ function busyWait(ms) {
93
+ const until = Date.now() + ms;
94
+ while (Date.now() < until) {
95
+ /* spin */
96
+ }
97
+ }
98
+ //# sourceMappingURL=atomicWrite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomicWrite.js","sourceRoot":"","sources":["../../../src/mcp/config/atomicWrite.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE7C,4EAA4E;AAC5E,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC;AACjC,MAAM,eAAe,GAAG,EAAE,CAAC;AAa3B;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;IAC3E,CAAC;IACD,IAAI,CAAC;QACH,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,cAAc,EAAE,EAAE,CAAC;YACrC,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,wDAAwD;IAC1D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,IAAY,EACZ,OAAe,EACf,UAA8B,EAAE;IAEhC,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,SAAS,GAAG,GAAG,MAAM,IAAI,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC;IAE/D,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,aAAa,IAAI,KAAK,EAAE,CAAC,CAAC;IACtF,aAAa,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC;IAErF,IAAI,CAAC;QACH,eAAe,CACb,SAAS,EACT,MAAM,EACN,OAAO,CAAC,KAAK,IAAI,QAAQ,EACzB,OAAO,CAAC,QAAQ,IAAI,UAAU,CAC/B,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC;YACH,UAAU,CAAC,SAAS,CAAC,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,yBAAyB;QAC3B,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CACtB,IAAY,EACZ,EAAU,EACV,KAA2B,EAC3B,MAA0C;IAE1C,KAAK,IAAI,OAAO,GAAG,CAAC,GAAI,OAAO,IAAI,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACjB,OAAO;QACT,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,GAAI,KAA+B,CAAC,IAAI,CAAC;YACnD,MAAM,SAAS,GAAG,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,QAAQ,CAAC;YAC5E,IAAI,CAAC,SAAS,IAAI,OAAO,IAAI,eAAe;gBAAE,MAAM,KAAK,CAAC;YAC1D,KAAK,CAAC,eAAe,GAAG,OAAO,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,QAAQ,CAAC,EAAU;IAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC;IAC9B,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;QAC1B,UAAU;IACZ,CAAC;AACH,CAAC"}
@@ -0,0 +1,171 @@
1
+ /**
2
+ * The five clients, one adapter each.
3
+ *
4
+ * They differ in three ways and no more: where the file lives, what the map
5
+ * of servers is called inside it, and how an HTTP server is spelled. Keeping
6
+ * them in one file makes those three differences readable side by side, which
7
+ * is the only reason anyone opens this module.
8
+ *
9
+ * The HTTP spellings, measured against what each client actually writes:
10
+ *
11
+ * Claude Code {"type":"http","url":…,"headers":{…}}
12
+ * Claude Desktop stdio only — it spawns `mcp-remote` for remote servers
13
+ * Cursor {"url":…,"headers":{…}} (no `type`)
14
+ * VS Code {"type":"http","url":…} (map is `servers`, not `mcpServers`)
15
+ * Windsurf {"serverUrl":…} (its own key)
16
+ */
17
+ import { join } from 'node:path';
18
+ import { clientConfigRoot, clientConfigRootIsOverridden } from '../proxy/paths.js';
19
+ import { wrappedEntryFor, } from './types.js';
20
+ /** The home directory, or `VIBEDEFEND_MCP_DIR` standing in for it (see `paths.ts`). */
21
+ function home(env) {
22
+ return clientConfigRoot(env);
23
+ }
24
+ /**
25
+ * Where a VS Code-family editor keeps its user profile.
26
+ *
27
+ * The same layout serves Claude Desktop, which is why it is shared: macOS
28
+ * puts it under `Application Support`, Windows under `APPDATA`, Linux under
29
+ * `XDG_CONFIG_HOME` or `~/.config`. When `VIBEDEFEND_MCP_DIR` stands in for
30
+ * the home directory, `APPDATA` and `XDG_CONFIG_HOME` are ignored too: they
31
+ * would point back at the real profile.
32
+ */
33
+ function applicationSupport(env, platform, app) {
34
+ const isolated = clientConfigRootIsOverridden(env);
35
+ if (platform === 'darwin')
36
+ return join(home(env), 'Library', 'Application Support', app);
37
+ if (platform === 'win32') {
38
+ const roaming = !isolated && env.APPDATA ? env.APPDATA : join(home(env), 'AppData', 'Roaming');
39
+ return join(roaming, app);
40
+ }
41
+ const xdg = !isolated && env.XDG_CONFIG_HOME ? env.XDG_CONFIG_HOME : join(home(env), '.config');
42
+ return join(xdg, app);
43
+ }
44
+ /** `{command, args, env, cwd}`, the shape every client accepts. */
45
+ function parseStdio(raw) {
46
+ const command = typeof raw.command === 'string' ? raw.command : '';
47
+ if (command === '')
48
+ return undefined;
49
+ return {
50
+ transport: 'stdio',
51
+ command,
52
+ args: Array.isArray(raw.args) ? raw.args.map((arg) => String(arg)) : [],
53
+ env: stringMap(raw.env),
54
+ cwd: typeof raw.cwd === 'string' ? raw.cwd : undefined,
55
+ };
56
+ }
57
+ function parseHttp(url, headers) {
58
+ if (typeof url !== 'string' || !/^https?:\/\//i.test(url))
59
+ return undefined;
60
+ return { transport: 'http', url, headers: stringMap(headers) };
61
+ }
62
+ function stringMap(value) {
63
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
64
+ return undefined;
65
+ const out = {};
66
+ for (const [key, entry] of Object.entries(value)) {
67
+ if (typeof entry === 'string')
68
+ out[key] = entry;
69
+ }
70
+ return Object.keys(out).length > 0 ? out : undefined;
71
+ }
72
+ function classify(spec) {
73
+ if (!spec)
74
+ return { kind: 'unknown' };
75
+ return { kind: spec.transport, spec };
76
+ }
77
+ /** The common case: one user-level file with an `mcpServers` object. */
78
+ function simpleAdapter(options) {
79
+ return {
80
+ id: options.id,
81
+ label: options.label,
82
+ locations: (env, platform) => [
83
+ { client: options.id, file: options.file(env, platform), scope: 'user' },
84
+ ],
85
+ serverMaps: () => [{ keys: [options.mapKey] }],
86
+ parse: (_name, raw) => classify(options.parse(raw)),
87
+ wrappedEntry: (upstreamName) => wrappedEntryFor(upstreamName),
88
+ };
89
+ }
90
+ export const claudeCodeAdapter = {
91
+ id: 'claude-code',
92
+ label: 'Claude Code',
93
+ locations: (env) => [{ client: 'claude-code', file: join(home(env), '.claude.json'), scope: 'user' }],
94
+ /**
95
+ * Two levels in one file. `mcpServers` at the root is what
96
+ * `claude mcp add --scope user` writes; `projects.<path>.mcpServers` is
97
+ * what the default `--scope local` writes, and it is visible only from that
98
+ * directory. BOTH are discovered and BOTH are rewritten: they live in the
99
+ * same user-owned file, and the second is where `claude mcp add` writes by
100
+ * default (see `McpConfigScope`, amended 2026-09-09).
101
+ */
102
+ serverMaps: (document, location) => {
103
+ const maps = [{ keys: ['mcpServers'] }];
104
+ const projects = document.value.projects;
105
+ if (typeof projects === 'object' && projects !== null && !Array.isArray(projects)) {
106
+ for (const projectPath of Object.keys(projects)) {
107
+ maps.push({ keys: ['projects', projectPath, 'mcpServers'] });
108
+ }
109
+ }
110
+ void location;
111
+ return maps;
112
+ },
113
+ parse: (_name, raw) => classify(raw.type === 'http' || raw.type === 'sse' || typeof raw.url === 'string'
114
+ ? parseHttp(raw.url, raw.headers)
115
+ : parseStdio(raw)),
116
+ wrappedEntry: (upstreamName) => wrappedEntryFor(upstreamName),
117
+ };
118
+ export const cursorAdapter = simpleAdapter({
119
+ id: 'cursor',
120
+ label: 'Cursor',
121
+ file: (env) => join(home(env), '.cursor', 'mcp.json'),
122
+ mapKey: 'mcpServers',
123
+ // Cursor tells the two apart by the presence of `url`, not by a `type`.
124
+ parse: (raw) => (typeof raw.url === 'string' ? parseHttp(raw.url, raw.headers) : parseStdio(raw)),
125
+ });
126
+ export const windsurfAdapter = simpleAdapter({
127
+ id: 'windsurf',
128
+ label: 'Windsurf',
129
+ file: (env) => join(home(env), '.codeium', 'windsurf', 'mcp_config.json'),
130
+ mapKey: 'mcpServers',
131
+ parse: (raw) => typeof raw.serverUrl === 'string'
132
+ ? parseHttp(raw.serverUrl, raw.headers)
133
+ : typeof raw.url === 'string'
134
+ ? parseHttp(raw.url, raw.headers)
135
+ : parseStdio(raw),
136
+ });
137
+ export const vscodeAdapter = simpleAdapter({
138
+ id: 'vscode',
139
+ label: 'VS Code',
140
+ file: (env, platform) => join(applicationSupport(env, platform, 'Code'), 'User', 'mcp.json'),
141
+ // `servers`, NOT `mcpServers`. Getting this wrong reads an empty file and
142
+ // reports "no MCP server configured" on a machine full of them.
143
+ mapKey: 'servers',
144
+ parse: (raw) => (typeof raw.url === 'string' ? parseHttp(raw.url, raw.headers) : parseStdio(raw)),
145
+ });
146
+ export const claudeDesktopAdapter = simpleAdapter({
147
+ id: 'claude-desktop',
148
+ label: 'Claude Desktop',
149
+ file: (env, platform) => join(applicationSupport(env, platform, 'Claude'), 'claude_desktop_config.json'),
150
+ mapKey: 'mcpServers',
151
+ // Claude Desktop has no HTTP transport: a remote server is configured as a
152
+ // command that spawns `mcp-remote`, so this is stdio-only on purpose.
153
+ parse: (raw) => parseStdio(raw),
154
+ });
155
+ export const MCP_CLIENT_ADAPTERS = [
156
+ claudeCodeAdapter,
157
+ cursorAdapter,
158
+ vscodeAdapter,
159
+ windsurfAdapter,
160
+ claudeDesktopAdapter,
161
+ ];
162
+ export function mcpAdapterFor(id) {
163
+ const adapter = MCP_CLIENT_ADAPTERS.find((candidate) => candidate.id === id);
164
+ if (!adapter)
165
+ throw new Error(`no MCP client adapter for "${id}"`);
166
+ return adapter;
167
+ }
168
+ export function isMcpClientId(value) {
169
+ return MCP_CLIENT_ADAPTERS.some((adapter) => adapter.id === value);
170
+ }
171
+ //# sourceMappingURL=clients.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clients.js","sourceRoot":"","sources":["../../../src/mcp/config/clients.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * CybeDefend's own MCP servers, which the proxy never wraps.
3
+ *
4
+ * Wrapping one bought nothing (its tools would be checked against itself) and
5
+ * cost a second OAuth sign-in, without which the agent lost the CybeDefend
6
+ * tools; and `vibedefend install` manages that entry itself, so each install
7
+ * wrote it back in clear and the next sync wrapped it again under another
8
+ * name.
9
+ *
10
+ * Trusted sources only, as `trustedMcpNames` in `uninstall.ts`: the built-in
11
+ * regions, the region recorded at install time and the user-global
12
+ * ~/.cybedefend/regions.json. Never the `vibedefend.regions.json` of the
13
+ * working directory: a cloned repository could list any server there and
14
+ * take it out of the proxy.
15
+ */
16
+ import { readFileSync } from 'node:fs';
17
+ import { homedir } from 'node:os';
18
+ import { join } from 'node:path';
19
+ import { sameMcpUrl } from '../../clients/mcp-url.js';
20
+ import { REGIONS } from '../../config.js';
21
+ function readJsonQuiet(path) {
22
+ try {
23
+ return JSON.parse(readFileSync(path, 'utf8'));
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ /** The MCP URLs of every CybeDefend region this machine trusts. */
30
+ export function cybedefendMcpUrls(env = process.env) {
31
+ const dir = join(env.CYBEDEFEND_HOME ?? env.HOME ?? homedir(), '.cybedefend');
32
+ const urls = REGIONS.map((region) => region.mcpUrl);
33
+ const runtime = readJsonQuiet(join(dir, 'runtime-config.json'));
34
+ urls.push(runtime?.region?.mcpUrl);
35
+ const userRegions = readJsonQuiet(join(dir, 'regions.json'));
36
+ if (Array.isArray(userRegions)) {
37
+ for (const region of userRegions) {
38
+ if (region && typeof region === 'object')
39
+ urls.push(region.mcpUrl);
40
+ }
41
+ }
42
+ return urls.filter((url) => typeof url === 'string' && url.trim() !== '');
43
+ }
44
+ /** Is `url` one of CybeDefend's own MCP servers? */
45
+ export function isCybeDefendMcpUrl(url, env = process.env) {
46
+ return cybedefendMcpUrls(env).some((known) => sameMcpUrl(known, url));
47
+ }
48
+ //# sourceMappingURL=cybedefend-servers.js.map