agentfootprint 12.1.0 → 12.2.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.
- package/AGENTS.md +1 -1
- package/CAPABILITIES.md +3 -1
- package/ai-instructions/claude-code/SKILL.md +1 -1
- package/dist/adapters/mcp/agentcoreRuntime/invoke.js +113 -7
- package/dist/adapters/mcp/agentcoreRuntime/invoke.js.map +1 -1
- package/dist/adapters/mcp/agentcoreRuntime/session.js +21 -21
- package/dist/adapters/mcp/agentcoreRuntime/session.js.map +1 -1
- package/dist/adapters/mcp/agentcoreRuntime/transport.js +79 -5
- package/dist/adapters/mcp/agentcoreRuntime/transport.js.map +1 -1
- package/dist/core/Agent.js +6 -0
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/agent/stages/toolCalls.js +4 -0
- package/dist/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/core/slots/buildToolsSlot.js +8 -0
- package/dist/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/esm/adapters/mcp/agentcoreRuntime/invoke.d.ts +23 -4
- package/dist/esm/adapters/mcp/agentcoreRuntime/invoke.js +112 -6
- package/dist/esm/adapters/mcp/agentcoreRuntime/invoke.js.map +1 -1
- package/dist/esm/adapters/mcp/agentcoreRuntime/session.d.ts +20 -6
- package/dist/esm/adapters/mcp/agentcoreRuntime/session.js +21 -21
- package/dist/esm/adapters/mcp/agentcoreRuntime/session.js.map +1 -1
- package/dist/esm/adapters/mcp/agentcoreRuntime/transport.js +81 -7
- package/dist/esm/adapters/mcp/agentcoreRuntime/transport.js.map +1 -1
- package/dist/esm/core/Agent.js +6 -0
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/agent/stages/toolCalls.js +4 -0
- package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/esm/core/slots/buildToolsSlot.d.ts +12 -0
- package/dist/esm/core/slots/buildToolsSlot.js +8 -0
- package/dist/esm/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/esm/events/payloads.d.ts +41 -0
- package/dist/esm/events/registry.d.ts +3 -1
- package/dist/esm/events/registry.js +2 -0
- package/dist/esm/events/registry.js.map +1 -1
- package/dist/esm/identity/types.d.ts +7 -3
- package/dist/esm/identity/types.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +4 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/lib/boundedJson.d.ts +14 -7
- package/dist/esm/lib/boundedJson.js +14 -7
- package/dist/esm/lib/boundedJson.js.map +1 -1
- package/dist/esm/mcp/callLifetime.js +5 -2
- package/dist/esm/mcp/callLifetime.js.map +1 -1
- package/dist/esm/mcp/clientPort.d.ts +78 -0
- package/dist/esm/mcp/clientPort.js +33 -0
- package/dist/esm/mcp/clientPort.js.map +1 -0
- package/dist/esm/mcp/gatewayTransport.js +13 -3
- package/dist/esm/mcp/gatewayTransport.js.map +1 -1
- package/dist/esm/mcp/index.d.ts +9 -1
- package/dist/esm/mcp/index.js +8 -0
- package/dist/esm/mcp/index.js.map +1 -1
- package/dist/esm/mcp/mcpClient.d.ts +18 -1
- package/dist/esm/mcp/mcpClient.js +203 -43
- package/dist/esm/mcp/mcpClient.js.map +1 -1
- package/dist/esm/mcp/mockMcpClient.js +13 -0
- package/dist/esm/mcp/mockMcpClient.js.map +1 -1
- package/dist/esm/mcp/pool/boundTool.d.ts +58 -0
- package/dist/esm/mcp/pool/boundTool.js +218 -0
- package/dist/esm/mcp/pool/boundTool.js.map +1 -0
- package/dist/esm/mcp/pool/clientSet.d.ts +73 -0
- package/dist/esm/mcp/pool/clientSet.js +152 -0
- package/dist/esm/mcp/pool/clientSet.js.map +1 -0
- package/dist/esm/mcp/pool/definitions.d.ts +103 -0
- package/dist/esm/mcp/pool/definitions.js +168 -0
- package/dist/esm/mcp/pool/definitions.js.map +1 -0
- package/dist/esm/mcp/pool/errors.d.ts +76 -0
- package/dist/esm/mcp/pool/errors.js +91 -0
- package/dist/esm/mcp/pool/errors.js.map +1 -0
- package/dist/esm/mcp/pool/identityKey.d.ts +57 -0
- package/dist/esm/mcp/pool/identityKey.js +94 -0
- package/dist/esm/mcp/pool/identityKey.js.map +1 -0
- package/dist/esm/mcp/pool/lease.d.ts +36 -0
- package/dist/esm/mcp/pool/lease.js +51 -0
- package/dist/esm/mcp/pool/lease.js.map +1 -0
- package/dist/esm/mcp/pool/listTools.d.ts +33 -0
- package/dist/esm/mcp/pool/listTools.js +146 -0
- package/dist/esm/mcp/pool/listTools.js.map +1 -0
- package/dist/esm/mcp/pool/mcpClientPool.d.ts +121 -0
- package/dist/esm/mcp/pool/mcpClientPool.js +151 -0
- package/dist/esm/mcp/pool/mcpClientPool.js.map +1 -0
- package/dist/esm/mcp/pool/options.d.ts +183 -0
- package/dist/esm/mcp/pool/options.js +119 -0
- package/dist/esm/mcp/pool/options.js.map +1 -0
- package/dist/esm/mcp/pool/plan.d.ts +77 -0
- package/dist/esm/mcp/pool/plan.js +134 -0
- package/dist/esm/mcp/pool/plan.js.map +1 -0
- package/dist/esm/mcp/pool/pool.d.ts +307 -0
- package/dist/esm/mcp/pool/pool.js +1071 -0
- package/dist/esm/mcp/pool/pool.js.map +1 -0
- package/dist/esm/mcp/pool/pooledClient.d.ts +93 -0
- package/dist/esm/mcp/pool/pooledClient.js +90 -0
- package/dist/esm/mcp/pool/pooledClient.js.map +1 -0
- package/dist/esm/mcp/pool/record.d.ts +80 -0
- package/dist/esm/mcp/pool/record.js +75 -0
- package/dist/esm/mcp/pool/record.js.map +1 -0
- package/dist/esm/mcp/pool/sessionEnded.d.ts +24 -0
- package/dist/esm/mcp/pool/sessionEnded.js +31 -0
- package/dist/esm/mcp/pool/sessionEnded.js.map +1 -0
- package/dist/esm/mcp/pool/work.d.ts +116 -0
- package/dist/esm/mcp/pool/work.js +213 -0
- package/dist/esm/mcp/pool/work.js.map +1 -0
- package/dist/esm/mcp/requestGuard.d.ts +70 -0
- package/dist/esm/mcp/requestGuard.js +98 -0
- package/dist/esm/mcp/requestGuard.js.map +1 -0
- package/dist/esm/mcp/requestLedger.d.ts +35 -1
- package/dist/esm/mcp/requestLedger.js +76 -3
- package/dist/esm/mcp/requestLedger.js.map +1 -1
- package/dist/esm/mcp/seriesEvidence.d.ts +10 -6
- package/dist/esm/mcp/seriesEvidence.js +16 -7
- package/dist/esm/mcp/seriesEvidence.js.map +1 -1
- package/dist/esm/mcp/terminate.d.ts +94 -0
- package/dist/esm/mcp/terminate.js +184 -0
- package/dist/esm/mcp/terminate.js.map +1 -0
- package/dist/esm/mcp/throttleRetry.js +71 -7
- package/dist/esm/mcp/throttleRetry.js.map +1 -1
- package/dist/esm/mcp/types.d.ts +85 -7
- package/dist/esm/redaction/knownStrings.js +34 -0
- package/dist/esm/redaction/knownStrings.js.map +1 -1
- package/dist/esm/tool-providers/index.d.ts +2 -2
- package/dist/esm/tool-providers/index.js +7 -1
- package/dist/esm/tool-providers/index.js.map +1 -1
- package/dist/esm/tools/mcpClientUse.d.ts +59 -0
- package/dist/esm/tools/mcpClientUse.js +216 -0
- package/dist/esm/tools/mcpClientUse.js.map +1 -0
- package/dist/esm/tools/toolProvider.d.ts +16 -0
- package/dist/esm/tools/toolSessions.d.ts +41 -0
- package/dist/esm/tools/toolSessions.js +41 -1
- package/dist/esm/tools/toolSessions.js.map +1 -1
- package/dist/events/registry.js +2 -0
- package/dist/events/registry.js.map +1 -1
- package/dist/identity/types.js.map +1 -1
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/lib/boundedJson.js +14 -7
- package/dist/lib/boundedJson.js.map +1 -1
- package/dist/mcp/callLifetime.js +5 -2
- package/dist/mcp/callLifetime.js.map +1 -1
- package/dist/mcp/clientPort.js +38 -0
- package/dist/mcp/clientPort.js.map +1 -0
- package/dist/mcp/gatewayTransport.js +13 -3
- package/dist/mcp/gatewayTransport.js.map +1 -1
- package/dist/mcp/index.js +16 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/mcpClient.js +207 -45
- package/dist/mcp/mcpClient.js.map +1 -1
- package/dist/mcp/mockMcpClient.js +13 -0
- package/dist/mcp/mockMcpClient.js.map +1 -1
- package/dist/mcp/pool/boundTool.js +223 -0
- package/dist/mcp/pool/boundTool.js.map +1 -0
- package/dist/mcp/pool/clientSet.js +157 -0
- package/dist/mcp/pool/clientSet.js.map +1 -0
- package/dist/mcp/pool/definitions.js +174 -0
- package/dist/mcp/pool/definitions.js.map +1 -0
- package/dist/mcp/pool/errors.js +96 -0
- package/dist/mcp/pool/errors.js.map +1 -0
- package/dist/mcp/pool/identityKey.js +99 -0
- package/dist/mcp/pool/identityKey.js.map +1 -0
- package/dist/mcp/pool/lease.js +55 -0
- package/dist/mcp/pool/lease.js.map +1 -0
- package/dist/mcp/pool/listTools.js +150 -0
- package/dist/mcp/pool/listTools.js.map +1 -0
- package/dist/mcp/pool/mcpClientPool.js +155 -0
- package/dist/mcp/pool/mcpClientPool.js.map +1 -0
- package/dist/mcp/pool/options.js +123 -0
- package/dist/mcp/pool/options.js.map +1 -0
- package/dist/mcp/pool/plan.js +140 -0
- package/dist/mcp/pool/plan.js.map +1 -0
- package/dist/mcp/pool/pool.js +1075 -0
- package/dist/mcp/pool/pool.js.map +1 -0
- package/dist/mcp/pool/pooledClient.js +94 -0
- package/dist/mcp/pool/pooledClient.js.map +1 -0
- package/dist/mcp/pool/record.js +80 -0
- package/dist/mcp/pool/record.js.map +1 -0
- package/dist/mcp/pool/sessionEnded.js +35 -0
- package/dist/mcp/pool/sessionEnded.js.map +1 -0
- package/dist/mcp/pool/work.js +219 -0
- package/dist/mcp/pool/work.js.map +1 -0
- package/dist/mcp/requestGuard.js +106 -0
- package/dist/mcp/requestGuard.js.map +1 -0
- package/dist/mcp/requestLedger.js +76 -3
- package/dist/mcp/requestLedger.js.map +1 -1
- package/dist/mcp/seriesEvidence.js +16 -7
- package/dist/mcp/seriesEvidence.js.map +1 -1
- package/dist/mcp/terminate.js +193 -0
- package/dist/mcp/terminate.js.map +1 -0
- package/dist/mcp/throttleRetry.js +71 -7
- package/dist/mcp/throttleRetry.js.map +1 -1
- package/dist/redaction/knownStrings.js +34 -0
- package/dist/redaction/knownStrings.js.map +1 -1
- package/dist/tool-providers/index.js +12 -1
- package/dist/tool-providers/index.js.map +1 -1
- package/dist/tools/mcpClientUse.js +225 -0
- package/dist/tools/mcpClientUse.js.map +1 -0
- package/dist/tools/toolSessions.js +44 -1
- package/dist/tools/toolSessions.js.map +1 -1
- package/dist/types/adapters/mcp/agentcoreRuntime/invoke.d.ts +23 -4
- package/dist/types/adapters/mcp/agentcoreRuntime/invoke.d.ts.map +1 -1
- package/dist/types/adapters/mcp/agentcoreRuntime/session.d.ts +20 -6
- package/dist/types/adapters/mcp/agentcoreRuntime/session.d.ts.map +1 -1
- package/dist/types/adapters/mcp/agentcoreRuntime/transport.d.ts.map +1 -1
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
- package/dist/types/core/slots/buildToolsSlot.d.ts +12 -0
- package/dist/types/core/slots/buildToolsSlot.d.ts.map +1 -1
- package/dist/types/events/payloads.d.ts +41 -0
- package/dist/types/events/payloads.d.ts.map +1 -1
- package/dist/types/events/registry.d.ts +3 -1
- package/dist/types/events/registry.d.ts.map +1 -1
- package/dist/types/identity/types.d.ts +7 -3
- package/dist/types/identity/types.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/boundedJson.d.ts +14 -7
- package/dist/types/lib/boundedJson.d.ts.map +1 -1
- package/dist/types/mcp/callLifetime.d.ts.map +1 -1
- package/dist/types/mcp/clientPort.d.ts +79 -0
- package/dist/types/mcp/clientPort.d.ts.map +1 -0
- package/dist/types/mcp/gatewayTransport.d.ts.map +1 -1
- package/dist/types/mcp/index.d.ts +9 -1
- package/dist/types/mcp/index.d.ts.map +1 -1
- package/dist/types/mcp/mcpClient.d.ts +18 -1
- package/dist/types/mcp/mcpClient.d.ts.map +1 -1
- package/dist/types/mcp/mockMcpClient.d.ts.map +1 -1
- package/dist/types/mcp/pool/boundTool.d.ts +59 -0
- package/dist/types/mcp/pool/boundTool.d.ts.map +1 -0
- package/dist/types/mcp/pool/clientSet.d.ts +74 -0
- package/dist/types/mcp/pool/clientSet.d.ts.map +1 -0
- package/dist/types/mcp/pool/definitions.d.ts +104 -0
- package/dist/types/mcp/pool/definitions.d.ts.map +1 -0
- package/dist/types/mcp/pool/errors.d.ts +77 -0
- package/dist/types/mcp/pool/errors.d.ts.map +1 -0
- package/dist/types/mcp/pool/identityKey.d.ts +58 -0
- package/dist/types/mcp/pool/identityKey.d.ts.map +1 -0
- package/dist/types/mcp/pool/lease.d.ts +37 -0
- package/dist/types/mcp/pool/lease.d.ts.map +1 -0
- package/dist/types/mcp/pool/listTools.d.ts +34 -0
- package/dist/types/mcp/pool/listTools.d.ts.map +1 -0
- package/dist/types/mcp/pool/mcpClientPool.d.ts +122 -0
- package/dist/types/mcp/pool/mcpClientPool.d.ts.map +1 -0
- package/dist/types/mcp/pool/options.d.ts +184 -0
- package/dist/types/mcp/pool/options.d.ts.map +1 -0
- package/dist/types/mcp/pool/plan.d.ts +78 -0
- package/dist/types/mcp/pool/plan.d.ts.map +1 -0
- package/dist/types/mcp/pool/pool.d.ts +308 -0
- package/dist/types/mcp/pool/pool.d.ts.map +1 -0
- package/dist/types/mcp/pool/pooledClient.d.ts +94 -0
- package/dist/types/mcp/pool/pooledClient.d.ts.map +1 -0
- package/dist/types/mcp/pool/record.d.ts +81 -0
- package/dist/types/mcp/pool/record.d.ts.map +1 -0
- package/dist/types/mcp/pool/sessionEnded.d.ts +25 -0
- package/dist/types/mcp/pool/sessionEnded.d.ts.map +1 -0
- package/dist/types/mcp/pool/work.d.ts +117 -0
- package/dist/types/mcp/pool/work.d.ts.map +1 -0
- package/dist/types/mcp/requestGuard.d.ts +71 -0
- package/dist/types/mcp/requestGuard.d.ts.map +1 -0
- package/dist/types/mcp/requestLedger.d.ts +35 -1
- package/dist/types/mcp/requestLedger.d.ts.map +1 -1
- package/dist/types/mcp/seriesEvidence.d.ts +10 -6
- package/dist/types/mcp/seriesEvidence.d.ts.map +1 -1
- package/dist/types/mcp/terminate.d.ts +95 -0
- package/dist/types/mcp/terminate.d.ts.map +1 -0
- package/dist/types/mcp/throttleRetry.d.ts.map +1 -1
- package/dist/types/mcp/types.d.ts +85 -7
- package/dist/types/mcp/types.d.ts.map +1 -1
- package/dist/types/redaction/knownStrings.d.ts.map +1 -1
- package/dist/types/tool-providers/index.d.ts +2 -2
- package/dist/types/tool-providers/index.d.ts.map +1 -1
- package/dist/types/tools/mcpClientUse.d.ts +60 -0
- package/dist/types/tools/mcpClientUse.d.ts.map +1 -0
- package/dist/types/tools/toolProvider.d.ts +16 -0
- package/dist/types/tools/toolProvider.d.ts.map +1 -1
- package/dist/types/tools/toolSessions.d.ts +41 -0
- package/dist/types/tools/toolSessions.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lease — the app's explicit, bounded hold on one conversation's client.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: a handle that releases at most once, by hand or by `await using`.
|
|
5
|
+
* Role: § 3.3 and § 5.2 of the design. `acquire` is for code that drives a
|
|
6
|
+
* sequence itself, or a host that must keep a conversation's client
|
|
7
|
+
* from eviction for a whole turn (it holds one around `agent.run()`, and
|
|
8
|
+
* the provider then finds the same client). It is the only way to
|
|
9
|
+
* protect a client between calls: there is no hidden grace period.
|
|
10
|
+
*
|
|
11
|
+
* The `acquire` that opened it was the operation; the held lease is not one. It
|
|
12
|
+
* is bounded by `maxLeaseMs`: past it, it is revoked at the next pool
|
|
13
|
+
* interaction and reported, and its next use throws `lease-expired`. Calls
|
|
14
|
+
* already in flight keep their own leases until they settle.
|
|
15
|
+
*/
|
|
16
|
+
import { ASYNC_DISPOSE } from '../../strategies/lifecycle.js';
|
|
17
|
+
import { noteRefusal } from './boundTool.js';
|
|
18
|
+
import { listTools } from './listTools.js';
|
|
19
|
+
import { UseFacts } from './record.js';
|
|
20
|
+
/** The lease handle for `held`, on `key`'s client. */
|
|
21
|
+
export function leaseHandle(pool, key, held) {
|
|
22
|
+
const release = () => {
|
|
23
|
+
if (held.released)
|
|
24
|
+
return;
|
|
25
|
+
held.released = true;
|
|
26
|
+
pool.endLease(held);
|
|
27
|
+
pool.interact();
|
|
28
|
+
};
|
|
29
|
+
const handle = {
|
|
30
|
+
async tools(options) {
|
|
31
|
+
pool.interact();
|
|
32
|
+
if (held.revoked || held.released)
|
|
33
|
+
throw pool.refused('lease-expired');
|
|
34
|
+
const facts = new UseFacts(pool.settings.name, 'list');
|
|
35
|
+
try {
|
|
36
|
+
return await listTools(pool, key, options?.signal, facts);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
noteRefusal(facts, error);
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
release,
|
|
44
|
+
[ASYNC_DISPOSE]: async () => release(),
|
|
45
|
+
};
|
|
46
|
+
// The computed key gives TypeScript a symbol index rather than the one
|
|
47
|
+
// well-known member `AsyncDisposable` names; at runtime it IS
|
|
48
|
+
// `Symbol.asyncDispose` on every engine this package supports.
|
|
49
|
+
return handle;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=lease.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lease.js","sourceRoot":"","sources":["../../../../src/mcp/pool/lease.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAE9D,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAiBvC,sDAAsD;AACtD,MAAM,UAAU,WAAW,CAAC,IAAU,EAAE,GAAY,EAAE,IAAe;IACnE,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACpB,IAAI,CAAC,QAAQ,EAAE,CAAC;IAClB,CAAC,CAAC;IACF,MAAM,MAAM,GAAG;QACb,KAAK,CAAC,KAAK,CAAC,OAA2C;YACrD,IAAI,CAAC,QAAQ,EAAE,CAAC;YAChB,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,QAAQ;gBAAE,MAAM,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;YACvE,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YACvD,IAAI,CAAC;gBACH,OAAO,MAAM,SAAS,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;YAC5D,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;gBAC1B,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;QACD,OAAO;QACP,CAAC,aAAa,CAAC,EAAE,KAAK,IAAmB,EAAE,CAAC,OAAO,EAAE;KACtD,CAAC;IACF,uEAAuE;IACvE,8DAA8D;IAC9D,+DAA+D;IAC/D,OAAO,MAAiC,CAAC;AAC3C,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* listTools — one conversation's tools, bound to it: one listing operation.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: one admitted operation per call, the provider's and the lease's alike.
|
|
5
|
+
* Role: § 3.2 and § 6 of the design. A listing is one `list(ctx)` for one
|
|
6
|
+
* conversation, admitted against `maxOperations`. Its setup — the open
|
|
7
|
+
* of the key's client, the `tools/list` — runs inside its slot.
|
|
8
|
+
*
|
|
9
|
+
* - PER CLIENT: the conversation's own client is opened (or reused) and its own
|
|
10
|
+
* list served, listed again when it cannot be served as it stands.
|
|
11
|
+
* - SHARED, under the app's contract: the plan is resolved and the context
|
|
12
|
+
* computed WITHOUT opening anything; a current entry is served touching no
|
|
13
|
+
* client (`client: 'not-needed'`); a listing already under way for it is
|
|
14
|
+
* joined; otherwise the listing runs on THIS conversation's own client —
|
|
15
|
+
* never in another conversation's session.
|
|
16
|
+
* - THE CALLER'S CONTEXT AFTER EVERY WAIT: the app's names are asked again
|
|
17
|
+
* after a joined listing and after this one's own (`Pool.ensureListed`); a
|
|
18
|
+
* list is served only under the context the caller has at that moment, and
|
|
19
|
+
* a moved context is resolved again — at most three rounds, then refused
|
|
20
|
+
* (`binding-changed`).
|
|
21
|
+
*
|
|
22
|
+
* A refused listing never reaches the model as a tool result: it fails that
|
|
23
|
+
* iteration's tool discovery (`agentfootprint.tools.discovery_failed`), as any
|
|
24
|
+
* failing `ToolProvider` does. A list that cannot be read to its end
|
|
25
|
+
* (`McpToolListIncompleteError`, from the client's own discovery) is never
|
|
26
|
+
* served cut short.
|
|
27
|
+
*/
|
|
28
|
+
import type { Tool } from '../../tools/tools.js';
|
|
29
|
+
import type { PoolKey } from './identityKey.js';
|
|
30
|
+
import type { Pool } from './pool.js';
|
|
31
|
+
import type { UseFacts } from './record.js';
|
|
32
|
+
/** List `key`'s conversation's tools, bound to it, under one admitted operation. */
|
|
33
|
+
export declare function listTools(pool: Pool, key: PoolKey, signal: AbortSignal | undefined, facts: UseFacts): Promise<readonly Tool[]>;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* listTools — one conversation's tools, bound to it: one listing operation.
|
|
3
|
+
*
|
|
4
|
+
* Pattern: one admitted operation per call, the provider's and the lease's alike.
|
|
5
|
+
* Role: § 3.2 and § 6 of the design. A listing is one `list(ctx)` for one
|
|
6
|
+
* conversation, admitted against `maxOperations`. Its setup — the open
|
|
7
|
+
* of the key's client, the `tools/list` — runs inside its slot.
|
|
8
|
+
*
|
|
9
|
+
* - PER CLIENT: the conversation's own client is opened (or reused) and its own
|
|
10
|
+
* list served, listed again when it cannot be served as it stands.
|
|
11
|
+
* - SHARED, under the app's contract: the plan is resolved and the context
|
|
12
|
+
* computed WITHOUT opening anything; a current entry is served touching no
|
|
13
|
+
* client (`client: 'not-needed'`); a listing already under way for it is
|
|
14
|
+
* joined; otherwise the listing runs on THIS conversation's own client —
|
|
15
|
+
* never in another conversation's session.
|
|
16
|
+
* - THE CALLER'S CONTEXT AFTER EVERY WAIT: the app's names are asked again
|
|
17
|
+
* after a joined listing and after this one's own (`Pool.ensureListed`); a
|
|
18
|
+
* list is served only under the context the caller has at that moment, and
|
|
19
|
+
* a moved context is resolved again — at most three rounds, then refused
|
|
20
|
+
* (`binding-changed`).
|
|
21
|
+
*
|
|
22
|
+
* A refused listing never reaches the model as a tool result: it fails that
|
|
23
|
+
* iteration's tool discovery (`agentfootprint.tools.discovery_failed`), as any
|
|
24
|
+
* failing `ToolProvider` does. A list that cannot be read to its end
|
|
25
|
+
* (`McpToolListIncompleteError`, from the client's own discovery) is never
|
|
26
|
+
* served cut short.
|
|
27
|
+
*/
|
|
28
|
+
import { untilAborted } from '../../lib/abort.js';
|
|
29
|
+
import { isLocalSessionHandleLost, LocalSessionHandleLostError } from '../../tools/toolSessions.js';
|
|
30
|
+
import { ServerEndedSessionError } from './sessionEnded.js';
|
|
31
|
+
import { boundTool } from './boundTool.js';
|
|
32
|
+
import { contextFor, resolvePlan } from './plan.js';
|
|
33
|
+
/** List `key`'s conversation's tools, bound to it, under one admitted operation. */
|
|
34
|
+
export async function listTools(pool, key, signal, facts) {
|
|
35
|
+
facts.keyHash = key.keyHash;
|
|
36
|
+
pool.interact();
|
|
37
|
+
const operation = pool.admit('listing', 'listing', signal);
|
|
38
|
+
try {
|
|
39
|
+
for (let round = 0; round < 3; round += 1) {
|
|
40
|
+
const current = pool.clients.currentFor(key.key);
|
|
41
|
+
let plan;
|
|
42
|
+
if (current === undefined) {
|
|
43
|
+
try {
|
|
44
|
+
plan = resolvePlan(pool.settings, key);
|
|
45
|
+
}
|
|
46
|
+
catch (cause) {
|
|
47
|
+
throw pool.refused('init-failed', cause);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// A key with a client: the app's names asked again, so a changed
|
|
51
|
+
// authorization reaches this conversation as it would a new one.
|
|
52
|
+
const definitions = current === undefined ? plan : pool.refreshContext(current);
|
|
53
|
+
if (definitions.shared) {
|
|
54
|
+
const served = await sharedWithoutClient(pool, key, definitions.context, operation.signal);
|
|
55
|
+
if (served !== undefined) {
|
|
56
|
+
// After that wait, the caller's names once more: served only under
|
|
57
|
+
// the context they give now — otherwise resolved again.
|
|
58
|
+
if (!callerStillIn(pool, key, plan, served.context))
|
|
59
|
+
continue;
|
|
60
|
+
facts.client = 'not-needed';
|
|
61
|
+
facts.definitions = 'shared';
|
|
62
|
+
facts.definitionsAgeMs = Math.max(0, pool.now() - served.list.listedAt);
|
|
63
|
+
return bindAll(pool, key, served, undefined);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const client = await pool.leaseClient(key, operation, facts, plan);
|
|
67
|
+
let served;
|
|
68
|
+
try {
|
|
69
|
+
// Under the client's context of now, checked with no wait since.
|
|
70
|
+
served = await pool.ensureListed(client, operation);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
throw classifiedListing(pool, client, facts, error);
|
|
74
|
+
}
|
|
75
|
+
facts.definitions = served.shared ? 'shared' : 'listed';
|
|
76
|
+
if (served.shared)
|
|
77
|
+
facts.definitionsAgeMs = Math.max(0, pool.now() - served.list.listedAt);
|
|
78
|
+
return bindAll(pool, key, served, client);
|
|
79
|
+
}
|
|
80
|
+
// The app's names kept moving under this listing.
|
|
81
|
+
throw pool.refused('binding-changed');
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
operation.finish();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Do the caller's names still give `context` — asked now? Through the key's
|
|
89
|
+
* client when it has one by now, else over the plan's configuration. False
|
|
90
|
+
* when `share` cannot answer: the next round says why.
|
|
91
|
+
*/
|
|
92
|
+
function callerStillIn(pool, key, plan, context) {
|
|
93
|
+
const current = pool.clients.currentFor(key.key);
|
|
94
|
+
if (current !== undefined)
|
|
95
|
+
return pool.contextHolds(current, context);
|
|
96
|
+
if (plan === undefined)
|
|
97
|
+
return false;
|
|
98
|
+
try {
|
|
99
|
+
return contextFor(pool.settings, key, plan.configHash).context === context;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The shared entry for `plan`, when it can be served without touching a client
|
|
107
|
+
* — at once, or once a listing already under way for it is done.
|
|
108
|
+
*/
|
|
109
|
+
async function sharedWithoutClient(pool, key, context, signal) {
|
|
110
|
+
const entry = pool.shared.entryFor(context, key.tenant);
|
|
111
|
+
const servable = () => entry.list.servable(pool.now(), pool.settings.definitionTtlMs);
|
|
112
|
+
if (!servable() && entry.list.listing !== undefined) {
|
|
113
|
+
// Joined inside this operation's own slot; if it fails, this one lists itself.
|
|
114
|
+
await untilAborted(entry.list.listing, signal).catch(() => undefined);
|
|
115
|
+
}
|
|
116
|
+
return servable() ? { list: entry.list, shared: true, context } : undefined;
|
|
117
|
+
}
|
|
118
|
+
/** Every definition of `served`, bound to `key` and to the list it came from. */
|
|
119
|
+
function bindAll(pool, key, served, client) {
|
|
120
|
+
const base = {
|
|
121
|
+
key,
|
|
122
|
+
context: served.context,
|
|
123
|
+
shared: served.shared,
|
|
124
|
+
listId: served.list.id,
|
|
125
|
+
generation: served.list.generation,
|
|
126
|
+
...(!served.shared && client !== undefined && { clientId: client.clientId }),
|
|
127
|
+
};
|
|
128
|
+
return served.list.all().map((definition) => boundTool(pool, { ...base, definition }));
|
|
129
|
+
}
|
|
130
|
+
/** A listing that failed because the client's session expired or its handle was lost. */
|
|
131
|
+
function classifiedListing(pool, client, facts, error) {
|
|
132
|
+
if (client.lost === 'expired') {
|
|
133
|
+
facts.client = 'expired';
|
|
134
|
+
facts.evidence = 'server-404';
|
|
135
|
+
return new ServerEndedSessionError(pool.settings.name, client.clientId, error);
|
|
136
|
+
}
|
|
137
|
+
if (client.lost === 'handle-lost') {
|
|
138
|
+
facts.client = 'handle-lost';
|
|
139
|
+
facts.evidence = 'transport-forgot-handle';
|
|
140
|
+
return isLocalSessionHandleLost(error)
|
|
141
|
+
? error
|
|
142
|
+
: new LocalSessionHandleLostError({ cause: error });
|
|
143
|
+
}
|
|
144
|
+
return error;
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=listTools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listTools.js","sourceRoot":"","sources":["../../../../src/mcp/pool/listTools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcpClientPool — a client per conversation, for one MCP server.
|
|
3
|
+
*
|
|
4
|
+
* const orders = mcpClientPool({
|
|
5
|
+
* name: 'orders',
|
|
6
|
+
* transport: () =>
|
|
7
|
+
* agentCoreRuntimeMcpTransport({ agentRuntimeArn, region, session: 'reuse', client: aws }),
|
|
8
|
+
* });
|
|
9
|
+
* const agent = Agent.create({ provider, model }).toolProvider(orders.toolProvider()).build();
|
|
10
|
+
* await agent.run({ message, identity: { tenant, principal, conversationId } });
|
|
11
|
+
*
|
|
12
|
+
* Pattern: Facade over one state machine (`pool.ts`), its accounting (`work.ts`),
|
|
13
|
+
* its definitions (`definitions.ts`) and its bound tools (`boundTool.ts`).
|
|
14
|
+
* Role: `docs/design/2026-10-mcp-client-pool.md`. A `'reuse'` session costs a
|
|
15
|
+
* cold start once per conversation instead of once per call — but only
|
|
16
|
+
* if each conversation has a client of its own, opened once, bounded,
|
|
17
|
+
* never closed while in use, ended remotely and said to be. This is
|
|
18
|
+
* the one place that does all of it:
|
|
19
|
+
*
|
|
20
|
+
* - one client per authenticated conversation, keyed by the library's
|
|
21
|
+
* identity encoding of the identity the CALLER named;
|
|
22
|
+
* - one open per key, under the pool's own signal and `initTimeoutMs`;
|
|
23
|
+
* - three budgets — open clients, endings, outstanding operations — each
|
|
24
|
+
* charged until the work under it has SETTLED, and `PoolFullError` at once
|
|
25
|
+
* when one is full: there is no queue;
|
|
26
|
+
* - tool definitions per client incarnation by default, shared only under the
|
|
27
|
+
* app's declared contract, every tool re-checked after each wait and at the
|
|
28
|
+
* send gate;
|
|
29
|
+
* - every ending through `terminate()`, with the remote outcome reported, and
|
|
30
|
+
* `close()` still only local.
|
|
31
|
+
*
|
|
32
|
+
* What it does not do: repeat a call, share anything across processes, bound a
|
|
33
|
+
* remote session it could not end, or make a server enforce per-user
|
|
34
|
+
* permissions.
|
|
35
|
+
*/
|
|
36
|
+
import type { ToolProvider } from '../../tools/toolProvider.js';
|
|
37
|
+
import type { McpTerminateOutcome } from '../types.js';
|
|
38
|
+
import { type McpPoolLease } from './lease.js';
|
|
39
|
+
import { type McpClientPoolOptions, type McpPoolIdentity } from './options.js';
|
|
40
|
+
import { type McpPoolStats } from './pool.js';
|
|
41
|
+
/** The scope `terminate` and `permissionsChanged` name. */
|
|
42
|
+
export interface McpPoolScope {
|
|
43
|
+
/** Matched exactly, always: an absent tenant is "no tenant", never "every tenant". */
|
|
44
|
+
readonly tenant?: string;
|
|
45
|
+
/** Absent: every principal of the tenant (`permissionsChanged` only). */
|
|
46
|
+
readonly principal?: string;
|
|
47
|
+
/** Absent: every conversation of the principal. */
|
|
48
|
+
readonly conversationId?: string;
|
|
49
|
+
}
|
|
50
|
+
/** One pool per MCP server: a client per conversation. */
|
|
51
|
+
export interface McpClientPool {
|
|
52
|
+
/** The pool's name: the server. */
|
|
53
|
+
readonly name: string;
|
|
54
|
+
/**
|
|
55
|
+
* The agent path: a `ToolProvider` (id `mcp-pool:<name>`) whose `list(ctx)`
|
|
56
|
+
* takes the run's caller identity (`ToolDispatchContext.callerIdentity`) and
|
|
57
|
+
* returns that conversation's tools, bound to it. A bound tool's `execute`
|
|
58
|
+
* takes a lease on the key's client for one call, opening the client first
|
|
59
|
+
* when the key has none; nothing is held between calls. A run with no caller
|
|
60
|
+
* identity is refused (`no-identity`) — a derived one counts as none.
|
|
61
|
+
*/
|
|
62
|
+
toolProvider(): ToolProvider;
|
|
63
|
+
/**
|
|
64
|
+
* Hold `identity`'s conversation client — opened or reused — until
|
|
65
|
+
* `release()`, at most `maxLeaseMs`. The only way to protect a client between
|
|
66
|
+
* calls (a host holds one around `agent.run()`).
|
|
67
|
+
*
|
|
68
|
+
* @throws `McpPoolRefusedError` (`no-identity`, `incomplete-identity`,
|
|
69
|
+
* `init-timeout`, `init-failed`, `closed`) or `PoolFullError`
|
|
70
|
+
*/
|
|
71
|
+
acquire(identity: McpPoolIdentity, options?: {
|
|
72
|
+
readonly signal?: AbortSignal;
|
|
73
|
+
}): Promise<McpPoolLease>;
|
|
74
|
+
/**
|
|
75
|
+
* End the conversations `scope` names — one (`{ tenant, principal,
|
|
76
|
+
* conversationId }`) or every one of a principal (`{ tenant, principal }`).
|
|
77
|
+
* New leases stop at once; the next acquire for a key opens a new client.
|
|
78
|
+
* In-flight work is waited for up to 5 s; then each client is ended through
|
|
79
|
+
* `terminate()` once an ending slot and an operation slot are reserved. A
|
|
80
|
+
* client still leased, or whose slots cannot be reserved yet, is reported
|
|
81
|
+
* deferred (`onEvent`), never as ended, and is ended when it can be.
|
|
82
|
+
*
|
|
83
|
+
* @returns one `McpTerminateOutcome` per client it ended
|
|
84
|
+
*/
|
|
85
|
+
terminate(scope: McpPoolScope): Promise<readonly McpTerminateOutcome[]>;
|
|
86
|
+
/**
|
|
87
|
+
* Shut down: refuse new work, revoke app leases, abort in-flight calls
|
|
88
|
+
* through the pool's own signal, then end every client as slots can be
|
|
89
|
+
* reserved, at most `maxEnding` at a time. Resolves after a bounded wait;
|
|
90
|
+
* the clients it could not end yet are reported deferred and the work still
|
|
91
|
+
* unsettled stays charged until it settles (`stats()`). Idempotent.
|
|
92
|
+
*
|
|
93
|
+
* @returns one `McpTerminateOutcome` per client it ended
|
|
94
|
+
*/
|
|
95
|
+
close(): Promise<readonly McpTerminateOutcome[]>;
|
|
96
|
+
/** Resolves once every charged operation and every ending has settled. */
|
|
97
|
+
settled(): Promise<void>;
|
|
98
|
+
/**
|
|
99
|
+
* The app's permission signal: every tool list in the scope stops being
|
|
100
|
+
* served, and every tool bound to one is refused (`stale-tools`) before it is
|
|
101
|
+
* sent. Per process: a fleet sends it to every process, or versions its
|
|
102
|
+
* `authorization` names.
|
|
103
|
+
*/
|
|
104
|
+
permissionsChanged(scope: {
|
|
105
|
+
readonly tenant?: string;
|
|
106
|
+
readonly principal?: string;
|
|
107
|
+
}): void;
|
|
108
|
+
/** The server changed (a deploy hook): every tool list stops being served. */
|
|
109
|
+
toolsChanged(): void;
|
|
110
|
+
/** Run the lazy sweep now: revoked leases, idle ends, deferred ends. No timer is ever installed. */
|
|
111
|
+
sweep(): void;
|
|
112
|
+
/** Counts only — never keys. They report; the budgets enforce. */
|
|
113
|
+
stats(): McpPoolStats;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Build a pool for one MCP server.
|
|
117
|
+
*
|
|
118
|
+
* @throws TypeError when an option is missing, mistyped or out of range, or when
|
|
119
|
+
* `connection` is given — here, when the pool is built
|
|
120
|
+
*/
|
|
121
|
+
export declare function mcpClientPool(options: McpClientPoolOptions): McpClientPool;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcpClientPool — a client per conversation, for one MCP server.
|
|
3
|
+
*
|
|
4
|
+
* const orders = mcpClientPool({
|
|
5
|
+
* name: 'orders',
|
|
6
|
+
* transport: () =>
|
|
7
|
+
* agentCoreRuntimeMcpTransport({ agentRuntimeArn, region, session: 'reuse', client: aws }),
|
|
8
|
+
* });
|
|
9
|
+
* const agent = Agent.create({ provider, model }).toolProvider(orders.toolProvider()).build();
|
|
10
|
+
* await agent.run({ message, identity: { tenant, principal, conversationId } });
|
|
11
|
+
*
|
|
12
|
+
* Pattern: Facade over one state machine (`pool.ts`), its accounting (`work.ts`),
|
|
13
|
+
* its definitions (`definitions.ts`) and its bound tools (`boundTool.ts`).
|
|
14
|
+
* Role: `docs/design/2026-10-mcp-client-pool.md`. A `'reuse'` session costs a
|
|
15
|
+
* cold start once per conversation instead of once per call — but only
|
|
16
|
+
* if each conversation has a client of its own, opened once, bounded,
|
|
17
|
+
* never closed while in use, ended remotely and said to be. This is
|
|
18
|
+
* the one place that does all of it:
|
|
19
|
+
*
|
|
20
|
+
* - one client per authenticated conversation, keyed by the library's
|
|
21
|
+
* identity encoding of the identity the CALLER named;
|
|
22
|
+
* - one open per key, under the pool's own signal and `initTimeoutMs`;
|
|
23
|
+
* - three budgets — open clients, endings, outstanding operations — each
|
|
24
|
+
* charged until the work under it has SETTLED, and `PoolFullError` at once
|
|
25
|
+
* when one is full: there is no queue;
|
|
26
|
+
* - tool definitions per client incarnation by default, shared only under the
|
|
27
|
+
* app's declared contract, every tool re-checked after each wait and at the
|
|
28
|
+
* send gate;
|
|
29
|
+
* - every ending through `terminate()`, with the remote outcome reported, and
|
|
30
|
+
* `close()` still only local.
|
|
31
|
+
*
|
|
32
|
+
* What it does not do: repeat a call, share anything across processes, bound a
|
|
33
|
+
* remote session it could not end, or make a server enforce per-user
|
|
34
|
+
* permissions.
|
|
35
|
+
*/
|
|
36
|
+
import { fileListUse } from '../../tools/mcpClientUse.js';
|
|
37
|
+
import { TOOL_TEARDOWN_TIMEOUT_MS } from '../../tools/toolSessions.js';
|
|
38
|
+
import { noteRefusal } from './boundTool.js';
|
|
39
|
+
import { poolKeyOf, scopeMatcher } from './identityKey.js';
|
|
40
|
+
import { leaseHandle } from './lease.js';
|
|
41
|
+
import { listTools } from './listTools.js';
|
|
42
|
+
import { readPoolOptions } from './options.js';
|
|
43
|
+
import { Pool } from './pool.js';
|
|
44
|
+
import { UseFacts } from './record.js';
|
|
45
|
+
/**
|
|
46
|
+
* Build a pool for one MCP server.
|
|
47
|
+
*
|
|
48
|
+
* @throws TypeError when an option is missing, mistyped or out of range, or when
|
|
49
|
+
* `connection` is given — here, when the pool is built
|
|
50
|
+
*/
|
|
51
|
+
export function mcpClientPool(options) {
|
|
52
|
+
const settings = readPoolOptions(options);
|
|
53
|
+
const pool = new Pool(settings);
|
|
54
|
+
const rules = {
|
|
55
|
+
requirePrincipal: settings.requirePrincipal,
|
|
56
|
+
requireTenant: settings.requireTenant,
|
|
57
|
+
};
|
|
58
|
+
let closing;
|
|
59
|
+
const provider = {
|
|
60
|
+
id: `mcp-pool:${settings.name}`,
|
|
61
|
+
list: async (ctx) => {
|
|
62
|
+
const facts = new UseFacts(settings.name, 'list');
|
|
63
|
+
try {
|
|
64
|
+
const key = poolKeyOf(ctx.callerIdentity, rules);
|
|
65
|
+
if (typeof key === 'string')
|
|
66
|
+
throw pool.refused(key);
|
|
67
|
+
return await listTools(pool, key, ctx.signal, facts);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
noteRefusal(facts, error);
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
// Filed against the context the tools slot passed; the slot emits it.
|
|
75
|
+
fileListUse(ctx, facts.toUse());
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
name: settings.name,
|
|
81
|
+
toolProvider: () => provider,
|
|
82
|
+
async acquire(identity, acquireOptions) {
|
|
83
|
+
const key = poolKeyOf(identity, rules);
|
|
84
|
+
if (typeof key === 'string')
|
|
85
|
+
throw pool.refused(key);
|
|
86
|
+
pool.interact();
|
|
87
|
+
const operation = pool.admit('acquire', 'open', acquireOptions?.signal);
|
|
88
|
+
try {
|
|
89
|
+
const facts = new UseFacts(settings.name, 'list');
|
|
90
|
+
for (let round = 0; round < 3; round += 1) {
|
|
91
|
+
const client = await pool.leaseClient(key, operation, facts);
|
|
92
|
+
// After that wait, before anything is held: the pool may have closed,
|
|
93
|
+
// the caller may have gone, the client may have been named by a
|
|
94
|
+
// terminate. Refused, the operation's own lease ends with it below.
|
|
95
|
+
if (pool.closed)
|
|
96
|
+
throw pool.refused('closed');
|
|
97
|
+
operation.check();
|
|
98
|
+
if (client.serving)
|
|
99
|
+
return leaseHandle(pool, key, pool.holdLease(client));
|
|
100
|
+
}
|
|
101
|
+
throw pool.refused('init-failed');
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
operation.finish();
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
async terminate(scope) {
|
|
108
|
+
assertScope(scope, settings.name);
|
|
109
|
+
pool.interact();
|
|
110
|
+
return pool.terminate(scopeMatcher(scope), 'terminated', TOOL_TEARDOWN_TIMEOUT_MS);
|
|
111
|
+
},
|
|
112
|
+
close() {
|
|
113
|
+
closing ??= pool.closeAll(TOOL_TEARDOWN_TIMEOUT_MS);
|
|
114
|
+
return closing;
|
|
115
|
+
},
|
|
116
|
+
settled: () => pool.settled(),
|
|
117
|
+
permissionsChanged(scope) {
|
|
118
|
+
assertFields(scope, settings.name, 'permissionsChanged');
|
|
119
|
+
pool.permissionsChanged(scope);
|
|
120
|
+
},
|
|
121
|
+
toolsChanged: () => pool.toolsChanged(),
|
|
122
|
+
sweep: () => pool.interact(),
|
|
123
|
+
stats: () => pool.stats(),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* A `terminate` scope names a principal or a conversation: ending every
|
|
128
|
+
* conversation of a whole tenant is `close()`'s job, and a mistyped scope must
|
|
129
|
+
* not do it.
|
|
130
|
+
*/
|
|
131
|
+
function assertScope(scope, name) {
|
|
132
|
+
assertFields(scope, name, 'terminate');
|
|
133
|
+
const named = (value) => value !== undefined && value !== '';
|
|
134
|
+
if (!named(scope.principal) && !named(scope.conversationId)) {
|
|
135
|
+
throw new TypeError(`mcpClientPool[${name}].terminate: the scope must name a principal or a conversation ` +
|
|
136
|
+
'({ tenant, principal } or { tenant, principal, conversationId }).');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Every field present is a string. Never quoted: a scope is an identity. */
|
|
140
|
+
function assertFields(scope, name, door) {
|
|
141
|
+
if (scope === null || typeof scope !== 'object') {
|
|
142
|
+
throw new TypeError(`mcpClientPool[${name}].${door}: the scope must be an object.`);
|
|
143
|
+
}
|
|
144
|
+
for (const field of ['tenant', 'principal', 'conversationId']) {
|
|
145
|
+
const value = scope[field];
|
|
146
|
+
if (value !== undefined && typeof value !== 'string') {
|
|
147
|
+
throw new TypeError(`mcpClientPool[${name}].${door}: \`${field}\` must be a string when present.`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=mcpClientPool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcpClientPool.js","sourceRoot":"","sources":["../../../../src/mcp/pool/mcpClientPool.ts"],"names":[],"mappings":"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"}
|