stitchkit 0.41.0 → 0.43.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 (66) hide show
  1. package/README.md +22 -5
  2. package/dist/cli.js +2 -2
  3. package/dist/contract/errors-factory.d.ts +88 -15
  4. package/dist/contract/errors-factory.d.ts.map +1 -1
  5. package/dist/contract/errors.d.ts +4 -4
  6. package/dist/contract/errors.d.ts.map +1 -1
  7. package/dist/contract/factory.d.ts +29 -2
  8. package/dist/contract/factory.d.ts.map +1 -1
  9. package/dist/contract/index.d.ts +2 -2
  10. package/dist/contract/index.d.ts.map +1 -1
  11. package/dist/contract/index.js +2 -2
  12. package/dist/{index-dnkefke9.js → index-310bfer5.js} +1 -1
  13. package/dist/{index-4e1c0hsw.js → index-esqmem78.js} +101 -67
  14. package/dist/{index-sc67e454.js → index-gex6gxhe.js} +7 -4
  15. package/dist/{index-ncqqn1bc.js → index-h2wdcsby.js} +95 -17
  16. package/dist/{index-xax049k6.js → index-r6czv7yt.js} +58 -22
  17. package/dist/{index-809wc1tt.js → index-s4qsmgwe.js} +4 -1
  18. package/dist/index.js +2 -2
  19. package/dist/internal/typed.d.ts +8 -0
  20. package/dist/internal/typed.d.ts.map +1 -1
  21. package/dist/node.js +2 -2
  22. package/dist/observability/audit.d.ts +27 -15
  23. package/dist/observability/audit.d.ts.map +1 -1
  24. package/dist/observability/event.d.ts +2 -2
  25. package/dist/observability/index.d.ts +4 -4
  26. package/dist/observability/index.d.ts.map +1 -1
  27. package/dist/observability/index.js +70 -59
  28. package/dist/react.js +1 -1
  29. package/dist/server/create.d.ts.map +1 -1
  30. package/dist/server/error-hook.d.ts +8 -4
  31. package/dist/server/error-hook.d.ts.map +1 -1
  32. package/dist/server/index.js +7 -6
  33. package/dist/server/logger.d.ts +0 -1
  34. package/dist/server/logger.d.ts.map +1 -1
  35. package/dist/server/types.d.ts +8 -13
  36. package/dist/server/types.d.ts.map +1 -1
  37. package/dist/tools/agent.d.ts +1 -1
  38. package/dist/tools/agent.d.ts.map +1 -1
  39. package/dist/tools/flatten-join.d.ts.map +1 -1
  40. package/dist/tools/list-names.d.ts +8 -6
  41. package/dist/tools/list-names.d.ts.map +1 -1
  42. package/dist/tools/manifest.d.ts +13 -5
  43. package/dist/tools/manifest.d.ts.map +1 -1
  44. package/dist/tools/mcp-handler.d.ts +5 -4
  45. package/dist/tools/mcp-handler.d.ts.map +1 -1
  46. package/dist/tools/mcp-stdio.d.ts +3 -2
  47. package/dist/tools/mcp-stdio.d.ts.map +1 -1
  48. package/dist/tools/mcp.d.ts +49 -9
  49. package/dist/tools/mcp.d.ts.map +1 -1
  50. package/dist/tools/native-mcp.d.ts +4 -18
  51. package/dist/tools/native-mcp.d.ts.map +1 -1
  52. package/dist/tools/runtime-tool.d.ts +32 -1
  53. package/dist/tools/runtime-tool.d.ts.map +1 -1
  54. package/dist/tools/surface.d.ts +37 -0
  55. package/dist/tools/surface.d.ts.map +1 -0
  56. package/dist/tools/toolkit.d.ts +4 -4
  57. package/dist/tools/toolkit.d.ts.map +1 -1
  58. package/dist/tools/transports.d.ts +11 -9
  59. package/dist/tools/transports.d.ts.map +1 -1
  60. package/dist/tools/view-file.d.ts +3 -3
  61. package/dist/tools.d.ts +6 -6
  62. package/dist/tools.d.ts.map +1 -1
  63. package/dist/tools.js +330 -155
  64. package/llms-full.txt +524 -131
  65. package/llms.txt +1 -0
  66. package/package.json +2 -2
