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.
- package/composition.json +3 -3
- package/dist/applied-default.d.ts +3 -0
- package/dist/applied-default.js +16 -0
- package/dist/cli.js +508 -204
- package/dist/composition.json +3 -3
- package/dist/discriminator.d.ts +9 -0
- package/dist/discriminator.js +32 -0
- package/dist/error-report.d.ts +2 -18
- package/dist/error-report.js +12 -14
- package/dist/http.js +8 -1
- package/dist/human-in-loop/gate.js +6 -0
- package/dist/index.compile-check.js +22 -5
- package/dist/index.d.ts +12 -5
- package/dist/index.js +50 -0
- package/dist/json-schema-converter.d.ts +14 -6
- package/dist/json-schema-converter.js +150 -20
- package/dist/mcp-proxy-metadata.d.ts +2 -0
- package/dist/mcp-proxy-metadata.js +13 -0
- package/dist/mcp-proxy.d.ts +1 -0
- package/dist/mcp-proxy.js +65 -47
- package/dist/mcp-result.d.ts +3 -0
- package/dist/mcp-result.js +10 -0
- package/dist/mcp.js +211 -153
- package/dist/redaction.js +26 -17
- package/dist/renderer.js +40 -29
- package/dist/schema-member-names.d.ts +2 -0
- package/dist/schema-member-names.js +39 -0
- package/dist/schema-scope.d.ts +1 -0
- package/dist/schema-scope.js +29 -16
- package/dist/schema.d.ts +1 -1
- package/dist/sdk.d.ts +2 -1
- package/dist/sdk.js +164 -50
- package/dist/stream.js +24 -23
- package/dist/testing/diagnostic-value.d.ts +1 -0
- package/dist/testing/diagnostic-value.js +57 -0
- package/dist/testing/fakes.js +2 -1
- package/dist/testing/harness.js +1 -0
- package/dist/testing/memory-fs.js +42 -1
- package/dist/testing/parity.js +50 -6
- package/dist/union-validation.d.ts +5 -0
- package/dist/union-validation.js +36 -0
- package/node_modules/@poe-code/process-runner/dist/types.d.ts +2 -0
- package/node_modules/mcp-oauth-server/dist/index.js +47 -6
- package/node_modules/tiny-http-mcp-server/dist/cli.js +14 -2
- package/node_modules/tiny-http-mcp-server/dist/composition.json +1 -1
- package/node_modules/tiny-http-mcp-server/dist/http-server.d.ts +7 -5
- package/node_modules/tiny-http-mcp-server/dist/http-server.js +3 -11
- package/node_modules/tiny-http-mcp-server/dist/http-transport.d.ts +6 -1
- package/node_modules/tiny-http-mcp-server/dist/http-transport.js +196 -15
- package/node_modules/tiny-http-mcp-server/dist/index.d.ts +2 -2
- package/node_modules/tiny-http-mcp-server/dist/modern-headers.d.ts +3 -0
- package/node_modules/tiny-http-mcp-server/dist/modern-headers.js +35 -0
- package/node_modules/tiny-http-mcp-server/dist/parse-body.d.ts +1 -0
- package/node_modules/tiny-http-mcp-server/dist/parse-body.js +9 -5
- package/node_modules/tiny-http-mcp-server/dist/test-support.js +17 -0
- package/node_modules/tiny-http-mcp-server/dist/testing.d.ts +1 -1
- package/node_modules/tiny-http-mcp-server/dist/testing.js +3 -2
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +21 -4
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +1 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js +14 -6
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-fetch.d.ts +1 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-fetch.js +8 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-response.d.ts +1 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-response.js +50 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.d.ts +2 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.js +2 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +21 -3
- package/node_modules/tiny-http-mcp-server/package.json +1 -1
- package/node_modules/tiny-mcp-client/README.md +1 -0
- package/node_modules/tiny-mcp-client/dist/index.d.ts +178 -101
- package/node_modules/tiny-mcp-client/dist/index.js +4696 -291
- package/node_modules/tiny-mcp-client/package.json +2 -1
- package/node_modules/tiny-stdio-mcp-server/dist/base64.d.ts +1 -0
- package/node_modules/tiny-stdio-mcp-server/dist/base64.js +5 -0
- package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
- package/node_modules/tiny-stdio-mcp-server/dist/content/audio.d.ts +3 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/convert.d.ts +5 -1
- package/node_modules/tiny-stdio-mcp-server/dist/content/convert.js +41 -6
- package/node_modules/tiny-stdio-mcp-server/dist/content/file.d.ts +5 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/image.d.ts +3 -0
- package/node_modules/tiny-stdio-mcp-server/dist/headers.d.ts +10 -0
- package/node_modules/tiny-stdio-mcp-server/dist/headers.js +180 -0
- package/node_modules/tiny-stdio-mcp-server/dist/index.d.ts +5 -5
- package/node_modules/tiny-stdio-mcp-server/dist/index.js +1 -1
- package/node_modules/tiny-stdio-mcp-server/dist/input-request-schema.d.ts +220 -0
- package/node_modules/tiny-stdio-mcp-server/dist/input-request-schema.js +1995 -0
- package/node_modules/tiny-stdio-mcp-server/dist/jsonrpc.d.ts +1 -0
- package/node_modules/tiny-stdio-mcp-server/dist/jsonrpc.js +38 -25
- package/node_modules/tiny-stdio-mcp-server/dist/metadata.d.ts +2 -0
- package/node_modules/tiny-stdio-mcp-server/dist/metadata.js +37 -0
- package/node_modules/tiny-stdio-mcp-server/dist/protocol-validation.d.ts +4 -0
- package/node_modules/tiny-stdio-mcp-server/dist/protocol-validation.js +53 -0
- package/node_modules/tiny-stdio-mcp-server/dist/protocol.d.ts +17 -0
- package/node_modules/tiny-stdio-mcp-server/dist/protocol.js +228 -0
- package/node_modules/tiny-stdio-mcp-server/dist/request-cancellation.d.ts +1 -0
- package/node_modules/tiny-stdio-mcp-server/dist/request-cancellation.js +18 -0
- package/node_modules/tiny-stdio-mcp-server/dist/sampling-messages.d.ts +18 -0
- package/node_modules/tiny-stdio-mcp-server/dist/sampling-messages.js +34 -0
- package/node_modules/tiny-stdio-mcp-server/dist/schema.d.ts +4 -1
- package/node_modules/tiny-stdio-mcp-server/dist/server.d.ts +9 -4
- package/node_modules/tiny-stdio-mcp-server/dist/server.js +334 -135
- package/node_modules/tiny-stdio-mcp-server/dist/stdio-input.d.ts +3 -0
- package/node_modules/tiny-stdio-mcp-server/dist/stdio-input.js +50 -28
- package/node_modules/tiny-stdio-mcp-server/dist/subscriptions.d.ts +11 -0
- package/node_modules/tiny-stdio-mcp-server/dist/subscriptions.js +128 -0
- package/node_modules/tiny-stdio-mcp-server/dist/types.d.ts +58 -18
- package/node_modules/tiny-stdio-mcp-server/dist/types.js +2 -1
- package/node_modules/tiny-stdio-mcp-server/dist/uri.d.ts +1 -0
- package/node_modules/tiny-stdio-mcp-server/dist/uri.js +41 -0
- package/node_modules/tiny-stdio-mcp-server/package.json +8 -0
- package/node_modules/toolcraft-design/dist/command-registry.d.ts +11 -0
- package/node_modules/toolcraft-design/dist/command-registry.js +24 -0
- package/node_modules/toolcraft-design/dist/dashboard/ansi.d.ts +2 -0
- package/node_modules/toolcraft-design/dist/dashboard/ansi.js +81 -27
- package/node_modules/toolcraft-design/dist/dashboard/buffer.js +39 -25
- package/node_modules/toolcraft-design/dist/dashboard/components/border.js +6 -5
- package/node_modules/toolcraft-design/dist/dashboard/components/footer.js +26 -21
- package/node_modules/toolcraft-design/dist/dashboard/components/output-pane.d.ts +1 -1
- package/node_modules/toolcraft-design/dist/dashboard/components/output-pane.js +12 -6
- package/node_modules/toolcraft-design/dist/dashboard/components/stats-pane.d.ts +2 -1
- package/node_modules/toolcraft-design/dist/dashboard/components/stats-pane.js +51 -10
- package/node_modules/toolcraft-design/dist/dashboard/dashboard.d.ts +4 -0
- package/node_modules/toolcraft-design/dist/dashboard/dashboard.js +77 -4
- package/node_modules/toolcraft-design/dist/dashboard/index.d.ts +2 -0
- package/node_modules/toolcraft-design/dist/dashboard/index.js +2 -0
- package/node_modules/toolcraft-design/dist/dashboard/keymap.js +26 -6
- package/node_modules/toolcraft-design/dist/dashboard/layout.d.ts +1 -0
- package/node_modules/toolcraft-design/dist/dashboard/layout.js +19 -3
- package/node_modules/toolcraft-design/dist/dashboard/line-buffer.d.ts +9 -0
- package/node_modules/toolcraft-design/dist/dashboard/line-buffer.js +63 -0
- package/node_modules/toolcraft-design/dist/dashboard/output-preview.d.ts +9 -0
- package/node_modules/toolcraft-design/dist/dashboard/output-preview.js +70 -0
- package/node_modules/toolcraft-design/dist/dashboard/snapshot.js +3 -1
- package/node_modules/toolcraft-design/dist/dashboard/store.js +9 -3
- package/node_modules/toolcraft-design/dist/dashboard/terminal-strings.d.ts +6 -0
- package/node_modules/toolcraft-design/dist/dashboard/terminal-strings.js +108 -0
- package/node_modules/toolcraft-design/dist/dashboard/terminal-width.js +3 -1
- package/node_modules/toolcraft-design/dist/dashboard/terminal.js +11 -1
- package/node_modules/toolcraft-design/dist/dashboard/testing/pipeline-scenario.d.ts +1 -0
- package/node_modules/toolcraft-design/dist/dashboard/testing/pipeline-scenario.js +126 -0
- package/node_modules/toolcraft-design/dist/dashboard/types.d.ts +3 -1
- package/node_modules/toolcraft-design/dist/event-groups.d.ts +19 -0
- package/node_modules/toolcraft-design/dist/event-groups.js +49 -0
- package/node_modules/toolcraft-design/dist/index.d.ts +6 -1
- package/node_modules/toolcraft-design/dist/index.js +5 -1
- package/node_modules/toolcraft-design/dist/inline-notice.d.ts +14 -0
- package/node_modules/toolcraft-design/dist/inline-notice.js +28 -0
- package/node_modules/toolcraft-design/dist/metric.d.ts +8 -0
- package/node_modules/toolcraft-design/dist/metric.js +21 -0
- package/node_modules/toolcraft-design/dist/overlay-manager.d.ts +6 -0
- package/node_modules/toolcraft-design/dist/overlay-manager.js +10 -0
- package/node_modules/toolcraft-design/dist/primitives.d.ts +12 -0
- package/node_modules/toolcraft-design/dist/primitives.js +7 -0
- package/node_modules/toolcraft-design/dist/progress-group.d.ts +7 -0
- package/node_modules/toolcraft-design/dist/progress-group.js +10 -0
- package/node_modules/toolcraft-design/dist/render-performance.d.ts +33 -0
- package/node_modules/toolcraft-design/dist/render-performance.js +71 -0
- package/node_modules/toolcraft-design/dist/task-tree.d.ts +22 -0
- package/node_modules/toolcraft-design/dist/task-tree.js +79 -0
- package/node_modules/toolcraft-design/dist/viewport.d.ts +18 -0
- package/node_modules/toolcraft-design/dist/viewport.js +56 -0
- package/node_modules/toolcraft-schema/dist/clone-default.d.ts +1 -0
- package/node_modules/toolcraft-schema/dist/clone-default.js +26 -0
- package/node_modules/toolcraft-schema/dist/index.compile-check.js +1 -0
- package/node_modules/toolcraft-schema/dist/index.d.ts +18 -9
- package/node_modules/toolcraft-schema/dist/index.js +46 -8
- package/node_modules/toolcraft-schema/dist/json-schema/compiler.d.ts +1 -0
- package/node_modules/toolcraft-schema/dist/json-schema/compiler.js +6 -0
- package/node_modules/toolcraft-schema/dist/json-schema/evaluate.js +6 -3
- package/node_modules/toolcraft-schema/dist/json-schema/normalize-nullability.d.ts +1 -0
- package/node_modules/toolcraft-schema/dist/json-schema/normalize-nullability.js +69 -0
- package/node_modules/toolcraft-schema/dist/json-schema/types.d.ts +1 -0
- package/node_modules/toolcraft-schema/dist/json.d.ts +4 -1
- package/node_modules/toolcraft-schema/dist/json.js +57 -1
- package/node_modules/toolcraft-schema/dist/native-json-schema.d.ts +10 -0
- package/node_modules/toolcraft-schema/dist/native-json-schema.js +11 -0
- package/node_modules/toolcraft-schema/dist/validate.d.ts +5 -1
- package/node_modules/toolcraft-schema/dist/validate.js +61 -47
- package/node_modules/toolcraft-schema/package.json +1 -1
- 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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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(
|
|
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"]),
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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,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
|
+
}
|
package/dist/mcp-proxy.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
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,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
|
+
}
|