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.
- 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
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
|
|
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(
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
...
|
|
131
|
+
...metadata,
|
|
104
132
|
...(additionalProperties === undefined ? {} : { additionalProperties }),
|
|
105
|
-
items: applySchemaCasing(
|
|
133
|
+
items: applySchemaCasing(canonical.item, casing, direction, schema.items),
|
|
106
134
|
...(oneOf === undefined ? {} : { oneOf })
|
|
107
135
|
};
|
|
108
136
|
}
|
|
109
137
|
return {
|
|
110
|
-
...
|
|
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
|
-
|
|
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
|
|
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
|
-
...
|
|
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
|
-
|
|
240
|
+
validateCasedSchemaMembers(params, (key) => formatSegment(key, casing), "MCP field");
|
|
212
241
|
if (node.result !== undefined) {
|
|
213
|
-
|
|
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(
|
|
260
|
+
inputSchema: applySchemaCasing(params, casing, "input"),
|
|
261
|
+
paramsSchema: params,
|
|
229
262
|
...(node.result === undefined
|
|
230
263
|
? {}
|
|
231
264
|
: {
|
|
232
|
-
outputSchema: applySchemaCasing(
|
|
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 (!
|
|
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
|
-
|
|
384
|
-
|
|
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
|
-
|
|
396
|
-
|
|
442
|
+
...validateObjectSchema(resolved.branch, resolved.value, casing, label, errors),
|
|
443
|
+
[unwrappedSchema.discriminator]: resolved.discriminator
|
|
397
444
|
};
|
|
398
445
|
}
|
|
399
446
|
case "union": {
|
|
400
|
-
|
|
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
|
-
|
|
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 ${
|
|
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 &&
|
|
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 ${
|
|
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 (
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
542
|
-
|
|
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
|
-
|
|
564
|
-
|
|
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
|
-
|
|
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 (!
|
|
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 =
|
|
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(
|
|
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
|
|
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.
|
|
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(() =>
|
|
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
|
|
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
|
-
|
|
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
|
|
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({
|