toolcraft 0.0.232 → 0.0.233

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 (180) hide show
  1. package/composition.json +3 -3
  2. package/dist/applied-default.d.ts +3 -0
  3. package/dist/applied-default.js +16 -0
  4. package/dist/cli.js +508 -204
  5. package/dist/composition.json +3 -3
  6. package/dist/discriminator.d.ts +9 -0
  7. package/dist/discriminator.js +32 -0
  8. package/dist/error-report.d.ts +2 -18
  9. package/dist/error-report.js +12 -14
  10. package/dist/http.js +8 -1
  11. package/dist/human-in-loop/gate.js +6 -0
  12. package/dist/index.compile-check.js +22 -5
  13. package/dist/index.d.ts +12 -5
  14. package/dist/index.js +50 -0
  15. package/dist/json-schema-converter.d.ts +14 -6
  16. package/dist/json-schema-converter.js +150 -20
  17. package/dist/mcp-proxy-metadata.d.ts +2 -0
  18. package/dist/mcp-proxy-metadata.js +13 -0
  19. package/dist/mcp-proxy.d.ts +1 -0
  20. package/dist/mcp-proxy.js +65 -47
  21. package/dist/mcp-result.d.ts +3 -0
  22. package/dist/mcp-result.js +10 -0
  23. package/dist/mcp.js +211 -153
  24. package/dist/redaction.js +26 -17
  25. package/dist/renderer.js +40 -29
  26. package/dist/schema-member-names.d.ts +2 -0
  27. package/dist/schema-member-names.js +39 -0
  28. package/dist/schema-scope.d.ts +1 -0
  29. package/dist/schema-scope.js +29 -16
  30. package/dist/schema.d.ts +1 -1
  31. package/dist/sdk.d.ts +2 -1
  32. package/dist/sdk.js +164 -50
  33. package/dist/stream.js +24 -23
  34. package/dist/testing/diagnostic-value.d.ts +1 -0
  35. package/dist/testing/diagnostic-value.js +57 -0
  36. package/dist/testing/fakes.js +2 -1
  37. package/dist/testing/harness.js +1 -0
  38. package/dist/testing/memory-fs.js +42 -1
  39. package/dist/testing/parity.js +50 -6
  40. package/dist/union-validation.d.ts +5 -0
  41. package/dist/union-validation.js +36 -0
  42. package/node_modules/@poe-code/process-runner/dist/types.d.ts +2 -0
  43. package/node_modules/mcp-oauth-server/dist/index.js +47 -6
  44. package/node_modules/tiny-http-mcp-server/dist/cli.js +14 -2
  45. package/node_modules/tiny-http-mcp-server/dist/composition.json +1 -1
  46. package/node_modules/tiny-http-mcp-server/dist/http-server.d.ts +7 -5
  47. package/node_modules/tiny-http-mcp-server/dist/http-server.js +3 -11
  48. package/node_modules/tiny-http-mcp-server/dist/http-transport.d.ts +6 -1
  49. package/node_modules/tiny-http-mcp-server/dist/http-transport.js +196 -15
  50. package/node_modules/tiny-http-mcp-server/dist/index.d.ts +2 -2
  51. package/node_modules/tiny-http-mcp-server/dist/modern-headers.d.ts +3 -0
  52. package/node_modules/tiny-http-mcp-server/dist/modern-headers.js +35 -0
  53. package/node_modules/tiny-http-mcp-server/dist/parse-body.d.ts +1 -0
  54. package/node_modules/tiny-http-mcp-server/dist/parse-body.js +9 -5
  55. package/node_modules/tiny-http-mcp-server/dist/test-support.js +17 -0
  56. package/node_modules/tiny-http-mcp-server/dist/testing.d.ts +1 -1
  57. package/node_modules/tiny-http-mcp-server/dist/testing.js +3 -2
  58. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +21 -4
  59. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +1 -1
  60. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js +14 -6
  61. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-fetch.d.ts +1 -0
  62. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-fetch.js +8 -0
  63. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-response.d.ts +1 -0
  64. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-response.js +50 -0
  65. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.d.ts +2 -0
  66. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.js +2 -0
  67. package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +21 -3
  68. package/node_modules/tiny-http-mcp-server/package.json +1 -1
  69. package/node_modules/tiny-mcp-client/README.md +1 -0
  70. package/node_modules/tiny-mcp-client/dist/index.d.ts +178 -101
  71. package/node_modules/tiny-mcp-client/dist/index.js +4696 -291
  72. package/node_modules/tiny-mcp-client/package.json +2 -1
  73. package/node_modules/tiny-stdio-mcp-server/dist/base64.d.ts +1 -0
  74. package/node_modules/tiny-stdio-mcp-server/dist/base64.js +5 -0
  75. package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
  76. package/node_modules/tiny-stdio-mcp-server/dist/content/audio.d.ts +3 -0
  77. package/node_modules/tiny-stdio-mcp-server/dist/content/convert.d.ts +5 -1
  78. package/node_modules/tiny-stdio-mcp-server/dist/content/convert.js +41 -6
  79. package/node_modules/tiny-stdio-mcp-server/dist/content/file.d.ts +5 -0
  80. package/node_modules/tiny-stdio-mcp-server/dist/content/image.d.ts +3 -0
  81. package/node_modules/tiny-stdio-mcp-server/dist/headers.d.ts +10 -0
  82. package/node_modules/tiny-stdio-mcp-server/dist/headers.js +180 -0
  83. package/node_modules/tiny-stdio-mcp-server/dist/index.d.ts +5 -5
  84. package/node_modules/tiny-stdio-mcp-server/dist/index.js +1 -1
  85. package/node_modules/tiny-stdio-mcp-server/dist/input-request-schema.d.ts +220 -0
  86. package/node_modules/tiny-stdio-mcp-server/dist/input-request-schema.js +1995 -0
  87. package/node_modules/tiny-stdio-mcp-server/dist/jsonrpc.d.ts +1 -0
  88. package/node_modules/tiny-stdio-mcp-server/dist/jsonrpc.js +38 -25
  89. package/node_modules/tiny-stdio-mcp-server/dist/metadata.d.ts +2 -0
  90. package/node_modules/tiny-stdio-mcp-server/dist/metadata.js +37 -0
  91. package/node_modules/tiny-stdio-mcp-server/dist/protocol-validation.d.ts +4 -0
  92. package/node_modules/tiny-stdio-mcp-server/dist/protocol-validation.js +53 -0
  93. package/node_modules/tiny-stdio-mcp-server/dist/protocol.d.ts +17 -0
  94. package/node_modules/tiny-stdio-mcp-server/dist/protocol.js +228 -0
  95. package/node_modules/tiny-stdio-mcp-server/dist/request-cancellation.d.ts +1 -0
  96. package/node_modules/tiny-stdio-mcp-server/dist/request-cancellation.js +18 -0
  97. package/node_modules/tiny-stdio-mcp-server/dist/sampling-messages.d.ts +18 -0
  98. package/node_modules/tiny-stdio-mcp-server/dist/sampling-messages.js +34 -0
  99. package/node_modules/tiny-stdio-mcp-server/dist/schema.d.ts +4 -1
  100. package/node_modules/tiny-stdio-mcp-server/dist/server.d.ts +9 -4
  101. package/node_modules/tiny-stdio-mcp-server/dist/server.js +334 -135
  102. package/node_modules/tiny-stdio-mcp-server/dist/stdio-input.d.ts +3 -0
  103. package/node_modules/tiny-stdio-mcp-server/dist/stdio-input.js +50 -28
  104. package/node_modules/tiny-stdio-mcp-server/dist/subscriptions.d.ts +11 -0
  105. package/node_modules/tiny-stdio-mcp-server/dist/subscriptions.js +128 -0
  106. package/node_modules/tiny-stdio-mcp-server/dist/types.d.ts +58 -18
  107. package/node_modules/tiny-stdio-mcp-server/dist/types.js +2 -1
  108. package/node_modules/tiny-stdio-mcp-server/dist/uri.d.ts +1 -0
  109. package/node_modules/tiny-stdio-mcp-server/dist/uri.js +41 -0
  110. package/node_modules/tiny-stdio-mcp-server/package.json +8 -0
  111. package/node_modules/toolcraft-design/dist/command-registry.d.ts +11 -0
  112. package/node_modules/toolcraft-design/dist/command-registry.js +24 -0
  113. package/node_modules/toolcraft-design/dist/dashboard/ansi.d.ts +2 -0
  114. package/node_modules/toolcraft-design/dist/dashboard/ansi.js +81 -27
  115. package/node_modules/toolcraft-design/dist/dashboard/buffer.js +39 -25
  116. package/node_modules/toolcraft-design/dist/dashboard/components/border.js +6 -5
  117. package/node_modules/toolcraft-design/dist/dashboard/components/footer.js +26 -21
  118. package/node_modules/toolcraft-design/dist/dashboard/components/output-pane.d.ts +1 -1
  119. package/node_modules/toolcraft-design/dist/dashboard/components/output-pane.js +12 -6
  120. package/node_modules/toolcraft-design/dist/dashboard/components/stats-pane.d.ts +2 -1
  121. package/node_modules/toolcraft-design/dist/dashboard/components/stats-pane.js +51 -10
  122. package/node_modules/toolcraft-design/dist/dashboard/dashboard.d.ts +4 -0
  123. package/node_modules/toolcraft-design/dist/dashboard/dashboard.js +77 -4
  124. package/node_modules/toolcraft-design/dist/dashboard/index.d.ts +2 -0
  125. package/node_modules/toolcraft-design/dist/dashboard/index.js +2 -0
  126. package/node_modules/toolcraft-design/dist/dashboard/keymap.js +26 -6
  127. package/node_modules/toolcraft-design/dist/dashboard/layout.d.ts +1 -0
  128. package/node_modules/toolcraft-design/dist/dashboard/layout.js +19 -3
  129. package/node_modules/toolcraft-design/dist/dashboard/line-buffer.d.ts +9 -0
  130. package/node_modules/toolcraft-design/dist/dashboard/line-buffer.js +63 -0
  131. package/node_modules/toolcraft-design/dist/dashboard/output-preview.d.ts +9 -0
  132. package/node_modules/toolcraft-design/dist/dashboard/output-preview.js +70 -0
  133. package/node_modules/toolcraft-design/dist/dashboard/snapshot.js +3 -1
  134. package/node_modules/toolcraft-design/dist/dashboard/store.js +9 -3
  135. package/node_modules/toolcraft-design/dist/dashboard/terminal-strings.d.ts +6 -0
  136. package/node_modules/toolcraft-design/dist/dashboard/terminal-strings.js +108 -0
  137. package/node_modules/toolcraft-design/dist/dashboard/terminal-width.js +3 -1
  138. package/node_modules/toolcraft-design/dist/dashboard/terminal.js +11 -1
  139. package/node_modules/toolcraft-design/dist/dashboard/testing/pipeline-scenario.d.ts +1 -0
  140. package/node_modules/toolcraft-design/dist/dashboard/testing/pipeline-scenario.js +126 -0
  141. package/node_modules/toolcraft-design/dist/dashboard/types.d.ts +3 -1
  142. package/node_modules/toolcraft-design/dist/event-groups.d.ts +19 -0
  143. package/node_modules/toolcraft-design/dist/event-groups.js +49 -0
  144. package/node_modules/toolcraft-design/dist/index.d.ts +6 -1
  145. package/node_modules/toolcraft-design/dist/index.js +5 -1
  146. package/node_modules/toolcraft-design/dist/inline-notice.d.ts +14 -0
  147. package/node_modules/toolcraft-design/dist/inline-notice.js +28 -0
  148. package/node_modules/toolcraft-design/dist/metric.d.ts +8 -0
  149. package/node_modules/toolcraft-design/dist/metric.js +21 -0
  150. package/node_modules/toolcraft-design/dist/overlay-manager.d.ts +6 -0
  151. package/node_modules/toolcraft-design/dist/overlay-manager.js +10 -0
  152. package/node_modules/toolcraft-design/dist/primitives.d.ts +12 -0
  153. package/node_modules/toolcraft-design/dist/primitives.js +7 -0
  154. package/node_modules/toolcraft-design/dist/progress-group.d.ts +7 -0
  155. package/node_modules/toolcraft-design/dist/progress-group.js +10 -0
  156. package/node_modules/toolcraft-design/dist/render-performance.d.ts +33 -0
  157. package/node_modules/toolcraft-design/dist/render-performance.js +71 -0
  158. package/node_modules/toolcraft-design/dist/task-tree.d.ts +22 -0
  159. package/node_modules/toolcraft-design/dist/task-tree.js +79 -0
  160. package/node_modules/toolcraft-design/dist/viewport.d.ts +18 -0
  161. package/node_modules/toolcraft-design/dist/viewport.js +56 -0
  162. package/node_modules/toolcraft-schema/dist/clone-default.d.ts +1 -0
  163. package/node_modules/toolcraft-schema/dist/clone-default.js +26 -0
  164. package/node_modules/toolcraft-schema/dist/index.compile-check.js +1 -0
  165. package/node_modules/toolcraft-schema/dist/index.d.ts +18 -9
  166. package/node_modules/toolcraft-schema/dist/index.js +46 -8
  167. package/node_modules/toolcraft-schema/dist/json-schema/compiler.d.ts +1 -0
  168. package/node_modules/toolcraft-schema/dist/json-schema/compiler.js +6 -0
  169. package/node_modules/toolcraft-schema/dist/json-schema/evaluate.js +6 -3
  170. package/node_modules/toolcraft-schema/dist/json-schema/normalize-nullability.d.ts +1 -0
  171. package/node_modules/toolcraft-schema/dist/json-schema/normalize-nullability.js +69 -0
  172. package/node_modules/toolcraft-schema/dist/json-schema/types.d.ts +1 -0
  173. package/node_modules/toolcraft-schema/dist/json.d.ts +4 -1
  174. package/node_modules/toolcraft-schema/dist/json.js +57 -1
  175. package/node_modules/toolcraft-schema/dist/native-json-schema.d.ts +10 -0
  176. package/node_modules/toolcraft-schema/dist/native-json-schema.js +11 -0
  177. package/node_modules/toolcraft-schema/dist/validate.d.ts +5 -1
  178. package/node_modules/toolcraft-schema/dist/validate.js +61 -47
  179. package/node_modules/toolcraft-schema/package.json +1 -1
  180. package/package.json +6 -3
