@corbits/mcp 0.3.0 → 0.4.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 (124) hide show
  1. package/README.md +120 -26
  2. package/dist/auth/callback.d.ts +14 -0
  3. package/dist/auth/callback.js +79 -0
  4. package/dist/auth/challenge.d.ts +9 -0
  5. package/dist/auth/challenge.js +52 -0
  6. package/dist/auth/contracts.d.ts +18 -0
  7. package/dist/auth/contracts.js +19 -0
  8. package/dist/auth/http.d.ts +2 -0
  9. package/dist/auth/http.js +2 -0
  10. package/dist/auth/index.d.ts +6 -0
  11. package/dist/auth/index.js +6 -0
  12. package/dist/auth/login.d.ts +62 -0
  13. package/dist/auth/login.js +175 -0
  14. package/dist/auth/oauth-http.d.ts +6 -0
  15. package/dist/auth/oauth-http.js +32 -0
  16. package/dist/auth/resource-server.d.ts +25 -0
  17. package/dist/auth/resource-server.js +94 -0
  18. package/dist/auth/url-policy.d.ts +10 -0
  19. package/dist/auth/url-policy.js +35 -0
  20. package/dist/client/resource-budget.d.ts +1 -0
  21. package/dist/client/resource-budget.js +1 -0
  22. package/dist/client/transport.d.ts +10 -0
  23. package/dist/client/transport.js +1 -0
  24. package/dist/client.d.ts +208 -26
  25. package/dist/client.js +1128 -90
  26. package/dist/extensions/apps.d.ts +58 -0
  27. package/dist/extensions/apps.js +55 -0
  28. package/dist/extensions/elicitation.d.ts +18 -0
  29. package/dist/extensions/elicitation.js +55 -0
  30. package/dist/extensions/index.d.ts +5 -0
  31. package/dist/extensions/index.js +5 -0
  32. package/dist/extensions/maturity.d.ts +44 -0
  33. package/dist/extensions/maturity.js +24 -0
  34. package/dist/extensions/skills/contracts.d.ts +53 -0
  35. package/dist/extensions/skills/contracts.js +1 -0
  36. package/dist/extensions/skills/index.d.ts +52 -0
  37. package/dist/extensions/skills/index.js +402 -0
  38. package/dist/extensions/tasks/contracts.d.ts +39 -0
  39. package/dist/extensions/tasks/contracts.js +1 -0
  40. package/dist/extensions/tasks/file-store.d.ts +12 -0
  41. package/dist/extensions/tasks/file-store.js +152 -0
  42. package/dist/extensions/tasks/index.d.ts +82 -0
  43. package/dist/extensions/tasks/index.js +221 -0
  44. package/dist/extensions/tasks/record.d.ts +3 -0
  45. package/dist/extensions/tasks/record.js +37 -0
  46. package/dist/hub/discover.d.ts +5 -1
  47. package/dist/hub/discover.js +36 -13
  48. package/dist/index.d.ts +3 -2
  49. package/dist/index.js +4 -2
  50. package/dist/interchange/client/capabilities.d.ts +4 -0
  51. package/dist/interchange/client/capabilities.js +53 -0
  52. package/dist/interchange/client/catalog.d.ts +14 -0
  53. package/dist/interchange/client/catalog.js +52 -0
  54. package/dist/interchange/client/contracts.d.ts +47 -0
  55. package/dist/interchange/client/contracts.js +1 -0
  56. package/dist/interchange/client/projection.d.ts +6 -0
  57. package/dist/interchange/client/projection.js +92 -0
  58. package/dist/interchange/client/skills.d.ts +16 -0
  59. package/dist/interchange/client/skills.js +87 -0
  60. package/dist/interchange/client.d.ts +8 -0
  61. package/dist/interchange/client.js +6 -0
  62. package/dist/interchange/server/contracts.d.ts +65 -0
  63. package/dist/interchange/server/contracts.js +1 -0
  64. package/dist/interchange/server.d.ts +5 -0
  65. package/dist/interchange/server.js +172 -0
  66. package/dist/naming.d.ts +2 -0
  67. package/dist/naming.js +7 -0
  68. package/dist/oauth-discovery.d.ts +107 -1
  69. package/dist/oauth-discovery.js +292 -56
  70. package/dist/protocol/content.d.ts +35 -0
  71. package/dist/protocol/content.js +64 -0
  72. package/dist/protocol/envelopes.d.ts +14 -0
  73. package/dist/protocol/envelopes.js +22 -0
  74. package/dist/protocol/errors.d.ts +26 -0
  75. package/dist/protocol/errors.js +31 -0
  76. package/dist/protocol/extensions.d.ts +34 -0
  77. package/dist/protocol/extensions.js +86 -0
  78. package/dist/protocol/headers.d.ts +12 -0
  79. package/dist/protocol/headers.js +128 -0
  80. package/dist/protocol/index.d.ts +11 -0
  81. package/dist/protocol/index.js +11 -0
  82. package/dist/protocol/json-schema.d.ts +8 -0
  83. package/dist/protocol/json-schema.js +152 -0
  84. package/dist/protocol/legacy.d.ts +12 -0
  85. package/dist/protocol/legacy.js +18 -0
  86. package/dist/protocol/methods.d.ts +116 -0
  87. package/dist/protocol/methods.js +78 -0
  88. package/dist/protocol/resource-budget.d.ts +9 -0
  89. package/dist/protocol/resource-budget.js +206 -0
  90. package/dist/protocol/schemas.d.ts +115 -0
  91. package/dist/protocol/schemas.js +553 -0
  92. package/dist/protocol/types.d.ts +295 -0
  93. package/dist/protocol/types.js +3 -0
  94. package/dist/protocol/ui.d.ts +3 -0
  95. package/dist/protocol/ui.js +18 -0
  96. package/dist/server/contracts.d.ts +67 -0
  97. package/dist/server/contracts.js +1 -0
  98. package/dist/server/errors.d.ts +6 -0
  99. package/dist/server/errors.js +21 -0
  100. package/dist/server/extensions.d.ts +7 -0
  101. package/dist/server/extensions.js +61 -0
  102. package/dist/server/fixtures.d.ts +2 -0
  103. package/dist/server/fixtures.js +34 -0
  104. package/dist/server/legacy.d.ts +9 -0
  105. package/dist/server/legacy.js +435 -0
  106. package/dist/server/tools.d.ts +17 -0
  107. package/dist/server/tools.js +93 -0
  108. package/dist/server.d.ts +14 -0
  109. package/dist/server.js +406 -0
  110. package/dist/sidecar-bundle.d.ts +7 -7
  111. package/dist/sidecar-bundle.js +36 -29
  112. package/dist/test-server.js +12 -3
  113. package/dist/tool.d.ts +8 -0
  114. package/dist/tool.js +35 -19
  115. package/dist/url.js +8 -3
  116. package/docs/conformance.md +135 -0
  117. package/docs/integration-status.md +98 -0
  118. package/docs/legacy-server.md +58 -0
  119. package/docs/migration.md +143 -0
  120. package/docs/public-api.md +106 -0
  121. package/examples/README.md +28 -0
  122. package/examples/standalone-client.ts +14 -0
  123. package/examples/standalone-server.ts +37 -0
  124. package/package.json +50 -15
