toolcraft 0.0.232 → 0.0.234

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
@@ -1,13 +1,110 @@
1
- import { S } from "toolcraft-schema";
1
+ import { S, withJsonSchema, normalizeLegacyNullability } from "toolcraft-schema";
2
2
  export function convertJsonSchema(schema) {
3
- if (hasSelfReferencingRef(schema, schema)) {
4
- return applyMetadata(S.Json(), schema, {
5
- nullable: schema.nullable === true
6
- });
3
+ const recursive = hasSelfReferencingRef(schema, schema);
4
+ if (recursive || needsNativeValidation(schema)) {
5
+ const resolved = resolveReferencedSchema(schema, schema, []);
6
+ const projection = resolved.type === "object"
7
+ ? createNativeObjectProjection(schema)
8
+ : recursive || schema.allOf !== undefined ? S.Json() : createNativeProjection(schema, schema, []);
9
+ return withJsonSchema(applyMetadata(projection, schema, {}), normalizeLegacyNullability(schema));
7
10
  }
8
11
  return convertSchema(schema, schema, []);
9
12
  }
13
+ function needsNativeValidation(schema) {
14
+ if (typeof schema === "boolean" || Array.isArray(schema.type) ||
15
+ (schema.type === "array" && schema.items === undefined) ||
16
+ (schema.type === undefined && schema.enum === undefined && schema.const === undefined))
17
+ return true;
18
+ if (schema.$ref !== undefined || getComposition(schema) !== undefined)
19
+ return true;
20
+ if (schema.type !== undefined && (schema.enum !== undefined || schema.const !== undefined))
21
+ return true;
22
+ if (schema.type === "object" && schema.additionalProperties !== false)
23
+ return true;
24
+ if (Object.keys(schema).some((key) => !projectionKeywords.has(key)))
25
+ return true;
26
+ if ((schema.enum !== undefined || schema.const !== undefined) && Object.keys(schema).some((key) => !literalProjectionKeywords.has(key)))
27
+ return true;
28
+ return [...Object.values(schema.properties ?? {}), ...Object.values(schema.$defs ?? {}),
29
+ ...(schema.items === undefined ? [] : Array.isArray(schema.items) ? schema.items : [schema.items]),
30
+ ...(typeof schema.additionalProperties === "object" ? [schema.additionalProperties] : [])]
31
+ .some(needsNativeValidation);
32
+ }
33
+ const projectionKeywords = new Set(["type", "properties", "required", "additionalProperties", "items", "enum", "const", "default", "description", "minLength", "maxLength", "minimum", "maximum", "minItems", "maxItems", "nullable", "pattern"]);
34
+ const literalProjectionKeywords = new Set(["type", "enum", "const", "default", "description", "nullable"]);
35
+ function createNativeProjection(schema, root, path) {
36
+ if (typeof schema === "boolean")
37
+ return S.Json();
38
+ if (schema.$ref !== undefined && (resolveLocalRef(root, schema.$ref) === undefined || typeof resolveLocalRef(root, schema.$ref) === "boolean"))
39
+ return applyMetadata(S.Json(), schema, {});
40
+ if (Array.isArray(schema.type)) {
41
+ const types = schema.type.filter(type => type !== "null");
42
+ return types.length === 1
43
+ ? convertSchema({ ...schema, type: types[0], nullable: schema.type.includes("null") || schema.nullable }, root, path)
44
+ : applyMetadata(S.Json(), schema, {});
45
+ }
46
+ if (hasSelfReferencingRef(schema, root) ||
47
+ (schema.type === "array" && (schema.items === undefined || Array.isArray(schema.items))) ||
48
+ (schema.type === undefined && schema.enum === undefined && schema.const === undefined && schema.$ref === undefined && getComposition(schema) === undefined)) {
49
+ return applyMetadata(S.Json(), schema, {});
50
+ }
51
+ return convertSchema(schema, root, path);
52
+ }
53
+ function createNativeObjectProjection(root) {
54
+ const properties = Object.create(null);
55
+ const required = new Set();
56
+ const unconditionalProperties = Object.create(null);
57
+ const conditionalProperties = new Map();
58
+ const visited = new Set();
59
+ const collect = (source, includeRequired = true) => {
60
+ if (visited.has(source))
61
+ return;
62
+ visited.add(source);
63
+ const resolved = resolveReferencedSchema(source, root, []);
64
+ Object.assign(properties, resolved.properties ?? {});
65
+ if (includeRequired)
66
+ Object.assign(unconditionalProperties, resolved.properties ?? {});
67
+ else
68
+ for (const [key, property] of Object.entries(resolved.properties ?? {})) {
69
+ const candidates = conditionalProperties.get(key) ?? [];
70
+ candidates.push(property);
71
+ conditionalProperties.set(key, candidates);
72
+ }
73
+ if (includeRequired)
74
+ for (const key of resolved.required ?? [])
75
+ required.add(key);
76
+ for (const branch of resolved.allOf ?? [])
77
+ collect(branch, includeRequired);
78
+ for (const branch of [...(resolved.anyOf ?? []), ...(resolved.oneOf ?? [])])
79
+ collect(branch, false);
80
+ };
81
+ collect(root);
82
+ const shape = {};
83
+ for (const [key, property] of Object.entries(properties)) {
84
+ const unconditional = Object.hasOwn(unconditionalProperties, key);
85
+ const projected = unconditional
86
+ ? createNativeProjection(unconditionalProperties[key], root, ["properties", key])
87
+ : createConditionalPropertyProjection(root, key, conditionalProperties.get(key) ?? [property]);
88
+ const field = unconditional ? projected : { ...projected, default: undefined };
89
+ setOwnShapeProperty(shape, key, required.has(key) ? field : S.Optional(field));
90
+ }
91
+ return S.Object(shape, { additionalProperties: root.additionalProperties !== false });
92
+ }
93
+ function createConditionalPropertyProjection(root, key, candidates) {
94
+ const branches = root.oneOf ?? root.anyOf ?? [];
95
+ if (branches.length > 1 && branches.every((branch) => Object.hasOwn(resolveReferencedSchema(branch, root, []).properties ?? {}, key))) {
96
+ const projected = candidates.map((candidate) => createNativeProjection(candidate, root, ["properties", key]));
97
+ if (projected.every((field) => field.kind === "enum")) {
98
+ const values = [...new Set(projected.flatMap((field) => field.kind === "enum" ? field.values : []))];
99
+ if (values.length > 0)
100
+ return S.Enum(values);
101
+ }
102
+ }
103
+ return S.Json();
104
+ }
10
105
  function convertSchema(schema, root, path) {
106
+ if (typeof schema === "boolean")
107
+ return S.Json();
11
108
  const resolvedSchema = resolveReferencedSchema(schema, root, path);
12
109
  const normalizedSchema = normalizeNullability(resolvedSchema);
13
110
  const composition = getComposition(normalizedSchema.schema);
@@ -37,29 +134,43 @@ function convertSchema(schema, root, path) {
37
134
  ...createCommonOptions(normalizedSchema.schema, normalizedSchema.nullable, getStringDefault(normalizedSchema.schema.default)),
38
135
  ...(normalizedSchema.schema.pattern === undefined
39
136
  ? {}
40
- : { pattern: normalizedSchema.schema.pattern })
137
+ : { pattern: normalizedSchema.schema.pattern }),
138
+ minLength: normalizedSchema.schema.minLength,
139
+ maxLength: normalizedSchema.schema.maxLength
41
140
  });
