stitchkit 0.36.1 → 0.38.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 (92) hide show
  1. package/README.md +4 -2
  2. package/dist/browser/client-url.d.ts +11 -0
  3. package/dist/browser/client-url.d.ts.map +1 -0
  4. package/dist/browser/client.d.ts +16 -4
  5. package/dist/browser/client.d.ts.map +1 -1
  6. package/dist/browser/http.d.ts +7 -3
  7. package/dist/browser/http.d.ts.map +1 -1
  8. package/dist/cli.js +3 -3
  9. package/dist/contract/define.d.ts +102 -2
  10. package/dist/contract/define.d.ts.map +1 -1
  11. package/dist/contract/errors.d.ts.map +1 -1
  12. package/dist/contract/index.d.ts +1 -1
  13. package/dist/contract/index.d.ts.map +1 -1
  14. package/dist/contract/index.js +1 -1
  15. package/dist/{index-bx49hskg.js → index-bgdd42pt.js} +1 -1
  16. package/dist/{index-0ed3bx43.js → index-frfyw9fa.js} +2 -2
  17. package/dist/{index-82gncajj.js → index-n5t4gnfz.js} +557 -337
  18. package/dist/{index-dvrn81q4.js → index-p3kwf73n.js} +165 -55
  19. package/dist/{index-pmftwk2a.js → index-x4wbc8sz.js} +54 -0
  20. package/dist/index.d.ts +2 -2
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +147 -114
  23. package/dist/node.d.ts +1 -1
  24. package/dist/node.d.ts.map +1 -1
  25. package/dist/node.js +7 -3
  26. package/dist/observability/index.js +3 -3
  27. package/dist/server/bun.d.ts +32 -0
  28. package/dist/server/bun.d.ts.map +1 -0
  29. package/dist/server/context.d.ts +1 -1
  30. package/dist/server/context.d.ts.map +1 -1
  31. package/dist/server/create.d.ts +2 -3
  32. package/dist/server/create.d.ts.map +1 -1
  33. package/dist/server/implement.d.ts.map +1 -1
  34. package/dist/server/index.d.ts +3 -2
  35. package/dist/server/index.d.ts.map +1 -1
  36. package/dist/server/index.js +41 -7
  37. package/dist/server/logger.d.ts +5 -5
  38. package/dist/server/logger.d.ts.map +1 -1
  39. package/dist/server/logging.d.ts +4 -1
  40. package/dist/server/logging.d.ts.map +1 -1
  41. package/dist/server/middleware/auth.d.ts +2 -2
  42. package/dist/server/middleware/auth.d.ts.map +1 -1
  43. package/dist/server/openapi.d.ts.map +1 -1
  44. package/dist/server/request-body.d.ts +4 -0
  45. package/dist/server/request-body.d.ts.map +1 -0
  46. package/dist/server/response-metadata.d.ts +9 -0
  47. package/dist/server/response-metadata.d.ts.map +1 -0
  48. package/dist/server/router.d.ts +3 -3
  49. package/dist/server/router.d.ts.map +1 -1
  50. package/dist/server/socket-io-config.d.ts +20 -0
  51. package/dist/server/socket-io-config.d.ts.map +1 -0
  52. package/dist/server/socket-io-node.d.ts +14 -0
  53. package/dist/server/socket-io-node.d.ts.map +1 -0
  54. package/dist/server/socket-io.d.ts +5 -32
  55. package/dist/server/socket-io.d.ts.map +1 -1
  56. package/dist/server/types.d.ts +63 -50
  57. package/dist/server/types.d.ts.map +1 -1
  58. package/dist/tools/agent.d.ts.map +1 -1
  59. package/dist/tools/execute.d.ts +46 -16
  60. package/dist/tools/execute.d.ts.map +1 -1
  61. package/dist/tools/flatten-join.d.ts +8 -0
  62. package/dist/tools/flatten-join.d.ts.map +1 -0
  63. package/dist/tools/flatten.d.ts +7 -44
  64. package/dist/tools/flatten.d.ts.map +1 -1
  65. package/dist/tools/json-schema.d.ts +1 -1
  66. package/dist/tools/json-schema.d.ts.map +1 -1
  67. package/dist/tools/manifest.d.ts.map +1 -1
  68. package/dist/tools/mcp-handler.d.ts +7 -13
  69. package/dist/tools/mcp-handler.d.ts.map +1 -1
  70. package/dist/tools/mcp.d.ts +61 -24
  71. package/dist/tools/mcp.d.ts.map +1 -1
  72. package/dist/tools/mount.d.ts +9 -6
  73. package/dist/tools/mount.d.ts.map +1 -1
  74. package/dist/tools/names.d.ts +1 -1
  75. package/dist/tools/native-mcp.d.ts +57 -0
  76. package/dist/tools/native-mcp.d.ts.map +1 -0
  77. package/dist/tools/portable-formats.d.ts +15 -0
  78. package/dist/tools/portable-formats.d.ts.map +1 -0
  79. package/dist/tools/presentation.d.ts +15 -0
  80. package/dist/tools/presentation.d.ts.map +1 -0
  81. package/dist/tools/remote.d.ts.map +1 -1
  82. package/dist/tools/schema.d.ts +3 -8
  83. package/dist/tools/schema.d.ts.map +1 -1
  84. package/dist/tools/tool-logger.d.ts.map +1 -1
  85. package/dist/tools/view-file.d.ts +5 -3
  86. package/dist/tools/view-file.d.ts.map +1 -1
  87. package/dist/tools.d.ts +7 -4
  88. package/dist/tools.d.ts.map +1 -1
  89. package/dist/tools.js +481 -205
  90. package/llms-full.txt +536 -87
  91. package/package.json +14 -11
  92. /package/dist/{index-17rdjw68.js → index-mzx0an0s.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-bgdd42pt.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-n5t4gnfz.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-mzx0an0s.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;
350
565
  }