@@ -0,0 +1,553 @@
1
+ import { type } from "arktype";
2
+ import { validateContentBlock, validateResourceContents } from "./content.js";
3
+ import { validateUiToolMeta } from "./ui.js";
4
+ import { McpError, McpErrorCode } from "./errors.js";
5
+ import { extensionEnabled, MCP_EXT_TASKS, MCP_EXT_SKILLS, } from "./extensions.js";
6
+ import { methodDescriptor } from "./methods.js";
7
+ import { assertJsonBounds, assertResourceJsonBounds, validateInstance, validateSchema, } from "./json-schema.js";
8
+ import { MCP_PROTOCOL_VERSION, } from "./types.js";
9
+ export const McpToolSchema = type({
10
+ name: "string",
11
+ "title?": "string",
12
+ "description?": "string",
13
+ inputSchema: "Record<string, unknown>",
14
+ "outputSchema?": "Record<string, unknown>",
15
+ "annotations?": {
16
+ "title?": "string",
17
+ "readOnlyHint?": "boolean",
18
+ "destructiveHint?": "boolean",
19
+ "idempotentHint?": "boolean",
20
+ "openWorldHint?": "boolean",
21
+ },
22
+ "icons?": "unknown[]",
23
+ "_meta?": "Record<string, unknown>",
24
+ });
25
+ export const PromptSchema = type({
26
+ name: "string",
27
+ "title?": "string",
28
+ "description?": "string",
29
+ "arguments?": type({
30
+ name: "string",
31
+ "description?": "string",
32
+ "required?": "boolean",
33
+ }).array(),
34
+ "icons?": "unknown[]",
35
+ "_meta?": "Record<string, unknown>",
36
+ });
37
+ export const ResourceSchema = type({
38
+ uri: "string",
39
+ name: "string",
40
+ "title?": "string",
41
+ "description?": "string",
42
+ "mimeType?": "string",
43
+ "size?": "number",
44
+ "annotations?": "Record<string, unknown>",
45
+ "icons?": "unknown[]",
46
+ "_meta?": "Record<string, unknown>",
47
+ });
48
+ export const TemplateSchema = type({
49
+ uriTemplate: "string",
50
+ name: "string",
51
+ "title?": "string",
52
+ "description?": "string",
53
+ "mimeType?": "string",
54
+ "annotations?": "Record<string, unknown>",
55
+ "icons?": "unknown[]",
56
+ "_meta?": "Record<string, unknown>",
57
+ });
58
+ export const SkillSchema = type({
59
+ uri: "string",
60
+ frontmatter: { name: "string", description: "string", "[string]": "unknown" },
61
+ resources: "unknown",
62
+ "_meta?": "Record<string, unknown>",
63
+ });
64
+ export const TaskHandleSchema = type({
65
+ taskId: "string",
66
+ status: "'working' | 'input_required' | 'completed' | 'failed' | 'cancelled'",
67
+ createdAt: "string",
68
+ lastUpdatedAt: "string",
69
+ ttlMs: "number | null",
70
+ "pollIntervalMs?": "number",
71
+ "statusMessage?": "string",
72
+ "_meta?": "Record<string, unknown>",
73
+ });
74
+ export const ToolCallResult = type({
75
+ content: "unknown",
76
+ "isError?": "boolean",
77
+ "structuredContent?": "unknown",
78
+ "_meta?": "Record<string, unknown>",
79
+ });
80
+ export const PromptResult = type({
81
+ "description?": "string",
82
+ messages: "unknown[]",
83
+ "_meta?": "Record<string, unknown>",
84
+ });
85
+ export const CompletionResult = type({
86
+ completion: { values: "string[]", "total?": "number", "hasMore?": "boolean" },
87
+ });
88
+ export const DiscoverResult = type({
89
+ supportedVersions: "string[]",
90
+ capabilities: "Record<string, unknown>",
91
+ "instructions?": "string",
92
+ "_meta?": "Record<string, unknown>",
93
+ });
94
+ export const JsonRpcResponse = type({
95
+ jsonrpc: "'2.0'",
96
+ id: "string | number | null",
97
+ "result?": "unknown",
98
+ "error?": { code: "number", message: "string", "data?": "unknown" },
99
+ });
100
+ function invalid(message, code = McpErrorCode.InvalidParams) {
101
+ throw new McpError(message, undefined, code);
102
+ }
103
+ export function record(value, label = "value") {
104
+ if (typeof value !== "object" || value === null || Array.isArray(value))
105
+ invalid(`${label} must be an object`);
106
+ return value;
107
+ }
108
+ function check(validator, value, label) {
109
+ const parsed = validator(value);
110
+ if (parsed instanceof type.errors)
111
+ invalid(`malformed ${label}: ${parsed.summary}`);
112
+ }
113
+ function string(value, label) {
114
+ if (typeof value !== "string")
115
+ invalid(`${label} must be a string`);
116
+ }
117
+ function integer(value, label) {
118
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
119
+ invalid(`${label} must be a nonnegative safe integer`);
120
+ }
121
+ function meta(value) {
122
+ if (value._meta !== undefined)
123
+ record(value._meta, "_meta");
124
+ }
125
+ function list(value, label, validate) {
126
+ if (!Array.isArray(value))
127
+ invalid(`${label} must be an array`);
128
+ for (const item of value)
129
+ validate(item);
130
+ }
131
+ /** Tokens are protocol integers (not fractional progress values) or strings. */
132
+ export function validateProgressToken(value) {
133
+ if (typeof value !== "string" &&
134
+ !(typeof value === "number" && Number.isSafeInteger(value)))
135
+ invalid("invalid progress token");
136
+ }
137
+ // Restricted elicitation union, not arbitrary JSON Schema. Reference:
138
+ // specification@271ecc9accafdd9b83a3c869fa67c22953b2af80/schema/2026-07-28/schema.ts
139
+ const elicitationAnnotations = {
140
+ "title?": "string",
141
+ "description?": "string",
142
+ };
143
+ const enumOption = type({ const: "string", title: "string" }).onUndeclaredKey("reject");
144
+ const elicitationProperty = type({
145
+ ...elicitationAnnotations,
146
+ type: "'string'",
147
+ "default?": "string",
148
+ "minLength?": "number",
149
+ "maxLength?": "number",
150
+ "format?": "'email' | 'uri' | 'date' | 'date-time'",
151
+ })
152
+ .onUndeclaredKey("reject")
153
+ .or(type({
154
+ ...elicitationAnnotations,
155
+ type: "'number' | 'integer'",
156
+ "default?": "number",
157
+ "minimum?": "number",
158
+ "maximum?": "number",
159
+ }).onUndeclaredKey("reject"))
160
+ .or(type({
161
+ ...elicitationAnnotations,
162
+ type: "'boolean'",
163
+ "default?": "boolean",
164
+ }).onUndeclaredKey("reject"))
165
+ .or(type({
166
+ ...elicitationAnnotations,
167
+ type: "'string'",
168
+ enum: "string[]",
169
+ "enumNames?": "string[]",
170
+ "default?": "string",
171
+ }).onUndeclaredKey("reject"))
172
+ .or(type({
173
+ ...elicitationAnnotations,
174
+ type: "'string'",
175
+ oneOf: enumOption.array(),
176
+ "default?": "string",
177
+ }).onUndeclaredKey("reject"))
178
+ .or(type({
179
+ ...elicitationAnnotations,
180
+ type: "'array'",
181
+ "minItems?": "number",
182
+ "maxItems?": "number",
183
+ "default?": "string[]",
184
+ items: type({ type: "'string'", enum: "string[]" })
185
+ .onUndeclaredKey("reject")
186
+ .or(type({ anyOf: enumOption.array() }).onUndeclaredKey("reject")),
187
+ }).onUndeclaredKey("reject"));
188
+ export function validateInputRequests(value, context = {}) {
189
+ for (const input of Object.values(record(value, "inputRequests"))) {
190
+ const request = record(input, "input request");
191
+ if (request.method === "sampling/createMessage" ||
192
+ request.method === "roots/list")
193
+ invalid(`unsupported input capability ${String(request.method)}`, McpErrorCode.MissingRequiredClientCapability);
194
+ if (request.method !== "elicitation/create")
195
+ invalid("unsupported input request method");
196
+ const params = record(request.params, "elicitation params");
197
+ string(params.message, "elicitation message");
198
+ if (params.mode === "url") {
199
+ string(params.url, "elicitation URL");
200
+ try {
201
+ const url = new URL(params.url);
202
+ if (url.protocol !== "https:" && url.protocol !== "http:")
203
+ invalid("unsupported elicitation URL scheme");
204
+ }
205
+ catch {
206
+ invalid("invalid elicitation URL");
207
+ }
208
+ }
209
+ else if (params.mode === undefined || params.mode === "form") {
210
+ const schema = record(params.requestedSchema, "requestedSchema");
211
+ if (schema.type !== "object")
212
+ invalid("elicitation schema must be an object schema");
213
+ for (const property of Object.values(record(schema.properties, "elicitation properties"))) {
214
+ check(elicitationProperty, property, "elicitation property");
215
+ }
216
+ validateSchema(schema);
217
+ }
218
+ else
219
+ invalid("unsupported elicitation mode");
220
+ if (context.clientCapabilities !== undefined) {
221
+ const capability = record(context.clientCapabilities.elicitation ?? {}, "elicitation capability");
222
+ const mode = params.mode ?? "form";
223
+ const legacyForm = context.clientCapabilities.elicitation !== undefined &&
224
+ Object.keys(capability).length === 0 &&
225
+ mode === "form";
226
+ if (!legacyForm && !Object.hasOwn(capability, String(mode)))
227
+ invalid(`missing elicitation ${String(mode)} capability`, McpErrorCode.MissingRequiredClientCapability);
228
+ }
229
+ }
230
+ }
231
+ export function validateInputResponses(value) {
232
+ for (const response of Object.values(record(value, "inputResponses"))) {
233
+ const input = record(response, "input response");
234
+ if (input.action !== "accept" &&
235
+ input.action !== "decline" &&
236
+ input.action !== "cancel")
237
+ invalid("invalid elicitation action");
238
+ if (input.content !== undefined)
239
+ for (const field of Object.values(record(input.content))) {
240
+ if (typeof field !== "string" &&
241
+ typeof field !== "number" &&
242
+ typeof field !== "boolean" &&
243
+ !(Array.isArray(field) &&
244
+ field.every((item) => typeof item === "string")))
245
+ invalid("invalid elicitation content");
246
+ }
247
+ }
248
+ }
249
+ export function validateTask(value, detailed = false, context = {}) {
250
+ check(TaskHandleSchema, value, "task");
251
+ const task = record(value);
252
+ for (const field of ["createdAt", "lastUpdatedAt"])
253
+ if (typeof task[field] !== "string" ||
254
+ !/^\d{4}-\d\d-\d\dT/.test(task[field]) ||
255
+ !Number.isFinite(Date.parse(task[field])))
256
+ invalid(`invalid task ${field}`);
257
+ if (task.ttlMs !== null)
258
+ integer(task.ttlMs, "task ttlMs");
259
+ if (task.pollIntervalMs !== undefined)
260
+ integer(task.pollIntervalMs, "task pollIntervalMs");
261
+ if (detailed) {
262
+ if (task.status === "input_required")
263
+ validateInputRequests(task.inputRequests, context);
264
+ if (task.status === "completed") {
265
+ const result = record(task.result, "completed task result");
266
+ if (result.resultType !== undefined && result.resultType !== "complete")
267
+ invalid("completed task requires a final complete result");
268
+ validateResult("tools/call", result, context);
269
+ }
270
+ if (task.status === "failed")
271
+ check(type({ code: "number", message: "string", "data?": "unknown" }), task.error, "task error");
272
+ }
273
+ return value;
274
+ }
275
+ export function validateSkill(value) {
276
+ check(SkillSchema, value, "skill");
277
+ const skill = record(value);
278
+ if (skill.resources !== "dynamic") {
279
+ const uris = new Set();
280
+ list(skill.resources, "skill resources", (item) => {
281
+ const resource = record(item);
282
+ string(resource.uri, "skill resource URI");
283
+ string(resource.digest, "skill digest");
284
+ integer(resource.size, "skill size");
285
+ if (!/^sha256:[a-f0-9]{64}$/.test(resource.digest))
286
+ invalid("invalid skill digest");
287
+ if (uris.has(resource.uri))
288
+ invalid("duplicate skill resource URI");
289
+ uris.add(resource.uri);
290
+ });
291
+ if (!uris.has(String(skill.uri)))
292
+ invalid("skill manifest omits SKILL.md");
293
+ }
294
+ return value;
295
+ }
296
+ function eligibility(method, context) {
297
+ const descriptor = methodDescriptor(method);
298
+ if (descriptor === undefined)
299
+ invalid(`unknown method ${method}`, McpErrorCode.MethodNotFound);
300
+ if (descriptor.extension !== undefined &&
301
+ (context.era === "legacy" ||
302
+ !extensionEnabled(context, descriptor.extension)))
303
+ invalid(`extension ${descriptor.extension} is not enabled`);
304
+ if (context.serverCapabilities !== undefined &&
305
+ descriptor.capability !== undefined &&
306
+ !Object.hasOwn(context.serverCapabilities, descriptor.capability))
307
+ invalid(`server does not advertise ${descriptor.capability}`, McpErrorCode.MethodNotFound);
308
+ }
309
+ export function validateRequest(method, value, context = {}) {
310
+ eligibility(method, context);
311
+ const params = record(value ?? {}, "params");
312
+ assertJsonBounds(params);
313
+ meta(params);
314
+ if (params._meta !== undefined) {
315
+ const metadata = record(params._meta);
316
+ if (Object.hasOwn(metadata, "progressToken"))
317
+ validateProgressToken(metadata.progressToken);
318
+ }
319
+ if (context.era !== "legacy") {
320
+ const metadata = record(params._meta, "request _meta");
321
+ const version = metadata["io.modelcontextprotocol/protocolVersion"];
322
+ if (version !== (context.protocolVersion ?? MCP_PROTOCOL_VERSION))
323
+ invalid("unsupported protocol version", McpErrorCode.UnsupportedProtocolVersion);
324
+ record(metadata["io.modelcontextprotocol/clientCapabilities"], "clientCapabilities");
325
+ }
326
+ if (params.cursor !== undefined)
327
+ string(params.cursor, "cursor");
328
+ if (params.requestState !== undefined)
329
+ string(params.requestState, "requestState");
330
+ if (params.inputResponses !== undefined) {
331
+ if (!methodDescriptor(method)?.inputRequired && method !== "tasks/update")
332
+ invalid(`inputResponses not allowed for ${method}`);
333
+ validateInputResponses(params.inputResponses);
334
+ }
335
+ if (context.era === "legacy" &&
336
+ (params.inputResponses !== undefined || params.requestState !== undefined))
337
+ invalid("MRTR is not available on legacy protocol");
338
+ if (["tools/call", "prompts/get"].includes(method))
339
+ string(params.name, "name");
340
+ if (["resources/read", "skills/get", "resources/directory/read"].includes(method))
341
+ string(params.uri, "uri");
342
+ if (method.startsWith("tasks/"))
343
+ string(params.taskId, "taskId");
344
+ if (method === "tasks/update")
345
+ validateInputResponses(params.inputResponses);
346
+ if (method === "tools/call") {
347
+ if (params.arguments !== undefined)
348
+ record(params.arguments, "arguments");
349
+ if (context.inputSchema !== undefined)
350
+ validateInstance(context.inputSchema, params.arguments ?? {});
351
+ }
352
+ if (method === "prompts/get" && params.arguments !== undefined)
353
+ for (const arg of Object.values(record(params.arguments)))
354
+ string(arg, "prompt argument");
355
+ if (method === "completion/complete") {
356
+ const ref = record(params.ref);
357
+ if (ref.type === "ref/prompt")
358
+ string(ref.name, "ref name");
359
+ else if (ref.type === "ref/resource")
360
+ string(ref.uri, "ref URI");
361
+ else
362
+ invalid("invalid completion ref");
363
+ const argument = record(params.argument);
364
+ string(argument.name, "argument name");
365
+ string(argument.value, "argument value");
366
+ }
367
+ if (method === "subscriptions/listen")
368
+ validateFilter(params.notifications, context);
369
+ if (method === "resources/directory/read") {
370
+ const extensions = record(context.serverCapabilities?.extensions ?? {}, "server extensions");
371
+ if (record(extensions[MCP_EXT_SKILLS] ?? {}).directoryRead !== true)
372
+ invalid("server does not advertise directoryRead");
373
+ }
374
+ if (method.startsWith("skills/") &&
375
+ context.serverCapabilities !== undefined &&
376
+ !Object.hasOwn(record(context.serverCapabilities.extensions ?? {}), MCP_EXT_SKILLS))
377
+ invalid("server does not advertise skills");
378
+ return params;
379
+ }
380
+ function validateFilter(value, context) {
381
+ const filter = record(value, "notifications");
382
+ for (const key of [
383
+ "toolsListChanged",
384
+ "promptsListChanged",
385
+ "resourcesListChanged",
386
+ ])
387
+ if (filter[key] !== undefined && typeof filter[key] !== "boolean")
388
+ invalid(`invalid ${key}`);
389
+ for (const key of ["resourceSubscriptions", "taskIds"])
390
+ if (filter[key] !== undefined)
391
+ list(filter[key], key, (item) => string(item, key));
392
+ if (filter.taskIds !== undefined && !extensionEnabled(context, MCP_EXT_TASKS))
393
+ invalid("tasks extension is not enabled");
394
+ }
395
+ export function validateResult(method, value, context = {}) {
396
+ eligibility(method, context);
397
+ const result = record(value, "result");
398
+ if (method === "resources/read" && context.maxDecodedBytes !== undefined)
399
+ assertResourceJsonBounds(result, context.maxDecodedBytes);
400
+ else
401
+ assertJsonBounds(result);
402
+ meta(result);
403
+ const kind = result.resultType === undefined ? "complete" : result.resultType;
404
+ const descriptor = methodDescriptor(method);
405
+ if (kind !== "complete" && kind !== "input_required" && kind !== "task")
406
+ invalid(`unrecognized resultType for ${method}`);
407
+ if (kind !== "complete" && context.era === "legacy")
408
+ invalid("polymorphic result on legacy protocol");
409
+ if (kind === "task") {
410
+ if (!extensionEnabled(context, MCP_EXT_TASKS))
411
+ invalid("tasks extension is not enabled");
412
+ if (!descriptor?.task)
413
+ invalid(`task result is not allowed for ${method}`);
414
+ validateTask(result, false, context);
415
+ return result;
416
+ }
417
+ if (kind === "input_required") {
418
+ if (!descriptor?.inputRequired)
419
+ invalid(`input_required is not allowed for ${method}`);
420
+ if (result.inputRequests === undefined && result.requestState === undefined)
421
+ invalid("input_required requires inputRequests or requestState");
422
+ if (result.requestState !== undefined)
423
+ string(result.requestState, "requestState");
424
+ if (result.inputRequests !== undefined)
425
+ validateInputRequests(result.inputRequests, context);
426
+ return result;
427
+ }
428
+ if (method !== "tasks/get" &&
429
+ result.ttlMs !== undefined &&
430
+ (typeof result.ttlMs !== "number" || !Number.isFinite(result.ttlMs)))
431
+ invalid("invalid ttlMs");
432
+ if (result.cacheScope !== undefined &&
433
+ result.cacheScope !== "public" &&
434
+ result.cacheScope !== "private")
435
+ invalid("invalid cacheScope");
436
+ if (result.nextCursor !== undefined)
437
+ string(result.nextCursor, "nextCursor");
438
+ switch (method) {
439
+ case "server/discover":
440
+ check(DiscoverResult, result, method);
441
+ break;
442
+ case "tools/list":
443
+ list(result.tools, "tools", (tool) => {
444
+ check(McpToolSchema, tool, "tool");
445
+ const entry = record(tool);
446
+ validateSchema(entry.inputSchema);
447
+ if (record(entry.inputSchema).type !== "object")
448
+ invalid("tool inputSchema must have type object");
449
+ if (entry._meta !== undefined) {
450
+ const metadata = record(entry._meta);
451
+ if (metadata.ui !== undefined)
452
+ validateUiToolMeta(metadata.ui);
453
+ }
454
+ if (entry.outputSchema !== undefined)
455
+ validateSchema(entry.outputSchema);
456
+ });
457
+ break;
458
+ case "prompts/list":
459
+ list(result.prompts, "prompts", (prompt) => check(PromptSchema, prompt, "prompt"));
460
+ break;
461
+ case "resources/list":
462
+ case "resources/directory/read":
463
+ list(result.resources, "resources", (resource) => check(ResourceSchema, resource, "resource"));
464
+ break;
465
+ case "resources/templates/list":
466
+ list(result.resourceTemplates, "resourceTemplates", (template) => check(TemplateSchema, template, "resource template"));
467
+ break;
468
+ case "tools/call":
469
+ check(ToolCallResult, result, method);
470
+ list(result.content, "tool content", (block) => {
471
+ validateContentBlock(block);
472
+ });
473
+ if (context.outputSchema !== undefined && result.isError !== true)
474
+ validateInstance(context.outputSchema, result.structuredContent);
475
+ break;
476
+ case "prompts/get":
477
+ check(PromptResult, result, method);
478
+ list(result.messages, "prompt messages", (value) => {
479
+ const message = record(value);
480
+ if (message.role !== "user" && message.role !== "assistant")
481
+ invalid("invalid prompt message role");
482
+ validateContentBlock(message.content);
483
+ });
484
+ break;
485
+ case "resources/read":
486
+ list(result.contents, "contents", (item) => {
487
+ validateResourceContents(item);
488
+ });
489
+ break;
490
+ case "completion/complete":
491
+ check(CompletionResult, result, method);
492
+ if (record(result.completion).values.length > 100)
493
+ invalid("too many completion values");
494
+ break;
495
+ case "tasks/get":
496
+ validateTask(result, true, context);
497
+ break;
498
+ case "skills/list":
499
+ list(result.skills, "skills", (skill) => {
500
+ validateSkill(skill);
501
+ });
502
+ break;
503
+ case "skills/get":
504
+ validateSkill(result.skill);
505
+ break;
506
+ }
507
+ return result;
508
+ }
509
+ export function validateNotification(method, value, context = {}) {
510
+ const params = record(value ?? {}, "notification params");
511
+ assertJsonBounds(params);
512
+ meta(params);
513
+ switch (method) {
514
+ case "notifications/progress":
515
+ validateProgressToken(params.progressToken);
516
+ if (typeof params.progress !== "number" ||
517
+ !Number.isFinite(params.progress))
518
+ invalid("invalid progress");
519
+ if (params.total !== undefined &&
520
+ (typeof params.total !== "number" || !Number.isFinite(params.total)))
521
+ invalid("invalid progress total");
522
+ if (params.message !== undefined)
523
+ string(params.message, "progress message");
524
+ break;
525
+ case "notifications/cancelled":
526
+ if (typeof params.requestId !== "string" &&
527
+ typeof params.requestId !== "number")
528
+ invalid("invalid cancelled requestId");
529
+ break;
530
+ case "notifications/tasks":
531
+ if (!extensionEnabled(context, MCP_EXT_TASKS))
532
+ invalid("tasks extension is not enabled");
533
+ validateTask(params, true, context);
534
+ break;
535
+ case "notifications/subscriptions/acknowledged":
536
+ validateFilter(params.notifications, context);
537
+ break;
538
+ case "notifications/resources/updated":
539
+ string(params.uri, "resource URI");
540
+ break;
541
+ case "notifications/tools/list_changed":
542
+ case "notifications/prompts/list_changed":
543
+ case "notifications/resources/list_changed":
544
+ break;
545
+ case "notifications/initialized":
546
+ if (context.era !== "legacy")
547
+ invalid("initialized is legacy only");
548
+ break;
549
+ default:
550
+ invalid(`unsupported notification ${method}`, McpErrorCode.MethodNotFound);
551
+ }
552
+ return params;
553
+ }