42
141
  case "number":
43
- return S.Number(createCommonOptions(normalizedSchema.schema, normalizedSchema.nullable, getNumberDefault(normalizedSchema.schema.default)));
142
+ return S.Number({
143
+ ...createCommonOptions(normalizedSchema.schema, normalizedSchema.nullable, getNumberDefault(normalizedSchema.schema.default)),
144
+ minimum: normalizedSchema.schema.minimum,
145
+ maximum: normalizedSchema.schema.maximum
146
+ });
44
147
  case "integer":
45
148
  return S.Number({
46
149
  ...createCommonOptions(normalizedSchema.schema, normalizedSchema.nullable, getIntegerDefault(normalizedSchema.schema.default)),
47
- jsonType: "integer"
150
+ jsonType: "integer",
151
+ minimum: normalizedSchema.schema.minimum,
152
+ maximum: normalizedSchema.schema.maximum
48
153
  });
49
154
  case "boolean":
50
155
  return S.Boolean(createCommonOptions(normalizedSchema.schema, normalizedSchema.nullable, getBooleanDefault(normalizedSchema.schema.default)));
51
156
  case "array":
157
+ if (Array.isArray(normalizedSchema.schema.items))
158
+ return applyMetadata(S.Json(), normalizedSchema.schema, {});
52
159
  if (normalizedSchema.schema.items === undefined) {
53
160
  throw new Error(`JSON Schema "${formatJsonSchemaPath(path)}" is an array but is missing the "items" field. Add "items": { ... } to declare the element type.`);
54
161
  }