package/dist/tools.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  inputIsQuery,
3
3
  signJwt,
4
4
  verifyPkce
5
- } from "./index-dnkefke9.js";
5
+ } from "./index-310bfer5.js";
6
6
  import {
7
7
  DEFAULT_CORS_ALLOW_HEADERS
8
8
  } from "./index-6jypn22c.js";
@@ -25,7 +25,7 @@ import {
25
25
  toolErrorFromResult,
26
26
  toolResultFromError,
27
27
  writeDownload
28
- } from "./index-ncqqn1bc.js";
28
+ } from "./index-h2wdcsby.js";
29
29
  import {
30
30
  toJsonSchema
31
31
  } from "./index-frfyw9fa.js";
@@ -39,7 +39,7 @@ import {
39
39
  mergeMeta,
40
40
  parseTrailingWildcard,
41
41
  typedEntries
42
- } from "./index-sc67e454.js";
42
+ } from "./index-gex6gxhe.js";
43
43
 
44
44
  // src/tools/agent.ts
45
45
  import { jsonSchema, tool } from "ai";
@@ -51,6 +51,43 @@ function defineRuntimeTool(definition) {
51
51
  }
52
52
  return definition;
53
53
  }
54
+ function createRuntimeToolFactory(config) {
55
+ function parseContext(context) {
56
+ const parsed = config.context.parse(context);
57
+ return { ...parsed, params: undefined, input: context.input };
58
+ }
59
+ function define(definition) {
60
+ if (definition.output !== undefined) {
61
+ const { action: action2, method: method2, meta: meta2, handler: handler2, ...tool2 } = definition;
62
+ const identity2 = {
63
+ serviceName: config.serviceName,
64
+ action: action2,
65
+ method: method2,
66
+ ...config.scope !== undefined && { scope: config.scope },
67
+ ...(meta2 ?? config.meta) !== undefined && { meta: meta2 ?? config.meta }
68
+ };
69
+ return defineRuntimeTool({
70
+ ...tool2,
71
+ identity: identity2,
72
+ handler: (context) => handler2(parseContext(context))
73
+ });
74
+ }
75
+ const { action, method, meta, handler, ...tool } = definition;
76
+ const identity = {
77
+ serviceName: config.serviceName,
78
+ action,
79
+ method,
80
+ ...config.scope !== undefined && { scope: config.scope },
81
+ ...(meta ?? config.meta) !== undefined && { meta: meta ?? config.meta }
82
+ };
83
+ return defineRuntimeTool({
84
+ ...tool,
85
+ identity,
86
+ handler: (context) => handler(parseContext(context))
87
+ });
88
+ }
89
+ return { define };
90
+ }
54
91
  function runtimeToolSupports(definition, transport) {
55
92
  if (definition.transports?.length === 0) {
56
93
  throw new Error(`Runtime tool "${definition.name}" must expose at least one transport`);
@@ -70,8 +107,10 @@ function runtimeToolIdentity(definition) {
70
107
  ui: definition.ui
71
108
  };
72
109
  }
73
- function runtimeToolMountable(definition) {
74
- assertToolName(definition.name, definition.identity.serviceName, definition.identity.action);
110
+ function runtimeToolMountable(definition, assertName = true) {
111
+ if (assertName) {
112
+ assertToolName(definition.name, definition.identity.serviceName, definition.identity.action);
113
+ }
75
114
  const method = {
76
115
  ...runtimeToolIdentity(definition),
77
116
  inputSchema: definition.input,
@@ -90,6 +129,55 @@ function runtimeToolMountable(definition) {
90
129
  };
91
130
  }
92
131
 
132
+ // src/tools/surface.ts
133
+ function duplicateLabel(transport) {
134
+ if (transport === "MCP")
135
+ return "MCP tool name";
136
+ if (transport === "AGENT")
137
+ return "agent tool name";
138
+ return "CLI command";
139
+ }
140
+ function collectToolSurface({
141
+ surface,
142
+ transport,
143
+ assertUniqueNames = true,
144
+ ...collectConfig
145
+ }) {
146
+ const entries = [];
147
+ const names = new Set;
148
+ const append = (entry) => {
149
+ if (assertUniqueNames) {
150
+ assertUniqueToolName(entry.mountable.name, names.has(entry.mountable.name), duplicateLabel(transport));
151
+ }
152
+ names.add(entry.mountable.name);
153
+ entries.push(entry);
154
+ };
155
+ for (const service of surface.services ?? []) {
156
+ for (const mountable of collectTools(service, transport, collectConfig)) {
157
+ append({
158
+ kind: "contract",
159
+ service: service.name,
160
+ action: mountable.method.key,
161
+ mountable
162
+ });
163
+ }
164
+ }
165
+ if (transport === "CLI")
166
+ return entries;
167
+ for (const definition of surface.runtimeTools ?? []) {
168
+ if (!runtimeToolSupports(definition, transport))
169
+ continue;
170
+ append({
171
+ kind: "runtime",
172
+ service: definition.identity.serviceName,
173
+ action: definition.identity.action,
174
+ mountable: runtimeToolMountable(definition, collectConfig.assertNames),
175
+ definition
176
+ });
177
+ }
178
+ return entries;
179
+ }
180
+
93
181
  // src/tools/agent.ts
94
182
  function mountAgent(services, config = {}) {
95
183
  const serviceList = Array.isArray(services) ? services : [services];
@@ -104,36 +192,13 @@ function mountAgent(services, config = {}) {
104
192
  coerceJsonArgs: config.coerceJsonArgs,
105
193
  onOutputStrip: config.onOutputStrip
106
194
  });
107
- for (const service of serviceList) {
108
- for (const mountable of collectTools(service, "AGENT", {
109
- extend: config.extend,
110
- flattenUnionInput: config.flattenUnionInput
111
- })) {
112
- assertUniqueToolName(mountable.name, Object.hasOwn(tools, mountable.name), "agent tool name");
113
- tools[mountable.name] = tool({
114
- description: mountable.method.desc,
115
- inputSchema: jsonSchema(mountable.presentationSchema, {
116
- validate: async (value) => isRecord(value) ? { success: true, value } : { success: false, error: new Error("Tool arguments must be an object") }
117
- }),
118
- execute: async (rawArgs) => {
119
- const args = isRecord(rawArgs) ? rawArgs : {};
120
- try {
121
- const result = await runTool(mountable, args);
122
- if (result.ok)
123
- return result.data;
124
- return formatToolError(result, mountable.name, config.errorHint);
125
- } catch (err) {
126
- return formatToolError(toolResultFromError(err), mountable.name, config.errorHint);
127
- }
128
- }
129
- });
130
- }
131
- }
132
- for (const definition of config.runtimeTools ?? []) {
133
- if (!runtimeToolSupports(definition, "AGENT"))
134
- continue;
135
- const mountable = runtimeToolMountable(definition);
136
- assertUniqueToolName(definition.name, Object.hasOwn(tools, definition.name), "agent tool name");
195
+ for (const entry of collectToolSurface({
196
+ surface: { services: serviceList, runtimeTools: config.runtimeTools },
197
+ transport: "AGENT",
198
+ extend: config.extend,
199
+ flattenUnionInput: config.flattenUnionInput
200
+ })) {
201
+ const { mountable } = entry;
137
202
  const inputSchema = jsonSchema(mountable.presentationSchema, {
138
203
  validate: async (value) => isRecord(value) ? { success: true, value } : { success: false, error: new Error("Tool arguments must be an object") }
139
204
  });
@@ -148,9 +213,13 @@ function mountAgent(services, config = {}) {
148
213
  return formatToolError(toolResultFromError(err), mountable.name, config.errorHint);
149
214
  }
150
215
  };
151
- if (definition.present?.agent) {
152
- const presenter = definition.present.agent;
216
+ const presenter = entry.kind === "runtime" ? entry.definition.present?.agent : undefined;
217
+ if (entry.kind === "runtime" && presenter) {
218
+ const { definition } = entry;
153
219
  const output = definition.output;
220
+ if (!output) {
221
+ throw new Error(`Runtime tool "${definition.name}" presenter requires an output schema`);
222
+ }
154
223
  tools[definition.name] = tool({
155
224
  description: definition.description,
156
225
  inputSchema,
@@ -165,10 +234,11 @@ function mountAgent(services, config = {}) {
165
234
  }
166
235
  });
167
236
  } else {
168
- tools[definition.name] = tool({
169
- description: definition.description,
237
+ const output = entry.kind === "runtime" ? entry.definition.output : undefined;
238
+ tools[mountable.name] = tool({
239
+ description: mountable.method.desc,
170
240
  inputSchema,
171
- ...definition.output && { outputSchema: definition.output },
241
+ ...output && { outputSchema: output },
172
242
  execute
173
243
  });
174
244
  }
@@ -224,34 +294,43 @@ function createToolInvoker(services, config) {
224
294
  }
225
295
  // src/tools/list-names.ts
226
296
  var TOOL_TRANSPORTS = ["MCP", "AGENT", "CLI"];
227
- function listToolNames(services) {
297
+ function listToolNames(surface) {
228
298
  const entries = new Map;
229
- for (const service of services) {
230
- for (const transport of TOOL_TRANSPORTS) {
231
- for (const tool2 of collectTools(service, transport, { assertNames: false })) {
232
- const id = `${service.name}\x00${tool2.method.key}`;
233
- const existing = entries.get(id);
234
- if (existing) {
235
- existing.transports.push(transport);
236
- } else {
237
- entries.set(id, {
238
- name: tool2.name,
239
- service: service.name,
240
- method: tool2.method.key,
241
- transports: [transport]
242
- });
243
- }
299
+ for (const transport of TOOL_TRANSPORTS) {
300
+ for (const entry of collectToolSurface({
301
+ surface,
302
+ transport,
303
+ assertNames: false,
304
+ assertUniqueNames: false
305
+ })) {
306
+ const id = `${entry.kind}\x00${entry.service}\x00${entry.action}`;
307
+ const existing = entries.get(id);
308
+ if (existing) {
309
+ existing.transports.push(transport);
310
+ } else {
311
+ entries.set(id, {
312
+ kind: entry.kind,
313
+ name: entry.mountable.name,
314
+ service: entry.service,
315
+ method: entry.action,
316
+ transports: [transport]
317
+ });
244
318
  }
245
319
  }
246
320
  }
247
321
  return [...entries.values()].sort((a, b) => a.name.localeCompare(b.name) || a.service.localeCompare(b.service));
248
322
  }
249
323
  // src/tools/manifest.ts
250
- function buildToolManifest(tools) {
251
- return tools.map((tool2) => ({
252
- name: tool2.name,
253
- description: tool2.method.desc,
254
- inputSchema: tool2.presentationSchema
324
+ function buildToolManifest(config) {
325
+ return collectToolSurface({
326
+ surface: config,
327
+ transport: config.transport,
328
+ extend: config.extend,
329
+ flattenUnionInput: config.flattenUnionInput
330
+ }).map(({ mountable }) => ({
331
+ name: mountable.name,
332
+ description: mountable.method.desc,
333
+ inputSchema: mountable.presentationSchema
255
334
  }));
256
335
  }
257
336
  // src/tools/mcp.ts
@@ -285,63 +364,53 @@ import { z } from "zod";
285
364
  function isCallToolResult(value) {
286
365
  return CallToolResultSchema.safeParse(value).success;
287
366
  }
288
- function createNativeMcpRegistrar(server, config) {
289
- return {
290
- rawServer: server,
291
- registerTool: (definition) => {
292
- if (!runtimeToolSupports(definition, "MCP"))
293
- return;
294
- assertUniqueToolName(definition.name, config.takenNames.has(definition.name), "MCP tool name");
295
- config.takenNames.add(definition.name);
296
- const mountable = runtimeToolMountable(definition);
297
- const [prepared] = config.prepare(mountable);
298
- if (!prepared)
299
- return;
300
- const toolConfig = {
301
- description: definition.description,
302
- inputSchema: z.looseObject({}).meta(presentationMetadata(prepared.inputSchema))
367
+ function mountPreparedRuntimeMcp(server, tools, config) {
368
+ for (const { definition, descriptor } of tools) {
369
+ const { mountable } = descriptor;
370
+ const toolConfig = {
371
+ description: definition.description,
372
+ inputSchema: z.looseObject({}).meta(presentationMetadata(descriptor.inputSchema))
373
+ };
374
+ if (descriptor.outputSchema)
375
+ toolConfig.outputSchema = descriptor.outputSchema;
376
+ if (definition.annotations)
377
+ toolConfig.annotations = definition.annotations;
378
+ if (definition.ui) {
379
+ toolConfig._meta = {
380
+ ui: definition.ui,
381
+ "ui/resourceUri": definition.ui.resourceUri
303
382
  };
304
- if (prepared.outputSchema)
305
- toolConfig.outputSchema = prepared.outputSchema;
306
- if (definition.annotations)
307
- toolConfig.annotations = definition.annotations;
308
- if (definition.ui) {
309
- toolConfig._meta = {
310
- ui: definition.ui,
311
- "ui/resourceUri": definition.ui.resourceUri
312
- };
383
+ }
384
+ server.registerTool(definition.name, toolConfig, async (rawArgs) => {
385
+ const args = isRecord(rawArgs) ? rawArgs : {};
386
+ const result = await executeToolMethod(mountable.method, definition.name, args, { ...config.context, source: "mcp" }, config.hooks, config.lifecycle, config.coerceJsonArgs ?? true, config.onOutputStrip ? (paths) => config.onOutputStrip?.(definition.name, paths) : undefined);
387
+ if (!result.ok || !definition.present?.mcp) {
388
+ return config.formatResult(result, descriptor.outputMode, definition.name);
313
389
  }
314
- server.registerTool(definition.name, toolConfig, async (rawArgs) => {
315
- const args = isRecord(rawArgs) ? rawArgs : {};
316
- const result = await executeToolMethod(mountable.method, definition.name, args, { ...config.context, source: "mcp" }, config.hooks, config.lifecycle, config.coerceJsonArgs ?? true, config.onOutputStrip ? (paths) => config.onOutputStrip?.(definition.name, paths) : undefined);
317
- if (!result.ok || !definition.present?.mcp) {
318
- return config.formatResult(result, prepared.outputMode, definition.name);
319
- }
320
- const presented = await definition.present.mcp(result.data);
321
- if ("structuredContent" in presented || "isError" in presented) {
322
- return config.formatResult({
323
- ok: false,
324
- code: "INTERNAL_SERVER_ERROR",
325
- details: {
326
- message: "Runtime MCP presenter cannot set framework-owned structuredContent or isError"
327
- }
328
- }, "none", definition.name);
329
- }
330
- const structured = config.formatResult(result, prepared.outputMode, definition.name).structuredContent;
331
- const response = {
332
- ...presented,
333
- ...structured && { structuredContent: structured }
334
- };
335
- if (isCallToolResult(response))
336
- return response;
390
+ const presented = await definition.present.mcp(result.data);
391
+ if ("structuredContent" in presented || "isError" in presented) {
337
392
  return config.formatResult({
338
393
  ok: false,
339
394
  code: "INTERNAL_SERVER_ERROR",
340
- details: { message: "Runtime MCP presenter returned an invalid CallToolResult" }
395
+ details: {
396
+ message: "Runtime MCP presenter cannot set framework-owned structuredContent or isError"
397
+ }
341
398
  }, "none", definition.name);
342
- });
343
- }
344
- };
399
+ }
400
+ const structured = config.formatResult(result, descriptor.outputMode, definition.name).structuredContent;
401
+ const response = {
402
+ ...presented,
403
+ ...structured && { structuredContent: structured }
404
+ };
405
+ if (isCallToolResult(response))
406
+ return response;
407
+ return config.formatResult({
408
+ ok: false,
409
+ code: "INTERNAL_SERVER_ERROR",
410
+ details: { message: "Runtime MCP presenter returned an invalid CallToolResult" }
411
+ }, "none", definition.name);
412
+ });
413
+ }
345
414
  }
346
415
 
347
416
  // src/tools/portable-formats.ts
@@ -629,6 +698,37 @@ function prepareMcpTools(tools, config = {}) {
629
698
  throwIfFailures(failures);
630
699
  return Object.freeze(prepared);
631
700
  }
701
+ function prepareMcpServerSurface(surface, config = {}) {
702
+ const contractMountables = [];
703
+ const definitions = [];
704
+ const runtimeMountables = [];
705
+ for (const entry of collectToolSurface({
706
+ surface,
707
+ transport: "MCP",
708
+ extend: config.extend,
709
+ flattenUnionInput: config.flattenUnionInput
710
+ })) {
711
+ if (entry.kind === "contract") {
712
+ contractMountables.push(entry.mountable);
713
+ } else {
714
+ definitions.push(entry.definition);
715
+ runtimeMountables.push(entry.mountable);
716
+ }
717
+ }
718
+ const contractTools = prepareMcpTools(contractMountables, config);
719
+ const runtimeDescriptors = prepareMcpTools(runtimeMountables, {
720
+ schemaValidation: config.schemaValidation,
721
+ logger: config.logger
722
+ });
723
+ const descriptorsByName = new Map(runtimeDescriptors.map((descriptor) => [descriptor.mountable.name, descriptor]));
724
+ const runtimeTools = [];
725
+ for (const definition of definitions) {
726
+ const descriptor = descriptorsByName.get(definition.name);
727
+ if (descriptor)
728
+ runtimeTools.push(Object.freeze({ definition, descriptor }));
729
+ }
730
+ return Object.freeze({ contractTools, runtimeTools: Object.freeze(runtimeTools) });
731
+ }
632
732
  function validateMcpSchemas(config) {
633
733
  prepareMcpSurface(config.services, {
634
734
  extend: config.extend,
@@ -686,8 +786,22 @@ function mountPreparedMcp(mcpServer, prepared, config = {}) {
686
786
  }
687
787
  }
688
788
  function buildMcpServer(config, auth) {
689
- const services = typeof config.services === "function" ? config.services(auth) : config.services;
690
- const prepared = prepareMcpSurface(services, {
789
+ let surface;
790
+ if (config.surfaces) {
791
+ const key = config.selectSurface(auth);
792
+ if (!Object.hasOwn(config.surfaces, key)) {
793
+ throw new Error(`[stitchkit] Unknown MCP surface "${key}"`);
794
+ }
795
+ const selected = config.surfaces[key];
796
+ if (!selected)
797
+ throw new Error(`[stitchkit] Unknown MCP surface "${key}"`);
798
+ surface = selected;
799
+ } else {
800
+ const services = typeof config.services === "function" ? config.services(auth) : config.services;
801
+ const runtimeTools = typeof config.runtimeTools === "function" ? config.runtimeTools(auth) : config.runtimeTools;
802
+ surface = { services, runtimeTools };
803
+ }
804
+ const prepared = prepareMcpServerSurface(surface, {
691
805
  extend: config.extend,
692
806
  flattenUnionInput: config.flattenUnionInput,
693
807
  schemaValidation: config.schemaValidation,
@@ -698,7 +812,7 @@ function buildMcpServer(config, auth) {
698
812
  function buildMcpServerFromPrepared(config, auth, prepared) {
699
813
  const server = new McpServer(config.serverInfo, config.instructions ? { instructions: config.instructions } : undefined);
700
814
  const context = config.context?.(auth);
701
- mountPreparedMcp(server, prepared, {
815
+ mountPreparedMcp(server, prepared.contractTools, {
702
816
  context,
703
817
  hooks: config.hooks,
704
818
  lifecycle: config.lifecycle,
@@ -710,23 +824,15 @@ function buildMcpServerFromPrepared(config, auth, prepared) {
710
824
  errorHint: config.errorHint,
711
825
  onOutputStrip: config.onOutputStrip
712
826
  });
713
- if (config.nativeTools) {
714
- const takenNames = new Set(prepared.map((tool2) => tool2.mountable.name));
715
- const registrar = createNativeMcpRegistrar(server, {
716
- context,
717
- hooks: config.hooks,
718
- lifecycle: config.lifecycle,
719
- coerceJsonArgs: config.coerceJsonArgs,
720
- onOutputStrip: config.onOutputStrip,
721
- takenNames,
722
- prepare: (tool2) => prepareMcpTools([tool2], {
723
- schemaValidation: config.schemaValidation,
724
- logger: config.logger
725
- }),
726
- formatResult: (result, mode, toolName) => formatMcpResult(result, mode, toolName, config.errorHint)
727
- });
728
- config.nativeTools(registrar, auth);
729
- }
827
+ mountPreparedRuntimeMcp(server, prepared.runtimeTools, {
828
+ context,
829
+ hooks: config.hooks,
830
+ lifecycle: config.lifecycle,
831
+ coerceJsonArgs: config.coerceJsonArgs,
832
+ onOutputStrip: config.onOutputStrip,
833
+ formatResult: (result, mode, toolName) => formatMcpResult(result, mode, toolName, config.errorHint)
834
+ });
835
+ config.rawTools?.(server, auth);
730
836
  for (const resource of config.resources ?? []) {
731
837
  mountMcpResource(server, resource);
732
838
  }
@@ -838,13 +944,37 @@ function jsonRpcError(code, message, status, headers) {
838
944
  }
839
945
  function createMcpHandler(config) {
840
946
  let buildServer;
841
- if (Array.isArray(config.services)) {
842
- const prepared = prepareMcpSurface(config.services, {
843
- logger: config.logger,
844
- extend: config.extend,
845
- flattenUnionInput: config.flattenUnionInput,
846
- schemaValidation: config.schemaValidation
847
- });
947
+ const preparation = {
948
+ logger: config.logger,
949
+ extend: config.extend,
950
+ flattenUnionInput: config.flattenUnionInput,
951
+ schemaValidation: config.schemaValidation
952
+ };
953
+ if (config.surfaces && config.selectSurface) {
954
+ const selectSurface = config.selectSurface;
955
+ const preparedByKey = new Map;
956
+ const preparedBySurface = new WeakMap;
957
+ for (const key in config.surfaces) {
958
+ if (!Object.hasOwn(config.surfaces, key))
959
+ continue;
960
+ const surface = config.surfaces[key];
961
+ if (!surface)
962
+ continue;
963
+ const existing = preparedBySurface.get(surface);
964
+ const prepared = existing ?? prepareMcpServerSurface(surface, preparation);
965
+ if (!existing)
966
+ preparedBySurface.set(surface, prepared);
967
+ preparedByKey.set(key, prepared);
968
+ }
969
+ buildServer = (auth) => {
970
+ const key = selectSurface(auth);
971
+ const prepared = preparedByKey.get(key);
972
+ if (!prepared)
973
+ throw new Error(`[stitchkit] Unknown MCP surface "${key}"`);
974
+ return buildMcpServerFromPrepared(config, auth, prepared);
975
+ };
976
+ } else if (config.services && typeof config.services !== "function" && typeof config.runtimeTools !== "function") {
977
+ const prepared = prepareMcpServerSurface({ services: config.services, runtimeTools: config.runtimeTools }, preparation);
848
978
  buildServer = (auth) => buildMcpServerFromPrepared(config, auth, prepared);
849
979
  } else {
850
980
  buildServer = (auth) => buildMcpServer(config, auth);
@@ -1727,27 +1857,71 @@ function createToolkit() {
1727
1857
  };
1728
1858
  }
1729
1859
  // src/tools/transports.ts
1860
+ var ALL_TRANSPORTS = ["HTTP", "MCP", "AGENT", "CLI"];
1730
1861
  var TOOL_TRANSPORTS2 = ["MCP", "AGENT", "CLI"];
1731
1862
  function emptyCounts() {
1732
1863
  return { HTTP: 0, MCP: 0, AGENT: 0, CLI: 0 };
1733
1864
  }
1734
- function summarizeTransports(services) {
1865
+ function contractCounts(service) {
1866
+ const counts = emptyCounts();
1867
+ for (const method of Object.values(service.methods)) {
1868
+ if (!method.expose || method.expose.includes("HTTP"))
1869
+ counts.HTTP += 1;
1870
+ }
1871
+ for (const transport of TOOL_TRANSPORTS2) {
1872
+ counts[transport] = collectToolSurface({
1873
+ surface: { services: [service] },
1874
+ transport,
1875
+ assertNames: false,
1876
+ assertUniqueNames: false
1877
+ }).length;
1878
+ }
1879
+ return counts;
1880
+ }
1881
+ function runtimeCounts(runtimeTools) {
1882
+ const counts = emptyCounts();
1883
+ for (const transport of TOOL_TRANSPORTS2) {
1884
+ counts[transport] = collectToolSurface({
1885
+ surface: { runtimeTools },
1886
+ transport,
1887
+ assertNames: false,
1888
+ assertUniqueNames: false
1889
+ }).length;
1890
+ }
1891
+ return counts;
1892
+ }
1893
+ function addCounts(target, source) {
1894
+ for (const transport of ALL_TRANSPORTS)
1895
+ target[transport] += source[transport];
1896
+ }
1897
+ function summarizeTransports(surface) {
1735
1898
  const totals = emptyCounts();
1736
- const perService = services.map((service) => {
1737
- const counts = emptyCounts();
1738
- for (const method of Object.values(service.methods)) {
1739
- if (!method.expose || method.expose.includes("HTTP"))
1740
- counts.HTTP += 1;
1741
- }
1742
- for (const transport of TOOL_TRANSPORTS2) {
1743
- counts[transport] = collectTools(service, transport, { assertNames: false }).length;
1744
- }
1745
- for (const transport of Object.keys(counts)) {
1746
- totals[transport] += counts[transport];
1747
- }
1748
- return { service: service.name, counts };
1749
- });
1750
- return { services: services.length, totals, perService };
1899
+ const sources = [];
1900
+ for (const service of surface.services ?? []) {
1901
+ const counts = contractCounts(service);
1902
+ addCounts(totals, counts);
1903
+ sources.push({ kind: "contract", service: service.name, counts });
1904
+ }
1905
+ const runtimeGroups = new Map;
1906
+ for (const definition of surface.runtimeTools ?? []) {
1907
+ const service = definition.identity.serviceName;
1908
+ const group = runtimeGroups.get(service);
1909
+ if (group)
1910
+ group.push(definition);
1911
+ else
1912
+ runtimeGroups.set(service, [definition]);
1913
+ }
1914
+ for (const [service, runtimeTools] of runtimeGroups) {
1915
+ const counts = runtimeCounts(runtimeTools);
1916
+ addCounts(totals, counts);
1917
+ sources.push({ kind: "runtime", service, counts });
1918
+ }
1919
+ return {
1920
+ contractServices: surface.services?.length ?? 0,
1921
+ runtimeTools: surface.runtimeTools?.length ?? 0,
1922
+ totals,
1923
+ sources
1924
+ };
1751
1925
  }
1752
1926
  // src/tools/view-file.ts
1753
1927
  import { readFile, realpath, stat } from "node:fs/promises";
@@ -1897,6 +2071,7 @@ export {
1897
2071
  createToolLogger,
1898
2072
  createToolInvoker,
1899
2073
  createStdioMcpServer,
2074
+ createRuntimeToolFactory,
1900
2075
  createMcpHandler,
1901
2076
  createCli,
1902
2077
  collectTools,