@kenkaiiii/ggcoder 5.34.2 → 5.35.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 (52) hide show
  1. package/dist/app-sidecar.js +11 -0
  2. package/dist/app-sidecar.js.map +1 -1
  3. package/dist/core/compaction/compactor.d.ts +50 -0
  4. package/dist/core/compaction/compactor.d.ts.map +1 -1
  5. package/dist/core/compaction/compactor.js +136 -35
  6. package/dist/core/compaction/compactor.js.map +1 -1
  7. package/dist/core/compaction/compactor.test.js +131 -1
  8. package/dist/core/compaction/compactor.test.js.map +1 -1
  9. package/dist/core/lsp/manager.d.ts +44 -9
  10. package/dist/core/lsp/manager.d.ts.map +1 -1
  11. package/dist/core/lsp/manager.js +64 -69
  12. package/dist/core/lsp/manager.js.map +1 -1
  13. package/dist/core/lsp/pool.d.ts +113 -0
  14. package/dist/core/lsp/pool.d.ts.map +1 -0
  15. package/dist/core/lsp/pool.js +255 -0
  16. package/dist/core/lsp/pool.js.map +1 -0
  17. package/dist/core/lsp/pool.test.d.ts +2 -0
  18. package/dist/core/lsp/pool.test.d.ts.map +1 -0
  19. package/dist/core/lsp/pool.test.js +286 -0
  20. package/dist/core/lsp/pool.test.js.map +1 -0
  21. package/dist/core/lsp/servers.d.ts.map +1 -1
  22. package/dist/core/lsp/servers.js +44 -3
  23. package/dist/core/lsp/servers.js.map +1 -1
  24. package/dist/core/lsp/servers.test.d.ts +2 -0
  25. package/dist/core/lsp/servers.test.d.ts.map +1 -0
  26. package/dist/core/lsp/servers.test.js +67 -0
  27. package/dist/core/lsp/servers.test.js.map +1 -0
  28. package/dist/core/lsp/windows.test.js +2 -14
  29. package/dist/core/lsp/windows.test.js.map +1 -1
  30. package/dist/core/mcp/client.d.ts +40 -0
  31. package/dist/core/mcp/client.d.ts.map +1 -1
  32. package/dist/core/mcp/client.js +119 -1
  33. package/dist/core/mcp/client.js.map +1 -1
  34. package/dist/core/mcp/index.d.ts +2 -0
  35. package/dist/core/mcp/index.d.ts.map +1 -1
  36. package/dist/core/mcp/index.js +1 -0
  37. package/dist/core/mcp/index.js.map +1 -1
  38. package/dist/core/mcp/session-elicitation-wiring.test.d.ts +2 -0
  39. package/dist/core/mcp/session-elicitation-wiring.test.d.ts.map +1 -0
  40. package/dist/core/mcp/session-elicitation-wiring.test.js +138 -0
  41. package/dist/core/mcp/session-elicitation-wiring.test.js.map +1 -0
  42. package/dist/core/mcp/shared-pool.d.ts +158 -0
  43. package/dist/core/mcp/shared-pool.d.ts.map +1 -0
  44. package/dist/core/mcp/shared-pool.js +235 -0
  45. package/dist/core/mcp/shared-pool.js.map +1 -0
  46. package/dist/core/mcp/shared-pool.test.d.ts +2 -0
  47. package/dist/core/mcp/shared-pool.test.d.ts.map +1 -0
  48. package/dist/core/mcp/shared-pool.test.js +425 -0
  49. package/dist/core/mcp/shared-pool.test.js.map +1 -0
  50. package/dist/core/mcp/types.d.ts +12 -0
  51. package/dist/core/mcp/types.d.ts.map +1 -1
  52. package/package.json +4 -4