566
+ if (mountable.method.ui) {
567
+ toolConfig._meta = {
568
+ ui: mountable.method.ui,
569
+ "ui/resourceUri": mountable.method.ui.resourceUri
570
+ };
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 : "";
@@ -976,6 +1235,117 @@ function appendFormFields(formData, values, skipKeys) {
976
1235
  }
977
1236
  }
978
1237
 
1238
+ // src/browser/client-url.ts
1239
+ function isParamArray(value) {
1240
+ return Array.isArray(value) && value.every((item) => typeof item === "string" || typeof item === "number");
1241
+ }
1242
+ function collectQueryParams(args, endpoint) {
1243
+ const params = {};
1244
+ let hasParams = false;
1245
+ for (const [key, value] of Object.entries(args)) {
1246
+ if (value === undefined || value === null)
1247
+ continue;
1248
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || isParamArray(value)) {
1249
+ params[key] = value;
1250
+ hasParams = true;
1251
+ continue;
1252
+ }
1253
+ const what = Array.isArray(value) ? "an array with non-primitive items" : typeof value === "object" ? "a nested object" : `a ${typeof value}`;
1254
+ throw new Error(`${endpoint.method} ${endpoint.path}: input field "${key}" is ${what} — it cannot ` + "travel as a query parameter. GET / DELETE input must be flat (string / number / " + "boolean, or an array of string / number); flatten the field or move the " + "operation to a body verb (POST).");
1255
+ }
1256
+ return hasParams ? params : undefined;
1257
+ }
1258
+ function hasStringKeys(args, keys) {
1259
+ for (const key of keys) {
1260
+ if (typeof args[key] !== "string")
1261
+ return false;
1262
+ }
1263
+ return true;
1264
+ }
1265
+ function resolvePathPrefix(config, args) {
1266
+ if (!config?.pathPrefix)
1267
+ return "";
1268
+ if (typeof config.pathPrefix === "string")
1269
+ return config.pathPrefix;
1270
+ const keys = config.stripPrefixKeys ?? [];
1271
+ if (!hasStringKeys(args, keys)) {
1272
+ const missing = keys.find((key) => typeof args[key] !== "string");
1273
+ throw new Error(`Missing path prefix key: ${missing}`);
1274
+ }
1275
+ return config.pathPrefix(args);
1276
+ }
1277
+ function extractParamNames(path) {
1278
+ const matches = path.match(/:(\w+)/g);
1279
+ const names = matches ? matches.map((match) => match.slice(1)) : [];
1280
+ if (path.endsWith("/*"))
1281
+ names.push("*");
1282
+ return names;
1283
+ }
1284
+ function fillPathParams(path, args) {
1285
+ let filled = path.replace(/:(\w+)/g, (_, key) => {
1286
+ const value = args[key];
1287
+ if (value === undefined || value === null) {
1288
+ throw new Error(`Missing path param: ${key}`);
1289
+ }
1290
+ return encodeURIComponent(String(value));
1291
+ });
1292
+ if (!filled.endsWith("/*"))
1293
+ return filled;
1294
+ const wildcard = args["*"];
1295
+ if (wildcard === undefined || wildcard === null) {
1296
+ throw new Error("Missing path param: *");
1297
+ }
1298
+ const remainder = String(wildcard).split("/").map((segment) => encodeURIComponent(segment)).join("/");
1299
+ filled = `${filled.slice(0, -1)}${remainder}`;
1300
+ return filled;
1301
+ }
1302
+ function stripConsumedArgs(args, path, scopeKeys) {
1303
+ const consumed = new Set(scopeKeys);
1304
+ for (const name of extractParamNames(path))
1305
+ consumed.add(name);
1306
+ const remaining = {};
1307
+ for (const [key, value] of Object.entries(args)) {
1308
+ if (!consumed.has(key) && value !== undefined)
1309
+ remaining[key] = value;
1310
+ }
1311
+ return remaining;
1312
+ }
1313
+ function appendQuery(relativeUrl, params) {
1314
+ if (!params)
1315
+ return relativeUrl;
1316
+ const search = new URLSearchParams;
1317
+ for (const [key, value] of Object.entries(params)) {
1318
+ if (Array.isArray(value)) {
1319
+ for (const item of value)
1320
+ search.append(key, String(item));
1321
+ } else {
1322
+ search.set(key, String(value));
1323
+ }
1324
+ }
1325
+ return search.size > 0 ? `${relativeUrl}?${search}` : relativeUrl;
1326
+ }
1327
+ function planClientRequest(endpoint, contractPrefix, args, config) {
1328
+ let pathPrefix = resolvePathPrefix(config, args);
1329
+ if (pathPrefix && !pathPrefix.endsWith("/"))
1330
+ pathPrefix += "/";
1331
+ if (pathPrefix.startsWith("/"))
1332
+ pathPrefix = pathPrefix.slice(1);
1333
+ const endpointPath = endpoint.path === "/" ? "" : endpoint.path;
1334
+ let relativeUrl = fillPathParams(`${pathPrefix}${contractPrefix}${endpointPath}`, args);
1335
+ if (relativeUrl.endsWith("/"))
1336
+ relativeUrl = relativeUrl.slice(0, -1);
1337
+ const remainingArgs = stripConsumedArgs(args, endpoint.path, config?.stripPrefixKeys ?? []);
1338
+ if (inputIsQuery(endpoint.method)) {
1339
+ relativeUrl = appendQuery(relativeUrl, collectQueryParams(remainingArgs, endpoint));
1340
+ }
1341
+ return { relativeUrl, remainingArgs };
1342
+ }
1343
+ function joinClientBaseUrl(baseUrl, relativeUrl) {
1344
+ const base = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
1345
+ const path = relativeUrl.startsWith("/") ? relativeUrl : `/${relativeUrl}`;
1346
+ return `${base}${path}`;
1347
+ }
1348
+
979
1349
  // src/browser/http.ts
