@combycode/llm-sdk 1.6.1 → 2.0.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/CHANGELOG.md +518 -1
- package/MIGRATION.md +93 -0
- package/README.md +17 -2
- package/dist/agent/loop-config.d.ts +22 -0
- package/dist/agent/loop-step-state.d.ts +2 -0
- package/dist/agent/loop.d.ts +7 -0
- package/dist/agent/reflect-retry.d.ts +56 -0
- package/dist/agent/tool-key.d.ts +3 -0
- package/dist/bus/hook-map.d.ts +11 -0
- package/dist/helpers/mcp.d.ts +24 -2
- package/dist/helpers/provenance-types.d.ts +63 -0
- package/dist/helpers/provenance.d.ts +12 -0
- package/dist/helpers/transcribe.d.ts +35 -6
- package/dist/index.browser.js +3134 -731
- package/dist/index.d.ts +19 -7
- package/dist/index.js +3134 -731
- package/dist/llm/moderation/native.d.ts +5 -4
- package/dist/llm/providers/anthropic/constants.d.ts +2 -0
- package/dist/llm/providers/google/constants.d.ts +17 -2
- package/dist/llm/providers/openai/completions.d.ts +18 -2
- package/dist/llm/providers/openai/provenance.d.ts +26 -0
- package/dist/llm/providers/openai/responses.d.ts +4 -2
- package/dist/llm/providers/openai/transcription.d.ts +39 -2
- package/dist/llm/providers/xai/completions.d.ts +2 -2
- package/dist/llm/providers/xai/media.d.ts +8 -0
- package/dist/llm/types/audio.d.ts +31 -0
- package/dist/llm/types/messages.d.ts +89 -1
- package/dist/llm/types/options.d.ts +15 -0
- package/dist/llm/types/request.d.ts +21 -1
- package/dist/llm/types/response.d.ts +31 -1
- package/dist/llm/types/stream.d.ts +14 -1
- package/dist/llm/types/tiers.d.ts +6 -6
- package/dist/llm/types/tools.d.ts +10 -1
- package/dist/network/queue-state-config.d.ts +5 -0
- package/dist/network/queue-state.d.ts +7 -0
- package/dist/network/types.d.ts +23 -0
- package/dist/plugins/context-guard/strategies/anchored.d.ts +47 -0
- package/dist/plugins/context-measurer/counter/hybrid.d.ts +4 -1
- package/dist/plugins/context-measurer/counter/tiktoken.d.ts +8 -1
- package/dist/plugins/mcp/base-transport.d.ts +16 -0
- package/dist/plugins/mcp/client.d.ts +144 -7
- package/dist/plugins/mcp/input-required.d.ts +35 -0
- package/dist/plugins/mcp/jsonrpc.d.ts +7 -0
- package/dist/plugins/mcp/oauth.d.ts +21 -1
- package/dist/plugins/mcp/protocol-version.d.ts +61 -0
- package/dist/plugins/mcp/result-cache.d.ts +31 -0
- package/dist/plugins/mcp/subscriptions.d.ts +69 -0
- package/dist/plugins/mcp/transport-http.d.ts +31 -0
- package/dist/plugins/mcp/transport-stdio.d.ts +2 -0
- package/dist/plugins/mcp/transport-ws.d.ts +11 -1
- package/dist/plugins/mcp/transport.d.ts +11 -0
- package/dist/plugins/mcp/types.d.ts +54 -2
- package/dist/plugins/media/source-image.d.ts +9 -0
- package/dist/plugins/media/types.d.ts +21 -0
- package/dist/plugins/model-catalog/catalog.d.ts +3 -0
- package/dist/plugins/telemetry/telemetry.d.ts +12 -0
- package/dist/util/http.d.ts +8 -0
- package/package.json +9 -6
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** AnchoredStrategy — one growing scratchpad instead of a chain of summaries.
|
|
2
|
+
*
|
|
3
|
+
* `LayeredStrategy` emits a NEW summary each time it compacts, so a long conversation accumulates
|
|
4
|
+
* summaries-of-summaries: the oldest facts get re-summarised repeatedly and drift further from
|
|
5
|
+
* what was actually said. This strategy keeps a SINGLE anchor entry at the head of history and
|
|
6
|
+
* merges each compaction into it — the anchor grows, but every fact is summarised from the raw
|
|
7
|
+
* text exactly once.
|
|
8
|
+
*
|
|
9
|
+
* The trade is real and worth stating: one anchor means one blast radius. A bad merge corrupts the
|
|
10
|
+
* whole record, where a chain of summaries only corrupts one link. Anchored suits long-running
|
|
11
|
+
* task/state tracking ("what have we established so far"); layered suits conversations where
|
|
12
|
+
* recency matters more than a durable state.
|
|
13
|
+
*
|
|
14
|
+
* Ported from google-adk-ts `AnchoredContextCompactor` (adk 1.5), including its refusal to split a
|
|
15
|
+
* tool call from its result. */
|
|
16
|
+
import type { HistoryEntry } from '../../../agent/history-types';
|
|
17
|
+
import type { ContextStrategy, ReactContext, StrategyDecision, TriggerLevel } from '../types';
|
|
18
|
+
export interface AnchoredStrategyConfig {
|
|
19
|
+
/** Raw entries to keep verbatim at the tail. */
|
|
20
|
+
keepRecent?: number;
|
|
21
|
+
/** Cap on the anchor's own length, so the thing that replaces history cannot become history. */
|
|
22
|
+
anchorMaxChars?: number;
|
|
23
|
+
triggers?: TriggerLevel[];
|
|
24
|
+
/** Above this usage ratio, report `decline` — compaction alone will not save the request. */
|
|
25
|
+
declineCeiling?: number;
|
|
26
|
+
}
|
|
27
|
+
/** Marks the anchor entry so it can be found again on the next compaction. Kept in the text rather
|
|
28
|
+
* than in metadata because the anchor has to survive an export/import round trip of history. */
|
|
29
|
+
export declare const ANCHOR_MARKER = "[context-anchor]";
|
|
30
|
+
/** Where the retained tail starts, refusing to split a tool call from its result.
|
|
31
|
+
*
|
|
32
|
+
* Cutting between them leaves a `tool_result` whose call is gone, which several providers reject
|
|
33
|
+
* outright and the rest silently misread. Walking the boundary backwards keeps the pair together.
|
|
34
|
+
* Direct port of adk's `calculateRetainStartIndex`. */
|
|
35
|
+
export declare function calculateRetainStartIndex(entries: readonly HistoryEntry[], keepRecent: number): number;
|
|
36
|
+
export declare class AnchoredStrategy implements ContextStrategy {
|
|
37
|
+
readonly name: "anchored";
|
|
38
|
+
readonly triggers: TriggerLevel[];
|
|
39
|
+
private readonly keepRecent;
|
|
40
|
+
private readonly anchorMaxChars;
|
|
41
|
+
private readonly declineCeiling;
|
|
42
|
+
constructor(config?: AnchoredStrategyConfig);
|
|
43
|
+
react(ctx: ReactContext): Promise<StrategyDecision>;
|
|
44
|
+
}
|
|
45
|
+
/** Fold a new summary into the anchor, bounded so the anchor cannot itself become the problem.
|
|
46
|
+
* When trimming is needed the NEWER text is kept — it already subsumes the older state. */
|
|
47
|
+
export declare function mergeAnchor(previous: string, addition: string, maxChars: number): string;
|
|
@@ -19,7 +19,10 @@ export interface HybridCounterConfig {
|
|
|
19
19
|
*/
|
|
20
20
|
export declare class HybridTokenCounter implements TokenCounter {
|
|
21
21
|
private heuristic;
|
|
22
|
-
|
|
22
|
+
/** Built on first use, not in the constructor — most consumers never route to the tiktoken
|
|
23
|
+
* strategy, and the optional peer dependency should not be reached for merely constructing a
|
|
24
|
+
* counter. See CONSTITUTION.md standing decisions (2026-08-08). */
|
|
25
|
+
private _tiktoken?;
|
|
23
26
|
private countApi;
|
|
24
27
|
private readonly _config;
|
|
25
28
|
constructor(config: HybridCounterConfig);
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
/** Tiktoken adapter — exact tokenization for OpenAI models.
|
|
1
|
+
/** Tiktoken adapter — exact tokenization for OpenAI models.
|
|
2
|
+
*
|
|
3
|
+
* `tiktoken` is an OPTIONAL PEER dependency: it is not installed unless the consumer asks for it,
|
|
4
|
+
* and nothing here runs until local token counting is actually used. See the standing decisions in
|
|
5
|
+
* CONSTITUTION.md (2026-08-08).
|
|
6
|
+
*/
|
|
2
7
|
import type { Message } from '../../../llm/types/messages';
|
|
3
8
|
import type { TokenCountContext, TokenCounter, LearnInput } from '../../../agent/types';
|
|
9
|
+
/** Build the error thrown when the optional peer is missing. Exported for tests; not public API. */
|
|
10
|
+
export declare function tiktokenUnavailableError(cause: unknown): Error;
|
|
4
11
|
export declare class TiktokenCounter implements TokenCounter {
|
|
5
12
|
private encodings;
|
|
6
13
|
estimate(text: string, ctx?: TokenCountContext): number;
|
|
@@ -39,11 +39,27 @@ export declare abstract class BaseJsonRpcTransport {
|
|
|
39
39
|
protected nextId: number;
|
|
40
40
|
protected handlers: IncomingMcpHandlers;
|
|
41
41
|
protected readonly pending: Map<number, Pending>;
|
|
42
|
+
/** Long-lived requests (`subscriptions/listen`) awaiting their end-of-stream response. Kept apart
|
|
43
|
+
* from `pending` because these must NOT time out. */
|
|
44
|
+
protected readonly longLived: Map<number, ((error?: unknown) => void) | undefined>;
|
|
42
45
|
setHandlers(handlers: IncomingMcpHandlers): void;
|
|
43
46
|
/** Write a serialised JSON-RPC object back to the peer. */
|
|
44
47
|
protected abstract sendMessage(obj: unknown): void | Promise<void>;
|
|
45
48
|
/** Allocate the next monotonic request id. */
|
|
46
49
|
protected allocateId(): number;
|
|
50
|
+
/** Send a request that is NOT expected to answer promptly, and return its id.
|
|
51
|
+
*
|
|
52
|
+
* `subscriptions/listen` (2026-07-28) is long-lived by design: the response arrives only when
|
|
53
|
+
* the server tears the subscription down, while notifications flow in the meantime. Routing it
|
|
54
|
+
* through `request()` would arm the normal timeout and kill a perfectly healthy subscription, so
|
|
55
|
+
* no pending entry is registered — the eventual response is dropped, its only meaning being
|
|
56
|
+
* "the stream ended".
|
|
57
|
+
*
|
|
58
|
+
* The caller correlates frames itself via the returned id. Duplex transports (stdio, WebSocket)
|
|
59
|
+
* support this; a request/response transport must override and reject. */
|
|
60
|
+
sendLongLivedRequest(method: string, params?: unknown, onEnd?: (error?: unknown) => void): Promise<number>;
|
|
61
|
+
/** Settle a long-lived request from its (late) response. Returns whether one was waiting. */
|
|
62
|
+
protected resolveLongLived(id: number | string, error?: unknown): boolean;
|
|
47
63
|
/** Register a pending request and arm its timeout. */
|
|
48
64
|
protected registerPending(id: number, resolve: (v: unknown) => void, reject: (e: unknown) => void, timeoutMs: number, method: string): void;
|
|
49
65
|
/** Dispatch a parsed inbound message to the correct handler. */
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
import type { HookBus } from '../../bus/hook-bus';
|
|
6
6
|
import type { McpTransport } from './transport';
|
|
7
7
|
import type { TraceContext } from '../../network/types';
|
|
8
|
-
import
|
|
8
|
+
import { type McpEra } from './protocol-version';
|
|
9
|
+
import { McpSubscription, type McpServerEvent, type McpSubscriptionFilter } from './subscriptions';
|
|
10
|
+
import type { McpCallResult, McpCompletionRef, McpCompletionResult, McpDiscoverResult, McpGetPromptResult, McpInitializeResult, McpLogLevel, McpPrompt, McpResource, McpResourceContent, McpResourceTemplate, McpTask, McpTaskMetadata, McpToolDef } from './types';
|
|
9
11
|
export interface McpClientOptions {
|
|
10
12
|
clientInfo?: {
|
|
11
13
|
name: string;
|
|
@@ -29,23 +31,92 @@ export interface McpClientOptions {
|
|
|
29
31
|
hooks: HookBus;
|
|
30
32
|
server: string;
|
|
31
33
|
};
|
|
32
|
-
/** Send a `ping` every N ms to keep the connection alive (0/undefined = off).
|
|
34
|
+
/** Send a `ping` every N ms to keep the connection alive (0/undefined = off).
|
|
35
|
+
* Ignored on a 2026-07-28 session, where `ping` no longer exists. */
|
|
33
36
|
keepAliveMs?: number;
|
|
37
|
+
/** How to negotiate the protocol revision.
|
|
38
|
+
*
|
|
39
|
+
* - `'auto'` (default) — probe `server/discover`; anything that is not positive evidence of a
|
|
40
|
+
* modern server falls back to the `initialize` handshake.
|
|
41
|
+
* - `'legacy'` — skip the probe and run the handshake, byte-identical to pre-2.0 behaviour.
|
|
42
|
+
* Use this for a server that mishandles unknown methods.
|
|
43
|
+
* - a version string (e.g. `'2026-07-28'`) — adopt that revision directly, no probe.
|
|
44
|
+
*
|
|
45
|
+
* Fallback is a DENYLIST: every JSON-RPC error falls back to the handshake except a
|
|
46
|
+
* `-32022` naming only modern versions we do not share. Transport/network errors are never
|
|
47
|
+
* treated as an era verdict — an outage must not silently downgrade the wire. */
|
|
48
|
+
protocolMode?: 'auto' | 'legacy' | (string & {});
|
|
49
|
+
/** Cap on `input_required` retry rounds before giving up (default 10, matching every other SDK).
|
|
50
|
+
* A handler that never satisfies the server would otherwise loop forever. */
|
|
51
|
+
inputRequiredMaxRounds?: number;
|
|
52
|
+
/** Honour the server's `ttlMs` / `cacheScope` hints on list and read results (2026-07-28).
|
|
53
|
+
*
|
|
54
|
+
* **Off by default.** Caching changes when a caller observes a server-side change, which is the
|
|
55
|
+
* caller's call to make. A server that sends no hints caches nothing either way, so this is a
|
|
56
|
+
* no-op against every pre-2026 server. Cache entries are dropped automatically on the matching
|
|
57
|
+
* `*_changed` notification. */
|
|
58
|
+
cacheResults?: boolean;
|
|
34
59
|
}
|
|
35
60
|
export declare class McpClient {
|
|
36
61
|
private readonly transport;
|
|
37
62
|
private readonly opts;
|
|
38
63
|
private serverInfo;
|
|
39
64
|
private pingTimer;
|
|
65
|
+
private negotiatedVersion;
|
|
66
|
+
private discovery;
|
|
67
|
+
private readonly cache;
|
|
68
|
+
private readonly subscriptions;
|
|
40
69
|
constructor(transport: McpTransport, opts?: McpClientOptions);
|
|
41
|
-
/**
|
|
70
|
+
/** Drop cached list/read results. Called automatically on the matching `*_changed`
|
|
71
|
+
* notification; exposed because a caller may know the server moved before it says so. */
|
|
72
|
+
invalidateCache(method?: string): void;
|
|
73
|
+
/** The server's `initialize` result, or null before `connect()`.
|
|
74
|
+
*
|
|
75
|
+
* On a 2026-07-28 session there is no `initialize`, so this is SYNTHESISED from the
|
|
76
|
+
* `server/discover` result (capabilities, instructions, and the `_meta` serverInfo stamp).
|
|
77
|
+
* Deliberate: the shape a caller reads must not depend on which wire was negotiated
|
|
78
|
+
* (CONSTITUTION.md R2 — absorb the difference, never expose a union). */
|
|
42
79
|
get info(): McpInitializeResult | null;
|
|
80
|
+
/** The revision actually negotiated. Defaults to the handshake-era version until `connect()`. */
|
|
81
|
+
get protocolVersion(): string;
|
|
82
|
+
/** Which wire this session speaks. `'handshake'` for 2025-11-25 and earlier. */
|
|
83
|
+
get era(): McpEra;
|
|
84
|
+
/** The raw `server/discover` result on a modern session; `null` on a handshake session.
|
|
85
|
+
* Carries the fields that have no handshake equivalent (`supportedVersions`, `ttlMs`,
|
|
86
|
+
* `cacheScope`). */
|
|
87
|
+
get discoverResult(): McpDiscoverResult | null;
|
|
43
88
|
/** Open the transport, run the initialize handshake, and start listening for
|
|
44
89
|
* server-initiated messages. */
|
|
45
90
|
connect(): Promise<McpInitializeResult>;
|
|
91
|
+
/** The pre-2026 path, unchanged: `initialize` + `notifications/initialized`. */
|
|
92
|
+
private handshake;
|
|
93
|
+
/** One `server/discover` at `version`. No retry, no adoption — the caller decides.
|
|
94
|
+
*
|
|
95
|
+
* The transport is told the version FIRST because on HTTP it is not just bookkeeping:
|
|
96
|
+
* a modern server routes by the `MCP-Protocol-Version` header, so a probe sent without
|
|
97
|
+
* it lands on the legacy handler and is rejected with `400 Missing session ID` — the
|
|
98
|
+
* connection fails outright instead of negotiating. Invisible over stdio, which has no
|
|
99
|
+
* headers; found against mcp-py 2.0.0 Streamable HTTP (2026-08-09). */
|
|
100
|
+
private sendDiscover;
|
|
101
|
+
/** Install a discover result as the live session. */
|
|
102
|
+
private adoptModern;
|
|
103
|
+
/** `mode: 'auto'` — probe modern, fall back to the handshake on anything that is not positive
|
|
104
|
+
* evidence of a modern server.
|
|
105
|
+
*
|
|
106
|
+
* The fallback is a DENYLIST, matching mcp-py 2.0.0 `client/_probe.py`: every JSON-RPC error
|
|
107
|
+
* falls back EXCEPT a `-32022` whose `supported` list is modern-only and disjoint from ours —
|
|
108
|
+
* that one is a real incompatibility and must surface. Transport/network failures are rethrown
|
|
109
|
+
* untouched: an outage is not an era verdict, and silently downgrading the wire because a
|
|
110
|
+
* socket blipped would be the worst possible failure mode. */
|
|
111
|
+
private negotiateAuto;
|
|
46
112
|
/** List every tool the server exposes (follows cursor pagination). */
|
|
47
113
|
listTools(): Promise<McpToolDef[]>;
|
|
48
|
-
/** Follow cursor pagination for a list method, collecting `field` from each page.
|
|
114
|
+
/** Follow cursor pagination for a list method, collecting `field` from each page.
|
|
115
|
+
*
|
|
116
|
+
* When result caching is enabled and the server sent a `ttlMs`, the assembled list is reused
|
|
117
|
+
* until it expires. A paginated list is only as fresh as its shortest-lived page, so the
|
|
118
|
+
* effective TTL is the MINIMUM across pages — taking the last page's value would let an early,
|
|
119
|
+
* more volatile page go stale unnoticed. */
|
|
49
120
|
private paginate;
|
|
50
121
|
/** Invoke a tool by its (un-namespaced) server name.
|
|
51
122
|
* Pass `trace` when the call originates from an AgentLoop run so that
|
|
@@ -57,14 +128,35 @@ export declare class McpClient {
|
|
|
57
128
|
listResourceTemplates(): Promise<McpResourceTemplate[]>;
|
|
58
129
|
/** Read a resource's contents by URI. */
|
|
59
130
|
readResource(uri: string): Promise<McpResourceContent[]>;
|
|
60
|
-
/** Subscribe to updates for a resource (server sends `notifications/resources/updated`).
|
|
131
|
+
/** Subscribe to updates for a resource (server sends `notifications/resources/updated`).
|
|
132
|
+
*
|
|
133
|
+
* Handshake era only — 2026-07-28 replaces per-resource subscription with the single
|
|
134
|
+
* `subscriptions/listen` stream. */
|
|
61
135
|
subscribeResource(uri: string): Promise<void>;
|
|
62
136
|
unsubscribeResource(uri: string): Promise<void>;
|
|
137
|
+
/** Open a `subscriptions/listen` stream (2026-07-28) — the single channel that replaces
|
|
138
|
+
* `resources/subscribe` and the standalone notification stream.
|
|
139
|
+
*
|
|
140
|
+
* Every kind is **opt-in**: the server may not send what was not requested, and it acknowledges
|
|
141
|
+
* with the subset it actually honoured — which can be narrower than what was asked for. Check
|
|
142
|
+
* `subscription.honored` / `isHonored(kind)` rather than assuming the request was granted.
|
|
143
|
+
*
|
|
144
|
+
* Events are level triggers ("this changed, re-fetch if you care"), so they carry no payload
|
|
145
|
+
* beyond the change itself. When result caching is on, the matching entries are dropped before
|
|
146
|
+
* `onEvent` runs, so a handler that immediately re-lists sees fresh data.
|
|
147
|
+
*
|
|
148
|
+
* Requires a modern session and a duplex transport (stdio / WebSocket) — see
|
|
149
|
+
* `sendLongLivedRequest` on the HTTP transport for why. */
|
|
150
|
+
listen(filter: McpSubscriptionFilter, onEvent: (event: McpServerEvent) => void): Promise<McpSubscription>;
|
|
63
151
|
/** List the server's prompts (follows cursor pagination). */
|
|
64
152
|
listPrompts(): Promise<McpPrompt[]>;
|
|
65
153
|
/** Render a prompt by name with arguments → its messages. */
|
|
66
154
|
getPrompt(name: string, args?: Record<string, string>): Promise<McpGetPromptResult>;
|
|
67
|
-
/** Set the server's log verbosity (it then sends `notifications/message`).
|
|
155
|
+
/** Set the server's log verbosity (it then sends `notifications/message`).
|
|
156
|
+
*
|
|
157
|
+
* Handshake era only — `logging/setLevel` was removed at 2026-07-28, where verbosity rides in
|
|
158
|
+
* each request's `_meta` instead. Throws rather than silently no-opping: a caller who asked for
|
|
159
|
+
* debug logging and got none would have no way to tell. */
|
|
68
160
|
setLogLevel(level: McpLogLevel): Promise<void>;
|
|
69
161
|
/** Argument autocompletion for a prompt or resource template. */
|
|
70
162
|
completeArgument(ref: McpCompletionRef, argument: {
|
|
@@ -85,8 +177,53 @@ export declare class McpClient {
|
|
|
85
177
|
pollIntervalMs?: number;
|
|
86
178
|
timeoutMs?: number;
|
|
87
179
|
}): Promise<McpTask>;
|
|
88
|
-
/** Low-level escape hatch: send any request method.
|
|
180
|
+
/** Low-level escape hatch: send any request method. Carries the modern-era identity
|
|
181
|
+
* envelope like every other request. */
|
|
89
182
|
request(method: string, params?: unknown): Promise<unknown>;
|
|
183
|
+
/** Every request goes through here.
|
|
184
|
+
*
|
|
185
|
+
* At 2026-07-28 there is no handshake and no session id, so each request states its
|
|
186
|
+
* own identity: `_meta` MUST carry the protocol version and the client capabilities
|
|
187
|
+
* (client info is optional). Only `server/discover` used to build that envelope, so
|
|
188
|
+
* every later call on a modern session was rejected:
|
|
189
|
+
*
|
|
190
|
+
* -32602 params._meta must be an object carrying the required
|
|
191
|
+
* 'io.modelcontextprotocol/protocolVersion' and
|
|
192
|
+
* 'io.modelcontextprotocol/clientCapabilities' envelope keys
|
|
193
|
+
*
|
|
194
|
+
* Unreachable without a real 2026-07-28 server — no public one exists yet; found by
|
|
195
|
+
* running mcp-py 2.0.0 over stdio (2026-08-09).
|
|
196
|
+
*
|
|
197
|
+
* Handshake-era sessions are untouched: identity lives in `initialize` there, and an
|
|
198
|
+
* unexpected `_meta` is exactly the sort of thing an older server can reject. */
|
|
199
|
+
private send;
|
|
200
|
+
/** Stamp the modern identity envelope onto a request's params.
|
|
201
|
+
*
|
|
202
|
+
* Shared by `send()` and the long-lived `subscriptions/listen`, because EVERY request
|
|
203
|
+
* needs it — and the long-lived path is the one where a missing envelope hides: the
|
|
204
|
+
* rejection arrives as the stream's end rather than a thrown error, so `listen()`
|
|
205
|
+
* returned a subscription that looked alive and delivered nothing. */
|
|
206
|
+
private withEnvelope;
|
|
90
207
|
close(): Promise<void>;
|
|
208
|
+
/** Resolve an `input_required` result to a terminal one.
|
|
209
|
+
*
|
|
210
|
+
* The dispatcher is `handleServerRequest` — the SAME path that serves a handshake-era server
|
|
211
|
+
* pushing `sampling/createMessage` at us. That is the whole point of routing MRTR through here:
|
|
212
|
+
* a caller wires up sampling once and it works on either wire, without knowing which is in play.
|
|
213
|
+
*
|
|
214
|
+
* Skips instantly when `resultType` is absent or `'complete'`, so the handshake path pays
|
|
215
|
+
* nothing. */
|
|
216
|
+
private driveInputRequired;
|
|
217
|
+
/** Guard a method the 2026-07-28 revision removed. Naming the negotiated version and the
|
|
218
|
+
* replacement matters: without it the caller sees a bare -32601 from the server and has no way
|
|
219
|
+
* to know the method existed until the session happened to negotiate modern. */
|
|
220
|
+
private requireHandshakeEra;
|
|
221
|
+
/** Map a change notification onto the cache entries it invalidates. Same vocabulary on both
|
|
222
|
+
* eras — these methods ride the `subscriptions/listen` stream at 2026-07-28 and the
|
|
223
|
+
* back-channel before it, but the meaning ("refetch if you care") is identical. */
|
|
224
|
+
private invalidateOnChange;
|
|
225
|
+
/** Same invalidation as `invalidateOnChange`, driven from a typed listen-stream event. */
|
|
226
|
+
private invalidateForEvent;
|
|
227
|
+
private clientInfo;
|
|
91
228
|
private handleServerRequest;
|
|
92
229
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Multi-round-trip requests (MRTR, SEP-2322) — the 2026-07-28 replacement for the server→client
|
|
2
|
+
* back-channel.
|
|
3
|
+
*
|
|
4
|
+
* On the handshake wire a server that needs sampling/elicitation/roots PUSHES a request at us
|
|
5
|
+
* mid-call. At 2026-07-28 there is no back-channel: the server instead RETURNS
|
|
6
|
+
* `resultType: 'input_required'` carrying the requests it needs answered, and the client re-issues
|
|
7
|
+
* the *same* call with the answers plus the server's opaque `requestState`.
|
|
8
|
+
*
|
|
9
|
+
* Both mechanisms feed the SAME callbacks — a caller who wired up sampling once gets it on either
|
|
10
|
+
* wire without knowing which is in play. Algorithm mirrors mcp-py 2.0.0 `client/_input_required.py`.
|
|
11
|
+
*/
|
|
12
|
+
import type { McpInputRequest } from './types';
|
|
13
|
+
/** Cap on retry rounds before the driver gives up. Matches the TypeScript SDK's default; the C#
|
|
14
|
+
* and Go SDKs use the same value as a hard constant. */
|
|
15
|
+
export declare const DEFAULT_INPUT_REQUIRED_MAX_ROUNDS = 10;
|
|
16
|
+
/** Answer one embedded request through the client's sampling / elicitation / roots handling. */
|
|
17
|
+
export type InputRequestDispatcher = (key: string, request: McpInputRequest) => Promise<unknown>;
|
|
18
|
+
/** Re-issue the original call with the collected answers and the latest state. */
|
|
19
|
+
export type InputRequiredRetry<T> = (responses: Record<string, unknown> | undefined, requestState: string | undefined) => Promise<T>;
|
|
20
|
+
/** True when a result is the server asking for more input rather than a final answer.
|
|
21
|
+
*
|
|
22
|
+
* The discriminant is `resultType`. Per spec an ABSENT `resultType` MUST be read as `'complete'`:
|
|
23
|
+
* servers on earlier revisions never send the field, and treating absent as anything else would
|
|
24
|
+
* make every legacy result look like a question. */
|
|
25
|
+
export declare function isInputRequired(result: unknown): boolean;
|
|
26
|
+
/** Drive an `input_required` result to a terminal one.
|
|
27
|
+
*
|
|
28
|
+
* Each round either answers every embedded request and retries with the responses, or — when the
|
|
29
|
+
* server sent state but no questions — backs off and retries empty. `requestState` is echoed back
|
|
30
|
+
* byte-exact and never inspected: it is the server's sealed continuation token. */
|
|
31
|
+
export declare function runInputRequiredDriver<T>(first: T, opts: {
|
|
32
|
+
dispatch: InputRequestDispatcher;
|
|
33
|
+
retry: InputRequiredRetry<T>;
|
|
34
|
+
maxRounds?: number;
|
|
35
|
+
}): Promise<T>;
|
|
@@ -9,6 +9,13 @@ export declare const McpErrorCode: {
|
|
|
9
9
|
readonly MethodNotFound: -32601;
|
|
10
10
|
readonly InvalidParams: -32602;
|
|
11
11
|
readonly InternalError: -32603;
|
|
12
|
+
/** A routing header disagrees with the request body. */
|
|
13
|
+
readonly HeaderMismatch: -32020;
|
|
14
|
+
/** The server requires a client capability this client did not declare. */
|
|
15
|
+
readonly MissingRequiredClientCapability: -32021;
|
|
16
|
+
/** The server does not speak the requested revision; `data.supported` lists the ones it does.
|
|
17
|
+
* The ONE error carrying era information, so negotiation reads it specifically. */
|
|
18
|
+
readonly UnsupportedProtocolVersion: -32022;
|
|
12
19
|
};
|
|
13
20
|
/** A JSON-RPC / transport level error (server unreachable, error response,
|
|
14
21
|
* timeout). Tool execution failures arrive as a normal result with
|
|
@@ -27,6 +27,9 @@ export interface McpOAuthClientMetadata {
|
|
|
27
27
|
grant_types?: string[];
|
|
28
28
|
response_types?: string[];
|
|
29
29
|
token_endpoint_auth_method?: string;
|
|
30
|
+
/** OIDC Registration §2 application type (SEP-837). Defaults to `'native'` at registration, since
|
|
31
|
+
* an MCP client is normally a local process with a loopback redirect. Set explicitly to override. */
|
|
32
|
+
application_type?: 'web' | 'native';
|
|
30
33
|
}
|
|
31
34
|
/** Consumer-implemented storage + interactive redirect. */
|
|
32
35
|
export interface McpAuthProvider {
|
|
@@ -51,7 +54,24 @@ export interface AuthServerMetadata {
|
|
|
51
54
|
authorization_endpoint: string;
|
|
52
55
|
token_endpoint: string;
|
|
53
56
|
registration_endpoint?: string;
|
|
57
|
+
/** The authorization server's issuer identifier (RFC 8414), used to validate the RFC 9207 `iss`
|
|
58
|
+
* returned with the authorization code. */
|
|
59
|
+
issuer?: string;
|
|
60
|
+
/** RFC 9207: the server states it returns `iss` on authorization responses. When true, a response
|
|
61
|
+
* WITHOUT `iss` is rejected — otherwise an attacker could simply strip the parameter to dodge
|
|
62
|
+
* the check. */
|
|
63
|
+
authorization_response_iss_parameter_supported?: boolean;
|
|
54
64
|
}
|
|
65
|
+
/** Validate the RFC 9207 authorization-response issuer.
|
|
66
|
+
*
|
|
67
|
+
* This is the mix-up-attack defence: without it a malicious authorization server can hand back a
|
|
68
|
+
* code minted by a DIFFERENT server, and the client will dutifully redeem it — replaying the
|
|
69
|
+
* user's credentials against a party they never intended to authorize.
|
|
70
|
+
*
|
|
71
|
+
* Comparison is **exact string equality** per RFC 9207 §2.4 (RFC 3986 §6.2.1) — deliberately NOT
|
|
72
|
+
* URL-normalised. Normalising would make `https://as.example.com` and `https://as.example.com/`
|
|
73
|
+
* compare equal, and that leniency is precisely what an attacker looks for. */
|
|
74
|
+
export declare function validateAuthorizationResponseIss(iss: string | undefined, meta: Pick<AuthServerMetadata, 'issuer' | 'authorization_response_iss_parameter_supported'>): void;
|
|
55
75
|
/** Security options for the OAuth flow. All fields default to the most
|
|
56
76
|
* restrictive posture. Re-exported from `url-guard` for consumer convenience. */
|
|
57
77
|
export type { SsrfGuardOptions as McpOAuthSecurityOptions };
|
|
@@ -114,7 +134,7 @@ export declare class McpOAuth {
|
|
|
114
134
|
reauthorize(): Promise<boolean>;
|
|
115
135
|
/** Finish the interactive flow: exchange the callback code for tokens.
|
|
116
136
|
* The `returnedState` MUST match the state persisted during redirect (CSRF guard). */
|
|
117
|
-
finish(code: string, returnedState: string): Promise<void>;
|
|
137
|
+
finish(code: string, returnedState: string, iss?: string): Promise<void>;
|
|
118
138
|
private startRedirect;
|
|
119
139
|
private tryRefresh;
|
|
120
140
|
private ensureMetadata;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** MCP protocol-version registry and era helpers.
|
|
2
|
+
*
|
|
3
|
+
* MCP has two ERAS, not just two versions:
|
|
4
|
+
* - **handshake** (2024-11-05 … 2025-11-25) — `initialize` + `notifications/initialized`, an
|
|
5
|
+
* `Mcp-Session-Id`, and a server→client back-channel (ping, logging/setLevel,
|
|
6
|
+
* resources/subscribe, push sampling / roots / elicitation).
|
|
7
|
+
* - **modern** (2026-07-28+) — no handshake and no session id. One `server/discover` probe,
|
|
8
|
+
* then every request carries its own identity in `_meta` and routing headers.
|
|
9
|
+
*
|
|
10
|
+
* Both are supported and neither is preferred: real servers overwhelmingly still speak
|
|
11
|
+
* 2025-11-25, so the handshake path must keep working byte-for-byte. See CONSTITUTION.md
|
|
12
|
+
* standing decisions (2026-08-08).
|
|
13
|
+
*/
|
|
14
|
+
/** Every released revision, oldest to newest. Verified against mcp-py 2.0.0
|
|
15
|
+
* (`mcp_types/version.py`, KNOWN_PROTOCOL_VERSIONS). */
|
|
16
|
+
export declare const MCP_KNOWN_PROTOCOL_VERSIONS: readonly ["2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25", "2026-07-28"];
|
|
17
|
+
/** Revisions reachable via the `initialize` handshake. */
|
|
18
|
+
export declare const MCP_HANDSHAKE_PROTOCOL_VERSIONS: readonly ["2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25"];
|
|
19
|
+
/** Revisions that use the stateless per-request envelope (`server/discover`). */
|
|
20
|
+
export declare const MCP_MODERN_PROTOCOL_VERSIONS: readonly ["2026-07-28"];
|
|
21
|
+
/** Newest revision reachable via the handshake — what we offer in `initialize`. */
|
|
22
|
+
export declare const MCP_LATEST_HANDSHAKE_VERSION = "2025-11-25";
|
|
23
|
+
/** Newest per-request-envelope revision — what the `server/discover` probe asks for. */
|
|
24
|
+
export declare const MCP_LATEST_MODERN_VERSION = "2026-07-28";
|
|
25
|
+
/** Which wire a negotiated version implies. */
|
|
26
|
+
export type McpEra = 'handshake' | 'modern';
|
|
27
|
+
/** Version strings are an ENUMERATED SET, not an ordered scalar.
|
|
28
|
+
*
|
|
29
|
+
* Released revisions happen to be dates that sort lexicographically, but future identifiers are
|
|
30
|
+
* not guaranteed to be date-shaped, and an unrecognised peer string must compare conservatively
|
|
31
|
+
* rather than accidentally (`'zzz' > '2025-11-25'` is true and meaningless). So era questions go
|
|
32
|
+
* through the lists above — never through `<` / `>`. Upstream calls this out explicitly, having
|
|
33
|
+
* been bitten by it. An unknown version reads as `handshake`: the older, safer wire. */
|
|
34
|
+
export declare function mcpEraOf(version: string): McpEra;
|
|
35
|
+
/** True when `version` is a revision this client can speak on the modern wire. */
|
|
36
|
+
export declare function isModernMcpVersion(version: string): boolean;
|
|
37
|
+
/** True when `version` is a revision reachable via the `initialize` handshake. */
|
|
38
|
+
export declare function isHandshakeMcpVersion(version: string): boolean;
|
|
39
|
+
/** The newest modern revision BOTH sides speak, or undefined if they share none. */
|
|
40
|
+
export declare function newestMutualModernVersion(theirs: readonly string[]): string | undefined;
|
|
41
|
+
/** Required on every modern request: the revision this request is written at. */
|
|
42
|
+
export declare const MCP_PROTOCOL_VERSION_META_KEY = "io.modelcontextprotocol/protocolVersion";
|
|
43
|
+
/** Required on every modern request: what the client can do (replaces the handshake exchange). */
|
|
44
|
+
export declare const MCP_CLIENT_CAPABILITIES_META_KEY = "io.modelcontextprotocol/clientCapabilities";
|
|
45
|
+
/** Optional client identity, display-only. */
|
|
46
|
+
export declare const MCP_CLIENT_INFO_META_KEY = "io.modelcontextprotocol/clientInfo";
|
|
47
|
+
/** Server identity stamped on modern results, display-only (absent and malformed both read as
|
|
48
|
+
* "unknown" rather than failing the connection). */
|
|
49
|
+
export declare const MCP_SERVER_INFO_META_KEY = "io.modelcontextprotocol/serverInfo";
|
|
50
|
+
/** Stamped on every `subscriptions/listen` stream frame; the value is that request's JSON-RPC id,
|
|
51
|
+
* which is how a frame is attributed to one subscription. */
|
|
52
|
+
export declare const MCP_SUBSCRIPTION_ID_META_KEY = "io.modelcontextprotocol/subscriptionId";
|
|
53
|
+
export declare const MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version";
|
|
54
|
+
export declare const MCP_METHOD_HEADER = "mcp-method";
|
|
55
|
+
export declare const MCP_NAME_HEADER = "mcp-name";
|
|
56
|
+
/** Methods whose primary subject goes in the `Mcp-Name` routing header, and the param it comes
|
|
57
|
+
* from. Lets an intermediary route/authorize without parsing the body. */
|
|
58
|
+
export declare const MCP_NAME_BEARING_METHODS: Readonly<Record<string, string>>;
|
|
59
|
+
/** Header values must be ASCII; a tool name or URI may not be. Encode out-of-range characters
|
|
60
|
+
* rather than emitting a header the runtime will reject. */
|
|
61
|
+
export declare function encodeMcpHeaderValue(value: string): string;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Opt-in response cache honouring the 2026-07-28 `ttlMs` / `cacheScope` hints (SEP-2578).
|
|
2
|
+
*
|
|
3
|
+
* A server tells the client how long a list/read result stays fresh. Without this, `listTools()`
|
|
4
|
+
* re-fetches on every call — the churn this hint exists to remove.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately conservative:
|
|
7
|
+
* - **Off unless asked for.** Caching changes when a caller observes a server change; that is the
|
|
8
|
+
* caller's decision, not ours.
|
|
9
|
+
* - **No hint, no caching.** `ttlMs` absent (every pre-2026 server) means nothing is stored, so
|
|
10
|
+
* behaviour is byte-identical to before.
|
|
11
|
+
* - **`ttlMs: 0` means immediately stale**, which is a real instruction — not a missing value to
|
|
12
|
+
* be replaced with a default.
|
|
13
|
+
* - **`cacheScope` is recorded, never used to widen sharing.** This cache lives inside one client
|
|
14
|
+
* with one credential, so `public` buys nothing here; storing the scope keeps the entry honest
|
|
15
|
+
* if the cache is ever shared.
|
|
16
|
+
*/
|
|
17
|
+
import type { McpCacheHints } from './types';
|
|
18
|
+
export declare class McpResultCache {
|
|
19
|
+
private readonly entries;
|
|
20
|
+
/** Cache key: the method plus its arguments. Two `resources/read` calls for different URIs are
|
|
21
|
+
* different entries. */
|
|
22
|
+
static key(method: string, params?: unknown): string;
|
|
23
|
+
get(key: string, now?: number): unknown | undefined;
|
|
24
|
+
/** Store only when the server actually asked for it. Returns whether anything was stored. */
|
|
25
|
+
set(key: string, value: unknown, hints: McpCacheHints | undefined, now?: number): boolean;
|
|
26
|
+
/** Drop everything — e.g. after a `*_changed` notification says the server moved on. */
|
|
27
|
+
clear(): void;
|
|
28
|
+
/** Drop every entry for one method, leaving the others. */
|
|
29
|
+
clearMethod(method: string): void;
|
|
30
|
+
get size(): number;
|
|
31
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** `subscriptions/listen` (2026-07-28, SEP-2575) — one stream for every change notification.
|
|
2
|
+
*
|
|
3
|
+
* At 2026-07-28 the per-resource `resources/subscribe` RPC and the standalone notification channel
|
|
4
|
+
* are both replaced by a single long-lived `subscriptions/listen` request. The client names the
|
|
5
|
+
* notification kinds it wants; **every kind is opt-in and the server MUST NOT send one that was
|
|
6
|
+
* not asked for**. The server acknowledges with the subset it actually honoured, which can be
|
|
7
|
+
* smaller than what was requested — so "I asked for it" never implies "I will receive it".
|
|
8
|
+
*
|
|
9
|
+
* Every frame on the stream is stamped with the listen request's id under
|
|
10
|
+
* `io.modelcontextprotocol/subscriptionId`, which is how frames are attributed to a subscription.
|
|
11
|
+
*
|
|
12
|
+
* Events are **level triggers**: "this changed, re-fetch if you care". They carry no payload
|
|
13
|
+
* beyond the fact of the change, so consecutive identical events collapse safely.
|
|
14
|
+
*/
|
|
15
|
+
/** The notification kinds a client may opt into. Mirrors the wire `SubscriptionFilter`. */
|
|
16
|
+
export interface McpSubscriptionFilter {
|
|
17
|
+
toolsListChanged?: boolean;
|
|
18
|
+
promptsListChanged?: boolean;
|
|
19
|
+
resourcesListChanged?: boolean;
|
|
20
|
+
/** Resource URIs to watch — the replacement for the `resources/subscribe` RPC. */
|
|
21
|
+
resourceSubscriptions?: string[];
|
|
22
|
+
}
|
|
23
|
+
/** A change the server announced. Level triggers: re-fetch if you care. */
|
|
24
|
+
export type McpServerEvent = {
|
|
25
|
+
type: 'tools_list_changed';
|
|
26
|
+
} | {
|
|
27
|
+
type: 'prompts_list_changed';
|
|
28
|
+
} | {
|
|
29
|
+
type: 'resources_list_changed';
|
|
30
|
+
} | {
|
|
31
|
+
type: 'resource_updated';
|
|
32
|
+
uri: string;
|
|
33
|
+
};
|
|
34
|
+
/** The notification methods that ride a listen stream at 2026-07-28. */
|
|
35
|
+
export declare const MCP_LISTEN_STREAM_METHODS: readonly ["notifications/tools/list_changed", "notifications/prompts/list_changed", "notifications/resources/list_changed", "notifications/resources/updated"];
|
|
36
|
+
/** The ack frame that carries the honoured subset. */
|
|
37
|
+
export declare const MCP_SUBSCRIPTIONS_ACKNOWLEDGED = "notifications/subscriptions/acknowledged";
|
|
38
|
+
/** The event a raw stream frame announces, or undefined when it carries none. */
|
|
39
|
+
export declare function eventFromWire(method: string, params: unknown): McpServerEvent | undefined;
|
|
40
|
+
/** The subscription id stamped on a frame, or undefined when it is not a listen frame. */
|
|
41
|
+
export declare function subscriptionIdFrom(params: unknown): string | number | undefined;
|
|
42
|
+
/** A live subscription: the honoured filter once acknowledged, plus event delivery. */
|
|
43
|
+
export declare class McpSubscription {
|
|
44
|
+
readonly id: string | number;
|
|
45
|
+
readonly requested: McpSubscriptionFilter;
|
|
46
|
+
private readonly onEvent;
|
|
47
|
+
private readonly onClose;
|
|
48
|
+
/** The subset the server agreed to send. `null` until the ack arrives — and it can be NARROWER
|
|
49
|
+
* than what was requested, so check it rather than assuming. */
|
|
50
|
+
honored: McpSubscriptionFilter | null;
|
|
51
|
+
private closed;
|
|
52
|
+
private endState;
|
|
53
|
+
constructor(id: string | number, requested: McpSubscriptionFilter, onEvent: (event: McpServerEvent) => void, onClose: () => void);
|
|
54
|
+
/** Feed a raw stream frame. Returns true when it belonged to this subscription. */
|
|
55
|
+
handleFrame(method: string, params: unknown): boolean;
|
|
56
|
+
/** True when the server acknowledged this kind. Unacknowledged ⇒ do not expect events. */
|
|
57
|
+
isHonored(kind: keyof McpSubscriptionFilter): boolean;
|
|
58
|
+
/** Set once the stream ends: `undefined` for a clean server-side teardown, otherwise the error
|
|
59
|
+
* that killed it. A subscription that stopped delivering is otherwise indistinguishable from one
|
|
60
|
+
* where nothing has changed yet. */
|
|
61
|
+
get ended(): {
|
|
62
|
+
error?: unknown;
|
|
63
|
+
} | null;
|
|
64
|
+
/** True while the subscription can still deliver. */
|
|
65
|
+
get active(): boolean;
|
|
66
|
+
/** Called by the transport when the underlying stream finishes. */
|
|
67
|
+
markEnded(error?: unknown): void;
|
|
68
|
+
close(): void;
|
|
69
|
+
}
|
|
@@ -26,11 +26,18 @@ export declare class HttpTransport extends BaseJsonRpcTransport implements McpTr
|
|
|
26
26
|
private nextHttpId;
|
|
27
27
|
private sessionId;
|
|
28
28
|
private protocolVersion;
|
|
29
|
+
/** Handshake until negotiation says otherwise — an un-negotiated connection must behave exactly
|
|
30
|
+
* as it did before 2026 support existed. */
|
|
31
|
+
private era;
|
|
29
32
|
private eventAbort;
|
|
30
33
|
private lastEventId;
|
|
34
|
+
/** Live `subscriptions/listen` streams, so `close()` can tear them down. Without this a closed
|
|
35
|
+
* client would leave the POST hanging until the server gave up on it. */
|
|
36
|
+
private readonly streamAborts;
|
|
31
37
|
constructor(config: McpHttpConfig, deps: HttpTransportDeps);
|
|
32
38
|
start(): Promise<void>;
|
|
33
39
|
setProtocolVersion(version: string): void;
|
|
40
|
+
setEra(era: 'handshake' | 'modern'): void;
|
|
34
41
|
/** Open the server->client GET SSE stream (best-effort: a 405 means the server
|
|
35
42
|
* is request/response only). Reconnects with backoff + Last-Event-ID until
|
|
36
43
|
* close(). Runs in the background. */
|
|
@@ -43,8 +50,32 @@ export declare class HttpTransport extends BaseJsonRpcTransport implements McpTr
|
|
|
43
50
|
close(): Promise<void>;
|
|
44
51
|
/** Send a JSON-RPC response back via POST (used by handleRequest from base). */
|
|
45
52
|
protected sendMessage(obj: unknown): Promise<void>;
|
|
53
|
+
/** Open a long-lived request whose RESPONSE BODY is the event stream.
|
|
54
|
+
*
|
|
55
|
+
* This is how `subscriptions/listen` works on Streamable HTTP: unlike an ordinary call, the POST
|
|
56
|
+
* does not return a single JSON-RPC message — the server holds the response open and writes
|
|
57
|
+
* notification frames as they occur, closing it only when the subscription ends. So it goes
|
|
58
|
+
* through `fetchStream` (streaming response) rather than the buffered `post()` path, which would
|
|
59
|
+
* surface the frames only after the stream closed.
|
|
60
|
+
*
|
|
61
|
+
* Frames are routed exactly like the GET channel's, so the client sees no difference between the
|
|
62
|
+
* two. The eventual JSON-RPC response has no pending entry to settle — its only meaning is "the
|
|
63
|
+
* stream ended", which is reported through `onEnd`. */
|
|
64
|
+
sendLongLivedRequest(method: string, params?: unknown, onEnd?: (error?: unknown) => void): Promise<number>;
|
|
46
65
|
private headers;
|
|
66
|
+
/** Modern-era routing headers: `Mcp-Method` on every request, plus `Mcp-Name` carrying the
|
|
67
|
+
* method's subject (tool name / prompt name / resource URI) so a gateway can route and
|
|
68
|
+
* authorize without parsing the body. No-op on the handshake wire. */
|
|
69
|
+
private routingHeaders;
|
|
47
70
|
/** Base headers + any OAuth bearer + per-call extras. */
|
|
48
71
|
private authedHeaders;
|
|
49
72
|
private post;
|
|
50
73
|
}
|
|
74
|
+
/** Extract the JSON-RPC response matching `id` from a JSON or SSE body. */
|
|
75
|
+
/** The media-type ESSENCE of a Content-Type: type/subtype, lowercased, parameters stripped.
|
|
76
|
+
*
|
|
77
|
+
* A substring test misroutes anything that merely *contains* the token — `application/json` with a
|
|
78
|
+
* vendor parameter mentioning it, or a hypothetical `application/vnd.text/event-stream+json` —
|
|
79
|
+
* while still needing to cope with the ordinary `text/event-stream; charset=utf-8`. Comparing the
|
|
80
|
+
* essence handles both. Matches the fix upstream shipped in mcp-ts 1.30. */
|
|
81
|
+
export declare function mediaTypeEssence(contentType: string): string;
|
|
@@ -9,8 +9,10 @@ export declare class StdioTransport extends BaseJsonRpcTransport implements McpT
|
|
|
9
9
|
private proc;
|
|
10
10
|
private buffer;
|
|
11
11
|
private readonly timeoutMs;
|
|
12
|
+
private readonly maxBufferSize;
|
|
12
13
|
constructor(config: McpStdioConfig, opts?: {
|
|
13
14
|
timeoutMs?: number;
|
|
15
|
+
maxBufferSize?: number;
|
|
14
16
|
});
|
|
15
17
|
start(): Promise<void>;
|
|
16
18
|
setProtocolVersion(): void;
|