@@ -0,0 +1,158 @@
1
+ import type { McpCatalogCache } from "./catalog-cache.js";
2
+ import type { MCPConnectResult, MCPElicitHandler } from "./client.js";
3
+ import type { MCPServerConfig } from "./types.js";
4
+ /**
5
+ * Process-wide pool of MCP connections that are safe for every session to
6
+ * share, so one stdio child serves the whole daemon instead of one per session.
7
+ *
8
+ * The daemon hosts many `AgentSession`s at once — one per window, plus Ken chat
9
+ * and Ken autopilot within each window — and each used to spawn its own child
10
+ * for every configured stdio server. For a stateless proxy such as
11
+ * `kencode-search` that is N identical processes doing identical work: measured
12
+ * at 7 live children, ~43 MB each, purely duplicated.
13
+ *
14
+ * Sharing is the DEFAULT for stdio servers. A stdio connection has nothing
15
+ * session-specific in it: every one is spawned with `cwd: os.homedir()`, so a
16
+ * server cannot observe which project the caller is in even in principle, and
17
+ * the pool key hashes command/args/env, so two configs differing in any way
18
+ * that changes behaviour already get their own process.
19
+ *
20
+ * The two concerns that sharing has to answer are handled rather than avoided:
21
+ *
22
+ * - **Per-caller state.** Multiplexing many sessions over one connection would
23
+ * let a server that remembers "the current project" leak one session's
24
+ * context into another's answers. That is invisible from a config, so it
25
+ * stays a declaration: `shared: false` opts such a server out and it keeps a
26
+ * private child per session.
27
+ * - **User prompting.** "Ask the user" has to name a window, so elicitation is
28
+ * routed to the session with a tool call in flight (see `dispatchElicit`),
29
+ * and cancelled rather than guessed when that is ambiguous. Because a
30
+ * connection declares its elicitation capability once at initialize, callers
31
+ * that can prompt and callers that cannot are keyed apart.
32
+ *
33
+ * The pool deliberately knows nothing about `MCPClientManager` — the connection
34
+ * is supplied as a `SharedConnector`. That keeps the dependency pointing one
35
+ * way (client → pool), and lets tests exercise refcounting against a fake.
36
+ */
37
+ /**
38
+ * Is this server shared across sessions in this process?
39
+ *
40
+ * Sharing is the DEFAULT for stdio servers, because a stdio MCP connection has
41
+ * nothing session-specific in it: every one is spawned with `cwd:
42
+ * os.homedir()` (see client.ts), so a server cannot observe which project the
43
+ * caller is in even in principle, and the pool key hashes command/args/env, so
44
+ * two configs that differ in any way that changes behaviour already get their
45
+ * own process. What remained — elicitation, the one genuinely per-window
46
+ * concern — is routed back to the calling session rather than dropped.
47
+ *
48
+ * `shared: false` opts a server out. That exists for a server that keeps
49
+ * per-CALLER state across requests (a cursor, a selected workspace, an open
50
+ * handle), where multiplexing two sessions over one connection would let one
51
+ * session's state change the other's answers. That property is invisible from
52
+ * a config, so it stays a declaration rather than a guess.
53
+ *
54
+ * HTTP servers are never shared. Sharing exists to collapse duplicate child
55
+ * PROCESSES and an HTTP server has none, so it would save nothing while adding
56
+ * real risk: OAuth tokens and `Mcp-Session-Id` are per-connection, and pooling
57
+ * them would cross one session's authenticated identity with another's.
58
+ */
59
+ export declare function isShareableServer(config: MCPServerConfig): boolean;
60
+ /** One pooled connection, owned by the pool and torn down at zero references. */
61
+ export interface SharedConnector {
62
+ connect(config: MCPServerConfig): Promise<MCPConnectResult>;
63
+ dispose(): Promise<void>;
64
+ }
65
+ export interface SharedAcquireOptions {
66
+ catalogCache?: McpCatalogCache;
67
+ modernProtocol?: boolean;
68
+ /** Invoked by the connection when its server exits on its own. */
69
+ onClosed?: () => void;
70
+ /**
71
+ * The acquiring session's elicitation handler. Invoked only while that
72
+ * session has a tool call in flight on this connection (see `dispatchElicit`).
73
+ */
74
+ onElicit?: MCPElicitHandler;
75
+ }
76
+ /**
77
+ * Builds the underlying connection the first time a config is pooled. The pool
78
+ * supplies its own `onElicit`: a dispatcher that routes each request to the
79
+ * session that asked for it, instead of any one session's handler.
80
+ */
81
+ export type SharedConnectorFactory = (opts: SharedAcquireOptions) => SharedConnector;
82
+ /** A session's claim on a shared connection. `release` is idempotent. */
83
+ export interface SharedServerHandle {
84
+ result: MCPConnectResult;
85
+ release: () => Promise<void>;
86
+ /**
87
+ * Mark a tool call from this session as in flight, returning the function
88
+ * that ends it. Elicitation arriving during the call is routed to this
89
+ * session's window.
90
+ */
91
+ beginCall: () => () => void;
92
+ }
93
+ export declare class SharedMcpPool {
94
+ private entries;
95
+ /**
96
+ * Key on everything that changes what the connection IS, not merely what it
97
+ * exposes: `hashServerConfig` covers command/args/env/url/transport, the name
98
+ * keeps two differently-named aliases apart, and the protocol flag is
99
+ * included because it changes the handshake — a session that opted into the
100
+ * modern revision must not be handed a legacy-negotiated connection.
101
+ *
102
+ * Whether the caller can prompt is part of the key for the same reason. A
103
+ * connection declares its elicitation capability once, at initialize, and
104
+ * servers use that declaration to decide whether to ask at all — so a
105
+ * headless caller (CLI, JSON mode) must not be handed a connection that
106
+ * promises prompting nobody can deliver, and a windowed caller must not be
107
+ * handed one that forecloses it. Splitting the key keeps each connection's
108
+ * declaration honest; in practice a daemon's sessions are uniformly windowed
109
+ * and the CLI's uniformly headless, so this still means one process.
110
+ */
111
+ private keyFor;
112
+ /**
113
+ * Get-or-create the shared connection for `config` and claim a reference.
114
+ *
115
+ * Concurrent acquirers share one connect attempt: the entry and its pending
116
+ * promise are registered synchronously before the first `await`, so a second
117
+ * caller arriving mid-connect joins the in-flight attempt instead of spawning
118
+ * a rival child.
119
+ */
120
+ acquire(config: MCPServerConfig, createConnector: SharedConnectorFactory, opts?: SharedAcquireOptions): Promise<SharedServerHandle>;
121
+ /**
122
+ * Drop a connection whose server died, without touching its callers' claims.
123
+ *
124
+ * The entry is unregistered but NOT disposed: its transport is already gone,
125
+ * and the sessions still holding handles will release them normally on their
126
+ * own dispose. Guarded on identity so a death notice arriving after the entry
127
+ * was already replaced cannot evict its successor.
128
+ */
129
+ private evictDead;
130
+ /**
131
+ * Route an elicitation to the session that provoked it.
132
+ *
133
+ * A shared connection is held by many windows, so "ask the user" has to name
134
+ * one. Elicitation only happens while a server is servicing a tool call, so
135
+ * the session with a call in flight IS the one whose window should show the
136
+ * form. When that is ambiguous — no call in flight, or two windows calling
137
+ * the same server at once — the request is cancelled rather than guessed:
138
+ * showing one project's consent form in another project's window would be a
139
+ * worse failure than a declined prompt, and `cancel` is exactly what a server
140
+ * already handles for a user who dismissed the dialog.
141
+ */
142
+ private dispatchElicit;
143
+ /**
144
+ * Drop one session's claim. At zero the entry is unregistered SYNCHRONOUSLY
145
+ * before the disposal await, so an `acquire` landing mid-teardown builds a
146
+ * fresh connection instead of receiving a client that is already closing.
147
+ */
148
+ private releaseKey;
149
+ /** Live shared connections. Test/diagnostic use. */
150
+ get size(): number;
151
+ /** Reference count for one config, or 0 when nothing is pooled for it. */
152
+ refCount(config: MCPServerConfig, opts?: SharedAcquireOptions): number;
153
+ /** Tear every shared connection down regardless of refs (process shutdown). */
154
+ disposeAll(): Promise<void>;
155
+ }
156
+ /** The daemon-wide pool. One per process, which is exactly the sharing scope. */
157
+ export declare const sharedMcpPool: SharedMcpPool;
158
+ //# sourceMappingURL=shared-pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-pool.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/shared-pool.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAElE;AAED,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC5D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,YAAY,CAAC,EAAE,eAAe,CAAC;IAC/B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,gBAAgB,CAAC;CAC7B;AAED;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,IAAI,EAAE,oBAAoB,KAAK,eAAe,CAAC;AAErF,yEAAyE;AACzE,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,gBAAgB,CAAC;IACzB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B;;;;OAIG;IACH,SAAS,EAAE,MAAM,MAAM,IAAI,CAAC;CAC7B;AAgBD,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAAgC;IAE/C;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,MAAM;IAMd;;;;;;;OAOG;IACG,OAAO,CACX,MAAM,EAAE,eAAe,EACvB,eAAe,EAAE,sBAAsB,EACvC,IAAI,GAAE,oBAAyB,GAC9B,OAAO,CAAC,kBAAkB,CAAC;IAwE9B;;;;;;;OAOG;IACH,OAAO,CAAC,SAAS;IAMjB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc;IActB;;;;OAIG;YACW,UAAU;IAUxB,oDAAoD;IACpD,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,GAAE,oBAAyB,GAAG,MAAM;IAI1E,+EAA+E;IACzE,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAKlC;AAED,iFAAiF;AACjF,eAAO,MAAM,aAAa,eAAsB,CAAC"}
@@ -0,0 +1,235 @@
1
+ import { hashServerConfig } from "./catalog-cache.js";
2
+ import { log } from "../logger.js";
3
+ /**
4
+ * Process-wide pool of MCP connections that are safe for every session to
5
+ * share, so one stdio child serves the whole daemon instead of one per session.
6
+ *
7
+ * The daemon hosts many `AgentSession`s at once — one per window, plus Ken chat
8
+ * and Ken autopilot within each window — and each used to spawn its own child
9
+ * for every configured stdio server. For a stateless proxy such as
10
+ * `kencode-search` that is N identical processes doing identical work: measured
11
+ * at 7 live children, ~43 MB each, purely duplicated.
12
+ *
13
+ * Sharing is the DEFAULT for stdio servers. A stdio connection has nothing
14
+ * session-specific in it: every one is spawned with `cwd: os.homedir()`, so a
15
+ * server cannot observe which project the caller is in even in principle, and
16
+ * the pool key hashes command/args/env, so two configs differing in any way
17
+ * that changes behaviour already get their own process.
18
+ *
19
+ * The two concerns that sharing has to answer are handled rather than avoided:
20
+ *
21
+ * - **Per-caller state.** Multiplexing many sessions over one connection would
22
+ * let a server that remembers "the current project" leak one session's
23
+ * context into another's answers. That is invisible from a config, so it
24
+ * stays a declaration: `shared: false` opts such a server out and it keeps a
25
+ * private child per session.
26
+ * - **User prompting.** "Ask the user" has to name a window, so elicitation is
27
+ * routed to the session with a tool call in flight (see `dispatchElicit`),
28
+ * and cancelled rather than guessed when that is ambiguous. Because a
29
+ * connection declares its elicitation capability once at initialize, callers
30
+ * that can prompt and callers that cannot are keyed apart.
31
+ *
32
+ * The pool deliberately knows nothing about `MCPClientManager` — the connection
33
+ * is supplied as a `SharedConnector`. That keeps the dependency pointing one
34
+ * way (client → pool), and lets tests exercise refcounting against a fake.
35
+ */
36
+ /**
37
+ * Is this server shared across sessions in this process?
38
+ *
39
+ * Sharing is the DEFAULT for stdio servers, because a stdio MCP connection has
40
+ * nothing session-specific in it: every one is spawned with `cwd:
41
+ * os.homedir()` (see client.ts), so a server cannot observe which project the
42
+ * caller is in even in principle, and the pool key hashes command/args/env, so
43
+ * two configs that differ in any way that changes behaviour already get their
44
+ * own process. What remained — elicitation, the one genuinely per-window
45
+ * concern — is routed back to the calling session rather than dropped.
46
+ *
47
+ * `shared: false` opts a server out. That exists for a server that keeps
48
+ * per-CALLER state across requests (a cursor, a selected workspace, an open
49
+ * handle), where multiplexing two sessions over one connection would let one
50
+ * session's state change the other's answers. That property is invisible from
51
+ * a config, so it stays a declaration rather than a guess.
52
+ *
53
+ * HTTP servers are never shared. Sharing exists to collapse duplicate child
54
+ * PROCESSES and an HTTP server has none, so it would save nothing while adding
55
+ * real risk: OAuth tokens and `Mcp-Session-Id` are per-connection, and pooling
56
+ * them would cross one session's authenticated identity with another's.
57
+ */
58
+ export function isShareableServer(config) {
59
+ return config.shared !== false && Boolean(config.command);
60
+ }
61
+ export class SharedMcpPool {
62
+ entries = new Map();
63
+ /**
64
+ * Key on everything that changes what the connection IS, not merely what it
65
+ * exposes: `hashServerConfig` covers command/args/env/url/transport, the name
66
+ * keeps two differently-named aliases apart, and the protocol flag is
67
+ * included because it changes the handshake — a session that opted into the
68
+ * modern revision must not be handed a legacy-negotiated connection.
69
+ *
70
+ * Whether the caller can prompt is part of the key for the same reason. A
71
+ * connection declares its elicitation capability once, at initialize, and
72
+ * servers use that declaration to decide whether to ask at all — so a
73
+ * headless caller (CLI, JSON mode) must not be handed a connection that
74
+ * promises prompting nobody can deliver, and a windowed caller must not be
75
+ * handed one that forecloses it. Splitting the key keeps each connection's
76
+ * declaration honest; in practice a daemon's sessions are uniformly windowed
77
+ * and the CLI's uniformly headless, so this still means one process.
78
+ */
79
+ keyFor(config, opts) {
80
+ const era = opts.modernProtocol ? "modern" : "legacy";
81
+ const prompting = opts.onElicit ? "interactive" : "headless";
82
+ return `${config.name}\u0000${hashServerConfig(config)}\u0000${era}\u0000${prompting}`;
83
+ }
84
+ /**
85
+ * Get-or-create the shared connection for `config` and claim a reference.
86
+ *
87
+ * Concurrent acquirers share one connect attempt: the entry and its pending
88
+ * promise are registered synchronously before the first `await`, so a second
89
+ * caller arriving mid-connect joins the in-flight attempt instead of spawning
90
+ * a rival child.
91
+ */
92
+ async acquire(config, createConnector, opts = {}) {
93
+ const key = this.keyFor(config, opts);
94
+ const caller = { onElicit: opts.onElicit, activeCalls: 0 };
95
+ let entry = this.entries.get(key);
96
+ if (!entry) {
97
+ // Built before the connector so the dispatcher can close over it: the
98
+ // connection needs an elicit handler at construction time, but which
99
+ // session that handler defers to is only knowable per request.
100
+ const created = {
101
+ connector: undefined,
102
+ callers: new Set(),
103
+ connected: undefined,
104
+ };
105
+ // A headless entry gets no dispatcher at all, so its connection declares
106
+ // no elicitation capability — matching what its callers can actually do.
107
+ created.connector = createConnector({
108
+ ...opts,
109
+ onElicit: opts.onElicit ? this.dispatchElicit(created) : undefined,
110
+ // A pooled child that dies would otherwise be handed to every session
111
+ // in the daemon, and to every session that connects later — one crash
112
+ // becoming a permanent outage. Unregister it so the next acquire builds
113
+ // a fresh connection. Sessions already holding it still see their calls
114
+ // fail, which is what a dead server means; they recover on reconnect.
115
+ onClosed: () => this.evictDead(key, created),
116
+ });
117
+ created.connected = created.connector.connect(config);
118
+ entry = created;
119
+ this.entries.set(key, entry);
120
+ log("INFO", "mcp", `Opening shared MCP connection for "${config.name}"`);
121
+ }
122
+ entry.callers.add(caller);
123
+ const claimed = entry;
124
+ let result;
125
+ try {
126
+ result = await claimed.connected;
127
+ }
128
+ catch (err) {
129
+ // The connector reports failure in-band, so a throw is unexpected. Drop
130
+ // the claim so a broken entry cannot outlive it.
131
+ await this.releaseKey(key, claimed, caller);
132
+ throw err;
133
+ }
134
+ if (!result.ok) {
135
+ // A failed connection is not worth pooling: keeping it would make every
136
+ // later session inherit this failure with no chance to retry. Release the
137
+ // claim (tearing the entry down at zero) and report the failure as-is.
138
+ await this.releaseKey(key, claimed, caller);
139
+ return { result, release: async () => { }, beginCall: () => () => { } };
140
+ }
141
+ let released = false;
142
+ return {
143
+ result,
144
+ release: async () => {
145
+ if (released)
146
+ return;
147
+ released = true;
148
+ await this.releaseKey(key, claimed, caller);
149
+ },
150
+ beginCall: () => {
151
+ caller.activeCalls += 1;
152
+ let ended = false;
153
+ return () => {
154
+ if (ended)
155
+ return;
156
+ ended = true;
157
+ caller.activeCalls = Math.max(0, caller.activeCalls - 1);
158
+ };
159
+ },
160
+ };
161
+ }
162
+ /**
163
+ * Drop a connection whose server died, without touching its callers' claims.
164
+ *
165
+ * The entry is unregistered but NOT disposed: its transport is already gone,
166
+ * and the sessions still holding handles will release them normally on their
167
+ * own dispose. Guarded on identity so a death notice arriving after the entry
168
+ * was already replaced cannot evict its successor.
169
+ */
170
+ evictDead(key, entry) {
171
+ if (this.entries.get(key) !== entry)
172
+ return;
173
+ this.entries.delete(key);
174
+ log("WARN", "mcp", "shared MCP connection died; next session will reconnect");
175
+ }
176
+ /**
177
+ * Route an elicitation to the session that provoked it.
178
+ *
179
+ * A shared connection is held by many windows, so "ask the user" has to name
180
+ * one. Elicitation only happens while a server is servicing a tool call, so
181
+ * the session with a call in flight IS the one whose window should show the
182
+ * form. When that is ambiguous — no call in flight, or two windows calling
183
+ * the same server at once — the request is cancelled rather than guessed:
184
+ * showing one project's consent form in another project's window would be a
185
+ * worse failure than a declined prompt, and `cancel` is exactly what a server
186
+ * already handles for a user who dismissed the dialog.
187
+ */
188
+ dispatchElicit(entry) {
189
+ return async (request) => {
190
+ const candidates = [...entry.callers].filter((c) => c.activeCalls > 0 && c.onElicit);
191
+ if (candidates.length !== 1) {
192
+ log("WARN", "mcp", "cancelled a shared-server elicitation with no unique caller", {
193
+ server: request.server,
194
+ candidates: String(candidates.length),
195
+ });
196
+ return { action: "cancel" };
197
+ }
198
+ return candidates[0].onElicit(request);
199
+ };
200
+ }
201
+ /**
202
+ * Drop one session's claim. At zero the entry is unregistered SYNCHRONOUSLY
203
+ * before the disposal await, so an `acquire` landing mid-teardown builds a
204
+ * fresh connection instead of receiving a client that is already closing.
205
+ */
206
+ async releaseKey(key, entry, caller) {
207
+ if (!entry.callers.delete(caller))
208
+ return;
209
+ if (entry.callers.size > 0)
210
+ return;
211
+ // Only unregister when this entry is still the live one for the key: a
212
+ // teardown racing a fresh acquire must not evict its replacement.
213
+ if (this.entries.get(key) === entry)
214
+ this.entries.delete(key);
215
+ await entry.connector.dispose();
216
+ log("INFO", "mcp", "Closed shared MCP connection (last session released it)");
217
+ }
218
+ /** Live shared connections. Test/diagnostic use. */
219
+ get size() {
220
+ return this.entries.size;
221
+ }
222
+ /** Reference count for one config, or 0 when nothing is pooled for it. */
223
+ refCount(config, opts = {}) {
224
+ return this.entries.get(this.keyFor(config, opts))?.callers.size ?? 0;
225
+ }
226
+ /** Tear every shared connection down regardless of refs (process shutdown). */
227
+ async disposeAll() {
228
+ const entries = [...this.entries.values()];
229
+ this.entries.clear();
230
+ await Promise.all(entries.map((entry) => entry.connector.dispose()));
231
+ }
232
+ }
233
+ /** The daemon-wide pool. One per process, which is exactly the sharing scope. */
234
+ export const sharedMcpPool = new SharedMcpPool();
235
+ //# sourceMappingURL=shared-pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-pool.js","sourceRoot":"","sources":["../../../src/core/mcp/shared-pool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=shared-pool.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-pool.test.d.ts","sourceRoot":"","sources":["../../../src/core/mcp/shared-pool.test.ts"],"names":[],"mappings":""}