package/dist/mcp.js CHANGED
@@ -1,18 +1,23 @@
1
1
  import "./node-require-shim.js";
2
- import { createServer, JSON_RPC_ERROR_CODES, ToolError } from "tiny-stdio-mcp-server";
3
- import { toJsonSchema } from "toolcraft-schema";
2
+ import { createServer, JSON_RPC_ERROR_CODES, ToolError, toContentBlocks } from "tiny-stdio-mcp-server";
3
+ import { cloneDefaultValue, compileJsonSchema, formatIssues, isPlainRecord, nativeJsonSchema, toJsonSchema, unicodeLength, validate } from "toolcraft-schema";
4
4
  import { createHttpErrorEnvelope, isHttpErrorLike } from "./api-error-summary.js";
5
5
  import { ToolcraftBugError, UserError, assertCommandRequirements, resolveCommandSecrets } from "./index.js";
6
6
  import { writeErrorReport } from "./error-report.js";
7
7
  import { ApprovalDeclinedError } from "./human-in-loop/types.js";
8
8
  import { assertHumanInLoopWired, mergeApprovalsRoot } from "./human-in-loop/wiring.js";
9
9
  import { hasMcpProxyGroups, resolveMcpProxies } from "./mcp-proxy.js";
10
+ import { isMCPResult } from "./mcp-result.js";
10
11
  import { getExpectedNumberDescription, isValidNumberSchemaValue } from "./number-schema.js";
