@burtson-labs/agent-core 1.6.13

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 (195) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +88 -0
  3. package/dist/index.d.ts +16 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +52 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/mcp/activation.d.ts +60 -0
  8. package/dist/mcp/activation.d.ts.map +1 -0
  9. package/dist/mcp/activation.js +139 -0
  10. package/dist/mcp/activation.js.map +1 -0
  11. package/dist/mcp/clientPool.d.ts +202 -0
  12. package/dist/mcp/clientPool.d.ts.map +1 -0
  13. package/dist/mcp/clientPool.js +469 -0
  14. package/dist/mcp/clientPool.js.map +1 -0
  15. package/dist/mcp/index.d.ts +18 -0
  16. package/dist/mcp/index.d.ts.map +1 -0
  17. package/dist/mcp/index.js +28 -0
  18. package/dist/mcp/index.js.map +1 -0
  19. package/dist/mcp/server.d.ts +43 -0
  20. package/dist/mcp/server.d.ts.map +1 -0
  21. package/dist/mcp/server.js +130 -0
  22. package/dist/mcp/server.js.map +1 -0
  23. package/dist/mcp/toolAdapter.d.ts +57 -0
  24. package/dist/mcp/toolAdapter.d.ts.map +1 -0
  25. package/dist/mcp/toolAdapter.js +223 -0
  26. package/dist/mcp/toolAdapter.js.map +1 -0
  27. package/dist/mcp/types.d.ts +122 -0
  28. package/dist/mcp/types.d.ts.map +1 -0
  29. package/dist/mcp/types.js +15 -0
  30. package/dist/mcp/types.js.map +1 -0
  31. package/dist/providers/deterministic-provider.d.ts +21 -0
  32. package/dist/providers/deterministic-provider.d.ts.map +1 -0
  33. package/dist/providers/deterministic-provider.js +80 -0
  34. package/dist/providers/deterministic-provider.js.map +1 -0
  35. package/dist/providers/provider-client.d.ts +12 -0
  36. package/dist/providers/provider-client.d.ts.map +1 -0
  37. package/dist/providers/provider-client.js +11 -0
  38. package/dist/providers/provider-client.js.map +1 -0
  39. package/dist/runtime/AgentRuntime.d.ts +67 -0
  40. package/dist/runtime/AgentRuntime.d.ts.map +1 -0
  41. package/dist/runtime/AgentRuntime.js +382 -0
  42. package/dist/runtime/AgentRuntime.js.map +1 -0
  43. package/dist/security/secretPatterns.d.ts +76 -0
  44. package/dist/security/secretPatterns.d.ts.map +1 -0
  45. package/dist/security/secretPatterns.js +290 -0
  46. package/dist/security/secretPatterns.js.map +1 -0
  47. package/dist/tools/ask-user-tool.d.ts +19 -0
  48. package/dist/tools/ask-user-tool.d.ts.map +1 -0
  49. package/dist/tools/ask-user-tool.js +148 -0
  50. package/dist/tools/ask-user-tool.js.map +1 -0
  51. package/dist/tools/compactMessages.d.ts +52 -0
  52. package/dist/tools/compactMessages.d.ts.map +1 -0
  53. package/dist/tools/compactMessages.js +158 -0
  54. package/dist/tools/compactMessages.js.map +1 -0
  55. package/dist/tools/core-tools.d.ts +29 -0
  56. package/dist/tools/core-tools.d.ts.map +1 -0
  57. package/dist/tools/core-tools.js +2214 -0
  58. package/dist/tools/core-tools.js.map +1 -0
  59. package/dist/tools/git-tools.d.ts +32 -0
  60. package/dist/tools/git-tools.d.ts.map +1 -0
  61. package/dist/tools/git-tools.js +330 -0
  62. package/dist/tools/git-tools.js.map +1 -0
  63. package/dist/tools/index.d.ts +15 -0
  64. package/dist/tools/index.d.ts.map +1 -0
  65. package/dist/tools/index.js +31 -0
  66. package/dist/tools/index.js.map +1 -0
  67. package/dist/tools/language-adapters.d.ts +48 -0
  68. package/dist/tools/language-adapters.d.ts.map +1 -0
  69. package/dist/tools/language-adapters.js +299 -0
  70. package/dist/tools/language-adapters.js.map +1 -0
  71. package/dist/tools/loop/compactionTrigger.d.ts +47 -0
  72. package/dist/tools/loop/compactionTrigger.d.ts.map +1 -0
  73. package/dist/tools/loop/compactionTrigger.js +32 -0
  74. package/dist/tools/loop/compactionTrigger.js.map +1 -0
  75. package/dist/tools/loop/finalAnswerNudges.d.ts +68 -0
  76. package/dist/tools/loop/finalAnswerNudges.d.ts.map +1 -0
  77. package/dist/tools/loop/finalAnswerNudges.js +87 -0
  78. package/dist/tools/loop/finalAnswerNudges.js.map +1 -0
  79. package/dist/tools/loop/goalAnchor.d.ts +72 -0
  80. package/dist/tools/loop/goalAnchor.d.ts.map +1 -0
  81. package/dist/tools/loop/goalAnchor.js +76 -0
  82. package/dist/tools/loop/goalAnchor.js.map +1 -0
  83. package/dist/tools/loop/llmStream.d.ts +70 -0
  84. package/dist/tools/loop/llmStream.d.ts.map +1 -0
  85. package/dist/tools/loop/llmStream.js +181 -0
  86. package/dist/tools/loop/llmStream.js.map +1 -0
  87. package/dist/tools/loop/parallelExecute.d.ts +57 -0
  88. package/dist/tools/loop/parallelExecute.d.ts.map +1 -0
  89. package/dist/tools/loop/parallelExecute.js +54 -0
  90. package/dist/tools/loop/parallelExecute.js.map +1 -0
  91. package/dist/tools/loop/singleToolExecute.d.ts +71 -0
  92. package/dist/tools/loop/singleToolExecute.d.ts.map +1 -0
  93. package/dist/tools/loop/singleToolExecute.js +139 -0
  94. package/dist/tools/loop/singleToolExecute.js.map +1 -0
  95. package/dist/tools/loop/toolCallNormalize.d.ts +57 -0
  96. package/dist/tools/loop/toolCallNormalize.d.ts.map +1 -0
  97. package/dist/tools/loop/toolCallNormalize.js +99 -0
  98. package/dist/tools/loop/toolCallNormalize.js.map +1 -0
  99. package/dist/tools/loop/turnSetup.d.ts +43 -0
  100. package/dist/tools/loop/turnSetup.d.ts.map +1 -0
  101. package/dist/tools/loop/turnSetup.js +48 -0
  102. package/dist/tools/loop/turnSetup.js.map +1 -0
  103. package/dist/tools/ocr.d.ts +52 -0
  104. package/dist/tools/ocr.d.ts.map +1 -0
  105. package/dist/tools/ocr.js +238 -0
  106. package/dist/tools/ocr.js.map +1 -0
  107. package/dist/tools/post-edit-checks.d.ts +46 -0
  108. package/dist/tools/post-edit-checks.d.ts.map +1 -0
  109. package/dist/tools/post-edit-checks.js +236 -0
  110. package/dist/tools/post-edit-checks.js.map +1 -0
  111. package/dist/tools/skill-loader.d.ts +94 -0
  112. package/dist/tools/skill-loader.d.ts.map +1 -0
  113. package/dist/tools/skill-loader.js +422 -0
  114. package/dist/tools/skill-loader.js.map +1 -0
  115. package/dist/tools/skill-registry.d.ts +44 -0
  116. package/dist/tools/skill-registry.d.ts.map +1 -0
  117. package/dist/tools/skill-registry.js +118 -0
  118. package/dist/tools/skill-registry.js.map +1 -0
  119. package/dist/tools/skill-types.d.ts +38 -0
  120. package/dist/tools/skill-types.d.ts.map +1 -0
  121. package/dist/tools/skill-types.js +10 -0
  122. package/dist/tools/skill-types.js.map +1 -0
  123. package/dist/tools/skills/code-review-skill.d.ts +9 -0
  124. package/dist/tools/skills/code-review-skill.d.ts.map +1 -0
  125. package/dist/tools/skills/code-review-skill.js +66 -0
  126. package/dist/tools/skills/code-review-skill.js.map +1 -0
  127. package/dist/tools/skills/core-skill.d.ts +13 -0
  128. package/dist/tools/skills/core-skill.d.ts.map +1 -0
  129. package/dist/tools/skills/core-skill.js +23 -0
  130. package/dist/tools/skills/core-skill.js.map +1 -0
  131. package/dist/tools/skills/git-skill.d.ts +10 -0
  132. package/dist/tools/skills/git-skill.d.ts.map +1 -0
  133. package/dist/tools/skills/git-skill.js +30 -0
  134. package/dist/tools/skills/git-skill.js.map +1 -0
  135. package/dist/tools/skills/index.d.ts +17 -0
  136. package/dist/tools/skills/index.d.ts.map +1 -0
  137. package/dist/tools/skills/index.js +49 -0
  138. package/dist/tools/skills/index.js.map +1 -0
  139. package/dist/tools/skills/interaction-skill.d.ts +14 -0
  140. package/dist/tools/skills/interaction-skill.d.ts.map +1 -0
  141. package/dist/tools/skills/interaction-skill.js +24 -0
  142. package/dist/tools/skills/interaction-skill.js.map +1 -0
  143. package/dist/tools/skills/mail-search-skill.d.ts +25 -0
  144. package/dist/tools/skills/mail-search-skill.d.ts.map +1 -0
  145. package/dist/tools/skills/mail-search-skill.js +343 -0
  146. package/dist/tools/skills/mail-search-skill.js.map +1 -0
  147. package/dist/tools/skills/plan-skill.d.ts +10 -0
  148. package/dist/tools/skills/plan-skill.d.ts.map +1 -0
  149. package/dist/tools/skills/plan-skill.js +126 -0
  150. package/dist/tools/skills/plan-skill.js.map +1 -0
  151. package/dist/tools/skills/semantic-search-skill.d.ts +22 -0
  152. package/dist/tools/skills/semantic-search-skill.d.ts.map +1 -0
  153. package/dist/tools/skills/semantic-search-skill.js +244 -0
  154. package/dist/tools/skills/semantic-search-skill.js.map +1 -0
  155. package/dist/tools/skills/test-gen-skill.d.ts +9 -0
  156. package/dist/tools/skills/test-gen-skill.d.ts.map +1 -0
  157. package/dist/tools/skills/test-gen-skill.js +123 -0
  158. package/dist/tools/skills/test-gen-skill.js.map +1 -0
  159. package/dist/tools/tool-registry.d.ts +60 -0
  160. package/dist/tools/tool-registry.d.ts.map +1 -0
  161. package/dist/tools/tool-registry.js +200 -0
  162. package/dist/tools/tool-registry.js.map +1 -0
  163. package/dist/tools/tool-types.d.ts +281 -0
  164. package/dist/tools/tool-types.d.ts.map +1 -0
  165. package/dist/tools/tool-types.js +10 -0
  166. package/dist/tools/tool-types.js.map +1 -0
  167. package/dist/tools/tool-use-loop.d.ts +231 -0
  168. package/dist/tools/tool-use-loop.d.ts.map +1 -0
  169. package/dist/tools/tool-use-loop.js +2057 -0
  170. package/dist/tools/tool-use-loop.js.map +1 -0
  171. package/dist/tools/tool-use-parser.d.ts +78 -0
  172. package/dist/tools/tool-use-parser.d.ts.map +1 -0
  173. package/dist/tools/tool-use-parser.js +427 -0
  174. package/dist/tools/tool-use-parser.js.map +1 -0
  175. package/dist/tools/toolAvailabilityDetector.d.ts +48 -0
  176. package/dist/tools/toolAvailabilityDetector.d.ts.map +1 -0
  177. package/dist/tools/toolAvailabilityDetector.js +156 -0
  178. package/dist/tools/toolAvailabilityDetector.js.map +1 -0
  179. package/dist/tools/unified-patch.d.ts +87 -0
  180. package/dist/tools/unified-patch.d.ts.map +1 -0
  181. package/dist/tools/unified-patch.js +217 -0
  182. package/dist/tools/unified-patch.js.map +1 -0
  183. package/dist/types/agent.d.ts +69 -0
  184. package/dist/types/agent.d.ts.map +1 -0
  185. package/dist/types/agent.js +54 -0
  186. package/dist/types/agent.js.map +1 -0
  187. package/dist/types/tasks.d.ts +22 -0
  188. package/dist/types/tasks.d.ts.map +1 -0
  189. package/dist/types/tasks.js +3 -0
  190. package/dist/types/tasks.js.map +1 -0
  191. package/dist/utils/event-emitter.d.ts +13 -0
  192. package/dist/utils/event-emitter.d.ts.map +1 -0
  193. package/dist/utils/event-emitter.js +54 -0
  194. package/dist/utils/event-emitter.js.map +1 -0
  195. package/package.json +33 -0
