stitchkit 0.36.1 → 0.37.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 (81) hide show
  1. package/README.md +4 -2
  2. package/dist/browser/http.d.ts +2 -2
  3. package/dist/browser/http.d.ts.map +1 -1
  4. package/dist/cli.js +3 -3
  5. package/dist/contract/define.d.ts +28 -2
  6. package/dist/contract/define.d.ts.map +1 -1
  7. package/dist/contract/errors.d.ts.map +1 -1
  8. package/dist/contract/index.js +1 -1
  9. package/dist/{index-dvrn81q4.js → index-4pvtq6h2.js} +83 -34
  10. package/dist/{index-0ed3bx43.js → index-frfyw9fa.js} +2 -2
  11. package/dist/{index-pmftwk2a.js → index-g3jrbd0z.js} +26 -0
  12. package/dist/{index-82gncajj.js → index-s6yhmg1k.js} +555 -337
  13. package/dist/{index-bx49hskg.js → index-xq45akyd.js} +1 -1
  14. package/dist/index.js +1 -1
  15. package/dist/node.d.ts +1 -1
  16. package/dist/node.d.ts.map +1 -1
  17. package/dist/node.js +7 -3
  18. package/dist/observability/index.js +3 -3
  19. package/dist/server/bun.d.ts +32 -0
  20. package/dist/server/bun.d.ts.map +1 -0
  21. package/dist/server/context.d.ts +1 -1
  22. package/dist/server/context.d.ts.map +1 -1
  23. package/dist/server/create.d.ts +2 -3
  24. package/dist/server/create.d.ts.map +1 -1
  25. package/dist/server/implement.d.ts.map +1 -1
  26. package/dist/server/index.d.ts +3 -2
  27. package/dist/server/index.d.ts.map +1 -1
  28. package/dist/server/index.js +25 -5
  29. package/dist/server/logger.d.ts +5 -5
  30. package/dist/server/logger.d.ts.map +1 -1
  31. package/dist/server/logging.d.ts +4 -1
  32. package/dist/server/logging.d.ts.map +1 -1
  33. package/dist/server/middleware/auth.d.ts +2 -2
  34. package/dist/server/middleware/auth.d.ts.map +1 -1
  35. package/dist/server/request-body.d.ts +4 -0
  36. package/dist/server/request-body.d.ts.map +1 -0
  37. package/dist/server/router.d.ts +3 -3
  38. package/dist/server/router.d.ts.map +1 -1
  39. package/dist/server/socket-io-config.d.ts +20 -0
  40. package/dist/server/socket-io-config.d.ts.map +1 -0
  41. package/dist/server/socket-io-node.d.ts +14 -0
  42. package/dist/server/socket-io-node.d.ts.map +1 -0
  43. package/dist/server/socket-io.d.ts +5 -32
  44. package/dist/server/socket-io.d.ts.map +1 -1
  45. package/dist/server/types.d.ts +54 -49
  46. package/dist/server/types.d.ts.map +1 -1
  47. package/dist/tools/agent.d.ts.map +1 -1
  48. package/dist/tools/execute.d.ts +46 -16
  49. package/dist/tools/execute.d.ts.map +1 -1
  50. package/dist/tools/flatten-join.d.ts +8 -0
  51. package/dist/tools/flatten-join.d.ts.map +1 -0
  52. package/dist/tools/flatten.d.ts +7 -44
  53. package/dist/tools/flatten.d.ts.map +1 -1
  54. package/dist/tools/json-schema.d.ts +1 -1
  55. package/dist/tools/json-schema.d.ts.map +1 -1
  56. package/dist/tools/manifest.d.ts.map +1 -1
  57. package/dist/tools/mcp-handler.d.ts +7 -13
  58. package/dist/tools/mcp-handler.d.ts.map +1 -1
  59. package/dist/tools/mcp.d.ts +61 -24
  60. package/dist/tools/mcp.d.ts.map +1 -1
  61. package/dist/tools/mount.d.ts +9 -6
  62. package/dist/tools/mount.d.ts.map +1 -1
  63. package/dist/tools/names.d.ts +1 -1
  64. package/dist/tools/native-mcp.d.ts +57 -0
  65. package/dist/tools/native-mcp.d.ts.map +1 -0
  66. package/dist/tools/portable-formats.d.ts +15 -0
  67. package/dist/tools/portable-formats.d.ts.map +1 -0
  68. package/dist/tools/presentation.d.ts +15 -0
  69. package/dist/tools/presentation.d.ts.map +1 -0
  70. package/dist/tools/remote.d.ts.map +1 -1
  71. package/dist/tools/schema.d.ts +3 -8
  72. package/dist/tools/schema.d.ts.map +1 -1
  73. package/dist/tools/tool-logger.d.ts.map +1 -1
  74. package/dist/tools/view-file.d.ts +5 -3
  75. package/dist/tools/view-file.d.ts.map +1 -1
  76. package/dist/tools.d.ts +7 -4
  77. package/dist/tools.d.ts.map +1 -1
  78. package/dist/tools.js +357 -95
  79. package/llms-full.txt +371 -77
  80. package/package.json +14 -11
  81. /package/dist/{index-17rdjw68.js → index-e497hxcy.js} +0 -0
