@graphorin/mcp 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +64 -3
  3. package/dist/client/adapt-result.d.ts +9 -1
  4. package/dist/client/adapt-result.d.ts.map +1 -1
  5. package/dist/client/adapt-result.js +28 -10
  6. package/dist/client/adapt-result.js.map +1 -1
  7. package/dist/client/client-handlers.js +7 -1
  8. package/dist/client/client-handlers.js.map +1 -1
  9. package/dist/client/client.d.ts.map +1 -1
  10. package/dist/client/client.js +45 -70
  11. package/dist/client/client.js.map +1 -1
  12. package/dist/client/inbound-filters.js +101 -1
  13. package/dist/client/inbound-filters.js.map +1 -1
  14. package/dist/client/index.d.ts +2 -1
  15. package/dist/client/index.js +2 -1
  16. package/dist/client/managed.d.ts +35 -0
  17. package/dist/client/managed.d.ts.map +1 -0
  18. package/dist/client/managed.js +136 -0
  19. package/dist/client/managed.js.map +1 -0
  20. package/dist/client/mcp-resource-reader.js +1 -1
  21. package/dist/client/mcp-resource-reader.js.map +1 -1
  22. package/dist/client/to-tools-run.js +119 -0
  23. package/dist/client/to-tools-run.js.map +1 -0
  24. package/dist/client/to-tools.d.ts +8 -0
  25. package/dist/client/to-tools.d.ts.map +1 -1
  26. package/dist/client/to-tools.js +27 -4
  27. package/dist/client/to-tools.js.map +1 -1
  28. package/dist/client/types.d.ts +12 -3
  29. package/dist/client/types.d.ts.map +1 -1
  30. package/dist/errors/index.d.ts +3 -3
  31. package/dist/errors/index.js +1 -1
  32. package/dist/errors/index.js.map +1 -1
  33. package/dist/helpers/identity.d.ts +11 -0
  34. package/dist/helpers/identity.d.ts.map +1 -1
  35. package/dist/helpers/identity.js +13 -2
  36. package/dist/helpers/identity.js.map +1 -1
  37. package/dist/index.d.ts +3 -2
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +3 -4
  40. package/dist/index.js.map +1 -1
  41. package/dist/package.js +1 -1
  42. package/dist/package.js.map +1 -1
  43. package/dist/registry/json-schema.js +26 -8
  44. package/dist/registry/json-schema.js.map +1 -1
  45. package/dist/transport/types.d.ts +9 -0
  46. package/package.json +13 -12
  47. package/src/client/adapt-result.ts +302 -0
  48. package/src/client/client-handlers.ts +215 -0
  49. package/src/client/client.ts +725 -0
  50. package/src/client/defer-loading.ts +108 -0
  51. package/src/client/inbound-filters.ts +246 -0
  52. package/src/client/index.ts +42 -0
  53. package/src/client/managed.ts +222 -0
  54. package/src/client/mcp-resource-reader.ts +183 -0
  55. package/src/client/pinning.ts +48 -0
  56. package/src/client/to-tools-run.ts +178 -0
  57. package/src/client/to-tools.ts +294 -0
  58. package/src/client/transport-factory.ts +117 -0
  59. package/src/client/types.ts +422 -0
  60. package/src/errors/index.ts +170 -0
  61. package/src/helpers/identity.ts +128 -0
  62. package/src/helpers/index.ts +8 -0
  63. package/src/helpers/validate-config.ts +95 -0
  64. package/src/index.ts +49 -0
  65. package/src/oauth/bridge.ts +143 -0
  66. package/src/oauth/index.ts +18 -0
  67. package/src/oauth/library.ts +61 -0
  68. package/src/registry/json-schema.ts +402 -0
  69. package/src/transport/index.ts +15 -0
  70. package/src/transport/types.ts +108 -0