55
- return S.Array(convertSchema(normalizedSchema.schema.items, root, [...path, "items"]), createCommonOptions(normalizedSchema.schema, normalizedSchema.nullable, getArrayDefault(normalizedSchema.schema.default)));
162
+ return S.Array(convertSchema(normalizedSchema.schema.items, root, [...path, "items"]), {
163
+ ...createCommonOptions(normalizedSchema.schema, normalizedSchema.nullable, getArrayDefault(normalizedSchema.schema.default)),
164
+ minItems: normalizedSchema.schema.minItems,
165
+ maxItems: normalizedSchema.schema.maxItems
166
+ });
56
167
  case "object":
57
168
  return convertObjectSchema(normalizedSchema.schema, root, {
58
169
  nullable: normalizedSchema.nullable,
59
170
  path
60
171
  });
61
172
  case "null":
62
- return applyMetadata(S.Json(), normalizedSchema.schema, {
173
+ return applyMetadata(S.Enum([null]), normalizedSchema.schema, {
63
174
  default: getJsonDefault(normalizedSchema.schema.default) ?? null,
64
175
  nullable: true
65
176
  });
@@ -83,7 +194,7 @@ function convertConstSchema(schema, nullable) {
83
194
  : {})
84
195
  });
85
196
  }
86
- return applyMetadata(S.Json(), schema, {
197
+ return applyMetadata(S.Json({ const: schema.const }), schema, {
87
198
  default: schema.const,
88
199
  nullable: nullable || schema.const === null,
89
200
  description: appendDescription(schema.description, `Constant JSON value: ${JSON.stringify(schema.const)}.`)
@@ -91,6 +202,9 @@ function convertConstSchema(schema, nullable) {
91
202
  }
92
203
  function convertEnumSchema(schema, nullable) {
93
204
  const values = schema.enum ?? [];
205
+ if (values.length === 1 && values[0] === null) {
206
+ return S.Enum([null], createCommonOptions(schema, nullable, schema.default === null ? null : undefined));
207
+ }
94
208
  const nonNullValues = values.filter((value) => value !== null);
95
209
  const hasNull = nonNullValues.length !== values.length;
96
210
  if (nonNullValues.every(isPrimitiveEnumValue) && nonNullValues.length > 0) {
@@ -101,7 +215,7 @@ function convertEnumSchema(schema, nullable) {
101
215
  : {})
102
216
  });
103
217
  }
104
- return applyMetadata(S.Json(), schema, {
218
+ return applyMetadata(S.Json({ enum: values }), schema, {
105
219
  nullable: nullable || hasNull,
106
220
  description: appendDescription(schema.description, `Allowed JSON values: ${values.map((value) => JSON.stringify(value)).join(", ")}.`)
107
221
  });
@@ -111,6 +225,9 @@ function convertCompositionSchema(schema, root, nullable, path) {
111
225
  const branchSchemas = composition?.branches ?? [];
112
226
  const keyword = composition?.keyword ?? "oneOf";
113
227
  const branches = branchSchemas.map((branch, index) => resolveReferencedSchema(branch, root, [...path, keyword, String(index)]));
228
+ if (branches.some((branch) => branch.type !== "object" && branch.properties === undefined)) {
229
+ return applyMetadata(S.Json(), schema, { nullable });
230
+ }
114
231
  const discriminator = findDiscriminator(branches, root, path);
115
232
  if (discriminator !== undefined) {
116
233
  const convertedBranches = Object.fromEntries(branches.map((branch, index) => [
@@ -127,6 +244,10 @@ function convertCompositionSchema(schema, root, nullable, path) {
127
244
  nullable
128
245
  });
129
246
  }
247
+ const fingerprints = branches.map((branch) => JSON.stringify([...(branch.required ?? [])].sort()));
248
+ if (new Set(fingerprints).size !== fingerprints.length) {
249
+ return applyMetadata(S.Json(), schema, { nullable });
250
+ }
130
251
  return applyMetadata(S.Union(branches.map((branch, index) => convertObjectSchema(branch, root, {
131
252
  path: [...path, keyword, String(index)]
132
253
  }))), schema, {
@@ -295,17 +416,22 @@ function getDiscriminatorLiteral(branch, key, root) {
295
416
  }
296
417
  return undefined;
297
418
  }
298
- function resolveReferencedSchema(schema, root, path) {
419
+ function resolveReferencedSchema(schema, root, path, activeReferences = new Set()) {
420
+ if (typeof schema === "boolean")
421
+ return {};
299
422
  if (schema.$ref === undefined) {
300
423
  return schema;
301
424
  }
425
+ if (activeReferences.has(schema))
426
+ return schema;
427
+ activeReferences.add(schema);
302
428
  const resolvedTarget = resolveLocalRef(root, schema.$ref);
303
- if (resolvedTarget === undefined) {
304
- throw new Error(`JSON Schema "${formatJsonSchemaPath(path)}" uses "$ref": ${schema.$ref}. toolcraft only supports internal refs like "#/components/schemas/Foo".`);
305
- }
306
429
  const { $ref: ignoredRef, ...siblingKeywords } = schema;
307
430
  void ignoredRef;
308
- const resolvedSchema = resolveReferencedSchema(resolvedTarget, root, path);
431
+ // The native compiler owns anchors, resource identifiers and unresolved-reference errors.
432
+ if (resolvedTarget === undefined || typeof resolvedTarget === "boolean")
433
+ return siblingKeywords;
434
+ const resolvedSchema = resolveReferencedSchema(resolvedTarget, root, path, activeReferences);
309
435
  if (Object.keys(siblingKeywords).length === 0) {
310
436
  return resolvedSchema;
311
437
  }
@@ -336,6 +462,8 @@ function mergeJsonSchemas(base, overlay) {
336
462
  };
337
463
  }
338
464
  function hasSelfReferencingRef(schema, root, path = "#", activePaths = new Set()) {
465
+ if (typeof schema === "boolean")
466
+ return false;
339
467
  const nextActivePaths = new Set(activePaths);
340
468
  nextActivePaths.add(path);
341
469
  const localRefPath = getLocalRefPath(schema.$ref);
@@ -350,7 +478,9 @@ function hasSelfReferencingRef(schema, root, path = "#", activePaths = new Set()
350
478
  }
351
479
  }
352
480
  if (schema.items !== undefined &&
353
- hasSelfReferencingRef(schema.items, root, `${path}/items`, nextActivePaths)) {
481
+ (Array.isArray(schema.items)
482
+ ? schema.items.some((item, index) => hasSelfReferencingRef(item, root, `${path}/items/${index}`, nextActivePaths))
483
+ : hasSelfReferencingRef(schema.items, root, `${path}/items`, nextActivePaths))) {
354
484
  return true;
355
485
  }
356
486
  if (typeof schema.additionalProperties === "object" &&
@@ -421,7 +551,7 @@ function resolveLocalRef(root, ref) {
421
551
  }
422
552
  current = current[segment];
423
553
  }
424
- return isPlainObject(current) ? current : undefined;
554
+ return typeof current === "boolean" || isPlainObject(current) ? current : undefined;
425
555
  }
426
556
  function appendDescription(description, addition) {
427
557
  if (addition === undefined || addition.length === 0) {
@@ -433,7 +563,7 @@ function appendDescription(description, addition) {
433
563
  return `${description} ${addition}`;
434
564
  }
435
565
  function isPrimitiveEnumValue(value) {
436
- return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
566
+ return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
437
567
  }
438
568
  function getStringDefault(value) {
439
569
  return typeof value === "string" ? value : undefined;
@@ -0,0 +1,2 @@
1
+ export declare function isProxyNode(node: object): boolean;
2
+ export declare function markProxyNode<TNode extends object>(node: TNode): TNode;
@@ -0,0 +1,13 @@
1
+ const proxyNodeSymbol = Symbol("toolcraft.mcpProxyNode");
2
+ export function isProxyNode(node) {
3
+ return Object.getOwnPropertySymbols(node).some((symbol) => symbol.description === proxyNodeSymbol.description && Reflect.get(node, symbol) === true);
4
+ }
5
+ export function markProxyNode(node) {
6
+ Object.defineProperty(node, proxyNodeSymbol, {
7
+ configurable: false,
8
+ enumerable: false,
9
+ value: true,
10
+ writable: false
11
+ });
12
+ return node;
13
+ }
@@ -10,3 +10,4 @@ export declare function resolveCachePath(name: string, projectRoot?: string): st
10
10
  export declare function parseRefreshEnv(value: string | undefined): "all" | Set<string> | undefined;
11
11
  export declare function dialUpstream(name: string, config: McpServerConfig): Promise<McpClient>;
12
12
  export declare function resolveMcpProxies(root: Group<any>, options?: ResolveMcpProxyOptions): Promise<void>;
13
+ export declare function disposeMcpProxies(root: Group<any>): Promise<void>;
package/dist/mcp-proxy.js CHANGED
@@ -5,7 +5,10 @@ import { createLogger } from "toolcraft-design";
5
5
  import { HttpTransport, McpClient, StdioTransport } from "tiny-mcp-client";
6
6
  import { hasOwnErrorCode } from "./error-codes.js";
7
7
  import { convertJsonSchema } from "./json-schema-converter.js";
8
+ import { asMCPResult } from "./mcp-result.js";
9
+ import { isProxyNode, markProxyNode } from "./mcp-proxy-metadata.js";
8
10
  import { findProjectRoot } from "./project-root.js";
11
+ import { formatIssues, validate } from "toolcraft-schema";
9
12
  export { findProjectRoot } from "./project-root.js";
10
13
  const GROUP_CONFIG_SYMBOL_DESCRIPTION = "toolcraft.group.config";
11
14
  const MCP_PROXY_SCHEMA_URL = "https://poe-platform.github.io/poe-code/schemas/toolcraft/mcp-proxy.schema.json";
@@ -13,9 +16,7 @@ const DEFAULT_CLIENT_INFO = {
13
16
  name: "toolcraft",
14
17
  version: "0.0.1",
15
18
  };
16
- const proxyNodeSymbol = Symbol("toolcraft.mcpProxyNode");
17
19
  const proxyConnectionSymbol = Symbol("toolcraft.mcpProxyConnection");
18
- const shutdownDisposers = new Set();
19
20
  function getInternalGroupConfig(group) {
20
21
  const symbol = Object.getOwnPropertySymbols(group).find((candidate) => candidate.description === GROUP_CONFIG_SYMBOL_DESCRIPTION);
21
22
  if (symbol === undefined) {
@@ -24,27 +25,12 @@ function getInternalGroupConfig(group) {
24
25
  return (group[symbol] ??
25
26
  {});
26
27
  }
27
- function isProxyNode(node) {
28
- return node[proxyNodeSymbol] === true;
29
- }
30
- function markProxyNode(node) {
31
- Object.defineProperty(node, proxyNodeSymbol, {
32
- configurable: false,
33
- enumerable: false,
34
- value: true,
35
- writable: false,
36
- });
37
- return node;
38
- }
39
28
  function cloneSecrets(secrets) {
40
29
  return { ...secrets };
41
30
  }
42
31
  function cloneScope(scope) {
43
32
  return scope === undefined ? undefined : [...scope];
44
33
  }
45
- function registerShutdownDispose(dispose) {
46
- shutdownDisposers.add(dispose);
47
- }
48
34
  function getProxyConnection(group) {
49
35
  return group[proxyConnectionSymbol];
50
36
  }
@@ -72,9 +58,6 @@ function createProxyCommand(parent, tool, commandName, connection) {
72
58
  const result = tool.outputSchema === undefined
73
59
  ? undefined
74
60
  : convertJsonSchema(tool.outputSchema);
75
- if (result !== undefined && result.kind !== "object") {
76
- throw new Error(`upstream tool "${tool.name}" must define an object output schema`);
77
- }
78
61
  return markProxyNode({
79
62
  kind: "command",
80
63
  name: commandName,
@@ -92,17 +75,33 @@ function createProxyCommand(parent, tool, commandName, connection) {
92
75
  confirm: false,
93
76
  requires: parent.requires,
94
77
  handler: async (ctx) => {
95
- const client = await ensureConnected(connection);
78
+ ctx.signal?.throwIfAborted();
79
+ const connecting = ensureConnected(connection);
80
+ const client = ctx.signal === undefined ? await connecting : await new Promise((resolve, reject) => {
81
+ const signal = ctx.signal;
82
+ const abort = () => {
83
+ signal.removeEventListener("abort", abort);
84
+ reject(signal.reason);
85
+ };
86
+ signal.addEventListener("abort", abort, { once: true });
87
+ connecting.then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
88
+ if (signal.aborted)
89
+ abort();
90
+ });
91
+ ctx.signal?.throwIfAborted();
96
92
  const toolResult = await client.callTool({
97
93
  name: tool.name,
98
94
  arguments: ctx.params,
99
- });
100
- if (result === undefined) {
101
- return toolResult;
95
+ }, { signal: ctx.signal });
96
+ if (result === undefined || toolResult.isError === true) {
97
+ return asMCPResult(toolResult);
102
98
  }
103
99
  if (toolResult.structuredContent === undefined) {
104
100
  throw new Error(`upstream tool "${tool.name}" declared outputSchema but returned no structuredContent`);
105
101
  }
102
+ const validation = validate(result, toolResult.structuredContent);
103
+ if (!validation.ok)
104
+ throw new Error(`upstream tool "${tool.name}" returned invalid structuredContent: ${formatIssues(validation.issues)}`);
106
105
  return toolResult.structuredContent;
107
106
  },
108
107
  render: undefined,
@@ -141,21 +140,26 @@ function createConnection(name, config) {
141
140
  const connection = {
142
141
  name,
143
142
  config,
144
- async dispose() {
145
- shutdownDisposers.delete(connection.dispose);
146
- connection.connecting = undefined;
147
- if (connection.client === undefined) {
148
- return;
149
- }
150
- const client = connection.client;
151
- connection.client = undefined;
152
- await client.close();
143
+ dispose() {
144
+ if (connection.closing !== undefined)
145
+ return connection.closing;
146
+ connection.closing = (async () => {
147
+ await connection.connecting?.catch(() => undefined);
148
+ const client = connection.client;
149
+ connection.client = undefined;
150
+ if (client !== undefined)
151
+ await client.close();
152
+ })().finally(() => {
153
+ connection.closing = undefined;
154
+ });
155
+ return connection.closing;
153
156
  },
154
157
  };
155
- registerShutdownDispose(connection.dispose);
156
158
  return connection;
157
159
  }
158
160
  async function ensureConnected(connection) {
161
+ if (connection.closing !== undefined)
162
+ await connection.closing;
159
163
  if (connection.client !== undefined && connection.client.state === "ready") {
160
164
  return connection.client;
161
165
  }
@@ -239,11 +243,22 @@ async function fetchCache(name, config) {
239
243
  try {
240
244
  logger.info(`MCP ${name}: listing tools`);
241
245
  const tools = [];
246
+ const seenCursors = new Set();
242
247
  let cursor;
248
+ let pages = 0;
243
249
  do {
244
250
  const page = await client.listTools(cursor === undefined ? {} : { cursor });
251
+ pages++;
245
252
  tools.push(...page.tools);
246
253
  cursor = page.nextCursor;
254
+ if (cursor !== undefined) {
255
+ if (seenCursors.has(cursor)) {
256
+ throw new Error("upstream tools/list returned a repeated pagination cursor");
257
+ }
258
+ seenCursors.add(cursor);
259
+ if (pages >= 128)
260
+ throw new Error("upstream exceeded the tool pagination limit (128 pages)");
261
+ }
247
262
  } while (cursor !== undefined);
248
263
  logger.info(`MCP ${name}: found ${tools.length} tools`);
249
264
  const upstream = client.serverInfo ?? {
@@ -295,18 +310,6 @@ function populateGroupFromTools(group, tools, rename, connection) {
295
310
  parent.children.push(createProxyCommand(parent, tool, commandName, connection));
296
311
  }
297
312
  }
298
- function replaceProxyChildrenSafely(group, tools, rename, connection) {
299
- const previousChildren = snapshotGroupChildren(group);
300
- try {
301
- populateGroupFromTools(group, tools, rename, connection);
302
- }
303
- catch (error) {
304
- for (const [capturedGroup, children] of previousChildren) {
305
- capturedGroup.children = children;
306
- }
307
- throw error;
308
- }
309
- }
310
313
  function snapshotGroupChildren(group) {
311
314
  const snapshot = new Map();
312
315
  const visit = (current) => {
@@ -356,8 +359,9 @@ async function resolveSingleProxy(group, options) {
356
359
  validateRenameMap(name, tools, internal.rename);
357
360
  const previousConnection = getProxyConnection(group);
358
361
  const nextConnection = createConnection(name, config);
362
+ const previousChildren = snapshotGroupChildren(group);
359
363
  try {
360
- replaceProxyChildrenSafely(group, tools, internal.rename, nextConnection);
364
+ populateGroupFromTools(group, tools, internal.rename, nextConnection);
361
365
  if (shouldWriteCache) {
362
366
  await writeCache(cachePath, cache);
363
367
  createLogger((message) => process.stderr.write(`${message}\n`)).info(`MCP ${name}: wrote ${cachePath}`);
@@ -365,6 +369,9 @@ async function resolveSingleProxy(group, options) {
365
369
  setProxyConnection(group, nextConnection);
366
370
  }
367
371
  catch (error) {
372
+ for (const [capturedGroup, children] of previousChildren) {
373
+ capturedGroup.children = children;
374
+ }
368
375
  await nextConnection.dispose();
369
376
  throw error;
370
377
  }
@@ -474,3 +481,14 @@ export async function resolveMcpProxies(root, options = {}) {
474
481
  const groups = collectProxyGroups(root);
475
482
  await Promise.all(groups.map((group) => resolveSingleProxy(group, options)));
476
483
  }
484
+ export async function disposeMcpProxies(root) {
485
+ const connections = new Set(collectProxyGroups(root).flatMap((group) => {
486
+ const connection = getProxyConnection(group);
487
+ return connection === undefined ? [] : [connection];
488
+ }));
489
+ const outcomes = await Promise.allSettled([...connections].map((connection) => connection.dispose()));
490
+ const failures = outcomes.flatMap((outcome) => outcome.status === "rejected" ? [outcome.reason] : []);
491
+ if (failures.length > 0) {
492
+ throw new AggregateError(failures, failures.map((error) => error instanceof Error ? error.message : String(error)).join("; "));
493
+ }
494
+ }
@@ -0,0 +1,3 @@
1
+ import type { CallToolResult } from "tiny-stdio-mcp-server";
2
+ export declare function asMCPResult<TResult extends CallToolResult>(result: TResult): TResult;
3
+ export declare function isMCPResult(value: unknown): value is CallToolResult;
@@ -0,0 +1,10 @@
1
+ const mcpResultSymbol = Symbol.for("toolcraft.mcp-result");
2
+ export function asMCPResult(result) {
3
+ if (result === null || typeof result !== "object" || Array.isArray(result) || !Array.isArray(result.content)) {
4
+ throw new TypeError("MCP results must contain a content array.");
5
+ }
6
+ return Object.defineProperty({ ...result }, mcpResultSymbol, { value: true });
7
+ }
8
+ export function isMCPResult(value) {
9
+ return value !== null && typeof value === "object" && Reflect.get(value, mcpResultSymbol) === true;
10
+ }