package/dist/tools.js CHANGED
@@ -2,42 +2,46 @@ import {
2
2
  inputIsQuery,
3
3
  signJwt,
4
4
  verifyPkce
5
- } from "./index-bx49hskg.js";
5
+ } from "./index-xq45akyd.js";
6
6
  import {
7
7
  DEFAULT_CORS_ALLOW_HEADERS
8
8
  } from "./index-czmqks7r.js";
9
9
  import {
10
10
  assertToolName,
11
11
  assertUniqueToolName,
12
+ buildToolPresentationSchema,
12
13
  coerceJsonArgs,
13
14
  collectTools,
14
15
  createCli,
15
16
  createToolRunner,
17
+ executeToolMethod,
16
18
  fetchGuarded,
17
- flattenDiscriminatedUnion,
18
- flattenUnionsDeep,
19
+ flattenToolJsonSchema,
19
20
  formatToolError,
21
+ isObjectPresentationSchema,
20
22
  pollUntil,
23
+ presentationMetadata,
21
24
  readCapped,
22
25
  toolResultFromError,
23
26
  writeDownload
24
- } from "./index-82gncajj.js";
27
+ } from "./index-s6yhmg1k.js";
25
28
  import {
26
29
  toJsonSchema
27
- } from "./index-0ed3bx43.js";
30
+ } from "./index-frfyw9fa.js";
28
31
  import {
29
32
  isWithinDir
30
33
  } from "./index-x3fcszf8.js";
31
34
  import {
32
35
  AppError,
36
+ formatZodError,
33
37
  getTraceId,
34
38
  isRecord,
35
39
  mergeMeta,
36
40
  typedEntries
37
- } from "./index-17rdjw68.js";
41
+ } from "./index-e497hxcy.js";
38
42
 
39
43
  // src/tools/agent.ts