12
+ import { validateAppliedDefault } from "./applied-default.js";
11
13
  import { findEntrypointPackageMetadata } from "./package-metadata.js";
12
14
  import { filterSchemaForScope } from "./schema-scope.js";
15
+ import { validateCasedSchemaMembers } from "./schema-member-names.js";
13
16
  import { enableSourceMaps } from "./stack-trim.js";
14
17
  import { suggest } from "./suggest.js";
15
18
  import { throwValidationErrors } from "./validation-errors.js";
19
+ import { resolveDiscriminatedBranch } from "./discriminator.js";
20
+ import { validateUnionSchema } from "./union-validation.js";
16
21
  import { createRuntimeLogger } from "./runtime-logging.js";
17
22
  import { createEnv, createFs, validateServices } from "./runtime/io.js";
18
23
  import { createManagedStream } from "./stream.js";
@@ -86,41 +91,80 @@ function unwrapOptional(schema) {
86
91
  function isOptional(schema) {
87
92
  return schema.kind === "optional";
88
93
  }
89
- function isPlainObject(value) {
94
+ function isObjectRecord(value) {
90
95
  return typeof value === "object" && value !== null && !Array.isArray(value);
91
96
  }
92
97
  function formatAvailableList(values) {
93
98
  return `Available: ${[...values].sort().join(", ")}.`;
94
99
  }
95
- function applySchemaCasing(schema, casing) {
96
- const oneOf = schema.oneOf?.map((child) => applySchemaCasing(child, casing));
97
- const additionalProperties = typeof schema.additionalProperties === "object" && schema.additionalProperties !== null
98
- ? applySchemaCasing(schema.additionalProperties, casing)
100
+ function applySchemaCasing(source, casing, direction = "output", schema = toJsonSchema(source)) {
101
+ const canonical = unwrapOptional(source);
102
+ if (canonical[nativeJsonSchema] !== undefined)
103
+ return schema;
104
+ const branches = canonical.kind === "oneOf"
105
+ ? Object.values(canonical.branches)
106
+ : canonical.kind === "union" ? canonical.branches : [];
107
+ const oneOf = schema.oneOf?.map((child, index) => {
108
+ const branch = branches[index];
109
+ if (branch === undefined)
110
+ return child;
111
+ const converted = applySchemaCasing(branch, casing, direction, child);
112
+ if (canonical.kind === "oneOf" && child.default !== undefined) {
113
+ converted.default = serializeResultValue(canonical, cloneDefaultValue(child.default), casing, "default", []);
114
+ if (!validate({ kind: "json" }, converted.default).ok)
115
+ delete converted.default;
116
+ }
117
+ return converted;
118
+ });
119
+ const additionalProperties = canonical.kind === "record"
120
+ ? applySchemaCasing(canonical.value, casing, direction, schema.additionalProperties)
99
121
  : schema.additionalProperties;
100
- if (schema.type !== "object" || schema.properties === undefined) {
101
- if (schema.type === "array" && schema.items !== undefined) {
122
+ const metadata = { ...schema };
123
+ if (schema.default !== undefined) {
124
+ metadata.default = serializeResultValue(canonical, cloneDefaultValue(canonical.default), casing, "default", []);
125
+ if (!validate({ kind: "json" }, metadata.default).ok)
126
+ delete metadata.default;
127
+ }
128
+ if (canonical.kind !== "object") {
129
+ if (canonical.kind === "array") {
102
130
  return {
103
- ...schema,
131
+ ...metadata,
104
132
  ...(additionalProperties === undefined ? {} : { additionalProperties }),
105
- items: applySchemaCasing(schema.items, casing),
133
+ items: applySchemaCasing(canonical.item, casing, direction, schema.items),
106
134
  ...(oneOf === undefined ? {} : { oneOf })
107
135
  };
108
136
  }
109
137
  return {
110
- ...schema,
138
+ ...metadata,
111
139
  ...(additionalProperties === undefined ? {} : { additionalProperties }),
112
140
  ...(oneOf === undefined ? {} : { oneOf })
113
141
  };
114
142
  }
115
- const properties = Object.fromEntries(Object.entries(schema.properties).map(([key, value]) => [
143
+ const properties = Object.fromEntries(Object.entries(schema.properties ?? {}).map(([key, value]) => [
116
144
  formatSegment(key, casing),
117
- applySchemaCasing(value, casing)
145
+ Object.prototype.hasOwnProperty.call(canonical.shape, key)
146
+ ? applySchemaCasing(canonical.shape[key], casing, direction, value)
147
+ : value
118
148
  ]));
119
- const required = schema.required?.map((key) => formatSegment(key, casing));
149
+ const required = schema.required
150
+ ?.filter((key) => direction === "output"
151
+ || !Object.hasOwn(canonical.shape, key)
152
+ || unwrapOptional(canonical.shape[key]).default === undefined)
153
+ .map((key) => formatSegment(key, casing));
154
+ const aliasConditions = direction === "input" && canonical.additionalProperties === true
155
+ ? Object.entries(canonical.shape).flatMap(([key, child]) => {
156
+ const childSchema = child;
157
+ const callerKey = formatSegment(key, casing);
158
+ return callerKey !== key && isOptional(childSchema) && unwrapOptional(childSchema).default === undefined
159
+ ? [{ anyOf: [{ not: { required: [key] } }, { required: [callerKey] }] }]
160
+ : [];
161
+ })
162
+ : [];
120
163
  return {
121
- ...schema,
164
+ ...metadata,
122
165
  ...(additionalProperties === undefined ? {} : { additionalProperties }),
123
166
  properties,
167
+ ...(aliasConditions.length === 0 ? {} : { allOf: [...(schema.allOf ?? []), ...aliasConditions] }),
124
168
  ...(required === undefined ? {} : { required }),
125
169
  ...(oneOf === undefined ? {} : { oneOf })
126
170
  };
@@ -130,7 +174,7 @@ function collectParamSummaries(schema, casing, path = [], inheritedOptional = fa
130
174
  for (const [key, rawChildSchema] of Object.entries(schema.shape)) {
131
175
  const childSchema = unwrapOptional(rawChildSchema);
132
176
  const nextPath = [...path, formatSegment(key, casing)];
133
- const optional = inheritedOptional || isOptional(rawChildSchema);
177
+ const optional = inheritedOptional || isOptional(rawChildSchema) || childSchema.default !== undefined;
134
178
  if (childSchema.kind === "object") {
135
179
  summaries.push(...collectParamSummaries(childSchema, casing, nextPath, optional));
136
180
  continue;
@@ -177,21 +221,6 @@ function matchesAllowlist(toolName, allowlist) {
177
221
  function formatToolName(path) {
178
222
  return path.map((segment) => formatSegment(segment, "snake")).join("__");
179
223
  }
180
- function validateUniqueMCPParameterFields(schema, casing) {
181
- const sourceKeysByField = new Map();
182
- for (const [key, rawChildSchema] of Object.entries(schema.shape)) {
183
- const field = formatSegment(key, casing);
184
- const existingKey = sourceKeysByField.get(field);
185
- if (existingKey !== undefined) {
186
- throw new UserError(`Parameters "${existingKey}" and "${key}" use conflicting MCP field "${field}".`);
187
- }
188
- sourceKeysByField.set(field, key);
189
- const childSchema = unwrapOptional(rawChildSchema);
190
- if (childSchema.kind === "object") {
191
- validateUniqueMCPParameterFields(childSchema, casing);
192
- }
193
- }
194
- }
195
224
  function enumerateTools(root, casing, allowlist, omitRootToolNamePrefix) {
196
225
  const tools = [];
197
226
  const commandPathsByToolName = new Map();
@@ -208,9 +237,12 @@ function enumerateTools(root, casing, allowlist, omitRootToolNamePrefix) {
208
237
  if (params === undefined || params.kind !== "object") {
209
238
  throw new ToolcraftBugError(`command "${name}" must define an object params schema for MCP.`);
210
239
  }
211
- validateUniqueMCPParameterFields(params, casing);
240
+ validateCasedSchemaMembers(params, (key) => formatSegment(key, casing), "MCP field");
212
241
  if (node.result !== undefined) {
213
- validateUniqueMCPParameterFields(node.result, casing);
242
+ validateCasedSchemaMembers(node.result, (key) => formatSegment(key, casing), "MCP field");
243
+ }
244
+ if (node.stream !== undefined) {
245
+ validateCasedSchemaMembers(node.stream.event, (key) => formatSegment(key, casing), "MCP field");
214
246
  }
215
247
  const resolvedCommandPath = [...commandPath, node.name].join(".");
216
248
  const existingPath = commandPathsByToolName.get(name);
@@ -225,11 +257,12 @@ function enumerateTools(root, casing, allowlist, omitRootToolNamePrefix) {
225
257
  name,
226
258
  ...(node.title === undefined ? {} : { title: node.title }),
227
259
  description: buildToolDescription(node.description, params, node.examples, node.name, casing),
228
- inputSchema: applySchemaCasing(toJsonSchema(params), casing),
260
+ inputSchema: applySchemaCasing(params, casing, "input"),
261
+ paramsSchema: params,
229
262
  ...(node.result === undefined
230
263
  ? {}
231
264
  : {
232
- outputSchema: applySchemaCasing(toJsonSchema(node.result), casing),
265
+ outputSchema: applySchemaCasing(node.result, casing),
233
266
  resultSchema: node.result
234
267
  })
235
268
  });
@@ -261,7 +294,7 @@ function renderPendingApproval(pending) {
261
294
  content: [
262
295
  {
263
296
  type: "text",
264
- text: `Queued for human approval (id: ${pending.approvalId}). Track with \`toolcraft approvals show ${pending.approvalId}\`.`
297
+ text: `Queued for human approval (id: ${pending.approvalId}). Track with \`toolcraft approvals show --approval-id ${pending.approvalId}\`.`
265
298
  },
266
299
  {
267
300
  type: "text",
@@ -307,15 +340,31 @@ function describeReceived(value) {
307
340
  if (Array.isArray(value))
308
341
  return `array(${value.length})`;
309
342
  if (typeof value === "object")
310
- return "object";
343
+ return isPlainRecord(value) ? "object" : "non-plain object";
311
344
  if (typeof value === "string") {
312
345
  const s = value.length > 40 ? `${value.slice(0, 40)}…` : value;
313
346
  return `${JSON.stringify(s)}`;
314
347
  }
315
348
  return JSON.stringify(value);
316
349
  }
350
+ function validateNativeMCPValue(schema, value, label, errors) {
351
+ const result = validate(schema, value);
352
+ if (!result.ok) {
353
+ errors.push(...result.issues.map((issue) => ({
354
+ path: [label, ...issue.path].filter((part) => part !== "").join("."),
355
+ message: formatIssues([issue])
356
+ })));
357
+ }
358
+ return value;
359
+ }
317
360
  function validateSchemaValue(schema, value, casing, label, errors) {
318
361
  const unwrappedSchema = unwrapOptional(schema);
362
+ if (isOptional(schema) && value === undefined) {
363
+ return validateAppliedDefault(unwrappedSchema, label, errors);
364
+ }
365
+ if (unwrappedSchema[nativeJsonSchema] !== undefined) {
366
+ return validateNativeMCPValue(unwrappedSchema, value, label, errors);
367
+ }
319
368
  if (value === null && unwrappedSchema.nullable === true) {
320
369
  return null;
321
370
  }
@@ -364,10 +413,15 @@ function validateSchemaValue(schema, value, casing, label, errors) {
364
413
  return value.map((item, index) => validateSchemaValue(unwrappedSchema.item, item, casing, `${label}[${index}]`, errors));
365
414
  case "object":
366
415
  return validateObjectSchema(unwrappedSchema, value, casing, label, errors);
367
- case "json":
416
+ case "json": {
417
+ const validation = validate(unwrappedSchema, value);
418
+ if (!validation.ok) {
419
+ errors.push(...validation.issues.map((issue) => ({ path: label, message: issue.message })));
420
+ }
368
421
  return value;
422
+ }
369
423
  case "record": {
370
- if (!isPlainObject(value)) {
424
+ if (!isPlainRecord(value)) {
371
425
  errors.push({
372
426
  path: label,
373
427
  message: `Invalid value for "${label}". Expected an object, got ${describeReceived(value)}.`
@@ -380,45 +434,32 @@ function validateSchemaValue(schema, value, casing, label, errors) {
380
434
  ]));
381
435
  }
382
436
  case "oneOf": {
383
- if (!isPlainObject(value)) {
384
- return value;
385
- }
386
- const discriminatorKey = formatSegment(unwrappedSchema.discriminator, casing);
387
- const discriminator = value[discriminatorKey];
388
- const branch = typeof discriminator === "string" ? unwrappedSchema.branches[discriminator] : undefined;
389
- if (branch === undefined) {
437
+ const resolved = resolveDiscriminatedBranch(unwrappedSchema, value, formatSegment(unwrappedSchema.discriminator, casing), label, errors);
438
+ if (resolved === undefined) {
390
439
  return value;
391
440
  }
392
- const { [discriminatorKey]: ignoredDiscriminator, ...branchValue } = value;
393
- void ignoredDiscriminator;
394
441
  return {
395
- [unwrappedSchema.discriminator]: discriminator,
396
- ...validateObjectSchema(branch, branchValue, casing, label, errors)
442
+ ...validateObjectSchema(resolved.branch, resolved.value, casing, label, errors),
443
+ [unwrappedSchema.discriminator]: resolved.discriminator
397
444
  };
398
445
  }
399
446
  case "union": {
400
- if (!isPlainObject(value)) {
401
- return value;
402
- }
403
- const branch = unwrappedSchema.branches.find((candidate) => Object.keys(candidate.shape).every((key) => candidate.shape[key]?.kind === "optional" ||
404
- Object.prototype.hasOwnProperty.call(value, formatSegment(key, casing))));
405
- return branch === undefined
406
- ? value
407
- : validateObjectSchema(branch, value, casing, label, errors);
447
+ return validateUnionSchema(unwrappedSchema, value, label, errors, (branch, branchErrors) => validateObjectSchema(branch, value, casing, label, branchErrors));
408
448
  }
409
449
  }
410
450
  }
411
451
  function validateStringConstraints(schema, value, label, errors) {
412
- if (schema.minLength !== undefined && value.length < schema.minLength) {
452
+ const length = unicodeLength(value);
453
+ if (schema.minLength !== undefined && length < schema.minLength) {
413
454
  errors.push({
414
455
  path: label,
415
- message: `Invalid value for "${label}". Expected a string with length at least ${schema.minLength}, got string with length ${value.length}.`
456
+ message: `Invalid value for "${label}". Expected a string with length at least ${schema.minLength}, got string with length ${length}.`
416
457
  });
417
458
  }
418
- if (schema.maxLength !== undefined && value.length > schema.maxLength) {
459
+ if (schema.maxLength !== undefined && length > schema.maxLength) {
419
460
  errors.push({
420
461
  path: label,
421
- message: `Invalid value for "${label}". Expected a string with length at most ${schema.maxLength}, got string with length ${value.length}.`
462
+ message: `Invalid value for "${label}". Expected a string with length at most ${schema.maxLength}, got string with length ${length}.`
422
463
  });
423
464
  }
424
465
  if (schema.pattern !== undefined && !new RegExp(schema.pattern).test(value)) {
@@ -443,7 +484,10 @@ function validateArrayConstraints(schema, value, label, errors) {
443
484
  }
444
485
  }
445
486
  function validateObjectSchema(schema, value, casing, label, errors) {
446
- if (!isPlainObject(value)) {
487
+ if (schema[nativeJsonSchema] !== undefined) {
488
+ return validateNativeMCPValue(schema, value, label, errors);
489
+ }
490
+ if (!isPlainRecord(value)) {
447
491
  errors.push({
448
492
  path: label,
449
493
  message: `Invalid value for "${label}". Expected an object, got ${describeReceived(value)}.`
@@ -477,10 +521,10 @@ function validateObjectSchema(schema, value, casing, label, errors) {
477
521
  const childSchema = unwrapOptional(rawChildSchema);
478
522
  const hasValue = Object.prototype.hasOwnProperty.call(value, inputKey);
479
523
  const fieldLabel = label.length === 0 ? inputKey : `${label}.${inputKey}`;
480
- if (!hasValue) {
524
+ if (!hasValue || (isOptional(rawChildSchema) && value[inputKey] === undefined)) {
481
525
  if (childSchema.default !== undefined) {
482
526
  Object.defineProperty(result, outputKey, {
483
- value: childSchema.default,
527
+ value: validateAppliedDefault(childSchema, fieldLabel, errors),
484
528
  enumerable: true,
485
529
  configurable: true,
486
530
  writable: true
@@ -488,6 +532,13 @@ function validateObjectSchema(schema, value, casing, label, errors) {
488
532
  continue;
489
533
  }
490
534
  if (isOptional(rawChildSchema)) {
535
+ if (inputKey !== outputKey && Object.hasOwn(result, outputKey)) {
536
+ const aliasLabel = label.length === 0 ? outputKey : `${label}.${outputKey}`;
537
+ errors.push({
538
+ path: aliasLabel,
539
+ message: `Unexpected parameter "${aliasLabel}". Use "${fieldLabel}" for this declared parameter.`
540
+ });
541
+ }
491
542
  continue;
492
543
  }
493
544
  errors.push({ path: fieldLabel, message: `Missing required parameter "${fieldLabel}".` });
@@ -510,6 +561,14 @@ function validateToolArguments(schema, argumentsValue, casing) {
510
561
  }
511
562
  function serializeResultValue(schema, value, casing, label, errors) {
512
563
  const unwrappedSchema = unwrapOptional(schema);
564
+ if (unwrappedSchema[nativeJsonSchema] !== undefined) {
565
+ return validateNativeMCPValue(unwrappedSchema, value, label, errors);
566
+ }
567
+ if (isOptional(schema) && value === undefined) {
568
+ return unwrappedSchema.default === undefined
569
+ ? undefined
570
+ : serializeResultValue(unwrappedSchema, cloneDefaultValue(unwrappedSchema.default), casing, label, errors);
571
+ }
513
572
  if (value === null && unwrappedSchema.nullable === true) {
514
573
  return null;
515
574
  }
@@ -524,9 +583,10 @@ function serializeResultValue(schema, value, casing, label, errors) {
524
583
  });
525
584
  return value;
526
585
  }
586
+ validateArrayConstraints(unwrappedSchema, value, label, errors);
527
587
  return value.map((item, index) => serializeResultValue(unwrappedSchema.item, item, casing, `${label}[${index}]`, errors));
528
588
  case "record":
529
- if (!isPlainObject(value)) {
589
+ if (!isPlainRecord(value)) {
530
590
  errors.push({
531
591
  path: label,
532
592
  message: `Invalid value for "${label}". Expected an object, got ${describeReceived(value)}.`
@@ -538,57 +598,24 @@ function serializeResultValue(schema, value, casing, label, errors) {
538
598
  serializeResultValue(unwrappedSchema.value, item, casing, `${label}.${key}`, errors)
539
599
  ]));
540
600
  case "oneOf": {
541
- if (!isPlainObject(value)) {
542
- errors.push({
543
- path: label,
544
- message: `Invalid value for "${label}". Expected an object, got ${describeReceived(value)}.`
545
- });
601
+ const resolved = resolveDiscriminatedBranch(unwrappedSchema, value, unwrappedSchema.discriminator, label, errors);
602
+ if (resolved === undefined) {
546
603
  return value;
547
604
  }
548
- const discriminator = value[unwrappedSchema.discriminator];
549
- const branch = typeof discriminator === "string" ? unwrappedSchema.branches[discriminator] : undefined;
550
- if (branch === undefined) {
551
- const branchNames = Object.keys(unwrappedSchema.branches);
552
- errors.push({
553
- path: label.length === 0
554
- ? unwrappedSchema.discriminator
555
- : `${label}.${unwrappedSchema.discriminator}`,
556
- message: `Invalid value for "${label.length === 0 ? unwrappedSchema.discriminator : `${label}.${unwrappedSchema.discriminator}`}". Expected one of: ${branchNames.join(", ")}, got ${describeReceived(discriminator)}.`
557
- });
558
- return value;
559
- }
560
- const { [unwrappedSchema.discriminator]: ignoredDiscriminator, ...branchValue } = value;
561
- void ignoredDiscriminator;
562
605
  return {
563
- [formatSegment(unwrappedSchema.discriminator, casing)]: discriminator,
564
- ...serializeResultObject(branch, branchValue, casing, label, errors)
606
+ ...serializeResultObject(resolved.branch, resolved.value, casing, label, errors),
607
+ [formatSegment(unwrappedSchema.discriminator, casing)]: resolved.discriminator
565
608
  };
566
609
  }
567
610
  case "union": {
568
- if (!isPlainObject(value)) {
569
- errors.push({
570
- path: label,
571
- message: `Invalid value for "${label}". Expected an object, got ${describeReceived(value)}.`
572
- });
573
- return value;
574
- }
575
- const branch = unwrappedSchema.branches.find((candidate) => Object.keys(candidate.shape).every((key) => candidate.shape[key]?.kind === "optional" ||
576
- Object.prototype.hasOwnProperty.call(value, key)));
577
- if (branch === undefined) {
578
- errors.push({
579
- path: label,
580
- message: `Invalid value for "${label}". Expected one union branch, got ${describeReceived(value)}.`
581
- });
582
- return value;
583
- }
584
- return serializeResultObject(branch, value, casing, label, errors);
611
+ return validateUnionSchema(unwrappedSchema, value, label, errors, (branch, branchErrors) => serializeResultObject(branch, value, casing, label, branchErrors));
585
612
  }
586
613
  default:
587
614
  return validateSchemaValue(schema, value, casing, label, errors);
588
615
  }
589
616
  }
590
617
  function serializeResultObject(schema, value, casing, label, errors) {
591
- if (!isPlainObject(value)) {
618
+ if (!isPlainRecord(value)) {
592
619
  errors.push({
593
620
  path: label,
594
621
  message: `Invalid value for "${label}". Expected an object, got ${describeReceived(value)}.`
@@ -611,10 +638,10 @@ function serializeResultObject(schema, value, casing, label, errors) {
611
638
  const hasValue = Object.prototype.hasOwnProperty.call(value, sourceKey);
612
639
  const wireKey = formatSegment(sourceKey, casing);
613
640
  const fieldLabel = label.length === 0 ? sourceKey : `${label}.${sourceKey}`;
614
- if (!hasValue) {
641
+ if (!hasValue || (isOptional(rawChildSchema) && value[sourceKey] === undefined)) {
615
642
  if (childSchema.default !== undefined) {
616
643
  Object.defineProperty(result, wireKey, {
617
- value: childSchema.default,
644
+ value: serializeResultValue(childSchema, cloneDefaultValue(childSchema.default), casing, fieldLabel, errors),
618
645
  enumerable: true,
619
646
  configurable: true,
620
647
  writable: true
@@ -635,8 +662,9 @@ function serializeResultObject(schema, value, casing, label, errors) {
635
662
  });
636
663
  }
637
664
  if (schema.additionalProperties === true) {
665
+ const wireKeys = new Set([...expectedKeys].map((key) => formatSegment(key, casing)));
638
666
  for (const key of Object.keys(value)) {
639
- if (!expectedKeys.has(key)) {
667
+ if (!expectedKeys.has(key) && !wireKeys.has(key)) {
640
668
  Object.defineProperty(result, key, {
641
669
  value: value[key],
642
670
  enumerable: true,
@@ -650,7 +678,7 @@ function serializeResultObject(schema, value, casing, label, errors) {
650
678
  }
651
679
  function validateCommandResult(schema, value, casing) {
652
680
  const errors = [];
653
- const result = serializeResultObject(schema, value, casing, "", errors);
681
+ const result = serializeResultValue(schema, value, casing, "", errors);
654
682
  throwResultValidationErrors(errors);
655
683
  return result;
656
684
  }
@@ -668,35 +696,6 @@ function throwResultValidationErrors(errors) {
668
696
  }
669
697
  throw new ToolError(JSON_RPC_ERROR_CODES.INTERNAL_ERROR, `${errors.length} result errors:\n${rendered.join("\n")}`);
670
698
  }
671
- function isContentBlock(value) {
672
- if (!isPlainObject(value) || typeof value.type !== "string") {
673
- return false;
674
- }
675
- return (value.type === "text" ||
676
- value.type === "image" ||
677
- value.type === "audio" ||
678
- value.type === "resource");
679
- }
680
- function toToolContent(result) {
681
- if (result === undefined) {
682
- return [];
683
- }
684
- if (Array.isArray(result)) {
685
- return result.flatMap((item) => toToolContent(item));
686
- }
687
- if (typeof result === "string" || typeof result === "number" || typeof result === "boolean") {
688
- return [{ type: "text", text: String(result) }];
689
- }
690
- if (result === null) {
691
- return [{ type: "text", text: "null" }];
692
- }
693
- if (isContentBlock(result)) {
694
- return [result];
695
- }
696
- const fallbackValue = result;
697
- const fallbackText = JSON.stringify(fallbackValue);
698
- return [{ type: "text", text: fallbackText }];
699
- }
700
699
  function toToolError(error, reportPath) {
701
700
  if (error instanceof ToolError) {
702
701
  return error;
@@ -716,6 +715,16 @@ function toToolError(error, reportPath) {
716
715
  }
717
716
  return new ToolError(JSON_RPC_ERROR_CODES.INTERNAL_ERROR, String(error));
718
717
  }
718
+ function withToolErrorMapping(handler) {
719
+ return async (params, session) => {
720
+ try {
721
+ return await handler(params, session);
722
+ }
723
+ catch (error) {
724
+ throw toToolError(error);
725
+ }
726
+ };
727
+ }
719
728
  function createResolvedMCPServer(root, options, runtime = {}) {
720
729
  const casing = options.casing ?? "snake";
721
730
  const services = (options.services ?? {});
@@ -727,6 +736,12 @@ function createResolvedMCPServer(root, options, runtime = {}) {
727
736
  });
728
737
  validateServices(services);
729
738
  const tools = enumerateTools(root, casing, options.tools, options.omitRootToolNamePrefix ?? false);
739
+ const streamTools = tools.filter((tool) => tool.command.stream !== undefined);
740
+ if (runtime.supportsStreaming === false && streamTools.length > 0) {
741
+ throw new UserError("MCP streams require a transport with session-scoped notifications. " +
742
+ `Unsupported streams: ${streamTools.map((tool) => JSON.stringify(tool.name)).join(", ")}. ` +
743
+ "Use stateful HTTP or stdio, or exclude these streams with the tools option.");
744
+ }
730
745
  const version = resolveMCPVersion(options.version);
731
746
  const server = runtime.createServer?.({
732
747
  name: options.name,
@@ -738,7 +753,6 @@ function createResolvedMCPServer(root, options, runtime = {}) {
738
753
  version,
739
754
  validateToolArguments: false
740
755
  });
741
- const streamTools = tools.filter((tool) => tool.command.stream !== undefined);
742
756
  const subscriptions = new Map();
743
757
  let nextSubscriptionId = 0;
744
758
  server.method(MCP_STREAM_METHODS.list, () => ({
@@ -746,11 +760,11 @@ function createResolvedMCPServer(root, options, runtime = {}) {
746
760
  name: tool.name,
747
761
  description: tool.description,
748
762
  inputSchema: tool.inputSchema,
749
- eventSchema: applySchemaCasing(toJsonSchema(tool.command.stream.event), casing),
763
+ eventSchema: applySchemaCasing(tool.command.stream.event, casing),
750
764
  bufferSize: tool.command.stream.bufferSize
751
765
  }))
752
766
  }));
753
- server.method(MCP_STREAM_METHODS.subscribe, async (request, session) => {
767
+ server.method(MCP_STREAM_METHODS.subscribe, withToolErrorMapping(async (request, session) => {
754
768
  const name = typeof request?.name === "string" ? request.name : undefined;
755
769
  const tool = streamTools.find((candidate) => candidate.name === name);
756
770
  if (tool === undefined) {
@@ -760,7 +774,9 @@ function createResolvedMCPServer(root, options, runtime = {}) {
760
774
  if (streamDefinition === undefined) {
761
775
  throw new ToolcraftBugError(`Command "${tool.commandPath}" is not a stream.`);
762
776
  }
763
- const argumentsValue = isPlainObject(request?.arguments) ? request.arguments : {};
777
+ const eventSchema = applySchemaCasing(streamDefinition.event, casing);
778
+ const eventValidator = compileJsonSchema(eventSchema);
779
+ const argumentsValue = isObjectRecord(request?.arguments) ? request.arguments : {};
764
780
  const secrets = resolveCommandSecrets(tool.command, options.env);
765
781
  const requestServices = await runtime.requestServices?.(runtime.getRequestContext?.());
766
782
  const baseContext = {
@@ -778,11 +794,31 @@ function createResolvedMCPServer(root, options, runtime = {}) {
778
794
  }
779
795
  };
780
796
  await assertCommandRequirements(tool.command, { ...baseContext, params: undefined }, { apiVersion: options.apiVersion, env: options.env });
781
- const params = validateToolArguments(tool.command.params, argumentsValue, casing);
797
+ const params = validateToolArguments(tool.paramsSchema, argumentsValue, casing);
782
798
  const subscriptionId = `stream-${++nextSubscriptionId}`;
783
799
  let notificationQueue = Promise.resolve();
800
+ let notificationFailed = false;
784
801
  const notify = (params) => {
785
- notificationQueue = notificationQueue.then(() => session.notify(MCP_STREAM_METHODS.notification, params));
802
+ notificationQueue = notificationQueue.then(async () => {
803
+ if (notificationFailed || session.signal.aborted)
804
+ return;
805
+ try {
806
+ await session.notify(MCP_STREAM_METHODS.notification, params);
807
+ }
808
+ catch (error) {
809
+ notificationFailed = true;
810
+ subscriptions.delete(subscriptionId);
811
+ void stream.cancel(error).catch(() => undefined);
812
+ if (!session.signal.aborted) {
813
+ diagnostics.emit({
814
+ level: "error",
815
+ category: "runtime",
816
+ message: "MCP stream notification delivery failed.",
817
+ data: { subscriptionId, notificationType: params.type }
818
+ });
819
+ }
820
+ }
821
+ }).catch(() => undefined);
786
822
  return notificationQueue;
787
823
  };
788
824
  const stream = createManagedStream({
@@ -811,10 +847,17 @@ function createResolvedMCPServer(root, options, runtime = {}) {
811
847
  void (async () => {
812
848
  try {
813
849
  for await (const event of stream) {
850
+ const errors = [];
851
+ const serialized = serializeResultValue(streamDefinition.event, event, casing, "", errors);
852
+ throwResultValidationErrors(errors);
853
+ const validation = eventValidator.validate(serialized);
854
+ if (!validation.ok) {
855
+ throw new ToolError(JSON_RPC_ERROR_CODES.INTERNAL_ERROR, `Invalid stream event: ${formatIssues(validation.issues)}`);
856
+ }
814
857
  await notify({
815
858
  subscriptionId,
816
859
  type: "data",
817
- event
860
+ event: serialized
818
861
  });
819
862
  }
820
863
  if (!session.signal.aborted) {
@@ -835,14 +878,14 @@ function createResolvedMCPServer(root, options, runtime = {}) {
835
878
  }
836
879
  finally {
837
880
  subscriptions.delete(subscriptionId);
838
- await stream.cancel();
881
+ await stream.cancel().catch(() => undefined);
839
882
  }
840
883
  })();
841
884
  return {
842
885
  subscriptionId,
843
- eventSchema: applySchemaCasing(toJsonSchema(streamDefinition.event), casing)
886
+ eventSchema
844
887
  };
845
- });
888
+ }));
846
889
  server.method(MCP_STREAM_METHODS.unsubscribe, async (request, session) => {
847
890
  const subscriptionId = typeof request?.subscriptionId === "string" ? request.subscriptionId : undefined;
848
891
  const subscription = subscriptionId === undefined ? undefined : subscriptions.get(subscriptionId);
@@ -860,8 +903,10 @@ function createResolvedMCPServer(root, options, runtime = {}) {
860
903
  let params;
861
904
  let secrets;
862
905
  try {
906
+ transportContext?.signal.throwIfAborted();
863
907
  secrets = resolveCommandSecrets(tool.command, options.env);
864
908
  const requestServices = await runtime.requestServices?.(transportContext);
909
+ transportContext?.signal.throwIfAborted();
865
910
  const baseContext = {
866
911
  ...services,
867
912
  ...requestServices,
@@ -872,6 +917,7 @@ function createResolvedMCPServer(root, options, runtime = {}) {
872
917
  fs: createFs(options.fs),
873
918
  env: createEnv(options.env),
874
919
  diagnostics,
920
+ signal: transportContext?.signal,
875
921
  progress(message) {
876
922
  diagnostics.emit({ level: "info", message, category: "progress" });
877
923
  }
@@ -880,7 +926,8 @@ function createResolvedMCPServer(root, options, runtime = {}) {
880
926
  apiVersion: options.apiVersion,
881
927
  env: options.env
882
928
  });
883
- params = validateToolArguments(tool.command.params, argumentsValue, casing);
929
+ transportContext?.signal.throwIfAborted();
930
+ params = validateToolArguments(tool.paramsSchema, argumentsValue, casing);
884
931
  const handlerContext = {
885
932
  ...baseContext,
886
933
  params
@@ -891,8 +938,11 @@ function createResolvedMCPServer(root, options, runtime = {}) {
891
938
  if (isHumanInLoopPending(result)) {
892
939
  return renderPendingApproval(result);
893
940
  }
941
+ if (isMCPResult(result) && (result.isError === true || tool.resultSchema === undefined)) {
942
+ return result;
943
+ }
894
944
  if (tool.resultSchema !== undefined) {
895
- let mcpResult = result;
945
+ let mcpResult = isMCPResult(result) ? result.structuredContent : result;
896
946
  if (tool.command.mcpResult !== undefined) {
897
947
  try {
898
948
  mcpResult = tool.command.mcpResult(result);
@@ -902,14 +952,19 @@ function createResolvedMCPServer(root, options, runtime = {}) {
902
952
  }
903
953
  }
904
954
  const structuredContent = validateCommandResult(tool.resultSchema, mcpResult, casing);
955
+ if (isMCPResult(result)) {
956
+ return { ...result, structuredContent };
957
+ }
905
958
  return {
906
959
  content: [{ type: "text", text: JSON.stringify(structuredContent) }],
907
960
  structuredContent
908
961
  };
909
962
  }
910
- return toToolContent(result);
963
+ return toContentBlocks(result);
911
964
  }
912
965
  catch (error) {
966
+ if (transportContext?.signal.aborted)
967
+ transportContext.signal.throwIfAborted();
913
968
  if (error instanceof ApprovalDeclinedError) {
914
969
  return renderDeclinedApproval(error);
915
970
  }
@@ -959,7 +1014,10 @@ function createDeferredMCPServer(root, options) {
959
1014
  serverPromise ??= (async () => {
960
1015
  await resolveMcpProxies(root, { projectRoot: options.projectRoot });
961
1016
  return createResolvedMCPServer(root, options);
962
- })();
1017
+ })().catch((error) => {
1018
+ serverPromise = undefined;
1019
+ throw error;
1020
+ });
963
1021
  return serverPromise;
964
1022
  };
965
1023
  return new Proxy({