@adcp/sdk 16.0.0-beta.2 → 16.0.0-beta.3
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/README.md +54 -0
- package/dist/lib/core/ConversationTypes.d.mts +8 -3
- package/dist/lib/core/ConversationTypes.d.ts +8 -3
- package/dist/lib/core/SingleAgentClient.d.mts +5 -3
- package/dist/lib/core/SingleAgentClient.d.ts +5 -3
- package/dist/lib/core/TaskExecutor.js +5 -1
- package/dist/lib/core/TaskExecutor.mjs +5 -1
- package/dist/lib/errors/index.d.mts +9 -4
- package/dist/lib/errors/index.d.ts +9 -4
- package/dist/lib/index.d.mts +2 -2
- package/dist/lib/index.d.ts +2 -2
- package/dist/lib/index.js +6 -0
- package/dist/lib/index.mjs +7 -1
- package/dist/lib/negotiation/types.d.mts +20 -39
- package/dist/lib/negotiation/types.d.ts +20 -39
- package/dist/lib/protocols/index.d.mts +60 -24
- package/dist/lib/protocols/index.d.ts +60 -24
- package/dist/lib/protocols/index.js +9 -4
- package/dist/lib/protocols/index.mjs +3 -0
- package/dist/lib/protocols/mcp-modern.js +1 -0
- package/dist/lib/protocols/mcp-modern.mjs +2 -1
- package/dist/lib/protocols/mcp.js +1 -0
- package/dist/lib/protocols/mcp.mjs +2 -1
- package/dist/lib/protocols/responseSizeLimit.d.mts +91 -5
- package/dist/lib/protocols/responseSizeLimit.d.ts +91 -5
- package/dist/lib/protocols/responseSizeLimit.js +195 -31
- package/dist/lib/protocols/responseSizeLimit.mjs +192 -31
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/signing/crypto.d.mts +15 -0
- package/dist/lib/signing/crypto.d.ts +15 -0
- package/dist/lib/signing/crypto.js +75 -3
- package/dist/lib/signing/crypto.mjs +78 -3
- package/dist/lib/signing/server.d.mts +1 -1
- package/dist/lib/signing/server.d.ts +1 -1
- package/dist/lib/signing/server.js +2 -0
- package/dist/lib/signing/server.mjs +2 -1
- package/dist/lib/signing/signer.js +2 -8
- package/dist/lib/signing/signer.mjs +3 -9
- package/dist/lib/signing/testing.js +2 -6
- package/dist/lib/signing/testing.mjs +3 -7
- package/dist/lib/signing/verifier.js +2 -2
- package/dist/lib/signing/verifier.mjs +3 -3
- package/dist/lib/signing/web-bot-auth.js +20 -7
- package/dist/lib/signing/web-bot-auth.mjs +21 -8
- package/dist/lib/signing/webhook-verifier.js +2 -2
- package/dist/lib/signing/webhook-verifier.mjs +3 -3
- package/dist/lib/substitution/compile.js +2 -1
- package/dist/lib/substitution/compile.mjs +2 -1
- package/dist/lib/substitution/consent-macros.d.mts +4 -0
- package/dist/lib/substitution/consent-macros.d.ts +4 -0
- package/dist/lib/substitution/consent-macros.js +45 -0
- package/dist/lib/substitution/consent-macros.mjs +20 -0
- package/dist/lib/substitution/index.d.mts +1 -1
- package/dist/lib/substitution/index.d.ts +1 -1
- package/dist/lib/substitution/index.js +2 -0
- package/dist/lib/substitution/index.mjs +2 -1
- package/dist/lib/substitution/translate.d.mts +15 -2
- package/dist/lib/substitution/translate.d.ts +15 -2
- package/dist/lib/substitution/translate.js +148 -26
- package/dist/lib/substitution/translate.mjs +147 -26
- package/dist/lib/testing/client.js +2 -1
- package/dist/lib/testing/client.mjs +2 -1
- package/dist/lib/testing/scenarios/capabilities.js +1 -1
- package/dist/lib/testing/scenarios/capabilities.mjs +1 -1
- package/dist/lib/testing/storyboard/request-signing/builder.js +2 -5
- package/dist/lib/testing/storyboard/request-signing/builder.mjs +3 -6
- package/dist/lib/testing/storyboard/signer-grader/grader.js +2 -7
- package/dist/lib/testing/storyboard/signer-grader/grader.mjs +2 -7
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/types/enums.generated.d.mts +3 -0
- package/dist/lib/types/enums.generated.d.ts +3 -0
- package/dist/lib/types/enums.generated.js +9 -0
- package/dist/lib/types/enums.generated.mjs +6 -0
- package/dist/lib/validation/schema-loader.js +25 -0
- package/dist/lib/validation/schema-loader.mjs +24 -0
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/docs/TYPE-SUMMARY.md +6 -1
- package/docs/guides/DISCOVERY-CACHE-AND-SESSION-REUSE.md +3 -2
- package/docs/guides/UNIVERSAL-MACRO-COMPILATION.md +38 -0
- package/docs/guides/VALIDATE-YOUR-AGENT.md +12 -0
- package/docs/llms.txt +1 -1
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/package.json +2 -1
|
@@ -11,6 +11,7 @@ export { withMCPConnectionScope };
|
|
|
11
11
|
export type { MCPCallOptions, MCPConnectionResult } from './mcp.mjs';
|
|
12
12
|
export { callA2ATool, createA2AClientFromCardUrl } from './a2a.mjs';
|
|
13
13
|
export { DEFAULT_REQUEST_TIMEOUT_MS } from './abort.mjs';
|
|
14
|
+
export { DEFAULT_MAX_RESPONSE_BYTES, SSE_CUMULATIVE_BUDGET_MULTIPLIER } from './responseSizeLimit.mjs';
|
|
14
15
|
export { callMCPToolWithTasks, getMCPTaskStatus, getMCPTaskResult, listMCPTasks, cancelMCPTask, mapMCPTaskStatus, serverSupportsTasks, } from './mcp-tasks.mjs';
|
|
15
16
|
import type { AgentConfig, DebugLogEntry } from '../types/index.mjs';
|
|
16
17
|
import type { PushNotificationConfig } from '../types/tools.generated.mjs';
|
|
@@ -36,36 +37,71 @@ export declare function bundleSupportsAdcpVersionField(bundleKey: string): boole
|
|
|
36
37
|
export interface TransportOptions {
|
|
37
38
|
/**
|
|
38
39
|
* Maximum response body size (in octets) the SDK will read before aborting
|
|
39
|
-
* with `ResponseTooLargeError`.
|
|
40
|
-
* matches the underlying MCP / A2A transport defaults.
|
|
40
|
+
* with `ResponseTooLargeError`.
|
|
41
41
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* (`/.well-known/agent.json`) on the same call as well.
|
|
42
|
+
* **Defaults to 4 MiB** (`DEFAULT_MAX_RESPONSE_BYTES`). Agent responses are
|
|
43
|
+
* untrusted input, so the cap is on by default on every MCP and A2A call
|
|
44
|
+
* path — tool calls, endpoint discovery, `tools/list`, and A2A agent-card
|
|
45
|
+
* lookup (`/.well-known/agent.json`). Counted across response chunks as
|
|
46
|
+
* they arrive; pre-cancels when `Content-Length` already exceeds the cap.
|
|
48
47
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
* Raise it for an agent that legitimately publishes a large catalog; lower
|
|
49
|
+
* it when crawling **untrusted** agents (registries, federated discovery
|
|
50
|
+
* layers, monitoring tools). Set it to exactly `0` to opt out of
|
|
51
|
+
* enforcement entirely — an explicit opt-out, never the default. `Infinity`
|
|
52
|
+
* and negative values are *not* opt-outs: they fall back to the 4 MiB
|
|
53
|
+
* default, because they almost always arrive from a misparsed env var or a
|
|
54
|
+
* budget subtraction rather than an intention. Use `0`.
|
|
55
|
+
*
|
|
56
|
+
* Per-call `transport` **replaces** the client constructor's `transport`
|
|
57
|
+
* object rather than merging field by field
|
|
58
|
+
* (`TaskOptions.transport` ?? `SingleAgentClientConfig.transport`), so a
|
|
59
|
+
* per-call object that omits `maxResponseBytes` gets the default back, not
|
|
60
|
+
* the constructor's value. Repeat the field when you override the object.
|
|
61
|
+
*
|
|
62
|
+
* Cached MCP connections are keyed on the cap, so varying it per call opens
|
|
63
|
+
* a separate connection (and MCP session) per distinct value against the
|
|
64
|
+
* same agent. That is deliberate — a connection established under one
|
|
65
|
+
* policy must not serve a call that configured another — but it means
|
|
66
|
+
* raising the cap for one tool is not free. Prefer a single client-level
|
|
67
|
+
* value where you can, or pin the session explicitly with
|
|
68
|
+
* `withMCPConnectionScope` when continuity matters.
|
|
51
69
|
*
|
|
52
70
|
* @remarks
|
|
53
|
-
* **
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
71
|
+
* **SSE is covered too.** `text/event-stream` responses used to be exempt,
|
|
72
|
+
* which let any peer opt itself out of the cap by changing one response
|
|
73
|
+
* header. They now carry two bounds: no single event frame may exceed
|
|
74
|
+
* `maxResponseBytes` (the memory bound — an SSE consumer buffers one frame
|
|
75
|
+
* at a time), and the cumulative event-stream octets read during one call
|
|
76
|
+
* may not exceed `maxResponseBytes * 2`
|
|
77
|
+
* (`SSE_CUMULATIVE_BUDGET_MULTIPLIER`, the liveness bound that stops an
|
|
78
|
+
* endless drip of individually legal frames). Both failures raise the same
|
|
79
|
+
* `ResponseTooLargeError`; nothing is silently truncated.
|
|
80
|
+
*
|
|
81
|
+
* A long-lived `GET` event-stream — MCP's standalone notification listener,
|
|
82
|
+
* the legacy SSE transport — outlives the call that opened it, so it meters
|
|
83
|
+
* its cumulative allowance per *connection* instead of per call. That makes
|
|
84
|
+
* the cumulative number a memory bound there, not a liveness bound: a peer
|
|
85
|
+
* that keeps tripping it gets a reconnect cycle at a bounded cost per
|
|
86
|
+
* cycle. Cached connections are keyed on the cap in effect, so a listener
|
|
87
|
+
* is only ever reused by calls that configured the same policy as the call
|
|
88
|
+
* that opened it.
|
|
59
89
|
*
|
|
60
90
|
* @remarks
|
|
61
|
-
* **
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
91
|
+
* **Compression and streaming failures.** The byte counter runs downstream
|
|
92
|
+
* of the runtime's content-decoder, so it measures decompressed octets: a
|
|
93
|
+
* small gzip blob that inflates to gigabytes still trips the cap after
|
|
94
|
+
* `maxResponseBytes` of output. The `Content-Length` pre-check, by
|
|
95
|
+
* contrast, sees the compressed length and is therefore only a fast path,
|
|
96
|
+
* not the guarantee.
|
|
97
|
+
*
|
|
98
|
+
* On a streaming transport the cap errors the response body. Some
|
|
99
|
+
* transports classify that as a stream disconnect and retry rather than
|
|
100
|
+
* propagating it, so the call would otherwise run out its deadline: the SDK
|
|
101
|
+
* records the violation and, if the call then fails, raises
|
|
102
|
+
* `ResponseTooLargeError` with the transport's own error as `cause` instead
|
|
103
|
+
* of the timeout. A buyer never gets a timeout for what is really an
|
|
104
|
+
* over-cap response.
|
|
69
105
|
*
|
|
70
106
|
* @remarks
|
|
71
107
|
* Future hardening knobs (DNS-rebind defense, scheme allow-list, request
|
|
@@ -11,6 +11,7 @@ export { withMCPConnectionScope };
|
|
|
11
11
|
export type { MCPCallOptions, MCPConnectionResult } from './mcp';
|
|
12
12
|
export { callA2ATool, createA2AClientFromCardUrl } from './a2a';
|
|
13
13
|
export { DEFAULT_REQUEST_TIMEOUT_MS } from './abort';
|
|
14
|
+
export { DEFAULT_MAX_RESPONSE_BYTES, SSE_CUMULATIVE_BUDGET_MULTIPLIER } from './responseSizeLimit';
|
|
14
15
|
export { callMCPToolWithTasks, getMCPTaskStatus, getMCPTaskResult, listMCPTasks, cancelMCPTask, mapMCPTaskStatus, serverSupportsTasks, } from './mcp-tasks';
|
|
15
16
|
import type { AgentConfig, DebugLogEntry } from '../types';
|
|
16
17
|
import type { PushNotificationConfig } from '../types/tools.generated';
|
|
@@ -36,36 +37,71 @@ export declare function bundleSupportsAdcpVersionField(bundleKey: string): boole
|
|
|
36
37
|
export interface TransportOptions {
|
|
37
38
|
/**
|
|
38
39
|
* Maximum response body size (in octets) the SDK will read before aborting
|
|
39
|
-
* with `ResponseTooLargeError`.
|
|
40
|
-
* matches the underlying MCP / A2A transport defaults.
|
|
40
|
+
* with `ResponseTooLargeError`.
|
|
41
41
|
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* (`/.well-known/agent.json`) on the same call as well.
|
|
42
|
+
* **Defaults to 4 MiB** (`DEFAULT_MAX_RESPONSE_BYTES`). Agent responses are
|
|
43
|
+
* untrusted input, so the cap is on by default on every MCP and A2A call
|
|
44
|
+
* path — tool calls, endpoint discovery, `tools/list`, and A2A agent-card
|
|
45
|
+
* lookup (`/.well-known/agent.json`). Counted across response chunks as
|
|
46
|
+
* they arrive; pre-cancels when `Content-Length` already exceeds the cap.
|
|
48
47
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
* Raise it for an agent that legitimately publishes a large catalog; lower
|
|
49
|
+
* it when crawling **untrusted** agents (registries, federated discovery
|
|
50
|
+
* layers, monitoring tools). Set it to exactly `0` to opt out of
|
|
51
|
+
* enforcement entirely — an explicit opt-out, never the default. `Infinity`
|
|
52
|
+
* and negative values are *not* opt-outs: they fall back to the 4 MiB
|
|
53
|
+
* default, because they almost always arrive from a misparsed env var or a
|
|
54
|
+
* budget subtraction rather than an intention. Use `0`.
|
|
55
|
+
*
|
|
56
|
+
* Per-call `transport` **replaces** the client constructor's `transport`
|
|
57
|
+
* object rather than merging field by field
|
|
58
|
+
* (`TaskOptions.transport` ?? `SingleAgentClientConfig.transport`), so a
|
|
59
|
+
* per-call object that omits `maxResponseBytes` gets the default back, not
|
|
60
|
+
* the constructor's value. Repeat the field when you override the object.
|
|
61
|
+
*
|
|
62
|
+
* Cached MCP connections are keyed on the cap, so varying it per call opens
|
|
63
|
+
* a separate connection (and MCP session) per distinct value against the
|
|
64
|
+
* same agent. That is deliberate — a connection established under one
|
|
65
|
+
* policy must not serve a call that configured another — but it means
|
|
66
|
+
* raising the cap for one tool is not free. Prefer a single client-level
|
|
67
|
+
* value where you can, or pin the session explicitly with
|
|
68
|
+
* `withMCPConnectionScope` when continuity matters.
|
|
51
69
|
*
|
|
52
70
|
* @remarks
|
|
53
|
-
* **
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
71
|
+
* **SSE is covered too.** `text/event-stream` responses used to be exempt,
|
|
72
|
+
* which let any peer opt itself out of the cap by changing one response
|
|
73
|
+
* header. They now carry two bounds: no single event frame may exceed
|
|
74
|
+
* `maxResponseBytes` (the memory bound — an SSE consumer buffers one frame
|
|
75
|
+
* at a time), and the cumulative event-stream octets read during one call
|
|
76
|
+
* may not exceed `maxResponseBytes * 2`
|
|
77
|
+
* (`SSE_CUMULATIVE_BUDGET_MULTIPLIER`, the liveness bound that stops an
|
|
78
|
+
* endless drip of individually legal frames). Both failures raise the same
|
|
79
|
+
* `ResponseTooLargeError`; nothing is silently truncated.
|
|
80
|
+
*
|
|
81
|
+
* A long-lived `GET` event-stream — MCP's standalone notification listener,
|
|
82
|
+
* the legacy SSE transport — outlives the call that opened it, so it meters
|
|
83
|
+
* its cumulative allowance per *connection* instead of per call. That makes
|
|
84
|
+
* the cumulative number a memory bound there, not a liveness bound: a peer
|
|
85
|
+
* that keeps tripping it gets a reconnect cycle at a bounded cost per
|
|
86
|
+
* cycle. Cached connections are keyed on the cap in effect, so a listener
|
|
87
|
+
* is only ever reused by calls that configured the same policy as the call
|
|
88
|
+
* that opened it.
|
|
59
89
|
*
|
|
60
90
|
* @remarks
|
|
61
|
-
* **
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
91
|
+
* **Compression and streaming failures.** The byte counter runs downstream
|
|
92
|
+
* of the runtime's content-decoder, so it measures decompressed octets: a
|
|
93
|
+
* small gzip blob that inflates to gigabytes still trips the cap after
|
|
94
|
+
* `maxResponseBytes` of output. The `Content-Length` pre-check, by
|
|
95
|
+
* contrast, sees the compressed length and is therefore only a fast path,
|
|
96
|
+
* not the guarantee.
|
|
97
|
+
*
|
|
98
|
+
* On a streaming transport the cap errors the response body. Some
|
|
99
|
+
* transports classify that as a stream disconnect and retry rather than
|
|
100
|
+
* propagating it, so the call would otherwise run out its deadline: the SDK
|
|
101
|
+
* records the violation and, if the call then fails, raises
|
|
102
|
+
* `ResponseTooLargeError` with the transport's own error as `cause` instead
|
|
103
|
+
* of the timeout. A buyer never gets a timeout for what is really an
|
|
104
|
+
* over-cap response.
|
|
69
105
|
*
|
|
70
106
|
* @remarks
|
|
71
107
|
* Future hardening knobs (DNS-rebind defense, scheme allow-list, request
|
|
@@ -19,9 +19,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
var protocols_exports = {};
|
|
20
20
|
__export(protocols_exports, {
|
|
21
21
|
BODY_SNIPPET_TIMEOUT_MS: () => import_transportDiagnostics2.BODY_SNIPPET_TIMEOUT_MS,
|
|
22
|
+
DEFAULT_MAX_RESPONSE_BYTES: () => import_responseSizeLimit.DEFAULT_MAX_RESPONSE_BYTES,
|
|
22
23
|
DEFAULT_REQUEST_TIMEOUT_MS: () => import_abort.DEFAULT_REQUEST_TIMEOUT_MS,
|
|
23
24
|
OBSERVER_FLUSH_TIMEOUT_MS: () => import_transportDiagnostics2.OBSERVER_FLUSH_TIMEOUT_MS,
|
|
24
25
|
ProtocolClient: () => ProtocolClient,
|
|
26
|
+
SSE_CUMULATIVE_BUDGET_MULTIPLIER: () => import_responseSizeLimit.SSE_CUMULATIVE_BUDGET_MULTIPLIER,
|
|
25
27
|
UnauthorizedError: () => import_mcp.UnauthorizedError,
|
|
26
28
|
applyVersionEnvelope: () => applyVersionEnvelope,
|
|
27
29
|
bundleSupportsAdcpVersionField: () => bundleSupportsAdcpVersionField,
|
|
@@ -59,6 +61,7 @@ var import_a2a = require('./a2a.js');
|
|
|
59
61
|
var import_node_crypto = require("node:crypto");
|
|
60
62
|
var import_a2a2 = require('./a2a.js');
|
|
61
63
|
var import_abort = require('./abort.js');
|
|
64
|
+
var import_responseSizeLimit = require('./responseSizeLimit.js');
|
|
62
65
|
var import_mcp_tasks = require('./mcp-tasks.js');
|
|
63
66
|
var import_mcp_tasks2 = require('./mcp-tasks.js');
|
|
64
67
|
var import_mcp3 = require('./mcp.js');
|
|
@@ -76,7 +79,7 @@ var import_version = require('../version.js');
|
|
|
76
79
|
var import_errors2 = require('../errors/index.js');
|
|
77
80
|
var import_schema_loader = require('../validation/schema-loader.js');
|
|
78
81
|
var import_client = require('../signing/client.js');
|
|
79
|
-
var
|
|
82
|
+
var import_responseSizeLimit2 = require('./responseSizeLimit.js');
|
|
80
83
|
var import_prepared_call_context = require('./prepared-call-context.js');
|
|
81
84
|
var import_adcp_version_config = require('../utils/adcp-version-config.js');
|
|
82
85
|
var import_transportDiagnostics = require('./transportDiagnostics.js');
|
|
@@ -259,7 +262,7 @@ class ProtocolClient {
|
|
|
259
262
|
wireAdcpVersion,
|
|
260
263
|
versionEnvelope: versionEnvelopeMode
|
|
261
264
|
});
|
|
262
|
-
return (0,
|
|
265
|
+
return (0, import_responseSizeLimit2.withResponseSizeLimit)(
|
|
263
266
|
transport?.maxResponseBytes,
|
|
264
267
|
() => (0, import_transportDiagnostics.withTransportDiagnostics)(
|
|
265
268
|
{
|
|
@@ -523,7 +526,7 @@ const createMCPClient = (agentUrl, authToken, headers, serverVersion, adcpVersio
|
|
|
523
526
|
transport = normalizeTransportOptions(transport);
|
|
524
527
|
const versionEnvelope = buildVersionEnvelopeForMode(versionEnvelopeMode, adcpVersion, serverVersion);
|
|
525
528
|
return {
|
|
526
|
-
callTool: (toolName, args, debugLogs) => (0,
|
|
529
|
+
callTool: (toolName, args, debugLogs) => (0, import_responseSizeLimit2.withResponseSizeLimit)(
|
|
527
530
|
transport?.maxResponseBytes,
|
|
528
531
|
() => (0, import_mcp_tasks2.callMCPToolWithTasks)(
|
|
529
532
|
agentUrl,
|
|
@@ -545,7 +548,7 @@ const createA2AClient = (agentUrl, authToken, headers, serverVersion, adcpVersio
|
|
|
545
548
|
transport = normalizeTransportOptions(transport);
|
|
546
549
|
const versionEnvelope = buildVersionEnvelopeForMode(versionEnvelopeMode, adcpVersion, serverVersion);
|
|
547
550
|
return {
|
|
548
|
-
callTool: (toolName, parameters, debugLogs) => (0,
|
|
551
|
+
callTool: (toolName, parameters, debugLogs) => (0, import_responseSizeLimit2.withResponseSizeLimit)(
|
|
549
552
|
transport?.maxResponseBytes,
|
|
550
553
|
() => (0, import_a2a3.callA2ATool)(
|
|
551
554
|
agentUrl,
|
|
@@ -569,9 +572,11 @@ const createA2AClient = (agentUrl, authToken, headers, serverVersion, adcpVersio
|
|
|
569
572
|
// Annotate the CommonJS export names for ESM import in node:
|
|
570
573
|
0 && (module.exports = {
|
|
571
574
|
BODY_SNIPPET_TIMEOUT_MS,
|
|
575
|
+
DEFAULT_MAX_RESPONSE_BYTES,
|
|
572
576
|
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
573
577
|
OBSERVER_FLUSH_TIMEOUT_MS,
|
|
574
578
|
ProtocolClient,
|
|
579
|
+
SSE_CUMULATIVE_BUDGET_MULTIPLIER,
|
|
575
580
|
UnauthorizedError,
|
|
576
581
|
applyVersionEnvelope,
|
|
577
582
|
bundleSupportsAdcpVersionField,
|
|
@@ -31,6 +31,7 @@ async function closeScopedConnections(protocol = "mcp") {
|
|
|
31
31
|
}
|
|
32
32
|
import { callA2ATool, createA2AClientFromCardUrl } from "./a2a.mjs";
|
|
33
33
|
import { DEFAULT_REQUEST_TIMEOUT_MS } from "./abort.mjs";
|
|
34
|
+
import { DEFAULT_MAX_RESPONSE_BYTES, SSE_CUMULATIVE_BUDGET_MULTIPLIER } from "./responseSizeLimit.mjs";
|
|
34
35
|
import {
|
|
35
36
|
callMCPToolWithTasks,
|
|
36
37
|
getMCPTaskStatus,
|
|
@@ -548,9 +549,11 @@ const createA2AClient = (agentUrl, authToken, headers, serverVersion, adcpVersio
|
|
|
548
549
|
};
|
|
549
550
|
export {
|
|
550
551
|
BODY_SNIPPET_TIMEOUT_MS,
|
|
552
|
+
DEFAULT_MAX_RESPONSE_BYTES,
|
|
551
553
|
DEFAULT_REQUEST_TIMEOUT_MS,
|
|
552
554
|
OBSERVER_FLUSH_TIMEOUT_MS,
|
|
553
555
|
ProtocolClient,
|
|
556
|
+
SSE_CUMULATIVE_BUDGET_MULTIPLIER,
|
|
554
557
|
UnauthorizedError,
|
|
555
558
|
applyVersionEnvelope,
|
|
556
559
|
bundleSupportsAdcpVersionField,
|
|
@@ -102,6 +102,7 @@ function connectionCacheKey(agentUrl, headers, signingCacheKey, authProvider, fe
|
|
|
102
102
|
if (fetchKey) parts.push(fetchKey);
|
|
103
103
|
if (handleLegacy !== void 0) parts.push(`handle-legacy:${handleLegacy}`);
|
|
104
104
|
if (allowPrivateIp !== void 0) parts.push(`allow-private-ip:${allowPrivateIp}`);
|
|
105
|
+
parts.push((0, import_responseSizeLimit.responseSizeCapCacheKey)());
|
|
105
106
|
const scopeKey = (0, import_mcp_scope.currentMCPConnectionScopeKey)();
|
|
106
107
|
if (scopeKey) parts.push(scopeKey);
|
|
107
108
|
return parts.join("::");
|
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
} from "./abort.mjs";
|
|
44
44
|
import { wrapFetchWithCapture } from "./rawResponseCapture.mjs";
|
|
45
45
|
import { wrapFetchWithSignedRequestRejection } from "./signedRequestRejection.mjs";
|
|
46
|
-
import { wrapFetchWithSizeLimit } from "./responseSizeLimit.mjs";
|
|
46
|
+
import { responseSizeCapCacheKey, wrapFetchWithSizeLimit } from "./responseSizeLimit.mjs";
|
|
47
47
|
import { wrapFetchWithTransportDiagnostics } from "./transportDiagnostics.mjs";
|
|
48
48
|
import { createAgentTransportFetch } from "../net/agent-transport-fetch.mjs";
|
|
49
49
|
const modernConnections = /* @__PURE__ */ new Map();
|
|
@@ -109,6 +109,7 @@ function connectionCacheKey(agentUrl, headers, signingCacheKey, authProvider, fe
|
|
|
109
109
|
if (fetchKey) parts.push(fetchKey);
|
|
110
110
|
if (handleLegacy !== void 0) parts.push(`handle-legacy:${handleLegacy}`);
|
|
111
111
|
if (allowPrivateIp !== void 0) parts.push(`allow-private-ip:${allowPrivateIp}`);
|
|
112
|
+
parts.push(responseSizeCapCacheKey());
|
|
112
113
|
const scopeKey = currentMCPConnectionScopeKey();
|
|
113
114
|
if (scopeKey) parts.push(scopeKey);
|
|
114
115
|
return parts.join("::");
|
|
@@ -89,6 +89,7 @@ function connectionCacheKey(agentUrl, authToken, signingCacheKey, authHeaders, t
|
|
|
89
89
|
if (signingCacheKey) parts.push(signingCacheKey);
|
|
90
90
|
if (transportFetch) parts.push(`fetch:${getTransportFetchFnDisambiguator(transportFetch)}`);
|
|
91
91
|
if (allowPrivateIp !== void 0) parts.push(`allow-private-ip:${allowPrivateIp}`);
|
|
92
|
+
parts.push((0, import_responseSizeLimit.responseSizeCapCacheKey)());
|
|
92
93
|
const scopeKey = (0, import_mcp_scope.currentMCPConnectionScopeKey)();
|
|
93
94
|
if (scopeKey) parts.push(scopeKey);
|
|
94
95
|
return parts.join("::");
|
|
@@ -19,7 +19,7 @@ import { buildAgentSigningFetch, signingContextStorage } from "../signing/client
|
|
|
19
19
|
import { redactArgsForLog } from "../utils/redact-args.mjs";
|
|
20
20
|
import { wrapFetchWithCapture } from "./rawResponseCapture.mjs";
|
|
21
21
|
import { getSignedRequestRejection, wrapFetchWithSignedRequestRejection } from "./signedRequestRejection.mjs";
|
|
22
|
-
import { wrapFetchWithSizeLimit } from "./responseSizeLimit.mjs";
|
|
22
|
+
import { responseSizeCapCacheKey, wrapFetchWithSizeLimit } from "./responseSizeLimit.mjs";
|
|
23
23
|
import { sanitizeTransportUrl, wrapFetchWithTransportDiagnostics } from "./transportDiagnostics.mjs";
|
|
24
24
|
import {
|
|
25
25
|
isAbortOrTimeoutError,
|
|
@@ -88,6 +88,7 @@ function connectionCacheKey(agentUrl, authToken, signingCacheKey, authHeaders, t
|
|
|
88
88
|
if (signingCacheKey) parts.push(signingCacheKey);
|
|
89
89
|
if (transportFetch) parts.push(`fetch:${getTransportFetchFnDisambiguator(transportFetch)}`);
|
|
90
90
|
if (allowPrivateIp !== void 0) parts.push(`allow-private-ip:${allowPrivateIp}`);
|
|
91
|
+
parts.push(responseSizeCapCacheKey());
|
|
91
92
|
const scopeKey = currentMCPConnectionScopeKey();
|
|
92
93
|
if (scopeKey) parts.push(scopeKey);
|
|
93
94
|
return parts.join("::");
|
|
@@ -1,23 +1,109 @@
|
|
|
1
|
+
import { ResponseTooLargeError } from '../errors/index.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* Default response-body cap, in octets. Applied to every agent response on
|
|
4
|
+
* the MCP and A2A call paths (tool calls, discovery, `tools/list`, agent
|
|
5
|
+
* card) unless the caller overrides it.
|
|
6
|
+
*
|
|
7
|
+
* 4 MiB matches the hosted compliance grader's cap and the SDK's own probe
|
|
8
|
+
* caps. Raise it per client or per call with `transport.maxResponseBytes`
|
|
9
|
+
* when an agent legitimately publishes a large catalog.
|
|
10
|
+
*/
|
|
11
|
+
export declare const DEFAULT_MAX_RESPONSE_BYTES: number;
|
|
12
|
+
/**
|
|
13
|
+
* Multiplier applied to the per-response cap to derive the cumulative
|
|
14
|
+
* byte budget for `text/event-stream` responses in a single call.
|
|
15
|
+
*
|
|
16
|
+
* SSE needs two numbers, not one. The per-event cap is the memory bound —
|
|
17
|
+
* a consumer buffers one event frame before dispatching it, so no single
|
|
18
|
+
* event may exceed `maxResponseBytes`. The cumulative budget is the
|
|
19
|
+
* liveness bound — it stops a peer from holding a connection open and
|
|
20
|
+
* streaming forever in well-formed small frames.
|
|
21
|
+
*
|
|
22
|
+
* Deliberately small. Every multiple is a multiple a hostile peer can claim
|
|
23
|
+
* for free by putting `Content-Type: text/event-stream` on an ordinary reply
|
|
24
|
+
* and inserting a blank line every kilobyte, so the gap between the headline
|
|
25
|
+
* cap and the worst case an adopter can actually suffer should stay narrow.
|
|
26
|
+
* `2` leaves room for a normal tool call (many small status frames plus one
|
|
27
|
+
* result frame) without turning the advertised 4 MiB into 32 MiB.
|
|
28
|
+
*/
|
|
29
|
+
export declare const SSE_CUMULATIVE_BUDGET_MULTIPLIER = 2;
|
|
30
|
+
/**
|
|
31
|
+
* Cumulative `text/event-stream` octets consumed so far. Shared by reference
|
|
32
|
+
* across nested `withResponseSizeLimit` scopes so a call spends one
|
|
33
|
+
* allowance, not one per SDK layer it passes through.
|
|
34
|
+
*/
|
|
35
|
+
interface StreamMeter {
|
|
36
|
+
consumed: number;
|
|
37
|
+
}
|
|
1
38
|
interface SizeLimitSlot {
|
|
2
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Per-response cap in octets, or `null` when the caller explicitly opted
|
|
41
|
+
* out of enforcement for this scope.
|
|
42
|
+
*/
|
|
43
|
+
maxResponseBytes: number | null;
|
|
44
|
+
/**
|
|
45
|
+
* Cumulative SSE allowance for this scope, in octets. Absent when
|
|
46
|
+
* enforcement is disabled.
|
|
47
|
+
*/
|
|
48
|
+
streamLimit?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Shared consumption counter for `text/event-stream` responses. Inherited
|
|
51
|
+
* from the enclosing scope so nesting narrows the allowance but never
|
|
52
|
+
* refills it. Absent when enforcement is disabled.
|
|
53
|
+
*/
|
|
54
|
+
streamMeter?: StreamMeter;
|
|
55
|
+
/**
|
|
56
|
+
* First cap violation observed in this scope. A `TransformStream` error
|
|
57
|
+
* reaches whoever reads the body, and some transports swallow it: the MCP
|
|
58
|
+
* SDK treats a failed SSE read as a stream disconnect, retries, and lets
|
|
59
|
+
* the request time out. Recording the typed error here lets
|
|
60
|
+
* `withResponseSizeLimit` re-surface it instead of handing the caller an
|
|
61
|
+
* unrelated timeout. See `rethrowRecordedViolation`.
|
|
62
|
+
*/
|
|
63
|
+
violation?: ResponseTooLargeError;
|
|
3
64
|
}
|
|
4
65
|
export declare const responseSizeLimitStorage: import("async_hooks").AsyncLocalStorage<SizeLimitSlot>;
|
|
5
66
|
/**
|
|
6
67
|
* Run `fn` with a response body byte cap active. Every fetch made through
|
|
7
68
|
* a wrapped transport inside `fn` aborts with `ResponseTooLargeError` when
|
|
8
|
-
* the response body exceeds
|
|
9
|
-
*
|
|
69
|
+
* the response body exceeds the cap.
|
|
70
|
+
*
|
|
71
|
+
* Passing `undefined` installs `DEFAULT_MAX_RESPONSE_BYTES` — unless an
|
|
72
|
+
* enclosing scope already set one, in which case the enclosing cap and its
|
|
73
|
+
* cumulative SSE allowance are inherited. More generally, **nesting only ever
|
|
74
|
+
* tightens**: both numbers take the minimum of the enclosing scope's and the
|
|
75
|
+
* new one's, and the cumulative SSE meter is shared rather than refilled.
|
|
76
|
+
*
|
|
77
|
+
* Nested SDK layers re-enter this helper on the way down (`callTool` →
|
|
78
|
+
* `callMCPToolWithTasks`), normally with the same value. Narrow-only means an
|
|
79
|
+
* inner layer that wasn't handed the caller's `transport` can't widen a tight
|
|
80
|
+
* outer cap back to the default, and re-entering with the same number can't
|
|
81
|
+
* multiply the cumulative allowance by the nesting depth. Per-call vs
|
|
82
|
+
* constructor `transport` is resolved with `??` before any scope is entered,
|
|
83
|
+
* so a per-call override still beats the constructor exactly as documented.
|
|
84
|
+
*
|
|
85
|
+
* Passing exactly `0` is the explicit opt-out: the slot is entered with
|
|
86
|
+
* enforcement disabled so an enclosing scope's cap (and the default the fetch
|
|
87
|
+
* wrapper would otherwise apply) is suppressed for the duration. `Infinity`
|
|
88
|
+
* and negative numbers are *not* opt-outs — they fall back to the default.
|
|
10
89
|
*/
|
|
11
90
|
export declare function withResponseSizeLimit<T>(maxResponseBytes: number | undefined, fn: () => Promise<T>): Promise<T>;
|
|
12
91
|
/**
|
|
13
|
-
* Wrap a fetch implementation so its responses honor the size cap
|
|
14
|
-
*
|
|
92
|
+
* Wrap a fetch implementation so its responses honor the response size cap.
|
|
93
|
+
*
|
|
94
|
+
* Uses the active `responseSizeLimitStorage` slot when there is one, and
|
|
95
|
+
* `DEFAULT_MAX_RESPONSE_BYTES` when there isn't — the cap is on by default,
|
|
96
|
+
* not conditional on a caller having opened a scope. Only an explicit
|
|
97
|
+
* opt-out slot (`withResponseSizeLimit(0, …)`) disables it.
|
|
15
98
|
*
|
|
16
99
|
* Pre-checks `Content-Length` and refuses to read the body when the declared
|
|
17
100
|
* size already exceeds the cap. Otherwise wraps `Response.body` in a
|
|
18
101
|
* `TransformStream` that counts bytes and errors with `ResponseTooLargeError`
|
|
19
102
|
* at the cap boundary, propagating to anyone reading the response (including
|
|
20
103
|
* `Response.clone()` branches used by the diagnostic capture wrapper).
|
|
104
|
+
*
|
|
105
|
+
* `text/event-stream` responses are enforced too, with a per-event cap and a
|
|
106
|
+
* cumulative per-call budget — see `enforceEventStreamLimit`.
|
|
21
107
|
*/
|
|
22
108
|
export declare function wrapFetchWithSizeLimit(upstream: typeof fetch): typeof fetch;
|
|
23
109
|
export {};
|
|
@@ -1,23 +1,109 @@
|
|
|
1
|
+
import { ResponseTooLargeError } from '../errors';
|
|
2
|
+
/**
|
|
3
|
+
* Default response-body cap, in octets. Applied to every agent response on
|
|
4
|
+
* the MCP and A2A call paths (tool calls, discovery, `tools/list`, agent
|
|
5
|
+
* card) unless the caller overrides it.
|
|
6
|
+
*
|
|
7
|
+
* 4 MiB matches the hosted compliance grader's cap and the SDK's own probe
|
|
8
|
+
* caps. Raise it per client or per call with `transport.maxResponseBytes`
|
|
9
|
+
* when an agent legitimately publishes a large catalog.
|
|
10
|
+
*/
|
|
11
|
+
export declare const DEFAULT_MAX_RESPONSE_BYTES: number;
|
|
12
|
+
/**
|
|
13
|
+
* Multiplier applied to the per-response cap to derive the cumulative
|
|
14
|
+
* byte budget for `text/event-stream` responses in a single call.
|
|
15
|
+
*
|
|
16
|
+
* SSE needs two numbers, not one. The per-event cap is the memory bound —
|
|
17
|
+
* a consumer buffers one event frame before dispatching it, so no single
|
|
18
|
+
* event may exceed `maxResponseBytes`. The cumulative budget is the
|
|
19
|
+
* liveness bound — it stops a peer from holding a connection open and
|
|
20
|
+
* streaming forever in well-formed small frames.
|
|
21
|
+
*
|
|
22
|
+
* Deliberately small. Every multiple is a multiple a hostile peer can claim
|
|
23
|
+
* for free by putting `Content-Type: text/event-stream` on an ordinary reply
|
|
24
|
+
* and inserting a blank line every kilobyte, so the gap between the headline
|
|
25
|
+
* cap and the worst case an adopter can actually suffer should stay narrow.
|
|
26
|
+
* `2` leaves room for a normal tool call (many small status frames plus one
|
|
27
|
+
* result frame) without turning the advertised 4 MiB into 32 MiB.
|
|
28
|
+
*/
|
|
29
|
+
export declare const SSE_CUMULATIVE_BUDGET_MULTIPLIER = 2;
|
|
30
|
+
/**
|
|
31
|
+
* Cumulative `text/event-stream` octets consumed so far. Shared by reference
|
|
32
|
+
* across nested `withResponseSizeLimit` scopes so a call spends one
|
|
33
|
+
* allowance, not one per SDK layer it passes through.
|
|
34
|
+
*/
|
|
35
|
+
interface StreamMeter {
|
|
36
|
+
consumed: number;
|
|
37
|
+
}
|
|
1
38
|
interface SizeLimitSlot {
|
|
2
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Per-response cap in octets, or `null` when the caller explicitly opted
|
|
41
|
+
* out of enforcement for this scope.
|
|
42
|
+
*/
|
|
43
|
+
maxResponseBytes: number | null;
|
|
44
|
+
/**
|
|
45
|
+
* Cumulative SSE allowance for this scope, in octets. Absent when
|
|
46
|
+
* enforcement is disabled.
|
|
47
|
+
*/
|
|
48
|
+
streamLimit?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Shared consumption counter for `text/event-stream` responses. Inherited
|
|
51
|
+
* from the enclosing scope so nesting narrows the allowance but never
|
|
52
|
+
* refills it. Absent when enforcement is disabled.
|
|
53
|
+
*/
|
|
54
|
+
streamMeter?: StreamMeter;
|
|
55
|
+
/**
|
|
56
|
+
* First cap violation observed in this scope. A `TransformStream` error
|
|
57
|
+
* reaches whoever reads the body, and some transports swallow it: the MCP
|
|
58
|
+
* SDK treats a failed SSE read as a stream disconnect, retries, and lets
|
|
59
|
+
* the request time out. Recording the typed error here lets
|
|
60
|
+
* `withResponseSizeLimit` re-surface it instead of handing the caller an
|
|
61
|
+
* unrelated timeout. See `rethrowRecordedViolation`.
|
|
62
|
+
*/
|
|
63
|
+
violation?: ResponseTooLargeError;
|
|
3
64
|
}
|
|
4
65
|
export declare const responseSizeLimitStorage: import("async_hooks").AsyncLocalStorage<SizeLimitSlot>;
|
|
5
66
|
/**
|
|
6
67
|
* Run `fn` with a response body byte cap active. Every fetch made through
|
|
7
68
|
* a wrapped transport inside `fn` aborts with `ResponseTooLargeError` when
|
|
8
|
-
* the response body exceeds
|
|
9
|
-
*
|
|
69
|
+
* the response body exceeds the cap.
|
|
70
|
+
*
|
|
71
|
+
* Passing `undefined` installs `DEFAULT_MAX_RESPONSE_BYTES` — unless an
|
|
72
|
+
* enclosing scope already set one, in which case the enclosing cap and its
|
|
73
|
+
* cumulative SSE allowance are inherited. More generally, **nesting only ever
|
|
74
|
+
* tightens**: both numbers take the minimum of the enclosing scope's and the
|
|
75
|
+
* new one's, and the cumulative SSE meter is shared rather than refilled.
|
|
76
|
+
*
|
|
77
|
+
* Nested SDK layers re-enter this helper on the way down (`callTool` →
|
|
78
|
+
* `callMCPToolWithTasks`), normally with the same value. Narrow-only means an
|
|
79
|
+
* inner layer that wasn't handed the caller's `transport` can't widen a tight
|
|
80
|
+
* outer cap back to the default, and re-entering with the same number can't
|
|
81
|
+
* multiply the cumulative allowance by the nesting depth. Per-call vs
|
|
82
|
+
* constructor `transport` is resolved with `??` before any scope is entered,
|
|
83
|
+
* so a per-call override still beats the constructor exactly as documented.
|
|
84
|
+
*
|
|
85
|
+
* Passing exactly `0` is the explicit opt-out: the slot is entered with
|
|
86
|
+
* enforcement disabled so an enclosing scope's cap (and the default the fetch
|
|
87
|
+
* wrapper would otherwise apply) is suppressed for the duration. `Infinity`
|
|
88
|
+
* and negative numbers are *not* opt-outs — they fall back to the default.
|
|
10
89
|
*/
|
|
11
90
|
export declare function withResponseSizeLimit<T>(maxResponseBytes: number | undefined, fn: () => Promise<T>): Promise<T>;
|
|
12
91
|
/**
|
|
13
|
-
* Wrap a fetch implementation so its responses honor the size cap
|
|
14
|
-
*
|
|
92
|
+
* Wrap a fetch implementation so its responses honor the response size cap.
|
|
93
|
+
*
|
|
94
|
+
* Uses the active `responseSizeLimitStorage` slot when there is one, and
|
|
95
|
+
* `DEFAULT_MAX_RESPONSE_BYTES` when there isn't — the cap is on by default,
|
|
96
|
+
* not conditional on a caller having opened a scope. Only an explicit
|
|
97
|
+
* opt-out slot (`withResponseSizeLimit(0, …)`) disables it.
|
|
15
98
|
*
|
|
16
99
|
* Pre-checks `Content-Length` and refuses to read the body when the declared
|
|
17
100
|
* size already exceeds the cap. Otherwise wraps `Response.body` in a
|
|
18
101
|
* `TransformStream` that counts bytes and errors with `ResponseTooLargeError`
|
|
19
102
|
* at the cap boundary, propagating to anyone reading the response (including
|
|
20
103
|
* `Response.clone()` branches used by the diagnostic capture wrapper).
|
|
104
|
+
*
|
|
105
|
+
* `text/event-stream` responses are enforced too, with a per-event cap and a
|
|
106
|
+
* cumulative per-call budget — see `enforceEventStreamLimit`.
|
|
21
107
|
*/
|
|
22
108
|
export declare function wrapFetchWithSizeLimit(upstream: typeof fetch): typeof fetch;
|
|
23
109
|
export {};
|