40
- import { tool, zodSchema } from "ai";
44
+ import { jsonSchema, tool } from "ai";
41
45
  function mountAgent(services, config = {}) {
42
46
  const serviceList = Array.isArray(services) ? services : [services];
43
47
  const tools = {};
@@ -59,7 +63,9 @@ function mountAgent(services, config = {}) {
59
63
  assertUniqueToolName(mountable.name, Object.hasOwn(tools, mountable.name), "agent tool name");
60
64
  tools[mountable.name] = tool({
61
65
  description: mountable.method.desc,
62
- inputSchema: zodSchema(mountable.schema),
66
+ inputSchema: jsonSchema(mountable.presentationSchema, {
67
+ validate: async (value) => isRecord(value) ? { success: true, value } : { success: false, error: new Error("Tool arguments must be an object") }
68
+ }),
63
69
  execute: async (rawArgs) => {
64
70
  const args = isRecord(rawArgs) ? rawArgs : {};
65
71
  try {
@@ -102,19 +108,15 @@ function listToolNames(services) {
102
108
  }
103
109
  // src/tools/manifest.ts
104
110
  function buildToolManifest(tools) {
105
- return tools.map((t) => {
106
- let inputSchema;
107
- try {
108
- inputSchema = toJsonSchema(t.schema, "input");
109
- } catch {
110
- inputSchema = {};
111
- }
112
- return { name: t.name, description: t.method.desc, inputSchema };
113
- });
111
+ return tools.map((tool2) => ({
112
+ name: tool2.name,
113
+ description: tool2.method.desc,
114
+ inputSchema: tool2.presentationSchema
115
+ }));
114
116
  }
115
117
  // src/tools/mcp.ts
116
118
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
117
- import { z } from "zod";
119
+ import { z as z2 } from "zod";
118
120
 
119
121
  // src/tools/mcp-app.ts
120
122
  import { readFileSync } from "node:fs";
@@ -137,6 +139,192 @@ function inlineMcpAppBundle(html) {
137
139
  return html.replace(EXT_APPS_BUNDLE_PLACEHOLDER, () => bundle);
138
140
  }
139
141
 
142
+ // src/tools/native-mcp.ts
143
+ import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
144
+ import { z } from "zod";
145
+ function operationIdentity(definition) {
146
+ return {
147
+ method: definition.identity.method,
148
+ desc: definition.description,
149
+ serviceName: definition.identity.serviceName,
150
+ key: definition.identity.action,
151
+ toolName: definition.name,
152
+ scope: definition.identity.scope,
153
+ meta: definition.identity.meta,
154
+ annotations: definition.annotations,
155
+ ui: definition.ui
156
+ };
157
+ }
158
+ function isCallToolResult(value) {
159
+ return CallToolResultSchema.safeParse(value).success;
160
+ }
161
+ function nativeResultSchema(output) {
162
+ const envelope = z.custom(isCallToolResult, {
163
+ error: "Native MCP handler must return a valid CallToolResult"
164
+ });
165
+ if (!output)
166
+ return envelope;
167
+ return envelope.transform((result, context) => {
168
+ const parsed = output.safeParse(result.structuredContent);
169
+ if (!parsed.success) {
170
+ context.addIssue({
171
+ code: "custom",
172
+ message: `Invalid structuredContent: ${formatZodError(parsed.error)}`
173
+ });
174
+ return z.NEVER;
175
+ }
176
+ return { ...result, structuredContent: parsed.data };
177
+ });
178
+ }
179
+ function createNativeMcpRegistrar(server, config) {
180
+ return {
181
+ rawServer: server,
182
+ registerTool: (definition) => {
183
+ const identity = operationIdentity(definition);
184
+ const advertisedOperation = {
185
+ ...identity,
186
+ inputSchema: definition.input,
187
+ outputSchema: definition.output,
188
+ handler: () => {
189
+ return;
190
+ }
191
+ };
192
+ const mountable = {
193
+ method: advertisedOperation,
194
+ name: definition.name,
195
+ argumentSchema: definition.input,
196
+ presentationSchema: buildToolPresentationSchema({
197
+ inputSchema: definition.input,
198
+ unrepresentable: "any"
199
+ }),
200
+ shouldExtend: false
201
+ };
202
+ const [prepared] = config.prepare(mountable);
203
+ if (!prepared)
204
+ return;
205
+ const executionOperation = {
206
+ ...identity,
207
+ inputSchema: definition.input,
208
+ outputSchema: nativeResultSchema(definition.output),
209
+ handler: definition.handler
210
+ };
211
+ const toolConfig = {
212
+ description: definition.description,
213
+ inputSchema: z.looseObject({}).meta(presentationMetadata(prepared.inputSchema))
214
+ };
215
+ if (prepared.outputSchema)
216
+ toolConfig.outputSchema = prepared.outputSchema;
217
+ if (definition.annotations)
218
+ toolConfig.annotations = definition.annotations;
219
+ if (definition.ui) {
220
+ toolConfig._meta = {
221
+ ui: definition.ui,
222
+ "ui/resourceUri": definition.ui.resourceUri
223
+ };
224
+ }
225
+ server.registerTool(definition.name, toolConfig, async (rawArgs) => {
226
+ const args = isRecord(rawArgs) ? rawArgs : {};
227
+ const result = await executeToolMethod(executionOperation, definition.name, args, { ...config.context, source: "mcp" }, config.hooks, config.lifecycle, config.coerceJsonArgs ?? true, config.onOutputStrip ? (paths) => config.onOutputStrip?.(definition.name, paths) : undefined);
228
+ if (!result.ok)
229
+ return config.formatError(result, definition.name);
230
+ if (isCallToolResult(result.data))
231
+ return result.data;
232
+ return config.formatError({
233
+ ok: false,
234
+ code: "INTERNAL_SERVER_ERROR",
235
+ details: { message: "Native MCP handler returned an invalid result" }
236
+ }, definition.name);
237
+ });
238
+ }
239
+ };
240
+ }
241
+
242
+ // src/tools/portable-formats.ts
243
+ var PORTABLE_JSON_SCHEMA_FORMATS = new Set([
244
+ "date",
245
+ "time",
246
+ "date-time",
247
+ "duration",
248
+ "uri",
249
+ "uri-reference",
250
+ "uri-template",
251
+ "email",
252
+ "hostname",
253
+ "ipv4",
254
+ "ipv6",
255
+ "regex",
256
+ "uuid",
257
+ "json-pointer",
258
+ "relative-json-pointer"
259
+ ]);
260
+ function propertyPath(prefix, property) {
261
+ return prefix ? `${prefix}.${property}` : property;
262
+ }
263
+ function keywordPath(prefix, keyword) {
264
+ if (prefix && keyword.startsWith("["))
265
+ return `${prefix}${keyword}`;
266
+ return prefix ? `${prefix}.${keyword}` : keyword;
267
+ }
268
+ function findNonPortableFormats(schema, allowFormats = []) {
269
+ const allowed = new Set([...PORTABLE_JSON_SCHEMA_FORMATS, ...allowFormats]);
270
+ const found = [];
271
+ const pending = [{ value: schema, path: "" }];
272
+ while (pending.length > 0) {
273
+ const current = pending.pop();
274
+ if (!current || !isRecord(current.value))
275
+ continue;
276
+ const format = current.value.format;
277
+ if (typeof format === "string" && !allowed.has(format)) {
278
+ found.push({ path: current.path || "(root)", format });
279
+ }
280
+ const properties = current.value.properties;
281
+ if (isRecord(properties)) {
282
+ for (const [key, value] of Object.entries(properties)) {
283
+ pending.push({ value, path: propertyPath(current.path, key) });
284
+ }
285
+ }
286
+ for (const mapKey of ["$defs", "definitions", "patternProperties", "dependentSchemas"]) {
287
+ const entries = current.value[mapKey];
288
+ if (!isRecord(entries))
289
+ continue;
290
+ for (const [key, value] of Object.entries(entries)) {
291
+ pending.push({ value, path: keywordPath(current.path, `${mapKey}.${key}`) });
292
+ }
293
+ }
294
+ const directChildren = [
295
+ ["items", "[]"],
296
+ ["contains", "contains"],
297
+ ["additionalProperties", "*"],
298
+ ["unevaluatedProperties", "*"],
299
+ ["propertyNames", "propertyNames"],
300
+ ["not", "not"],
301
+ ["if", "if"],
302
+ ["then", "then"],
303
+ ["else", "else"]
304
+ ];
305
+ for (const [keyword, segment] of directChildren) {
306
+ const child = current.value[keyword];
307
+ if (isRecord(child)) {
308
+ pending.push({ value: child, path: keywordPath(current.path, segment) });
309
+ }
310
+ if (Array.isArray(child)) {
311
+ child.forEach((value, index) => {
312
+ pending.push({ value, path: keywordPath(current.path, `${segment}[${index}]`) });
313
+ });
314
+ }
315
+ }
316
+ for (const keyword of ["prefixItems", "allOf", "anyOf", "oneOf"]) {
317
+ const branches = current.value[keyword];
318
+ if (!Array.isArray(branches))
319
+ continue;
320
+ branches.forEach((value, index) => {
321
+ pending.push({ value, path: keywordPath(current.path, `${keyword}[${index}]`) });
322
+ });
323
+ }
324
+ }
325
+ return found.sort((left, right) => left.path.localeCompare(right.path) || left.format.localeCompare(right.format));
326
+ }
327
+
140
328
  // src/tools/untyped-properties.ts
141
329
  var TYPE_KEYWORDS = ["type", "enum", "const", "anyOf", "oneOf", "allOf", "$ref", "not"];
142
330
  function saysWhatItIs(schema) {
@@ -239,10 +427,10 @@ function probeSchema(schema, io) {
239
427
  function resolveOutputSchema(outputSchema) {
240
428
  if (!outputSchema)
241
429
  return null;
242
- if (outputSchema instanceof z.ZodObject) {
430
+ if (outputSchema instanceof z2.ZodObject) {
243
431
  return { schema: outputSchema, mode: "direct" };
244
432
  }
245
- return { schema: z.object({ result: outputSchema }), mode: "wrapped" };
433
+ return { schema: z2.object({ result: outputSchema }), mode: "wrapped" };
246
434
  }
247
435
  function reportIncompatible(message, policy, logger, failures) {
248
436
  if (policy === "throw") {
@@ -261,51 +449,99 @@ function throwIfFailures(failures) {
261
449
  - `)}`);
262
450
  }
263
451
  }
264
- function prepareMcpTool(mountable, policy, logger, failures, seen) {
452
+ function prepareMcpTool(mountable, config, validation, logger, failures, seen) {
453
+ const policy = validation.policy ?? "throw";
265
454
  assertUniqueToolName(mountable.name, seen.has(mountable.name), "MCP tool name");
266
455
  seen.add(mountable.name);
267
- if (!(mountable.schema instanceof z.ZodObject)) {
268
- reportIncompatible(`MCP tool "${mountable.name}" — input must be an object schema; a union, discriminated union or scalar cannot be an MCP tool input (flatten it in the contract, or drop MCP from \`expose\`)`, policy, logger, failures);
456
+ let inputJsonSchema;
457
+ try {
458
+ inputJsonSchema = buildToolPresentationSchema({
459
+ paramsSchema: mountable.method.paramsSchema,
460
+ inputSchema: mountable.method.inputSchema,
461
+ extendSchema: mountable.shouldExtend && config.extend ? config.extend.schema : undefined,
462
+ flattenUnionInput: config.flattenUnionInput,
463
+ unrepresentable: "throw"
464
+ });
465
+ } catch (err) {
466
+ reportIncompatible(`MCP tool "${mountable.name}" — input schema is not JSON Schema-compatible: ${err instanceof Error ? err.message : String(err)}`, policy, logger, failures);
269
467
  return null;
270
468
  }
271
- const inputError = probeSchema(mountable.schema, "input");
272
- if (inputError) {
273
- reportIncompatible(`MCP tool "${mountable.name}" — input schema is not JSON Schema-compatible: ${inputError}`, policy, logger, failures);
469
+ if (!isObjectPresentationSchema(inputJsonSchema)) {
470
+ reportIncompatible(`MCP tool "${mountable.name}" — input must be an object schema; a union, discriminated union or scalar cannot be an MCP tool input (flatten it in the contract, or drop MCP from \`expose\`)`, policy, logger, failures);
274
471
  return null;
275
472
  }
473
+ if (validation.requireTypedProperties) {
474
+ const allowed = new Set(validation.allowUntyped ?? []);
475
+ for (const untyped of findUntypedProperties(inputJsonSchema)) {
476
+ const path = `${mountable.name}.${untyped.path}`;
477
+ if (allowed.has(path))
478
+ continue;
479
+ const clue = untyped.description ? ` (only a description: "${untyped.description}")` : "";
480
+ reportIncompatible(`MCP tool "${mountable.name}" — input property "${untyped.path}" carries no type, enum or $ref${clue}. ` + "A model is given no way to know what to send. Widen the contract, or list it in `allowUntyped` if it is deliberately free-form.", policy === "skip" ? "warn" : policy, logger, failures);
481
+ }
482
+ }
483
+ if (validation.requirePortableFormats) {
484
+ for (const finding of findNonPortableFormats(inputJsonSchema, validation.allowFormats)) {
485
+ reportIncompatible(`MCP tool "${mountable.name}" — input property "${finding.path}" uses non-portable JSON Schema format "${finding.format}". ` + "Use a portable pattern/schema, or list the format in `allowFormats` only when every MCP client supports it.", policy === "skip" ? "warn" : policy, logger, failures);
486
+ }
487
+ }
276
488
  const resolved = resolveOutputSchema(mountable.method.outputSchema);
277
489
  if (!resolved)
278
- return { outputMode: "none" };
490
+ return { mountable, inputSchema: inputJsonSchema, outputMode: "none" };
279
491
  const outputError = probeSchema(resolved.schema, "output");
280
492
  if (outputError) {
281
493
  reportIncompatible(`MCP tool "${mountable.name}" — output schema is not JSON Schema-compatible: ${outputError}`, policy, logger, failures);
282
- return { outputMode: "none" };
494
+ return { mountable, inputSchema: inputJsonSchema, outputMode: "none" };
495
+ }
496
+ if (validation.requirePortableFormats) {
497
+ for (const finding of findNonPortableFormats(toJsonSchema(resolved.schema, "output"), validation.allowFormats)) {
498
+ reportIncompatible(`MCP tool "${mountable.name}" — output property "${finding.path}" uses non-portable JSON Schema format "${finding.format}". ` + "Use a portable pattern/schema, or list the format in `allowFormats` only when every MCP client supports it.", policy === "skip" ? "warn" : policy, logger, failures);
499
+ }
283
500
  }
284
- return { outputSchema: resolved.schema, outputMode: resolved.mode };
501
+ return {
502
+ mountable,
503
+ inputSchema: inputJsonSchema,
504
+ outputSchema: resolved.schema,
505
+ outputMode: resolved.mode
506
+ };
507
+ }
508
+ function prepareMcpSurface(services, config = {}) {
509
+ const serviceList = Array.isArray(services) ? services : [services];
510
+ const tools = serviceList.flatMap((service) => collectTools(service, "MCP", config));
511
+ return prepareMcpTools(tools, config);
285
512
  }
286
- function validateMcpSchemas(services, onIncompatibleSchema = "throw", logger, options) {
513
+ function prepareMcpTools(tools, config = {}) {
287
514
  const seen = new Set;
288
515
  const failures = [];
289
- const allowed = new Set(options?.allowUntyped ?? []);
290
- for (const service of services) {
291
- for (const mountable of collectTools(service, "MCP", options)) {
292
- const prepared = prepareMcpTool(mountable, onIncompatibleSchema, logger, failures, seen);
293
- if (!prepared || !options?.requireTypedProperties)
294
- continue;
295
- for (const untyped of findUntypedProperties(toJsonSchema(mountable.schema, "input"))) {
296
- const path = `${mountable.name}.${untyped.path}`;
297
- if (allowed.has(path))
298
- continue;
299
- const clue = untyped.description ? ` (only a description: "${untyped.description}")` : "";
300
- reportIncompatible(`MCP tool "${mountable.name}" — property "${untyped.path}" carries no type, enum or $ref${clue}. ` + "A model is given no way to know what to send. Widen the contract, or list it in `allowUntyped` if it is deliberately free-form.", onIncompatibleSchema === "skip" ? "warn" : onIncompatibleSchema, logger, failures);
301
- }
516
+ const prepared = [];
517
+ for (const mountable of tools) {
518
+ const tool2 = prepareMcpTool(mountable, config, config.schemaValidation ?? {}, config.logger, failures, seen);
519
+ if (tool2) {
520
+ Object.freeze(tool2.mountable);
521
+ prepared.push(Object.freeze(tool2));
302
522
  }
303
523
  }
304
524
  throwIfFailures(failures);
525
+ return Object.freeze(prepared);
526
+ }
527
+ function validateMcpSchemas(config) {
528
+ prepareMcpSurface(config.services, {
529
+ extend: config.extend,
530
+ flattenUnionInput: config.flattenUnionInput,
531
+ schemaValidation: config,
532
+ logger: config.logger
533
+ });
305
534
  }
306
535
  function mountMcp(mcpServer, services, config = {}) {
307
- const serviceList = Array.isArray(services) ? services : [services];
308
- const policy = config.onIncompatibleSchema ?? "throw";
536
+ const prepared = prepareMcpSurface(services, {
537
+ extend: config.extend,
538
+ flattenUnionInput: config.flattenUnionInput,
539
+ schemaValidation: config.schemaValidation,
540
+ logger: config.logger
541
+ });
542
+ mountPreparedMcp(mcpServer, prepared, config);
543
+ }
544
+ function mountPreparedMcp(mcpServer, prepared, config = {}) {
309
545
  const runTool = createToolRunner({
310
546
  source: "mcp",
311
547
  extend: config.extend,
@@ -316,57 +552,74 @@ function mountMcp(mcpServer, services, config = {}) {
316
552
  coerceJsonArgs: config.coerceJsonArgs,
317
553
  onOutputStrip: config.onOutputStrip
318
554
  });
319
- const seen = new Set;
320
- const failures = [];
321
- for (const service of serviceList) {
322
- for (const mountable of collectTools(service, "MCP", {
323
- extend: config.extend,
324
- flattenUnionInput: config.flattenUnionInput
325
- })) {
326
- const prepared = prepareMcpTool(mountable, policy, config.logger, failures, seen);
327
- if (!prepared)
328
- continue;
329
- const toolConfig = { description: mountable.method.desc, inputSchema: mountable.schema };
330
- if (prepared.outputSchema)
331
- toolConfig.outputSchema = prepared.outputSchema;
332
- if (mountable.method.annotations) {
333
- toolConfig.annotations = mountable.method.annotations;
334
- }
335
- if (mountable.method.ui) {
336
- toolConfig._meta = {
337
- ui: mountable.method.ui,
338
- "ui/resourceUri": mountable.method.ui.resourceUri
339
- };
340
- }
341
- mcpServer.registerTool(mountable.name, toolConfig, async (rawArgs) => {
342
- const args = isRecord(rawArgs) ? rawArgs : {};
343
- try {
344
- const result = await runTool(mountable, args);
345
- return formatMcpResult(result, prepared.outputMode, mountable.name, config.errorHint);
346
- } catch (err) {
347
- return formatMcpResult(toolResultFromError(err), "none", mountable.name, config.errorHint);
348
- }
349
- });
555
+ for (const descriptor of prepared) {
556
+ const { mountable } = descriptor;
557
+ const toolConfig = {
558
+ description: mountable.method.desc,
559
+ inputSchema: z2.looseObject({}).meta(presentationMetadata(descriptor.inputSchema))
560
+ };
561
+ if (descriptor.outputSchema)
562
+ toolConfig.outputSchema = descriptor.outputSchema;
563
+ if (mountable.method.annotations) {
564
+ toolConfig.annotations = mountable.method.annotations;
565
+ }
566
+ if (mountable.method.ui) {
567
+ toolConfig._meta = {
568
+ ui: mountable.method.ui,
569
+ "ui/resourceUri": mountable.method.ui.resourceUri
570
+ };
350
571
  }
572
+ mcpServer.registerTool(mountable.name, toolConfig, async (rawArgs) => {
573
+ const args = isRecord(rawArgs) ? rawArgs : {};
574
+ try {
575
+ const result = await runTool(mountable, args);
576
+ return formatMcpResult(result, descriptor.outputMode, mountable.name, config.errorHint);
577
+ } catch (err) {
578
+ return formatMcpResult(toolResultFromError(err), "none", mountable.name, config.errorHint);
579
+ }
580
+ });
351
581
  }
352
- throwIfFailures(failures);
353
582
  }
354
583
  function buildMcpServer(config, auth) {
355
- const server = new McpServer(config.serverInfo, config.instructions ? { instructions: config.instructions } : undefined);
356
584
  const services = typeof config.services === "function" ? config.services(auth) : config.services;
585
+ const prepared = prepareMcpSurface(services, {
586
+ extend: config.extend,
587
+ flattenUnionInput: config.flattenUnionInput,
588
+ schemaValidation: config.schemaValidation,
589
+ logger: config.logger
590
+ });
591
+ return buildMcpServerFromPrepared(config, auth, prepared);
592
+ }
593
+ function buildMcpServerFromPrepared(config, auth, prepared) {
594
+ const server = new McpServer(config.serverInfo, config.instructions ? { instructions: config.instructions } : undefined);
357
595
  const context = config.context?.(auth);
358
- mountMcp(server, services, {
596
+ mountPreparedMcp(server, prepared, {
359
597
  context,
360
598
  hooks: config.hooks,
361
599
  lifecycle: config.lifecycle,
362
600
  extend: config.extend,
363
- onIncompatibleSchema: config.onIncompatibleSchema,
601
+ schemaValidation: config.schemaValidation,
364
602
  logger: config.logger,
365
603
  coerceJsonArgs: config.coerceJsonArgs,
366
604
  flattenUnionInput: config.flattenUnionInput,
367
- errorHint: config.errorHint
605
+ errorHint: config.errorHint,
606
+ onOutputStrip: config.onOutputStrip
368
607
  });
369
- config.nativeTools?.(server, auth);
608
+ if (config.nativeTools) {
609
+ const registrar = createNativeMcpRegistrar(server, {
610
+ context,
611
+ hooks: config.hooks,
612
+ lifecycle: config.lifecycle,
613
+ coerceJsonArgs: config.coerceJsonArgs,
614
+ onOutputStrip: config.onOutputStrip,
615
+ prepare: (tool2) => prepareMcpTools([tool2], {
616
+ schemaValidation: config.schemaValidation,
617
+ logger: config.logger
618
+ }),
619
+ formatError: (result, toolName) => formatMcpResult(result, "none", toolName, config.errorHint)
620
+ });
621
+ config.nativeTools(registrar, auth);
622
+ }
370
623
  for (const resource of config.resources ?? []) {
371
624
  mountMcpResource(server, resource);
372
625
  }
@@ -477,17 +730,23 @@ function jsonRpcError(code, message, status, headers) {
477
730
  return Response.json({ jsonrpc: "2.0", error: { code, message }, id: null }, { status, headers });
478
731
  }
479
732
  function createMcpHandler(config) {
733
+ let buildServer;
480
734
  if (Array.isArray(config.services)) {
481
- validateMcpSchemas(config.services, config.onIncompatibleSchema, config.logger, {
735
+ const prepared = prepareMcpSurface(config.services, {
736
+ logger: config.logger,
482
737
  extend: config.extend,
483
- flattenUnionInput: config.flattenUnionInput
738
+ flattenUnionInput: config.flattenUnionInput,
739
+ schemaValidation: config.schemaValidation
484
740
  });
741
+ buildServer = (auth) => buildMcpServerFromPrepared(config, auth, prepared);
742
+ } else {
743
+ buildServer = (auth) => buildMcpServer(config, auth);
485
744
  }
486
745
  const unauthorized = () => {
487
746
  const headers = config.protectedResource ? { "WWW-Authenticate": wwwAuthenticateHeader(config.protectedResource.resource) } : undefined;
488
747
  return jsonRpcError(-32001, "Authorization required", 401, headers);
489
748
  };
490
- if (config.stateless) {
749
+ if ((config.sessionMode ?? "stateless") === "stateless") {
491
750
  return async (req) => {
492
751
  const auth = await config.auth(req);
493
752
  if (!auth)
@@ -496,7 +755,7 @@ function createMcpHandler(config) {
496
755
  sessionIdGenerator: undefined,
497
756
  enableJsonResponse: true
498
757
  });
499
- const server = buildMcpServer(config, auth);
758
+ const server = buildServer(auth);
500
759
  await server.connect(transport);
501
760
  return transport.handleRequest(req);
502
761
  };
@@ -555,7 +814,7 @@ function createMcpHandler(config) {
555
814
  sessionIdGenerator: () => newSessionId,
556
815
  eventStore
557
816
  });
558
- const server = buildMcpServer(config, auth);
817
+ const server = buildServer(auth);
559
818
  sessions.set(newSessionId, { transport, server, lastSeen: Date.now() });
560
819
  transport.onclose = () => {
561
820
  sessions.delete(newSessionId);
@@ -644,13 +903,13 @@ function mountDownload(server, config) {
644
903
  });
645
904
  }
646
905
  // src/tools/mount-upload.ts
647
- import { z as z2 } from "zod";
906
+ import { z as z3 } from "zod";
648
907
  function mountUpload(server, config) {
649
908
  const name = config.name ?? "upload";
650
909
  assertToolName(name, "<native>", "upload");
651
910
  server.registerTool(name, {
652
911
  description: config.description,
653
- inputSchema: { path: z2.string().describe("Path to a local file on this machine") }
912
+ inputSchema: { path: z3.string().describe("Path to a local file on this machine") }
654
913
  }, async (rawArgs) => {
655
914
  const args = isRecord(rawArgs) ? rawArgs : {};
656
915
  const path = typeof args.path === "string" ? args.path : "";
@@ -1255,7 +1514,7 @@ function implementRemote(contract, http, options) {
1255
1514
  serviceName: contract.meta.prefix,
1256
1515
  key,
1257
1516
  toolName: "toolName" in endpoint ? endpoint.toolName : undefined,
1258
- expose: endpoint.rawResponse ? HTTP_ONLY : endpoint.expose,
1517
+ expose: endpoint.rawResponse || endpoint.rawBody ? HTTP_ONLY : endpoint.expose,
1259
1518
  ui: "ui" in endpoint ? endpoint.ui : undefined,
1260
1519
  annotations: "annotations" in endpoint ? endpoint.annotations : undefined,
1261
1520
  meta: mergeMeta(contract.meta.meta, endpoint.meta),
@@ -1264,8 +1523,10 @@ function implementRemote(contract, http, options) {
1264
1523
  inputSchema: endpoint.input,
1265
1524
  outputSchema: endpoint.output,
1266
1525
  multipart: endpoint.multipart,
1526
+ maxJsonBodyBytes: endpoint.maxJsonBodyBytes,
1267
1527
  idempotent: endpoint.idempotent,
1268
1528
  rawResponse: endpoint.rawResponse,
1529
+ rawBody: endpoint.rawBody,
1269
1530
  contentType: "contentType" in endpoint ? endpoint.contentType : undefined,
1270
1531
  handler: async (ctx) => {
1271
1532
  const call = client[key];
@@ -1296,7 +1557,7 @@ function implementRemote(contract, http, options) {
1296
1557
  function createToolLogger(config = {}) {
1297
1558
  const log = config.log ?? ((line) => console.info(line));
1298
1559
  return {
1299
- afterToolCall: (toolName, _args, result, durationMs, context, endpoint) => {
1560
+ afterToolCall: ({ toolName, result, durationMs, context, endpoint }) => {
1300
1561
  const record = {
1301
1562
  tool: toolName,
1302
1563
  service: endpoint.serviceName,
@@ -1350,7 +1611,7 @@ function summarizeTransports(services) {
1350
1611
  // src/tools/view-file.ts
1351
1612
  import { readFile, realpath, stat } from "node:fs/promises";
1352
1613
  import { extname as extname2, resolve } from "node:path";
1353
- import { z as z3 } from "zod";
1614
+ import { z as z4 } from "zod";
1354
1615
  var MAX_INLINE_BYTES = 20 * 1024 * 1024;
1355
1616
  var EXT_MIME = {
1356
1617
  ".png": "image/png",
@@ -1450,7 +1711,7 @@ function mountViewFile(server, options = {}) {
1450
1711
  server.registerTool("view_file", {
1451
1712
  description: "View media (image, audio, video) by URL or local path — returns it as content you can SEE / HEAR. Pass several paths to view multiple files at once. Use it on a generation `output` url to inspect the result.",
1452
1713
  inputSchema: {
1453
- paths: z3.union([z3.string(), z3.array(z3.string())]).describe("Media URL(s) or file path(s) to view")
1714
+ paths: z4.union([z4.string(), z4.array(z4.string())]).describe("Media URL(s) or file path(s) to view")
1454
1715
  },
1455
1716
  annotations: { title: "View Media", readOnlyHint: true, idempotentHint: true }
1456
1717
  }, async (args) => {
@@ -1487,9 +1748,9 @@ export {
1487
1748
  listToolNames,
1488
1749
  inlineMcpAppBundle,
1489
1750
  implementRemote,
1490
- flattenUnionsDeep,
1491
- flattenDiscriminatedUnion,
1751
+ flattenToolJsonSchema,
1492
1752
  findUntypedProperties,
1753
+ findNonPortableFormats,
1493
1754
  createToolkit,
1494
1755
  createToolLogger,
1495
1756
  createStdioMcpServer,
@@ -1501,5 +1762,6 @@ export {
1501
1762
  buildMcpServer,
1502
1763
  RESOURCE_MIME_TYPE,
1503
1764
  PROTECTED_RESOURCE_PATH,
1765
+ PORTABLE_JSON_SCHEMA_FORMATS,
1504
1766
  EXT_APPS_BUNDLE_PLACEHOLDER
1505
1767
  };