980
1350
  import ky, { isHTTPError } from "ky";
981
1351
  class ApiError extends Error {
@@ -1026,25 +1396,6 @@ function withOutput(endpoint, result) {
1026
1396
  return result;
1027
1397
  return result.then((value) => value === undefined ? undefined : schema.parse(value));
1028
1398
  }
1029
- function isParamArray(value) {
1030
- return Array.isArray(value) && value.every((v) => typeof v === "string" || typeof v === "number");
1031
- }
1032
- function collectQueryParams(args, skipKeys, endpoint) {
1033
- const params = {};
1034
- let hasParams = false;
1035
- for (const [key, value] of Object.entries(args)) {
1036
- if (skipKeys.has(key) || value === undefined || value === null)
1037
- continue;
1038
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || isParamArray(value)) {
1039
- params[key] = value;
1040
- hasParams = true;
1041
- continue;
1042
- }
1043
- const what = Array.isArray(value) ? "an array with non-primitive items" : typeof value === "object" ? "a nested object" : `a ${typeof value}`;
1044
- throw new Error(`${endpoint.method} ${endpoint.path}: input field "${key}" is ${what} — it cannot ` + "travel as a query parameter. GET / DELETE input must be flat (string / number / " + "boolean, or an array of string / number); flatten the field or move the " + "operation to a body verb (POST).");
1045
- }
1046
- return hasParams ? params : undefined;
1047
- }
1048
1399
  function createClient(contract, configOrClient, contractConfig) {
1049
1400
  const client = {};
1050
1401
  const makeMethod = isHttpAdapter(configOrClient) ? (endpoint) => createHttpMethod(endpoint, contract.meta.prefix, configOrClient, contractConfig) : (endpoint) => createFetchMethod(endpoint, contract.meta.prefix, configOrClient, contractConfig);
@@ -1063,27 +1414,9 @@ function setClientMethod(target, key, method) {
1063
1414
  }
1064
1415
  function createHttpMethod(endpoint, prefix, client, config) {
1065
1416
  const httpMethod = endpoint.method.toLowerCase();
1066
- const isGet = httpMethod === "get";
1067
- const paramNames = extractParamNames(endpoint.path);
1068
- const prefixKeys = new Set([...config?.stripPrefixKeys ?? [], ...paramNames]);
1069
1417
  return (...args) => {
1070
1418
  const firstArg = args[0] ?? {};
1071
- let pathPrefixStr = "";
1072
- if (config?.pathPrefix) {
1073
- pathPrefixStr = typeof config.pathPrefix === "function" ? config.pathPrefix(firstArg) : config.pathPrefix;
1074
- if (pathPrefixStr && !pathPrefixStr.endsWith("/"))
1075
- pathPrefixStr += "/";
1076
- }
1077
- let url = `${pathPrefixStr}${prefix}${endpoint.path}`;
1078
- for (const name of paramNames) {
1079
- const value = firstArg[name];
1080
- if (value === undefined || value === null) {
1081
- throw new Error(`Missing path param: ${name}`);
1082
- }
1083
- url = url.replace(`:${name}`, encodeURIComponent(String(value)));
1084
- }
1085
- if (url.endsWith("/"))
1086
- url = url.slice(0, -1);
1419
+ const plan = planClientRequest(endpoint, prefix, firstArg, config);
1087
1420
  if (endpoint.multipart) {
1088
1421
  const file = firstArg[endpoint.multipart];
1089
1422
  if (!isMultipartFile(file)) {
@@ -1094,59 +1427,28 @@ function createHttpMethod(endpoint, prefix, client, config) {
1094
1427
  }
1095
1428
  const formData = new FormData;
1096
1429
  appendMultipartFile(formData, endpoint.multipart, file);
1097
- appendFormFields(formData, firstArg, new Set([...prefixKeys, endpoint.multipart]));
1098
- return withOutput(endpoint, client[httpMethod](url, formData, withTimeout(undefined, endpoint)));
1430
+ appendFormFields(formData, plan.remainingArgs, new Set([endpoint.multipart]));
1431
+ return withOutput(endpoint, client[httpMethod](plan.relativeUrl, formData, withTimeout(undefined, endpoint)));
1099
1432
  }
1100
- if (isGet) {
1101
- const params = collectQueryParams(firstArg, prefixKeys, endpoint);
1102
- return withOutput(endpoint, client.get(url, withTimeout(params ? { params } : undefined, endpoint)));
1433
+ if (httpMethod === "get") {
1434
+ return withOutput(endpoint, client.get(plan.relativeUrl, withTimeout(undefined, endpoint)));
1103
1435
  }
1104
1436
  if (httpMethod === "delete") {
1105
- const params = collectQueryParams(firstArg, prefixKeys, endpoint);
1106
- return withOutput(endpoint, client.delete(url, withTimeout(params ? { params } : undefined, endpoint)));
1107
- }
1108
- const payload = {};
1109
- for (const [key, value] of Object.entries(firstArg)) {
1110
- if (!prefixKeys.has(key) && value !== undefined) {
1111
- payload[key] = value;
1112
- }
1437
+ return withOutput(endpoint, client.delete(plan.relativeUrl, withTimeout(undefined, endpoint)));
1113
1438
  }
1114
- return withOutput(endpoint, client[httpMethod](url, Object.keys(payload).length > 0 ? payload : undefined, withTimeout(undefined, endpoint)));
1439
+ return withOutput(endpoint, client[httpMethod](plan.relativeUrl, Object.keys(plan.remainingArgs).length > 0 ? plan.remainingArgs : undefined, withTimeout(undefined, endpoint)));
1115
1440
  };
1116
1441
  }
1117
1442
  function createFetchMethod(endpoint, prefix, config, contractConfig) {
1118
- const prefixKeys = new Set(contractConfig?.stripPrefixKeys ?? []);
1119
1443
  return async (args) => {
1120
- let pathPrefixStr = "";
1121
- if (contractConfig?.pathPrefix) {
1122
- pathPrefixStr = typeof contractConfig.pathPrefix === "function" ? contractConfig.pathPrefix(args ?? {}) : contractConfig.pathPrefix;
1123
- if (pathPrefixStr && !pathPrefixStr.endsWith("/"))
1124
- pathPrefixStr += "/";
1125
- }
1126
- let url = buildFetchUrl(config.baseUrl, prefix, endpoint.path, args, pathPrefixStr);
1444
+ const plan = planClientRequest(endpoint, prefix, args ?? {}, contractConfig);
1445
+ const url = joinClientBaseUrl(config.baseUrl, plan.relativeUrl);
1127
1446
  const headers = {
1128
1447
  Accept: "application/json",
1129
1448
  ...typeof config.headers === "function" ? config.headers() : config.headers
1130
1449
  };
1131
1450
  const signal = endpoint.timeout !== undefined ? AbortSignal.timeout(endpoint.timeout) : undefined;
1132
- const isQuery = inputIsQuery(endpoint.method);
1133
- const hasBody = !isQuery && !endpoint.multipart && endpoint.input && args;
1134
- if (isQuery && args) {
1135
- const remaining = stripParams(args, endpoint.path, prefixKeys);
1136
- const params = collectQueryParams(remaining, new Set, endpoint);
1137
- if (params) {
1138
- const searchParams = new URLSearchParams;
1139
- for (const [k, v] of Object.entries(params)) {
1140
- if (Array.isArray(v)) {
1141
- for (const item of v)
1142
- searchParams.append(k, String(item));
1143
- } else {
1144
- searchParams.set(k, String(v));
1145
- }
1146
- }
1147
- url += `?${searchParams}`;
1148
- }
1149
- }
1451
+ const hasBody = endpoint.method !== "GET" && endpoint.method !== "DELETE" && !endpoint.multipart && endpoint.input && args;
1150
1452
  if (hasBody)
1151
1453
  headers["Content-Type"] = "application/json";
1152
1454
  if (endpoint.multipart && args) {
@@ -1156,7 +1458,7 @@ function createFetchMethod(endpoint, prefix, config, contractConfig) {
1156
1458
  }
1157
1459
  const formData = new FormData;
1158
1460
  appendMultipartFile(formData, endpoint.multipart, file);
1159
- appendFormFields(formData, stripParams(args, endpoint.path, prefixKeys), new Set([endpoint.multipart]));
1461
+ appendFormFields(formData, plan.remainingArgs, new Set([endpoint.multipart]));
1160
1462
  const res2 = await fetch(url, {
1161
1463
  method: endpoint.method,
1162
1464
  headers,
@@ -1180,7 +1482,7 @@ function createFetchMethod(endpoint, prefix, config, contractConfig) {
1180
1482
  credentials: config.credentials,
1181
1483
  signal,
1182
1484
  ...hasBody && {
1183
- body: JSON.stringify(stripParams(hasBody, endpoint.path, prefixKeys))
1485
+ body: JSON.stringify(plan.remainingArgs)
1184
1486
  }
1185
1487
  });
1186
1488
  if (!res.ok) {
@@ -1203,36 +1505,6 @@ async function throwForErrorResponse(res, config, fallbackBody) {
1203
1505
  }
1204
1506
  throw new ApiError("HTTP_ERROR", res.status, { body });
1205
1507
  }
1206
- function extractParamNames(path) {
1207
- const matches = path.match(/:(\w+)/g);
1208
- return matches ? matches.map((m) => m.slice(1)) : [];
1209
- }
1210
- function buildFetchUrl(baseUrl, prefix, path, args, pathPrefix = "") {
1211
- let fullPath = `/${pathPrefix}${prefix}${path === "/" ? "" : path}`;
1212
- if (args) {
1213
- fullPath = fullPath.replace(/:(\w+)/g, (_, key) => {
1214
- const val = args[key];
1215
- if (val === undefined || val === null) {
1216
- throw new Error(`Missing path param: ${key}`);
1217
- }
1218
- return encodeURIComponent(String(val));
1219
- });
1220
- }
1221
- return `${baseUrl}${fullPath}`;
1222
- }
1223
- function stripParams(args, path, extra) {
1224
- const skip = new Set(extra);
1225
- for (const match of path.matchAll(/:(\w+)/g)) {
1226
- if (match[1])
1227
- skip.add(match[1]);
1228
- }
1229
- const result = {};
1230
- for (const [k, v] of Object.entries(args)) {
1231
- if (!skip.has(k))
1232
- result[k] = v;
1233
- }
1234
- return result;
1235
- }
1236
1508
 
1237
1509
  // src/tools/remote.ts
1238
1510
  var HTTP_ONLY = Object.freeze(["HTTP"]);
@@ -1255,7 +1527,7 @@ function implementRemote(contract, http, options) {
1255
1527
  serviceName: contract.meta.prefix,
1256
1528
  key,
1257
1529
  toolName: "toolName" in endpoint ? endpoint.toolName : undefined,
1258
- expose: endpoint.rawResponse ? HTTP_ONLY : endpoint.expose,
1530
+ expose: endpoint.rawResponse || endpoint.rawBody || endpoint.responseMeta ? HTTP_ONLY : endpoint.expose,
1259
1531
  ui: "ui" in endpoint ? endpoint.ui : undefined,
1260
1532
  annotations: "annotations" in endpoint ? endpoint.annotations : undefined,
1261
1533
  meta: mergeMeta(contract.meta.meta, endpoint.meta),
@@ -1264,8 +1536,11 @@ function implementRemote(contract, http, options) {
1264
1536
  inputSchema: endpoint.input,
1265
1537
  outputSchema: endpoint.output,
1266
1538
  multipart: endpoint.multipart,
1539
+ maxJsonBodyBytes: endpoint.maxJsonBodyBytes,
1267
1540
  idempotent: endpoint.idempotent,
1268
1541
  rawResponse: endpoint.rawResponse,
1542
+ rawBody: endpoint.rawBody,
1543
+ responseMeta: endpoint.responseMeta,
1269
1544
  contentType: "contentType" in endpoint ? endpoint.contentType : undefined,
1270
1545
  handler: async (ctx) => {
1271
1546
  const call = client[key];
@@ -1296,7 +1571,7 @@ function implementRemote(contract, http, options) {
1296
1571
  function createToolLogger(config = {}) {
1297
1572
  const log = config.log ?? ((line) => console.info(line));
1298
1573
  return {
1299
- afterToolCall: (toolName, _args, result, durationMs, context, endpoint) => {
1574
+ afterToolCall: ({ toolName, result, durationMs, context, endpoint }) => {
1300
1575
  const record = {
1301
1576
  tool: toolName,
1302
1577
  service: endpoint.serviceName,
@@ -1350,7 +1625,7 @@ function summarizeTransports(services) {
1350
1625
  // src/tools/view-file.ts
1351
1626
  import { readFile, realpath, stat } from "node:fs/promises";
1352
1627
  import { extname as extname2, resolve } from "node:path";
1353
- import { z as z3 } from "zod";
1628
+ import { z as z4 } from "zod";
1354
1629
  var MAX_INLINE_BYTES = 20 * 1024 * 1024;
1355
1630
  var EXT_MIME = {
1356
1631
  ".png": "image/png",
@@ -1450,7 +1725,7 @@ function mountViewFile(server, options = {}) {
1450
1725
  server.registerTool("view_file", {
1451
1726
  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
1727
  inputSchema: {
1453
- paths: z3.union([z3.string(), z3.array(z3.string())]).describe("Media URL(s) or file path(s) to view")
1728
+ paths: z4.union([z4.string(), z4.array(z4.string())]).describe("Media URL(s) or file path(s) to view")
1454
1729
  },
1455
1730
  annotations: { title: "View Media", readOnlyHint: true, idempotentHint: true }
1456
1731
  }, async (args) => {
@@ -1487,9 +1762,9 @@ export {
1487
1762
  listToolNames,
1488
1763
  inlineMcpAppBundle,
1489
1764
  implementRemote,
1490
- flattenUnionsDeep,
1491
- flattenDiscriminatedUnion,
1765
+ flattenToolJsonSchema,
1492
1766
  findUntypedProperties,
1767
+ findNonPortableFormats,
1493
1768
  createToolkit,
1494
1769
  createToolLogger,
1495
1770
  createStdioMcpServer,
@@ -1501,5 +1776,6 @@ export {
1501
1776
  buildMcpServer,
1502
1777
  RESOURCE_MIME_TYPE,
1503
1778
  PROTECTED_RESOURCE_PATH,
1779
+ PORTABLE_JSON_SCHEMA_FORMATS,
1504
1780
  EXT_APPS_BUNDLE_PLACEHOLDER
1505
1781
  };