@@ -0,0 +1,402 @@
1
+ /**
2
+ * Lightweight JSON-Schema -> {@link ZodLikeSchema} adapter.
3
+ *
4
+ * The Model Context Protocol carries tool input + output schemas as
5
+ * JSON Schema documents; the Graphorin tool registry types its
6
+ * schemas via the `ZodLikeSchema` structural contract from
7
+ * `@graphorin/core`. This module bridges the two without pulling
8
+ * `zod` into the MCP boundary or relying on code generation +
9
+ * runtime `eval` (the popular `json-schema-to-zod` package generates
10
+ * source code that needs `new Function(...)` to execute).
11
+ *
12
+ * The adapter validates the canonical subset of JSON Schema the MCP
13
+ * spec uses: `object` (with `properties`, `required`,
14
+ * `additionalProperties`), `array` (with `items`, `minItems`,
15
+ * `maxItems`), `string` (with `enum`, `minLength`, `maxLength`,
16
+ * `pattern`), `number` / `integer` (with `enum`, `minimum`,
17
+ * `maximum`), `boolean`, `null`, and the primitive composition
18
+ * keywords `enum`, `const`, `oneOf`, `anyOf`, `allOf`. Unknown keys
19
+ * are accepted permissively so newer MCP server schemas that ship
20
+ * additional vocabulary do not break the adapter.
21
+ *
22
+ * @packageDocumentation
23
+ */
24
+
25
+ import type { ZodLikeError, ZodLikeSafeParseResult, ZodLikeSchema } from '@graphorin/core';
26
+
27
+ /** JSON Schema document subset accepted by {@link buildJsonSchemaValidator}. */
28
+ export type JsonSchemaLike =
29
+ | boolean
30
+ | (Readonly<Record<string, unknown>> & {
31
+ readonly type?: string | ReadonlyArray<string>;
32
+ readonly properties?: Readonly<Record<string, JsonSchemaLike>>;
33
+ readonly required?: ReadonlyArray<string>;
34
+ readonly additionalProperties?: boolean | JsonSchemaLike;
35
+ readonly items?: JsonSchemaLike | ReadonlyArray<JsonSchemaLike>;
36
+ readonly minItems?: number;
37
+ readonly maxItems?: number;
38
+ readonly minimum?: number;
39
+ readonly maximum?: number;
40
+ readonly minLength?: number;
41
+ readonly maxLength?: number;
42
+ readonly pattern?: string;
43
+ readonly enum?: ReadonlyArray<unknown>;
44
+ readonly const?: unknown;
45
+ readonly oneOf?: ReadonlyArray<JsonSchemaLike>;
46
+ readonly anyOf?: ReadonlyArray<JsonSchemaLike>;
47
+ readonly allOf?: ReadonlyArray<JsonSchemaLike>;
48
+ });
49
+
50
+ /**
51
+ * Build a {@link ZodLikeSchema} that validates `data` against
52
+ * `schema`. The returned instance follows the structural Zod
53
+ * contract from `@graphorin/core` (a `parse` method that throws + a
54
+ * `safeParse` method that returns a `ZodLikeSafeParseResult`).
55
+ *
56
+ * The validator also retains the **source JSON Schema** and exposes it
57
+ * via `toJSON()` (tools-01): `toolToDefinition` and the code-mode
58
+ * signature projection honour `toJSON()`, so without it an MCP tool's
59
+ * parameters serialise to `{}` on the provider wire body - the model
60
+ * would see an argument-less tool. Boolean schemas normalise to their
61
+ * record equivalents (`true` → `{}`, `false` → `{ not: {} }`).
62
+ *
63
+ * @stable
64
+ */
65
+ export function buildJsonSchemaValidator<T = unknown>(schema: JsonSchemaLike): ZodLikeSchema<T> {
66
+ function parse(data: unknown): T {
67
+ const issues = validate(data, schema, []);
68
+ if (issues.length === 0) return data as T;
69
+ throw buildError(issues);
70
+ }
71
+ function safeParse(data: unknown): ZodLikeSafeParseResult<T, unknown> {
72
+ const issues = validate(data, schema, []);
73
+ if (issues.length === 0) {
74
+ return { success: true, data: data as T };
75
+ }
76
+ return { success: false, error: buildError(issues) };
77
+ }
78
+ function toJSON(): Readonly<Record<string, unknown>> {
79
+ if (schema === true) return {};
80
+ if (schema === false) return { not: {} };
81
+ return schema;
82
+ }
83
+ return Object.freeze({ parse, safeParse, toJSON });
84
+ }
85
+
86
+ interface Issue {
87
+ readonly path: ReadonlyArray<string | number>;
88
+ readonly message: string;
89
+ }
90
+
91
+ function validate(
92
+ value: unknown,
93
+ schema: JsonSchemaLike,
94
+ path: ReadonlyArray<string | number>,
95
+ ): Issue[] {
96
+ if (schema === true) return [];
97
+ if (schema === false) return [{ path, message: 'rejected by schema (false)' }];
98
+
99
+ const issues: Issue[] = [];
100
+
101
+ if ('const' in schema && schema.const !== undefined) {
102
+ if (!equalsDeep(value, schema.const)) {
103
+ issues.push({ path, message: `expected const ${formatValue(schema.const)}` });
104
+ }
105
+ }
106
+ if (Array.isArray(schema.enum)) {
107
+ if (!schema.enum.some((candidate) => equalsDeep(value, candidate))) {
108
+ issues.push({ path, message: 'value does not match any enum entry' });
109
+ }
110
+ }
111
+ if (Array.isArray(schema.allOf)) {
112
+ for (const sub of schema.allOf) {
113
+ issues.push(...validate(value, sub, path));
114
+ }
115
+ }
116
+ if (Array.isArray(schema.anyOf)) {
117
+ const anyOk = schema.anyOf.some((sub) => validate(value, sub, path).length === 0);
118
+ if (!anyOk) issues.push({ path, message: 'value did not match any of the anyOf branches' });
119
+ }
120
+ if (Array.isArray(schema.oneOf)) {
121
+ const matchCount = schema.oneOf.filter((sub) => validate(value, sub, path).length === 0).length;
122
+ if (matchCount !== 1) {
123
+ issues.push({
124
+ path,
125
+ message: `expected exactly one oneOf branch to match (got ${matchCount})`,
126
+ });
127
+ }
128
+ }
129
+
130
+ if (schema.type !== undefined) {
131
+ const typeIssues = validateType(value, schema, path);
132
+ issues.push(...typeIssues);
133
+ }
134
+
135
+ return issues;
136
+ }
137
+
138
+ function validateType(
139
+ value: unknown,
140
+ schema: JsonSchemaLike,
141
+ path: ReadonlyArray<string | number>,
142
+ ): Issue[] {
143
+ if (schema === true || schema === false) return [];
144
+ const types: ReadonlyArray<string> = Array.isArray(schema.type)
145
+ ? schema.type
146
+ : schema.type === undefined
147
+ ? []
148
+ : [schema.type];
149
+ if (types.length === 0) return [];
150
+ if (!types.some((t) => matchesType(value, t))) {
151
+ return [{ path, message: `expected type ${types.join(' | ')}` }];
152
+ }
153
+
154
+ if (
155
+ matchesType(value, 'object') &&
156
+ (schema.properties !== undefined ||
157
+ schema.required !== undefined ||
158
+ schema.additionalProperties !== undefined)
159
+ ) {
160
+ return validateObject(value as Record<string, unknown>, schema, path);
161
+ }
162
+ if (matchesType(value, 'array')) {
163
+ return validateArray(value as unknown[], schema, path);
164
+ }
165
+ if (matchesType(value, 'string')) {
166
+ return validateString(value as string, schema, path);
167
+ }
168
+ if (matchesType(value, 'number') || matchesType(value, 'integer')) {
169
+ return validateNumber(value as number, schema, path);
170
+ }
171
+ return [];
172
+ }
173
+
174
+ function validateObject(
175
+ value: Record<string, unknown>,
176
+ schema: JsonSchemaLike & { readonly type?: unknown },
177
+ path: ReadonlyArray<string | number>,
178
+ ): Issue[] {
179
+ if (schema === true || schema === false) return [];
180
+ const issues: Issue[] = [];
181
+ const required = Array.isArray(schema.required) ? schema.required : [];
182
+ for (const key of required) {
183
+ if (!(key in value)) {
184
+ issues.push({ path: [...path, key], message: 'is required' });
185
+ }
186
+ }
187
+ const properties = schema.properties ?? {};
188
+ for (const [key, subSchema] of Object.entries(properties)) {
189
+ if (!(key in value)) continue;
190
+ issues.push(...validate(value[key], subSchema, [...path, key]));
191
+ }
192
+ if (schema.additionalProperties === false) {
193
+ for (const key of Object.keys(value)) {
194
+ if (!(key in properties)) {
195
+ issues.push({ path: [...path, key], message: 'unexpected additional property' });
196
+ }
197
+ }
198
+ } else if (
199
+ typeof schema.additionalProperties === 'object' &&
200
+ schema.additionalProperties !== null
201
+ ) {
202
+ for (const key of Object.keys(value)) {
203
+ if (key in properties) continue;
204
+ issues.push(
205
+ ...validate(value[key], schema.additionalProperties as JsonSchemaLike, [...path, key]),
206
+ );
207
+ }
208
+ }
209
+ return issues;
210
+ }
211
+
212
+ function validateArray(
213
+ value: unknown[],
214
+ schema: JsonSchemaLike & { readonly type?: unknown },
215
+ path: ReadonlyArray<string | number>,
216
+ ): Issue[] {
217
+ if (schema === true || schema === false) return [];
218
+ const issues: Issue[] = [];
219
+ if (typeof schema.minItems === 'number' && value.length < schema.minItems) {
220
+ issues.push({ path, message: `expected at least ${schema.minItems} items` });
221
+ }
222
+ if (typeof schema.maxItems === 'number' && value.length > schema.maxItems) {
223
+ issues.push({ path, message: `expected at most ${schema.maxItems} items` });
224
+ }
225
+ const items = schema.items;
226
+ if (items === undefined) return issues;
227
+ if (Array.isArray(items)) {
228
+ for (let i = 0; i < items.length; i++) {
229
+ if (i >= value.length) break;
230
+ const sub = items[i];
231
+ if (sub === undefined) continue;
232
+ issues.push(...validate(value[i], sub, [...path, i]));
233
+ }
234
+ } else {
235
+ for (let i = 0; i < value.length; i++) {
236
+ issues.push(...validate(value[i], items as JsonSchemaLike, [...path, i]));
237
+ }
238
+ }
239
+ return issues;
240
+ }
241
+
242
+ function validateString(
243
+ value: string,
244
+ schema: JsonSchemaLike & { readonly type?: unknown },
245
+ path: ReadonlyArray<string | number>,
246
+ ): Issue[] {
247
+ if (schema === true || schema === false) return [];
248
+ const issues: Issue[] = [];
249
+ if (typeof schema.minLength === 'number' && value.length < schema.minLength) {
250
+ issues.push({ path, message: `expected at least ${schema.minLength} characters` });
251
+ }
252
+ if (typeof schema.maxLength === 'number' && value.length > schema.maxLength) {
253
+ issues.push({ path, message: `expected at most ${schema.maxLength} characters` });
254
+ }
255
+ if (typeof schema.pattern === 'string') {
256
+ // mcp-skills-07: the pattern comes VERBATIM from the (untrusted)
257
+ // MCP server and runs on every validated input - a
258
+ // catastrophic-backtracking pattern (`(a+)+$`) plus a long
259
+ // model-generated string stalls the agent's event loop. Guards:
260
+ // cap the pattern and the tested-string length, and reject the
261
+ // classic nested-quantifier shapes heuristically. A guarded-out
262
+ // pattern degrades to permissive (same as a malformed one).
263
+ // W-078 defense-in-depth: any quantified group can still backtrack
264
+ // polynomially even when the heuristic below admits it, so the
265
+ // tested-string cap shrinks whenever one is present.
266
+ const maxTestLength = QUANTIFIED_GROUP_RE.test(schema.pattern)
267
+ ? MAX_QUANTIFIED_GROUP_TEST_LENGTH
268
+ : MAX_PATTERN_TEST_LENGTH;
269
+ if (
270
+ schema.pattern.length <= MAX_PATTERN_LENGTH &&
271
+ value.length <= maxTestLength &&
272
+ !looksCatastrophic(schema.pattern)
273
+ ) {
274
+ try {
275
+ const re = new RegExp(schema.pattern);
276
+ if (!re.test(value))
277
+ issues.push({ path, message: `did not match pattern ${schema.pattern}` });
278
+ } catch {
279
+ // Treat malformed patterns as permissive (mirrors Ajv default).
280
+ }
281
+ }
282
+ }
283
+ return issues;
284
+ }
285
+
286
+ /** mcp-skills-07: hard caps on server-supplied `pattern` evaluation. */
287
+ const MAX_PATTERN_LENGTH = 1_000;
288
+ const MAX_PATTERN_TEST_LENGTH = 10_000;
289
+ /** W-078: reduced cap applied whenever the pattern has a quantified group. */
290
+ const MAX_QUANTIFIED_GROUP_TEST_LENGTH = 1_000;
291
+
292
+ /** A group that is itself quantified: `(...)+`, `(...)*`, `(...){2,}`. */
293
+ const QUANTIFIED_GROUP_RE = /\)[*+]|\)\{\d+,(?:\d+)?\}/;
294
+ /** A group whose inner expression ends with a quantifier: `...+)`. */
295
+ const GROUP_BODY_ENDS_QUANTIFIED_RE = /[*+}]\s*\)/;
296
+
297
+ /**
298
+ * Cheap heuristic for exponential-backtracking shapes in a quantified
299
+ * group (mcp-skills-07 / W-078). A pattern is rejected when a
300
+ * quantified group is present AND either:
301
+ *
302
+ * - the group body ends with a quantifier - the classic nested
303
+ * quantifier family (`(a+)+`, `(a*)*`, `(a+){2,}`, `(?:x+)*`); or
304
+ * - the pattern contains an alternation - the alternation-overlap
305
+ * family (`(a|a)+`, `^(\w|\d)*$`), where overlapping branches make
306
+ * the quantified group ambiguous.
307
+ *
308
+ * Deliberately conservative: false positives (a harmless alternation
309
+ * inside a quantified group) merely degrade that pattern to permissive
310
+ * validation, which is the already-documented semantics for guarded
311
+ * and malformed patterns. A linear-time engine (re2) remains the exact
312
+ * solution; this heuristic plus the reduced test-string cap above
313
+ * bound the damage of an untrusted server-supplied pattern.
314
+ */
315
+ function looksCatastrophic(pattern: string): boolean {
316
+ if (!QUANTIFIED_GROUP_RE.test(pattern)) return false;
317
+ return GROUP_BODY_ENDS_QUANTIFIED_RE.test(pattern) || pattern.includes('|');
318
+ }
319
+
320
+ function validateNumber(
321
+ value: number,
322
+ schema: JsonSchemaLike & { readonly type?: unknown },
323
+ path: ReadonlyArray<string | number>,
324
+ ): Issue[] {
325
+ if (schema === true || schema === false) return [];
326
+ const issues: Issue[] = [];
327
+ const types: ReadonlyArray<string> = Array.isArray(schema.type)
328
+ ? schema.type
329
+ : schema.type === undefined
330
+ ? []
331
+ : [schema.type];
332
+ if (types.includes('integer') && !Number.isInteger(value)) {
333
+ issues.push({ path, message: 'expected an integer' });
334
+ }
335
+ if (typeof schema.minimum === 'number' && value < schema.minimum) {
336
+ issues.push({ path, message: `expected >= ${schema.minimum}` });
337
+ }
338
+ if (typeof schema.maximum === 'number' && value > schema.maximum) {
339
+ issues.push({ path, message: `expected <= ${schema.maximum}` });
340
+ }
341
+ return issues;
342
+ }
343
+
344
+ function matchesType(value: unknown, type: string): boolean {
345
+ switch (type) {
346
+ case 'string':
347
+ return typeof value === 'string';
348
+ case 'number':
349
+ return typeof value === 'number' && Number.isFinite(value);
350
+ case 'integer':
351
+ return typeof value === 'number' && Number.isFinite(value);
352
+ case 'boolean':
353
+ return typeof value === 'boolean';
354
+ case 'null':
355
+ return value === null;
356
+ case 'array':
357
+ return Array.isArray(value);
358
+ case 'object':
359
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
360
+ default:
361
+ return true;
362
+ }
363
+ }
364
+
365
+ function equalsDeep(a: unknown, b: unknown): boolean {
366
+ if (a === b) return true;
367
+ if (a === null || b === null) return a === b;
368
+ if (typeof a !== typeof b) return false;
369
+ if (Array.isArray(a) && Array.isArray(b)) {
370
+ if (a.length !== b.length) return false;
371
+ return a.every((item, i) => equalsDeep(item, b[i]));
372
+ }
373
+ if (typeof a === 'object' && typeof b === 'object') {
374
+ const ak = Object.keys(a as Record<string, unknown>);
375
+ const bk = Object.keys(b as Record<string, unknown>);
376
+ if (ak.length !== bk.length) return false;
377
+ return ak.every((key) =>
378
+ equalsDeep((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key]),
379
+ );
380
+ }
381
+ return false;
382
+ }
383
+
384
+ function formatValue(value: unknown): string {
385
+ try {
386
+ return JSON.stringify(value);
387
+ } catch {
388
+ return Object.prototype.toString.call(value);
389
+ }
390
+ }
391
+
392
+ function buildError(issues: ReadonlyArray<Issue>): ZodLikeError {
393
+ return {
394
+ name: 'GraphorinMCPSchemaError',
395
+ message: issues
396
+ .map((i) => `${i.path.length === 0 ? '.' : i.path.join('.')}: ${i.message}`)
397
+ .join('; '),
398
+ issues: Object.freeze(
399
+ issues.map((i) => ({ path: Object.freeze([...i.path]), message: i.message })),
400
+ ),
401
+ };
402
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Transport descriptors + identity helpers for `@graphorin/mcp`.
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+
7
+ export { deriveServerIdentity, formatMCPServerName } from '../helpers/identity.js';
8
+ export { validateMCPServerConfig } from '../helpers/validate-config.js';
9
+ export type {
10
+ MCPTransportConfig,
11
+ ServerIdentity,
12
+ SseTransportConfig,
13
+ StdioTransportConfig,
14
+ StreamableHttpTransportConfig,
15
+ } from './types.js';
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Transport descriptors accepted by {@link createMCPClient}.
3
+ *
4
+ * The discriminated union mirrors the three transports the
5
+ * `@modelcontextprotocol/sdk@^1.29.0` package exports:
6
+ *
7
+ * - `'stdio'` - the primary transport for local MCP servers
8
+ * started as a child process. The transport spawns the configured
9
+ * command, pipes JSON-RPC over stdio, and tears the child down on
10
+ * `client.close()`.
11
+ * - `'streamable-http'` - the current default transport for remote MCP
12
+ * servers (the spec-recommended replacement for the legacy SSE
13
+ * transport). Supports server-assigned `Mcp-Session-Id` + the
14
+ * `Last-Event-ID` resume handshake when the server advertises it on
15
+ * `initialize`.
16
+ * - `'sse'` - the deprecated legacy transport. Kept for
17
+ * back-compat with MCP servers that have not yet migrated to the
18
+ * streamable HTTP transport. The runtime emits one WARN-per-process
19
+ * on transport selection; the transport is not eligible for the
20
+ * resumable-session features.
21
+ *
22
+ * @stable
23
+ */
24
+ export type MCPTransportConfig =
25
+ | StdioTransportConfig
26
+ | StreamableHttpTransportConfig
27
+ | SseTransportConfig;
28
+
29
+ /** Options for the `'stdio'` transport. */
30
+ export interface StdioTransportConfig {
31
+ readonly kind: 'stdio';
32
+ readonly command: string;
33
+ readonly args?: ReadonlyArray<string>;
34
+ readonly env?: Readonly<Record<string, string>>;
35
+ readonly cwd?: string;
36
+ /**
37
+ * How to handle the spawned child's stderr stream. Defaults to
38
+ * `'inherit'` so operator-supplied servers print diagnostics to the
39
+ * host process's stderr; `'pipe'` collects stderr into the
40
+ * transport for in-process logging; `'ignore'` discards it.
41
+ */
42
+ readonly stderr?: 'inherit' | 'pipe' | 'ignore';
43
+ }
44
+
45
+ /** Options for the `'streamable-http'` transport. */
46
+ export interface StreamableHttpTransportConfig {
47
+ readonly kind: 'streamable-http';
48
+ readonly url: string | URL;
49
+ readonly headers?: Readonly<Record<string, string>>;
50
+ /**
51
+ * Optional pre-existing session id. Most operators leave this
52
+ * unset - the server assigns one on `initialize` and the client
53
+ * persists it for the lifetime of the connection.
54
+ */
55
+ readonly sessionId?: string;
56
+ /** Custom `fetch` implementation; defaults to the global `fetch`. */
57
+ readonly fetch?: typeof fetch;
58
+ }
59
+
60
+ /** Options for the deprecated `'sse'` transport. */
61
+ export interface SseTransportConfig {
62
+ readonly kind: 'sse';
63
+ readonly url: string | URL;
64
+ readonly headers?: Readonly<Record<string, string>>;
65
+ readonly fetch?: typeof fetch;
66
+ }
67
+
68
+ /**
69
+ * Server identity descriptor attached to every MCP-derived `Tool`.
70
+ * Mirrors the shape consumed by the tool-registry collision
71
+ * resolver; the `argsHash` / `urlHostname` fields are the
72
+ * disambiguation keys the registry uses when surfacing collision
73
+ * resolutions, while the canonical `id` field is the operator-
74
+ * facing label.
75
+ *
76
+ * @stable
77
+ */
78
+ export type ServerIdentity =
79
+ | {
80
+ readonly kind: 'mcp-stdio';
81
+ /** Transport-derived id (W-016) - see the union TSDoc. */
82
+ readonly id: string;
83
+ readonly command: string;
84
+ readonly argsHash: string;
85
+ readonly serverInfoName?: string;
86
+ /** Self-reported `serverInfo.name` - display/logs ONLY, never identity. */
87
+ readonly reportedServerName?: string;
88
+ }
89
+ | {
90
+ readonly kind: 'mcp-streamable-http';
91
+ /** Transport-derived id including a non-default port (W-016). */
92
+ readonly id: string;
93
+ readonly urlHostname: string;
94
+ readonly urlPath: string;
95
+ readonly serverInfoName?: string;
96
+ /** Self-reported `serverInfo.name` - display/logs ONLY, never identity. */
97
+ readonly reportedServerName?: string;
98
+ }
99
+ | {
100
+ readonly kind: 'mcp-sse';
101
+ /** Transport-derived id including a non-default port (W-016). */
102
+ readonly id: string;
103
+ readonly urlHostname: string;
104
+ readonly urlPath: string;
105
+ readonly serverInfoName?: string;
106
+ /** Self-reported `serverInfo.name` - display/logs ONLY, never identity. */
107
+ readonly reportedServerName?: string;
108
+ };