@@ -0,0 +1,202 @@
1
+ /**
2
+ * MCP client pool — manages spawn / handshake / lifecycle for N
3
+ * configured MCP servers. Each entry corresponds to one mcp-servers.json
4
+ * stanza. The pool is intentionally small surface:
5
+ *
6
+ * register(name, config) — store config (no spawn yet)
7
+ * ensureConnected(name) — spawn + handshake + cache. Idempotent.
8
+ * discoverTools(name) — list tools from a connected server (cached
9
+ * after first call until disconnect)
10
+ * callTool(name, tool, args) — proxy through to server.callTool()
11
+ * snapshot() — status view for /mcp and the IDE Connections tab
12
+ * dispose() — close every spawned process. Idempotent.
13
+ *
14
+ * Lazy spawn: nothing happens at register-time. The first ensureConnected
15
+ * triggers the actual child_process. Avoids paying spawn cost for servers
16
+ * the user configures but never invokes in this session.
17
+ *
18
+ * Failure isolation: a spawn/handshake error is recorded as the server's
19
+ * status and never thrown to the caller. discoverTools returns [] for
20
+ * failed servers. The agent loop continues with native tools only —
21
+ * one bad server doesn't kill the session.
22
+ */
23
+ import type { McpServerConfig, McpServerSnapshot } from './types';
24
+ /**
25
+ * Trust gate. Spawning an MCP server is unconstrained code execution
26
+ * via child_process. The host (CLI / extension) supplies a callback
27
+ * that decides whether a never-seen-before server config is allowed
28
+ * to spawn. Decision is made on `(name, command, args, env-keys)` —
29
+ * env VALUES are intentionally NOT part of the fingerprint so a token
30
+ * rotation doesn't re-trigger the prompt.
31
+ *
32
+ * Returning `true` allows the spawn for this session. Persisting the
33
+ * "always allow" decision to disk is the host's responsibility — the
34
+ * pool only sees the boolean answer.
35
+ *
36
+ * When no gate is wired (default), every config is allowed — backwards
37
+ * compatible with hosts that don't yet implement trust prompts.
38
+ */
39
+ /**
40
+ * Trust gate input — one shape for stdio servers (existing) and one for
41
+ * URL-based remote servers (v1.7.333+). The discriminator is the
42
+ * `kind` field so existing handlers that destructure
43
+ * `{name, command, args, envKeys}` keep working on stdio entries — they
44
+ * just need to check `kind === 'url'` and surface a URL-shaped prompt
45
+ * when one arrives.
46
+ */
47
+ export type McpTrustGate = (params: {
48
+ kind: 'stdio';
49
+ name: string;
50
+ command: string;
51
+ args: string[];
52
+ envKeys: string[];
53
+ } | {
54
+ kind: 'url';
55
+ name: string;
56
+ url: string;
57
+ /** Short label for the auth strategy, e.g. "bandit-api-key" / "bearer" /
58
+ * "header(X-Foo)" / "none". The gate decides whether to show / how to
59
+ * phrase the auth in the trust prompt. */
60
+ authKind: string;
61
+ }) => Promise<boolean>;
62
+ /**
63
+ * Stable fingerprint of a server config. Used by hosts to remember
64
+ * "the user already approved this exact shape" across sessions.
65
+ * env VALUES are excluded — only env KEYS count — so rotating a token
66
+ * doesn't re-trigger the trust prompt.
67
+ */
68
+ export declare function fingerprintServerConfig(name: string, config: McpServerConfig): string;
69
+ /** Tool definition as advertised by an MCP server's listTools response.
70
+ * Exposed so hosts that persist the tool list (see McpClientPoolOptions.
71
+ * onToolsDiscovered) can round-trip the right shape into
72
+ * `primeDiscoveryCache` on the next session. */
73
+ export interface RemoteToolDef {
74
+ name: string;
75
+ description?: string;
76
+ inputSchema?: {
77
+ type?: string;
78
+ properties?: Record<string, {
79
+ type?: string;
80
+ description?: string;
81
+ }>;
82
+ required?: string[];
83
+ };
84
+ }
85
+ /** Fired once after the pool successfully fetches a server's tool list
86
+ * from a live spawn — so the host can persist the result and prime
87
+ * future sessions without paying the spawn cost just to enumerate. */
88
+ export type McpToolsDiscoveredCallback = (name: string, fingerprint: string, tools: RemoteToolDef[]) => void;
89
+ export interface McpClientPoolOptions {
90
+ /** Optional trust gate — see McpTrustGate. When omitted, every
91
+ * server config is allowed to spawn (current behavior). When
92
+ * provided, the gate is consulted before each first-spawn and the
93
+ * spawn is rejected with a "trust_denied" status if the gate
94
+ * returns false. */
95
+ trustGate?: McpTrustGate;
96
+ /** Optional disk-cache hook. Fired once per (name, fingerprint) after
97
+ * the first successful listTools — hosts persist the result here so
98
+ * subsequent sessions can `primeDiscoveryCache` and skip the
99
+ * enumeration spawn entirely (which is what fires the trust gate
100
+ * even on prompts that never use any MCP tool). */
101
+ onToolsDiscovered?: McpToolsDiscoveredCallback;
102
+ /** Resolve an opaque auth token by kind, for URL-based remote MCP
103
+ * servers (v1.7.333+). Today only `'bandit-api-key'` is asked for —
104
+ * the host should return the configured Bandit Cloud API key (env
105
+ * BANDIT_API_KEY → ~/.bandit/config.json `bandit.apiKey`). Returns
106
+ * undefined when no key is configured; the pool then connects to
107
+ * the server without an Authorization header and surfaces whatever
108
+ * 401/403 the server returns. Future kinds (`oauth-bandit`, etc.)
109
+ * slot in here without a breaking change. */
110
+ resolveAuthToken?: (kind: string) => string | undefined;
111
+ }
112
+ /**
113
+ * Pool managing the lifetime of every configured MCP server.
114
+ * Single instance per Bandit session (extension or CLI process).
115
+ */
116
+ export declare class McpClientPool {
117
+ private readonly entries;
118
+ private readonly trustGate?;
119
+ private readonly trustedFingerprints;
120
+ private readonly onToolsDiscovered?;
121
+ private readonly resolveAuthToken?;
122
+ constructor(options?: McpClientPoolOptions);
123
+ /** Pre-populate the in-memory tool cache for a named server from a
124
+ * prior session's disk cache, keyed by config fingerprint. When the
125
+ * fingerprint matches the currently-registered server's config,
126
+ * `discoverTools(name)` short-circuits to these tools WITHOUT
127
+ * spawning — which is the whole point: no spawn means no trust-gate
128
+ * prompt on prompts that never use MCP. When the fingerprint doesn't
129
+ * match, the prime is silently dropped (config changed; we have to
130
+ * re-spawn to learn the new tool list). */
131
+ primeDiscoveryCache(name: string, fingerprint: string, tools: RemoteToolDef[]): void;
132
+ /** Mark a server fingerprint as trusted for this session — bypasses
133
+ * the gate on subsequent spawns. Hosts call this after the user
134
+ * approves "always allow" so re-prompting doesn't happen mid-session. */
135
+ trustFingerprint(fingerprint: string): void;
136
+ /** Register a server's config without spawning it. Idempotent — a
137
+ * second register for the same name updates the config and forces a
138
+ * reconnect on next ensureConnected. */
139
+ register(name: string, config: McpServerConfig): void;
140
+ /** Server names currently registered (not necessarily connected). */
141
+ list(): string[];
142
+ /** True when this server has a tool list cached in memory — either
143
+ * from a prior live `discoverTools` this session or from
144
+ * `primeDiscoveryCache` on boot. Callers use this to decide whether
145
+ * enumerating the server would require a spawn (and therefore fire
146
+ * the trust gate). Returns false for unknown or disabled servers. */
147
+ hasCachedTools(name: string): boolean;
148
+ /** Snapshot of every registered server for status views. */
149
+ snapshot(): McpServerSnapshot[];
150
+ /**
151
+ * Spawn + handshake the named server if it isn't already connected.
152
+ * Returns successfully when the server's `initialize` handshake has
153
+ * completed. Returns false when the server is disabled, missing, or
154
+ * a previous spawn failed and we don't retry on every call.
155
+ */
156
+ ensureConnected(name: string): Promise<boolean>;
157
+ /**
158
+ * Force a reconnect for a previously errored or disconnected server.
159
+ * Used by the `/mcp connect <name>` slash command after the user
160
+ * fixes config / installs the server / sets the right env var.
161
+ */
162
+ reconnect(name: string): Promise<boolean>;
163
+ /** Tools advertised by a connected server. Returns [] for unknown,
164
+ * disabled, errored, or never-connected servers.
165
+ *
166
+ * Short-circuits to the in-memory cache (populated either by a prior
167
+ * live listTools or by `primeDiscoveryCache`) without spawning the
168
+ * child process. The trust gate sits inside `spawnAndHandshake`, so
169
+ * bypassing the spawn here means the gate doesn't fire just because
170
+ * the host wanted to enumerate the registry — it now only fires
171
+ * when the agent actually invokes a tool via `callTool`. */
172
+ discoverTools(name: string): Promise<RemoteToolDef[]>;
173
+ /**
174
+ * Invoke a tool on a connected server. The pool ensures the server
175
+ * is up before the call. Returns the structured result; throws on
176
+ * RPC error so the caller's try/catch surfaces the failure to the
177
+ * agent's loop with a clear message instead of a silent empty
178
+ * result.
179
+ */
180
+ callTool(name: string, toolName: string, args: Record<string, unknown>): Promise<{
181
+ content?: Array<{
182
+ type: string;
183
+ text?: string;
184
+ }>;
185
+ isError?: boolean;
186
+ }>;
187
+ /** Close every spawned process. Idempotent. */
188
+ dispose(): Promise<void>;
189
+ /** Close one server's process. Removes the entry. Idempotent. */
190
+ private disposeOne;
191
+ private spawnAndHandshake;
192
+ /**
193
+ * Build the HTTP headers for a remote MCP request based on the server's
194
+ * auth config. `bandit-api-key` resolves through the host-provided
195
+ * resolveAuthToken callback (env BANDIT_API_KEY → ~/.bandit/config.json
196
+ * `bandit.apiKey`). Returns an empty object when no auth is configured
197
+ * — the server will respond 401 if it needs auth, which surfaces as a
198
+ * normal MCP error the user can act on.
199
+ */
200
+ private buildAuthHeaders;
201
+ }
202
+ //# sourceMappingURL=clientPool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clientPool.d.ts","sourceRoot":"","sources":["../../src/mcp/clientPool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,469 @@
1
+ "use strict";
2
+ /**
3
+ * MCP client pool — manages spawn / handshake / lifecycle for N
4
+ * configured MCP servers. Each entry corresponds to one mcp-servers.json
5
+ * stanza. The pool is intentionally small surface:
6
+ *
7
+ * register(name, config) — store config (no spawn yet)
8
+ * ensureConnected(name) — spawn + handshake + cache. Idempotent.
9
+ * discoverTools(name) — list tools from a connected server (cached
10
+ * after first call until disconnect)
11
+ * callTool(name, tool, args) — proxy through to server.callTool()
12
+ * snapshot() — status view for /mcp and the IDE Connections tab
13
+ * dispose() — close every spawned process. Idempotent.
14
+ *
15
+ * Lazy spawn: nothing happens at register-time. The first ensureConnected
16
+ * triggers the actual child_process. Avoids paying spawn cost for servers
17
+ * the user configures but never invokes in this session.
18
+ *
19
+ * Failure isolation: a spawn/handshake error is recorded as the server's
20
+ * status and never thrown to the caller. discoverTools returns [] for
21
+ * failed servers. The agent loop continues with native tools only —
22
+ * one bad server doesn't kill the session.
23
+ */
24
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ var desc = Object.getOwnPropertyDescriptor(m, k);
27
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
28
+ desc = { enumerable: true, get: function() { return m[k]; } };
29
+ }
30
+ Object.defineProperty(o, k2, desc);
31
+ }) : (function(o, m, k, k2) {
32
+ if (k2 === undefined) k2 = k;
33
+ o[k2] = m[k];
34
+ }));
35
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
36
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
37
+ }) : function(o, v) {
38
+ o["default"] = v;
39
+ });
40
+ var __importStar = (this && this.__importStar) || (function () {
41
+ var ownKeys = function(o) {
42
+ ownKeys = Object.getOwnPropertyNames || function (o) {
43
+ var ar = [];
44
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
45
+ return ar;
46
+ };
47
+ return ownKeys(o);
48
+ };
49
+ return function (mod) {
50
+ if (mod && mod.__esModule) return mod;
51
+ var result = {};
52
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
53
+ __setModuleDefault(result, mod);
54
+ return result;
55
+ };
56
+ })();
57
+ Object.defineProperty(exports, "__esModule", { value: true });
58
+ exports.McpClientPool = void 0;
59
+ exports.fingerprintServerConfig = fingerprintServerConfig;
60
+ const crypto = __importStar(require("crypto"));
61
+ /**
62
+ * Stable fingerprint of a server config. Used by hosts to remember
63
+ * "the user already approved this exact shape" across sessions.
64
+ * env VALUES are excluded — only env KEYS count — so rotating a token
65
+ * doesn't re-trigger the trust prompt.
66
+ */
67
+ function fingerprintServerConfig(name, config) {
68
+ // URL-based remote servers fingerprint on (name, url, authKind). The
69
+ // token VALUE isn't mixed in so rotating a bearer or the Bandit API
70
+ // key doesn't re-trigger the trust prompt — same shape rule as the
71
+ // stdio envKeys-not-envValues policy.
72
+ if (config.url) {
73
+ const payload = {
74
+ name,
75
+ kind: 'url',
76
+ url: config.url,
77
+ authKind: describeAuth(config.auth)
78
+ };
79
+ return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex').slice(0, 16);
80
+ }
81
+ const payload = {
82
+ name,
83
+ kind: 'stdio',
84
+ command: config.command ?? '',
85
+ args: config.args ?? [],
86
+ envKeys: Object.keys(config.env ?? {}).sort()
87
+ };
88
+ return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex').slice(0, 16);
89
+ }
90
+ // SDK imports are deferred to spawn time (see loadMcpSdk below) so a
91
+ // dependency-resolution failure inside @modelcontextprotocol/sdk never
92
+ // runs at module-load time. This matters for VS Code extensions: the
93
+ // installed VSIX's node_modules can have broken transitive symlinks
94
+ // (pnpm's symlinked layout doesn't always survive packaging), and a
95
+ // failing require at module top would block extension activation
96
+ // entirely. Deferring means a misconfigured SDK only takes down MCP
97
+ // itself — the rest of the extension keeps working.
98
+ // Cached references to the SDK's classes after the first successful
99
+ // load. Reused on subsequent spawns so we don't pay the require cost
100
+ // repeatedly.
101
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
102
+ let cachedClient = null;
103
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
104
+ let cachedStdioTransport = null;
105
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
106
+ let cachedHttpTransport = null;
107
+ function loadMcpSdk() {
108
+ if (!cachedClient) {
109
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
110
+ const clientMod = require('@modelcontextprotocol/sdk/client/index.js');
111
+ cachedClient = clientMod.Client;
112
+ }
113
+ if (!cachedStdioTransport) {
114
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
115
+ const stdioMod = require('@modelcontextprotocol/sdk/client/stdio.js');
116
+ cachedStdioTransport = stdioMod.StdioClientTransport;
117
+ }
118
+ if (!cachedHttpTransport) {
119
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
120
+ const httpMod = require('@modelcontextprotocol/sdk/client/streamableHttp.js');
121
+ cachedHttpTransport = httpMod.StreamableHTTPClientTransport;
122
+ }
123
+ return {
124
+ Client: cachedClient,
125
+ StdioClientTransport: cachedStdioTransport,
126
+ StreamableHTTPClientTransport: cachedHttpTransport
127
+ };
128
+ }
129
+ /**
130
+ * Short label describing the auth strategy for trust-gate display +
131
+ * fingerprinting. Stable across a config's lifetime — env values aren't
132
+ * mixed in so rotating a bearer token doesn't re-trigger the trust
133
+ * prompt (mirrors the stdio fingerprint's "envKeys not envValues" rule).
134
+ */
135
+ function describeAuth(auth) {
136
+ if (!auth)
137
+ return 'none';
138
+ if (auth === 'bandit')
139
+ return 'bandit-api-key';
140
+ if (auth.type === 'bandit-api-key')
141
+ return 'bandit-api-key';
142
+ if (auth.type === 'bearer')
143
+ return 'bearer';
144
+ if (auth.type === 'header')
145
+ return `header(${auth.name})`;
146
+ return 'unknown';
147
+ }
148
+ /**
149
+ * Pool managing the lifetime of every configured MCP server.
150
+ * Single instance per Bandit session (extension or CLI process).
151
+ */
152
+ class McpClientPool {
153
+ constructor(options = {}) {
154
+ this.entries = new Map();
155
+ this.trustedFingerprints = new Set();
156
+ this.trustGate = options.trustGate;
157
+ this.onToolsDiscovered = options.onToolsDiscovered;
158
+ this.resolveAuthToken = options.resolveAuthToken;
159
+ }
160
+ /** Pre-populate the in-memory tool cache for a named server from a
161
+ * prior session's disk cache, keyed by config fingerprint. When the
162
+ * fingerprint matches the currently-registered server's config,
163
+ * `discoverTools(name)` short-circuits to these tools WITHOUT
164
+ * spawning — which is the whole point: no spawn means no trust-gate
165
+ * prompt on prompts that never use MCP. When the fingerprint doesn't
166
+ * match, the prime is silently dropped (config changed; we have to
167
+ * re-spawn to learn the new tool list). */
168
+ primeDiscoveryCache(name, fingerprint, tools) {
169
+ const entry = this.entries.get(name);
170
+ if (!entry)
171
+ return;
172
+ const current = fingerprintServerConfig(name, entry.config);
173
+ if (current !== fingerprint)
174
+ return;
175
+ entry.cachedTools = tools;
176
+ // Status stays `idle` — we haven't actually spawned the server.
177
+ // The cache exists purely so the enumeration path can answer
178
+ // "what tools does this server expose?" without a child process.
179
+ }
180
+ /** Mark a server fingerprint as trusted for this session — bypasses
181
+ * the gate on subsequent spawns. Hosts call this after the user
182
+ * approves "always allow" so re-prompting doesn't happen mid-session. */
183
+ trustFingerprint(fingerprint) {
184
+ this.trustedFingerprints.add(fingerprint);
185
+ }
186
+ /** Register a server's config without spawning it. Idempotent — a
187
+ * second register for the same name updates the config and forces a
188
+ * reconnect on next ensureConnected. */
189
+ register(name, config) {
190
+ const existing = this.entries.get(name);
191
+ if (existing) {
192
+ // Config changed — close any open process and treat the entry
193
+ // as fresh. The caller is responsible for re-invoking
194
+ // ensureConnected after the config change if they need it
195
+ // immediately.
196
+ this.disposeOne(name);
197
+ }
198
+ const status = config.disabled
199
+ ? { state: 'disabled' }
200
+ : { state: 'idle' };
201
+ this.entries.set(name, { config, status });
202
+ }
203
+ /** Server names currently registered (not necessarily connected). */
204
+ list() {
205
+ return [...this.entries.keys()];
206
+ }
207
+ /** True when this server has a tool list cached in memory — either
208
+ * from a prior live `discoverTools` this session or from
209
+ * `primeDiscoveryCache` on boot. Callers use this to decide whether
210
+ * enumerating the server would require a spawn (and therefore fire
211
+ * the trust gate). Returns false for unknown or disabled servers. */
212
+ hasCachedTools(name) {
213
+ const entry = this.entries.get(name);
214
+ return !!entry?.cachedTools && entry.cachedTools.length > 0;
215
+ }
216
+ /** Snapshot of every registered server for status views. */
217
+ snapshot() {
218
+ return [...this.entries.entries()].map(([name, entry]) => ({
219
+ name,
220
+ config: entry.config,
221
+ status: entry.status
222
+ }));
223
+ }
224
+ /**
225
+ * Spawn + handshake the named server if it isn't already connected.
226
+ * Returns successfully when the server's `initialize` handshake has
227
+ * completed. Returns false when the server is disabled, missing, or
228
+ * a previous spawn failed and we don't retry on every call.
229
+ */
230
+ async ensureConnected(name) {
231
+ const entry = this.entries.get(name);
232
+ if (!entry)
233
+ return false;
234
+ if (entry.status.state === 'disabled')
235
+ return false;
236
+ if (entry.status.state === 'connected')
237
+ return true;
238
+ if (entry.pendingConnect) {
239
+ await entry.pendingConnect;
240
+ // Cast widens the narrowed type — entry.status mutates inside the
241
+ // pending promise's catch handler but TypeScript's narrowing
242
+ // doesn't follow that.
243
+ return entry.status.state === 'connected';
244
+ }
245
+ if (entry.status.state === 'error') {
246
+ // Don't auto-retry errored servers — the user has to explicitly
247
+ // reconnect after fixing whatever was wrong (token rotated,
248
+ // server not installed, etc). Saves us from a thundering-herd
249
+ // of failed spawns on every tool invocation.
250
+ return false;
251
+ }
252
+ entry.status = { state: 'connecting' };
253
+ entry.pendingConnect = this.spawnAndHandshake(name, entry).catch((err) => {
254
+ const message = err instanceof Error ? err.message : String(err);
255
+ entry.status = { state: 'error', message };
256
+ }).finally(() => {
257
+ entry.pendingConnect = undefined;
258
+ });
259
+ await entry.pendingConnect;
260
+ return entry.status.state === 'connected';
261
+ }
262
+ /**
263
+ * Force a reconnect for a previously errored or disconnected server.
264
+ * Used by the `/mcp connect <name>` slash command after the user
265
+ * fixes config / installs the server / sets the right env var.
266
+ */
267
+ async reconnect(name) {
268
+ const entry = this.entries.get(name);
269
+ if (!entry)
270
+ return false;
271
+ if (entry.status.state === 'connected' || entry.status.state === 'connecting') {
272
+ this.disposeOne(name);
273
+ // Re-create the entry shell after dispose (which removed it).
274
+ this.register(name, entry.config);
275
+ }
276
+ else if (entry.status.state === 'error') {
277
+ entry.status = { state: 'idle' };
278
+ }
279
+ return this.ensureConnected(name);
280
+ }
281
+ /** Tools advertised by a connected server. Returns [] for unknown,
282
+ * disabled, errored, or never-connected servers.
283
+ *
284
+ * Short-circuits to the in-memory cache (populated either by a prior
285
+ * live listTools or by `primeDiscoveryCache`) without spawning the
286
+ * child process. The trust gate sits inside `spawnAndHandshake`, so
287
+ * bypassing the spawn here means the gate doesn't fire just because
288
+ * the host wanted to enumerate the registry — it now only fires
289
+ * when the agent actually invokes a tool via `callTool`. */
290
+ async discoverTools(name) {
291
+ const entry = this.entries.get(name);
292
+ if (!entry)
293
+ return [];
294
+ // Cache hit: hand back the cached list without spawning.
295
+ if (entry.cachedTools)
296
+ return entry.cachedTools;
297
+ const ok = await this.ensureConnected(name);
298
+ if (!ok)
299
+ return [];
300
+ try {
301
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
302
+ const result = await entry.client.listTools();
303
+ const tools = Array.isArray(result?.tools) ? result.tools : [];
304
+ entry.cachedTools = tools;
305
+ entry.status = { state: 'connected', toolCount: tools.length };
306
+ // Notify the host so it can persist this tool list — next session
307
+ // primes from disk and never spawns just to enumerate.
308
+ if (this.onToolsDiscovered) {
309
+ try {
310
+ this.onToolsDiscovered(name, fingerprintServerConfig(name, entry.config), tools);
311
+ }
312
+ catch {
313
+ // Host cache write must never break the agent loop.
314
+ }
315
+ }
316
+ return tools;
317
+ }
318
+ catch (err) {
319
+ const message = err instanceof Error ? err.message : String(err);
320
+ entry.status = { state: 'error', message };
321
+ return [];
322
+ }
323
+ }
324
+ /**
325
+ * Invoke a tool on a connected server. The pool ensures the server
326
+ * is up before the call. Returns the structured result; throws on
327
+ * RPC error so the caller's try/catch surfaces the failure to the
328
+ * agent's loop with a clear message instead of a silent empty
329
+ * result.
330
+ */
331
+ async callTool(name, toolName, args) {
332
+ const entry = this.entries.get(name);
333
+ if (!entry)
334
+ throw new Error(`MCP server "${name}" is not registered.`);
335
+ const ok = await this.ensureConnected(name);
336
+ if (!ok) {
337
+ const reason = entry.status.state === 'error'
338
+ ? entry.status.message
339
+ : `state=${entry.status.state}`;
340
+ throw new Error(`MCP server "${name}" is not connected (${reason}).`);
341
+ }
342
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
343
+ const client = entry.client;
344
+ return await client.callTool({ name: toolName, arguments: args });
345
+ }
346
+ /** Close every spawned process. Idempotent. */
347
+ async dispose() {
348
+ for (const name of [...this.entries.keys()]) {
349
+ this.disposeOne(name);
350
+ }
351
+ }
352
+ /** Close one server's process. Removes the entry. Idempotent. */
353
+ disposeOne(name) {
354
+ const entry = this.entries.get(name);
355
+ if (!entry)
356
+ return;
357
+ try {
358
+ entry.transport?.close?.();
359
+ }
360
+ catch { /* ignore */ }
361
+ try {
362
+ entry.client?.close?.();
363
+ }
364
+ catch { /* ignore */ }
365
+ this.entries.delete(name);
366
+ }
367
+ async spawnAndHandshake(name, entry) {
368
+ // Lazy-load the SDK only when we actually need to spawn. Any
369
+ // dependency-resolution failure inside @modelcontextprotocol/sdk
370
+ // surfaces here as a normal error (caught by the pool's status-
371
+ // tracking wrapper) instead of crashing module load — a hard
372
+ // requirement for the VS Code extension whose host can't tolerate
373
+ // a throw at top-level require time.
374
+ const { Client, StdioClientTransport, StreamableHTTPClientTransport } = loadMcpSdk();
375
+ // URL-based remote server — Streamable HTTP transport. This is the
376
+ // path Bandit Cloud-hosted MCP servers (mcp.burtson.ai and friends)
377
+ // use; the SDK speaks the JSON-RPC-over-HTTP envelope and we just
378
+ // attach the right auth header so the server can identify the user.
379
+ if (entry.config.url) {
380
+ if (this.trustGate) {
381
+ const fingerprint = fingerprintServerConfig(name, entry.config);
382
+ if (!this.trustedFingerprints.has(fingerprint)) {
383
+ const allowed = await this.trustGate({
384
+ kind: 'url',
385
+ name,
386
+ url: entry.config.url,
387
+ authKind: describeAuth(entry.config.auth)
388
+ });
389
+ if (!allowed) {
390
+ throw new Error(`Trust denied: connecting to remote MCP "${entry.config.url}" requires user approval. Approve in the Connections panel (extension) or via /mcp trust ${name} (CLI).`);
391
+ }
392
+ this.trustedFingerprints.add(fingerprint);
393
+ }
394
+ }
395
+ const headers = this.buildAuthHeaders(entry.config.auth);
396
+ const transport = new StreamableHTTPClientTransport(new URL(entry.config.url), { requestInit: { headers } });
397
+ const client = new Client({ name: 'bandit', version: '1.0.0' }, { capabilities: {} });
398
+ await client.connect(transport);
399
+ entry.client = client;
400
+ entry.transport = transport;
401
+ entry.status = { state: 'connected', toolCount: 0 };
402
+ return;
403
+ }
404
+ // Stdio path — original behavior, unchanged.
405
+ if (this.trustGate) {
406
+ const fingerprint = fingerprintServerConfig(name, entry.config);
407
+ if (!this.trustedFingerprints.has(fingerprint)) {
408
+ const allowed = await this.trustGate({
409
+ kind: 'stdio',
410
+ name,
411
+ command: entry.config.command ?? '',
412
+ args: entry.config.args ?? [],
413
+ envKeys: Object.keys(entry.config.env ?? {})
414
+ });
415
+ if (!allowed) {
416
+ throw new Error(`Trust denied: spawning "${entry.config.command} ${(entry.config.args ?? []).join(' ')}" requires user approval. Approve in the Connections panel (extension) or via /mcp trust ${name} (CLI).`);
417
+ }
418
+ this.trustedFingerprints.add(fingerprint);
419
+ }
420
+ }
421
+ if (!entry.config.command) {
422
+ throw new Error(`MCP server "${name}" config is missing both \`command\` (stdio) and \`url\` (remote) — one of the two is required.`);
423
+ }
424
+ const transport = new StdioClientTransport({
425
+ command: entry.config.command,
426
+ args: entry.config.args ?? [],
427
+ env: entry.config.env ?? undefined,
428
+ // pipe stderr so a misbehaving server doesn't dump bytes into
429
+ // the user's terminal — surfaces only when we explicitly read it.
430
+ stderr: 'pipe'
431
+ });
432
+ const client = new Client({ name: 'bandit', version: '1.0.0' }, { capabilities: {} });
433
+ await client.connect(transport);
434
+ entry.client = client;
435
+ entry.transport = transport;
436
+ entry.status = { state: 'connected', toolCount: 0 };
437
+ }
438
+ /**
439
+ * Build the HTTP headers for a remote MCP request based on the server's
440
+ * auth config. `bandit-api-key` resolves through the host-provided
441
+ * resolveAuthToken callback (env BANDIT_API_KEY → ~/.bandit/config.json
442
+ * `bandit.apiKey`). Returns an empty object when no auth is configured
443
+ * — the server will respond 401 if it needs auth, which surfaces as a
444
+ * normal MCP error the user can act on.
445
+ */
446
+ buildAuthHeaders(auth) {
447
+ if (!auth)
448
+ return {};
449
+ const normalized = typeof auth === 'string' ? { type: 'bandit-api-key' } : auth;
450
+ if (normalized.type === 'bandit-api-key') {
451
+ const token = this.resolveAuthToken?.('bandit-api-key');
452
+ if (!token)
453
+ return {};
454
+ // mcp.burtson.ai accepts both `X-API-Key: <key>` and
455
+ // `Authorization: Bearer <jwt>`. We send X-API-Key because the
456
+ // Bandit Cloud key is a raw API key, not a JWT.
457
+ return { 'X-API-Key': token };
458
+ }
459
+ if (normalized.type === 'bearer') {
460
+ return { Authorization: `Bearer ${normalized.token}` };
461
+ }
462
+ if (normalized.type === 'header') {
463
+ return { [normalized.name]: normalized.value };
464
+ }
465
+ return {};
466
+ }
467
+ }
468
+ exports.McpClientPool = McpClientPool;
469
+ //# sourceMappingURL=clientPool.js.map