stitchkit 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -7
- package/dist/browser/http.d.ts +5 -0
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/contract/define.d.ts.map +1 -1
- package/dist/contract/errors.d.ts +4 -3
- package/dist/contract/errors.d.ts.map +1 -1
- package/dist/contract/index.js +1 -1
- package/dist/{index-gfzn1n4n.js → index-0t460v7k.js} +13 -5
- package/dist/{index-n7bmdwmz.js → index-4b1j82gp.js} +8 -4
- package/dist/{index-5sxnvwb1.js → index-4kz3aqpx.js} +2 -2
- package/dist/index-dpj1jcys.js +677 -0
- package/dist/{index-7wfkbvss.js → index-kckky6zw.js} +6 -2
- package/dist/index-kzfs85xp.js +9 -0
- package/dist/index-mwmpw6j1.js +60 -0
- package/dist/index-q6ja2qwq.js +7 -0
- package/dist/index.js +4 -3
- package/dist/internal/errors.d.ts +13 -1
- package/dist/internal/errors.d.ts.map +1 -1
- package/dist/internal/safe-json.d.ts +19 -0
- package/dist/internal/safe-json.d.ts.map +1 -0
- package/dist/internal/within-dir.d.ts +7 -0
- package/dist/internal/within-dir.d.ts.map +1 -0
- package/dist/node.d.ts +4 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +29 -0
- package/dist/observability/context.d.ts +9 -1
- package/dist/observability/context.d.ts.map +1 -1
- package/dist/observability/index.js +35 -15
- package/dist/observability/sanitize.d.ts.map +1 -1
- package/dist/server/context.d.ts +3 -2
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/create.d.ts +3 -3
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/event-bus.d.ts +10 -1
- package/dist/server/event-bus.d.ts.map +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +94 -606
- package/dist/server/logger.d.ts +5 -4
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/middleware/auth.d.ts +24 -1
- package/dist/server/middleware/auth.d.ts.map +1 -1
- package/dist/server/middleware/cookies.d.ts.map +1 -1
- package/dist/server/middleware/cors.d.ts +7 -0
- package/dist/server/middleware/cors.d.ts.map +1 -1
- package/dist/server/multipart.d.ts +2 -1
- package/dist/server/multipart.d.ts.map +1 -1
- package/dist/server/node.d.ts +12 -0
- package/dist/server/node.d.ts.map +1 -0
- package/dist/server/rate-limit.d.ts +7 -2
- package/dist/server/rate-limit.d.ts.map +1 -1
- package/dist/server/request.d.ts +27 -4
- package/dist/server/request.d.ts.map +1 -1
- package/dist/server/router.d.ts +4 -2
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/stream.d.ts +3 -1
- package/dist/server/stream.d.ts.map +1 -1
- package/dist/server/types.d.ts +35 -20
- package/dist/server/types.d.ts.map +1 -1
- package/dist/tools/agent.d.ts +15 -2
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/coerce.d.ts +12 -0
- package/dist/tools/coerce.d.ts.map +1 -0
- package/dist/tools/execute.d.ts +26 -2
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/flatten.d.ts +15 -0
- package/dist/tools/flatten.d.ts.map +1 -0
- package/dist/tools/json-schema.d.ts +18 -0
- package/dist/tools/json-schema.d.ts.map +1 -0
- package/dist/tools/manifest.d.ts +17 -0
- package/dist/tools/manifest.d.ts.map +1 -0
- package/dist/tools/mcp-handler.d.ts.map +1 -1
- package/dist/tools/mcp.d.ts +53 -3
- package/dist/tools/mcp.d.ts.map +1 -1
- package/dist/tools/mount.d.ts +18 -6
- package/dist/tools/mount.d.ts.map +1 -1
- package/dist/tools/schema.d.ts +19 -2
- package/dist/tools/schema.d.ts.map +1 -1
- package/dist/tools/view-file.d.ts.map +1 -1
- package/dist/tools.d.ts +6 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +415 -185
- package/package.json +15 -4
- package/dist/index-ke4mx4ea.js +0 -38
package/dist/tools.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createClient
|
|
3
|
-
} from "./index-
|
|
3
|
+
} from "./index-4kz3aqpx.js";
|
|
4
|
+
import {
|
|
5
|
+
isWithinDir
|
|
6
|
+
} from "./index-q6ja2qwq.js";
|
|
4
7
|
import {
|
|
5
8
|
formatZodError,
|
|
6
|
-
normalizeError
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
normalizeError,
|
|
10
|
+
validateHandlerOutput
|
|
11
|
+
} from "./index-0t460v7k.js";
|
|
12
|
+
import"./index-kckky6zw.js";
|
|
13
|
+
import {
|
|
14
|
+
isUnsafeKey,
|
|
15
|
+
safeJsonParse
|
|
16
|
+
} from "./index-kzfs85xp.js";
|
|
9
17
|
import {
|
|
10
18
|
isRecord
|
|
11
19
|
} from "./index-809wc1tt.js";
|
|
@@ -13,22 +21,100 @@ import {
|
|
|
13
21
|
// src/tools/agent.ts
|
|
14
22
|
import { tool, zodSchema } from "ai";
|
|
15
23
|
|
|
16
|
-
// src/tools/
|
|
24
|
+
// src/tools/coerce.ts
|
|
25
|
+
import { z } from "zod";
|
|
26
|
+
function needsJsonCoercion(field) {
|
|
27
|
+
if (field instanceof z.ZodArray || field instanceof z.ZodObject)
|
|
28
|
+
return true;
|
|
29
|
+
if (field instanceof z.ZodOptional || field instanceof z.ZodNullable || field instanceof z.ZodDefault) {
|
|
30
|
+
return needsJsonCoercion(field.unwrap());
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
function coerceJsonArgs(args, schema) {
|
|
35
|
+
if (!(schema instanceof z.ZodObject))
|
|
36
|
+
return args;
|
|
37
|
+
const shape = schema.shape;
|
|
38
|
+
const out = {};
|
|
39
|
+
for (const [key, value] of Object.entries(args)) {
|
|
40
|
+
const field = shape[key];
|
|
41
|
+
if (field && needsJsonCoercion(field) && typeof value === "string") {
|
|
42
|
+
try {
|
|
43
|
+
out[key] = safeJsonParse(value);
|
|
44
|
+
continue;
|
|
45
|
+
} catch {}
|
|
46
|
+
}
|
|
47
|
+
out[key] = value;
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/tools/schema.ts
|
|
17
53
|
import { z as z2 } from "zod";
|
|
54
|
+
function objectShapeKeys(schema) {
|
|
55
|
+
return schema instanceof z2.ZodObject ? Object.keys(schema.shape) : [];
|
|
56
|
+
}
|
|
57
|
+
function mergeSchemas(paramsSchema, inputSchema) {
|
|
58
|
+
if (paramsSchema && !(paramsSchema instanceof z2.ZodObject)) {
|
|
59
|
+
throw new Error("Tool params schema must be a z.object()");
|
|
60
|
+
}
|
|
61
|
+
const paramsObject = paramsSchema instanceof z2.ZodObject ? paramsSchema : undefined;
|
|
62
|
+
if (!inputSchema) {
|
|
63
|
+
return paramsObject ?? z2.object({});
|
|
64
|
+
}
|
|
65
|
+
if (inputSchema instanceof z2.ZodObject) {
|
|
66
|
+
if (paramsObject) {
|
|
67
|
+
const conflicts = Object.keys(paramsObject.shape).filter((key) => (key in inputSchema.shape));
|
|
68
|
+
if (conflicts.length > 0) {
|
|
69
|
+
throw new Error(`Schema merge conflict: ${conflicts.join(", ")} appear in both params and input`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return z2.object({ ...paramsObject?.shape ?? {}, ...inputSchema.shape });
|
|
73
|
+
}
|
|
74
|
+
return paramsObject ? z2.intersection(paramsObject, inputSchema) : inputSchema;
|
|
75
|
+
}
|
|
18
76
|
|
|
19
77
|
// src/tools/execute.ts
|
|
20
|
-
|
|
78
|
+
function toolResultFromError(err) {
|
|
79
|
+
const appErr = normalizeError(err);
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
code: appErr.code,
|
|
83
|
+
details: appErr.details ?? { message: appErr.message },
|
|
84
|
+
...appErr.hint && { hint: appErr.hint }
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
async function executeToolMethod(method, toolName, rawArgs, context, hooks, lifecycle, coerceJson = false) {
|
|
21
88
|
const startedAt = Date.now();
|
|
22
89
|
const finish = async (result) => {
|
|
23
90
|
await hooks?.afterToolCall?.(toolName, rawArgs, result, Date.now() - startedAt, context);
|
|
24
91
|
return result;
|
|
25
92
|
};
|
|
26
93
|
if (hooks?.beforeToolCall) {
|
|
27
|
-
|
|
94
|
+
try {
|
|
95
|
+
await hooks.beforeToolCall(toolName, rawArgs, context);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
return finish(toolResultFromError(err));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const paramKeys = new Set(objectShapeKeys(method.paramsSchema));
|
|
101
|
+
let paramArgs = {};
|
|
102
|
+
let inputArgs = {};
|
|
103
|
+
for (const [key, value] of Object.entries(rawArgs)) {
|
|
104
|
+
if (isUnsafeKey(key))
|
|
105
|
+
continue;
|
|
106
|
+
if (paramKeys.has(key))
|
|
107
|
+
paramArgs[key] = value;
|
|
108
|
+
else
|
|
109
|
+
inputArgs[key] = value;
|
|
110
|
+
}
|
|
111
|
+
if (coerceJson) {
|
|
112
|
+
paramArgs = coerceJsonArgs(paramArgs, method.paramsSchema);
|
|
113
|
+
inputArgs = coerceJsonArgs(inputArgs, method.inputSchema);
|
|
28
114
|
}
|
|
29
115
|
let params;
|
|
30
116
|
if (method.paramsSchema) {
|
|
31
|
-
const result = method.paramsSchema.safeParse(
|
|
117
|
+
const result = method.paramsSchema.safeParse(paramArgs);
|
|
32
118
|
if (!result.success) {
|
|
33
119
|
return finish({
|
|
34
120
|
ok: false,
|
|
@@ -40,7 +126,7 @@ async function executeToolMethod(method, toolName, rawArgs, context, hooks) {
|
|
|
40
126
|
}
|
|
41
127
|
let input;
|
|
42
128
|
if (method.inputSchema) {
|
|
43
|
-
const result = method.inputSchema.safeParse(
|
|
129
|
+
const result = method.inputSchema.safeParse(inputArgs);
|
|
44
130
|
if (!result.success) {
|
|
45
131
|
return finish({
|
|
46
132
|
ok: false,
|
|
@@ -51,120 +137,55 @@ async function executeToolMethod(method, toolName, rawArgs, context, hooks) {
|
|
|
51
137
|
input = result.data;
|
|
52
138
|
}
|
|
53
139
|
try {
|
|
54
|
-
const ctx = { params, input,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return finish({ ok: true, data: output });
|
|
58
|
-
} catch (err) {
|
|
59
|
-
const appErr = normalizeError(err);
|
|
60
|
-
return finish({
|
|
61
|
-
ok: false,
|
|
62
|
-
code: appErr.code,
|
|
63
|
-
details: appErr.details ?? { message: appErr.message },
|
|
64
|
-
...appErr.hint && { hint: appErr.hint }
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// src/tools/names.ts
|
|
70
|
-
var SINGULAR_EXCEPTIONS = new Set([
|
|
71
|
-
"analytics",
|
|
72
|
-
"status",
|
|
73
|
-
"stats",
|
|
74
|
-
"settings",
|
|
75
|
-
"media",
|
|
76
|
-
"progress",
|
|
77
|
-
"news"
|
|
78
|
-
]);
|
|
79
|
-
function singularize(name) {
|
|
80
|
-
if (SINGULAR_EXCEPTIONS.has(name))
|
|
81
|
-
return name;
|
|
82
|
-
if (name.endsWith("ies"))
|
|
83
|
-
return `${name.slice(0, -3)}y`;
|
|
84
|
-
if (name.endsWith("s") && !name.endsWith("ss"))
|
|
85
|
-
return name.slice(0, -1);
|
|
86
|
-
return name;
|
|
87
|
-
}
|
|
88
|
-
function toToolName(serviceName, methodName) {
|
|
89
|
-
const normalized = serviceName.replace(/-/g, "_");
|
|
90
|
-
const singular = singularize(normalized);
|
|
91
|
-
if (methodName === "list")
|
|
92
|
-
return `list_${normalized}`;
|
|
93
|
-
if (methodName === "get")
|
|
94
|
-
return `get_${singular}`;
|
|
95
|
-
if (methodName === "create")
|
|
96
|
-
return `create_${singular}`;
|
|
97
|
-
if (methodName === "update")
|
|
98
|
-
return `update_${singular}`;
|
|
99
|
-
if (methodName === "delete")
|
|
100
|
-
return `delete_${singular}`;
|
|
101
|
-
const snake = methodName.replace(/([A-Z])/g, "_$1").toLowerCase();
|
|
102
|
-
return `${snake}_${singular}`.replace(/^_/, "");
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// src/tools/schema.ts
|
|
106
|
-
import { z } from "zod";
|
|
107
|
-
function needsJsonCoercion(field) {
|
|
108
|
-
if (field instanceof z.ZodArray || field instanceof z.ZodObject)
|
|
109
|
-
return true;
|
|
110
|
-
if (field instanceof z.ZodOptional || field instanceof z.ZodNullable || field instanceof z.ZodDefault) {
|
|
111
|
-
return needsJsonCoercion(field.unwrap());
|
|
112
|
-
}
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
var jsonCoerce = (val) => {
|
|
116
|
-
if (typeof val === "string") {
|
|
117
|
-
try {
|
|
118
|
-
return JSON.parse(val);
|
|
119
|
-
} catch {
|
|
120
|
-
return val;
|
|
140
|
+
const ctx = { ...context, params, input, source: context.source };
|
|
141
|
+
if (lifecycle?.beforeHandle) {
|
|
142
|
+
await lifecycle.beforeHandle(ctx, method);
|
|
121
143
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const coerced = {};
|
|
128
|
-
for (const [key, field] of Object.entries(shape)) {
|
|
129
|
-
if (!needsJsonCoercion(field)) {
|
|
130
|
-
coerced[key] = field;
|
|
131
|
-
continue;
|
|
144
|
+
let data = await method.handler(ctx);
|
|
145
|
+
if (lifecycle?.afterHandle) {
|
|
146
|
+
const transformed = await lifecycle.afterHandle(ctx, data, method);
|
|
147
|
+
if (transformed !== undefined)
|
|
148
|
+
data = transformed;
|
|
132
149
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
inner = inner.unwrap();
|
|
150
|
+
if (method.outputSchema) {
|
|
151
|
+
const checked = validateHandlerOutput(method.outputSchema, data);
|
|
152
|
+
if (!checked.ok) {
|
|
153
|
+
return finish({
|
|
154
|
+
ok: false,
|
|
155
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
156
|
+
details: { message: checked.message }
|
|
157
|
+
});
|
|
142
158
|
}
|
|
159
|
+
data = checked.data;
|
|
143
160
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
coerced[key] = result;
|
|
161
|
+
const output = (data === undefined || data === null) && !method.outputSchema ? { status: "ok" } : data;
|
|
162
|
+
return finish({ ok: true, data: output });
|
|
163
|
+
} catch (err) {
|
|
164
|
+
return finish(toolResultFromError(err));
|
|
149
165
|
}
|
|
150
|
-
return z.object(coerced);
|
|
151
166
|
}
|
|
167
|
+
|
|
168
|
+
// src/tools/mount.ts
|
|
169
|
+
import { z as z4 } from "zod";
|
|
170
|
+
|
|
171
|
+
// src/tools/flatten.ts
|
|
172
|
+
import { z as z3 } from "zod";
|
|
152
173
|
function flattenDiscriminatedUnion(union) {
|
|
153
174
|
const discriminator = union.def.discriminator;
|
|
154
175
|
const literalValues = [];
|
|
155
176
|
const fieldToVariants = new Map;
|
|
156
177
|
const fieldSchemas = {};
|
|
157
178
|
for (const opt of union.def.options) {
|
|
158
|
-
if (!(opt instanceof
|
|
159
|
-
throw new Error(`flattenDiscriminatedUnion:
|
|
179
|
+
if (!(opt instanceof z3.ZodObject)) {
|
|
180
|
+
throw new Error(`flattenDiscriminatedUnion: variant for discriminator '${discriminator}' is not a ZodObject`);
|
|
160
181
|
}
|
|
161
182
|
const discField = opt.shape[discriminator];
|
|
162
|
-
if (!(discField instanceof
|
|
183
|
+
if (!(discField instanceof z3.ZodLiteral)) {
|
|
163
184
|
throw new Error(`flattenDiscriminatedUnion: discriminator field '${discriminator}' must be z.literal()`);
|
|
164
185
|
}
|
|
165
186
|
const [literalValue] = discField.def.values;
|
|
166
187
|
if (typeof literalValue !== "string") {
|
|
167
|
-
throw new Error(
|
|
188
|
+
throw new Error("flattenDiscriminatedUnion: discriminator literal must be a string");
|
|
168
189
|
}
|
|
169
190
|
literalValues.push(literalValue);
|
|
170
191
|
for (const [key, field] of Object.entries(opt.shape)) {
|
|
@@ -174,7 +195,7 @@ function flattenDiscriminatedUnion(union) {
|
|
|
174
195
|
variants.push(literalValue);
|
|
175
196
|
fieldToVariants.set(key, variants);
|
|
176
197
|
if (!(key in fieldSchemas)) {
|
|
177
|
-
fieldSchemas[key] = field instanceof
|
|
198
|
+
fieldSchemas[key] = field instanceof z3.ZodOptional ? field : z3.optional(field);
|
|
178
199
|
}
|
|
179
200
|
}
|
|
180
201
|
}
|
|
@@ -183,32 +204,65 @@ function flattenDiscriminatedUnion(union) {
|
|
|
183
204
|
throw new Error("flattenDiscriminatedUnion: union has no options");
|
|
184
205
|
}
|
|
185
206
|
const shape = {
|
|
186
|
-
[discriminator]:
|
|
207
|
+
[discriminator]: z3.enum([firstLiteral, ...restLiterals])
|
|
187
208
|
};
|
|
188
209
|
for (const [key, field] of Object.entries(fieldSchemas)) {
|
|
189
210
|
const variants = fieldToVariants.get(key) ?? [];
|
|
190
211
|
const hint = `Required if ${discriminator} = ${variants.join(" | ")}`;
|
|
191
|
-
shape[key] = field
|
|
212
|
+
shape[key] = field.describe(hint);
|
|
192
213
|
}
|
|
193
|
-
return
|
|
214
|
+
return z3.object(shape);
|
|
194
215
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
216
|
+
|
|
217
|
+
// src/tools/names.ts
|
|
218
|
+
var SINGULAR_EXCEPTIONS = new Set([
|
|
219
|
+
"analytics",
|
|
220
|
+
"status",
|
|
221
|
+
"stats",
|
|
222
|
+
"settings",
|
|
223
|
+
"media",
|
|
224
|
+
"progress",
|
|
225
|
+
"news"
|
|
226
|
+
]);
|
|
227
|
+
function singularize(name) {
|
|
228
|
+
if (SINGULAR_EXCEPTIONS.has(name))
|
|
229
|
+
return name;
|
|
230
|
+
if (name.endsWith("ies"))
|
|
231
|
+
return `${name.slice(0, -3)}y`;
|
|
232
|
+
if (name.endsWith("s") && !name.endsWith("ss"))
|
|
233
|
+
return name.slice(0, -1);
|
|
234
|
+
return name;
|
|
235
|
+
}
|
|
236
|
+
function toToolName(serviceName, methodName) {
|
|
237
|
+
const normalized = serviceName.replace(/-/g, "_");
|
|
238
|
+
const singular = singularize(normalized);
|
|
239
|
+
if (methodName === "list")
|
|
240
|
+
return `list_${normalized}`;
|
|
241
|
+
if (methodName === "get")
|
|
242
|
+
return `get_${singular}`;
|
|
243
|
+
if (methodName === "create")
|
|
244
|
+
return `create_${singular}`;
|
|
245
|
+
if (methodName === "update")
|
|
246
|
+
return `update_${singular}`;
|
|
247
|
+
if (methodName === "delete")
|
|
248
|
+
return `delete_${singular}`;
|
|
249
|
+
const snake = methodName.replace(/([A-Z])/g, "_$1").toLowerCase();
|
|
250
|
+
return `${snake}_${singular}`.replace(/^_/, "");
|
|
208
251
|
}
|
|
209
252
|
|
|
210
253
|
// src/tools/mount.ts
|
|
211
|
-
function
|
|
254
|
+
function applyExtend(base, extra) {
|
|
255
|
+
if (base instanceof z4.ZodObject) {
|
|
256
|
+
const conflicts = Object.keys(extra).filter((key) => (key in base.shape));
|
|
257
|
+
if (conflicts.length > 0) {
|
|
258
|
+
throw new Error(`Tool extend conflict: ${conflicts.join(", ")} already declared by the contract`);
|
|
259
|
+
}
|
|
260
|
+
return z4.object({ ...extra, ...base.shape });
|
|
261
|
+
}
|
|
262
|
+
return z4.intersection(z4.object(extra), base);
|
|
263
|
+
}
|
|
264
|
+
function collectTools(service, transport, config = {}) {
|
|
265
|
+
const { extend, flattenUnionInput = false } = config;
|
|
212
266
|
const tools = [];
|
|
213
267
|
for (const [methodName, method] of Object.entries(service.methods)) {
|
|
214
268
|
if (method.expose && !method.expose.includes(transport))
|
|
@@ -216,9 +270,12 @@ function collectTools(service, transport, extend) {
|
|
|
216
270
|
if (method.multipart)
|
|
217
271
|
continue;
|
|
218
272
|
const name = method.toolName ?? toToolName(service.name, methodName);
|
|
219
|
-
|
|
273
|
+
let baseSchema = mergeSchemas(method.paramsSchema, method.inputSchema);
|
|
274
|
+
if (flattenUnionInput && baseSchema instanceof z4.ZodDiscriminatedUnion) {
|
|
275
|
+
baseSchema = flattenDiscriminatedUnion(baseSchema);
|
|
276
|
+
}
|
|
220
277
|
const shouldExtend = !!extend && (!extend.filter || extend.filter(service, method));
|
|
221
|
-
const schema = shouldExtend ?
|
|
278
|
+
const schema = shouldExtend && extend ? applyExtend(baseSchema, extend.schema) : baseSchema;
|
|
222
279
|
tools.push({ method, name, schema, shouldExtend });
|
|
223
280
|
}
|
|
224
281
|
return tools;
|
|
@@ -231,111 +288,230 @@ function createToolRunner(config) {
|
|
|
231
288
|
extraContext = await config.extend.resolve(rawArgs);
|
|
232
289
|
}
|
|
233
290
|
const cleanArgs = extendKeys ? Object.fromEntries(Object.entries(rawArgs).filter(([key]) => !extendKeys.has(key))) : rawArgs;
|
|
234
|
-
return executeToolMethod(tool.method, tool.name, cleanArgs, { source: config.source,
|
|
291
|
+
return executeToolMethod(tool.method, tool.name, cleanArgs, { ...config.context, ...extraContext, source: config.source }, config.hooks, config.lifecycle, config.coerceJsonArgs ?? true);
|
|
235
292
|
};
|
|
236
293
|
}
|
|
237
|
-
function formatToolError(result) {
|
|
294
|
+
function formatToolError(result, toolName, errorHint) {
|
|
238
295
|
const err = { error: result.code };
|
|
239
296
|
if (result.details)
|
|
240
297
|
err.details = result.details;
|
|
298
|
+
const hints = [];
|
|
241
299
|
if (result.hint)
|
|
242
|
-
|
|
300
|
+
hints.push(result.hint);
|
|
301
|
+
if (errorHint && toolName) {
|
|
302
|
+
const global = errorHint(toolName, result.code);
|
|
303
|
+
if (global)
|
|
304
|
+
hints.push(global);
|
|
305
|
+
}
|
|
306
|
+
if (hints.length > 0)
|
|
307
|
+
err._hint = hints.join(" ");
|
|
243
308
|
return err;
|
|
244
309
|
}
|
|
245
310
|
|
|
246
311
|
// src/tools/agent.ts
|
|
247
|
-
function
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
function mountAgent(service, config = {}) {
|
|
312
|
+
function mountAgent(services, config = {}) {
|
|
313
|
+
const serviceList = Array.isArray(services) ? services : [services];
|
|
251
314
|
const tools = {};
|
|
252
315
|
const runTool = createToolRunner({
|
|
253
316
|
source: "agent",
|
|
254
317
|
extend: config.extend,
|
|
255
318
|
context: config.context,
|
|
256
|
-
hooks: config.hooks
|
|
319
|
+
hooks: config.hooks,
|
|
320
|
+
lifecycle: config.lifecycle,
|
|
321
|
+
errorHint: config.errorHint,
|
|
322
|
+
coerceJsonArgs: config.coerceJsonArgs
|
|
257
323
|
});
|
|
258
|
-
for (const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
return formatAgentResult(result);
|
|
266
|
-
} catch (err) {
|
|
267
|
-
const appErr = normalizeError(err);
|
|
268
|
-
return { error: appErr.code, details: appErr.details };
|
|
269
|
-
}
|
|
324
|
+
for (const service of serviceList) {
|
|
325
|
+
for (const mountable of collectTools(service, "AGENT", {
|
|
326
|
+
extend: config.extend,
|
|
327
|
+
flattenUnionInput: config.flattenUnionInput
|
|
328
|
+
})) {
|
|
329
|
+
if (mountable.name in tools) {
|
|
330
|
+
throw new Error(`Duplicate agent tool name "${mountable.name}" across mounted services`);
|
|
270
331
|
}
|
|
271
|
-
|
|
332
|
+
tools[mountable.name] = tool({
|
|
333
|
+
description: mountable.method.desc,
|
|
334
|
+
inputSchema: zodSchema(mountable.schema),
|
|
335
|
+
execute: async (rawArgs) => {
|
|
336
|
+
const args = isRecord(rawArgs) ? rawArgs : {};
|
|
337
|
+
try {
|
|
338
|
+
const result = await runTool(mountable, args);
|
|
339
|
+
if (result.ok)
|
|
340
|
+
return result.data;
|
|
341
|
+
return formatToolError(result, mountable.name, config.errorHint);
|
|
342
|
+
} catch (err) {
|
|
343
|
+
return formatToolError(toolResultFromError(err), mountable.name, config.errorHint);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
272
348
|
}
|
|
273
349
|
return tools;
|
|
274
350
|
}
|
|
351
|
+
// src/tools/json-schema.ts
|
|
352
|
+
import { z as z5 } from "zod";
|
|
353
|
+
function toJsonSchema(schema, io) {
|
|
354
|
+
return z5.toJSONSchema(schema, {
|
|
355
|
+
io,
|
|
356
|
+
target: "draft-2020-12",
|
|
357
|
+
unrepresentable: "throw",
|
|
358
|
+
cycles: "ref"
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// src/tools/manifest.ts
|
|
363
|
+
function buildToolManifest(tools) {
|
|
364
|
+
return tools.map((t) => {
|
|
365
|
+
let inputSchema;
|
|
366
|
+
try {
|
|
367
|
+
inputSchema = toJsonSchema(t.schema, "input");
|
|
368
|
+
} catch {
|
|
369
|
+
inputSchema = {};
|
|
370
|
+
}
|
|
371
|
+
return { name: t.name, description: t.method.desc, inputSchema };
|
|
372
|
+
});
|
|
373
|
+
}
|
|
275
374
|
// src/tools/mcp.ts
|
|
276
375
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
277
|
-
import { z as
|
|
376
|
+
import { z as z6 } from "zod";
|
|
278
377
|
function textBlock(text) {
|
|
279
378
|
return [{ type: "text", text }];
|
|
280
379
|
}
|
|
281
|
-
function formatMcpResult(result,
|
|
380
|
+
function formatMcpResult(result, mode, toolName, errorHint) {
|
|
282
381
|
if (result.ok) {
|
|
283
382
|
const content = textBlock(JSON.stringify(result.data, null, 2));
|
|
284
|
-
if (
|
|
383
|
+
if (mode === "wrapped") {
|
|
384
|
+
return { content, structuredContent: { result: result.data } };
|
|
385
|
+
}
|
|
386
|
+
if (mode === "direct" && isRecord(result.data)) {
|
|
285
387
|
return { content, structuredContent: result.data };
|
|
286
388
|
}
|
|
287
389
|
return { content };
|
|
288
390
|
}
|
|
289
391
|
return {
|
|
290
|
-
content: textBlock(JSON.stringify(formatToolError(result), null, 2)),
|
|
392
|
+
content: textBlock(JSON.stringify(formatToolError(result, toolName, errorHint), null, 2)),
|
|
291
393
|
isError: true
|
|
292
394
|
};
|
|
293
395
|
}
|
|
396
|
+
function probeSchema(schema, io) {
|
|
397
|
+
try {
|
|
398
|
+
toJsonSchema(schema, io);
|
|
399
|
+
return null;
|
|
400
|
+
} catch (err) {
|
|
401
|
+
return err instanceof Error ? err.message : String(err);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function resolveOutputSchema(outputSchema) {
|
|
405
|
+
if (!outputSchema)
|
|
406
|
+
return null;
|
|
407
|
+
if (outputSchema instanceof z6.ZodObject) {
|
|
408
|
+
return { schema: outputSchema, mode: "direct" };
|
|
409
|
+
}
|
|
410
|
+
return { schema: z6.object({ result: outputSchema }), mode: "wrapped" };
|
|
411
|
+
}
|
|
412
|
+
function reportIncompatible(message, policy, logger, failures) {
|
|
413
|
+
if (policy === "throw") {
|
|
414
|
+
failures.push(message);
|
|
415
|
+
} else if (policy === "warn") {
|
|
416
|
+
if (logger)
|
|
417
|
+
logger.warn(`[stitchkit] ${message}`);
|
|
418
|
+
else
|
|
419
|
+
console.warn(`[stitchkit] ${message}`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function throwIfFailures(failures) {
|
|
423
|
+
if (failures.length > 0) {
|
|
424
|
+
throw new Error(`[stitchkit] ${failures.length} MCP tool(s) have an incompatible schema:
|
|
425
|
+
- ${failures.join(`
|
|
426
|
+
- `)}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
function prepareMcpTool(mountable, policy, logger, failures, seen) {
|
|
430
|
+
if (seen.has(mountable.name)) {
|
|
431
|
+
throw new Error(`Duplicate MCP tool name "${mountable.name}" across mounted services`);
|
|
432
|
+
}
|
|
433
|
+
seen.add(mountable.name);
|
|
434
|
+
if (!(mountable.schema instanceof z6.ZodObject)) {
|
|
435
|
+
reportIncompatible(`MCP tool "${mountable.name}" — input must be an object schema; a union, discriminated union or scalar cannot be an MCP tool input (flatten it in the contract, or drop MCP from \`expose\`)`, policy, logger, failures);
|
|
436
|
+
return null;
|
|
437
|
+
}
|
|
438
|
+
const inputError = probeSchema(mountable.schema, "input");
|
|
439
|
+
if (inputError) {
|
|
440
|
+
reportIncompatible(`MCP tool "${mountable.name}" — input schema is not JSON Schema-compatible: ${inputError}`, policy, logger, failures);
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
const resolved = resolveOutputSchema(mountable.method.outputSchema);
|
|
444
|
+
if (!resolved)
|
|
445
|
+
return { outputMode: "none" };
|
|
446
|
+
const outputError = probeSchema(resolved.schema, "output");
|
|
447
|
+
if (outputError) {
|
|
448
|
+
reportIncompatible(`MCP tool "${mountable.name}" — output schema is not JSON Schema-compatible: ${outputError}`, policy, logger, failures);
|
|
449
|
+
return { outputMode: "none" };
|
|
450
|
+
}
|
|
451
|
+
return { outputSchema: resolved.schema, outputMode: resolved.mode };
|
|
452
|
+
}
|
|
453
|
+
function validateMcpSchemas(services, onIncompatibleSchema = "throw", logger) {
|
|
454
|
+
const seen = new Set;
|
|
455
|
+
const failures = [];
|
|
456
|
+
for (const service of services) {
|
|
457
|
+
for (const mountable of collectTools(service, "MCP", undefined)) {
|
|
458
|
+
prepareMcpTool(mountable, onIncompatibleSchema, logger, failures, seen);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
throwIfFailures(failures);
|
|
462
|
+
}
|
|
294
463
|
function mountMcp(mcpServer, services, config = {}) {
|
|
295
464
|
const serviceList = Array.isArray(services) ? services : [services];
|
|
465
|
+
const policy = config.onIncompatibleSchema ?? "throw";
|
|
296
466
|
const runTool = createToolRunner({
|
|
297
467
|
source: "mcp",
|
|
298
468
|
extend: config.extend,
|
|
299
469
|
context: config.context,
|
|
300
|
-
hooks: config.hooks
|
|
470
|
+
hooks: config.hooks,
|
|
471
|
+
lifecycle: config.lifecycle,
|
|
472
|
+
errorHint: config.errorHint,
|
|
473
|
+
coerceJsonArgs: config.coerceJsonArgs
|
|
301
474
|
});
|
|
475
|
+
const seen = new Set;
|
|
476
|
+
const failures = [];
|
|
302
477
|
for (const service of serviceList) {
|
|
303
|
-
for (const mountable of collectTools(service, "MCP",
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
478
|
+
for (const mountable of collectTools(service, "MCP", {
|
|
479
|
+
extend: config.extend,
|
|
480
|
+
flattenUnionInput: config.flattenUnionInput
|
|
481
|
+
})) {
|
|
482
|
+
const prepared = prepareMcpTool(mountable, policy, config.logger, failures, seen);
|
|
483
|
+
if (!prepared)
|
|
308
484
|
continue;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
try {
|
|
313
|
-
z3.toJSONSchema(mountable.method.outputSchema);
|
|
314
|
-
outputSchema = mountable.method.outputSchema;
|
|
315
|
-
} catch {
|
|
316
|
-
outputSchema = undefined;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
const toolConfig = { description: mountable.method.desc, inputSchema: mountable.schema.shape };
|
|
320
|
-
if (outputSchema)
|
|
321
|
-
toolConfig.outputSchema = outputSchema.shape;
|
|
485
|
+
const toolConfig = { description: mountable.method.desc, inputSchema: mountable.schema };
|
|
486
|
+
if (prepared.outputSchema)
|
|
487
|
+
toolConfig.outputSchema = prepared.outputSchema;
|
|
322
488
|
mcpServer.registerTool(mountable.name, toolConfig, async (rawArgs) => {
|
|
489
|
+
const args = isRecord(rawArgs) ? rawArgs : {};
|
|
323
490
|
try {
|
|
324
|
-
const result = await runTool(mountable,
|
|
325
|
-
return formatMcpResult(result,
|
|
491
|
+
const result = await runTool(mountable, args);
|
|
492
|
+
return formatMcpResult(result, prepared.outputMode, mountable.name, config.errorHint);
|
|
326
493
|
} catch (err) {
|
|
327
|
-
|
|
328
|
-
return formatMcpResult({ ok: false, code: appErr.code, details: appErr.details }, false);
|
|
494
|
+
return formatMcpResult(toolResultFromError(err), "none", mountable.name, config.errorHint);
|
|
329
495
|
}
|
|
330
496
|
});
|
|
331
497
|
}
|
|
332
498
|
}
|
|
499
|
+
throwIfFailures(failures);
|
|
333
500
|
}
|
|
334
501
|
function buildMcpServer(config, auth) {
|
|
335
502
|
const server = new McpServer(config.serverInfo, config.instructions ? { instructions: config.instructions } : undefined);
|
|
336
503
|
const services = typeof config.services === "function" ? config.services(auth) : config.services;
|
|
337
504
|
const context = config.context?.(auth);
|
|
338
|
-
mountMcp(server, services, {
|
|
505
|
+
mountMcp(server, services, {
|
|
506
|
+
context,
|
|
507
|
+
hooks: config.hooks,
|
|
508
|
+
lifecycle: config.lifecycle,
|
|
509
|
+
onIncompatibleSchema: config.onIncompatibleSchema,
|
|
510
|
+
logger: config.logger,
|
|
511
|
+
coerceJsonArgs: config.coerceJsonArgs,
|
|
512
|
+
flattenUnionInput: config.flattenUnionInput,
|
|
513
|
+
errorHint: config.errorHint
|
|
514
|
+
});
|
|
339
515
|
config.nativeTools?.(server);
|
|
340
516
|
return server;
|
|
341
517
|
}
|
|
@@ -346,6 +522,9 @@ import {
|
|
|
346
522
|
} from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
347
523
|
var EVENT_TTL_MS = 10 * 60 * 1000;
|
|
348
524
|
var SESSION_TTL_MS = 30 * 60 * 1000;
|
|
525
|
+
var SWEEP_INTERVAL_MS = 60 * 1000;
|
|
526
|
+
var MAX_EVENTS = 1e4;
|
|
527
|
+
var MAX_SESSIONS = 1000;
|
|
349
528
|
|
|
350
529
|
class InMemoryEventStore {
|
|
351
530
|
events = new Map;
|
|
@@ -353,6 +532,11 @@ class InMemoryEventStore {
|
|
|
353
532
|
async storeEvent(streamId, message) {
|
|
354
533
|
const eventId = String(++this.counter);
|
|
355
534
|
this.events.set(eventId, { streamId, message, timestamp: Date.now() });
|
|
535
|
+
if (this.events.size > MAX_EVENTS) {
|
|
536
|
+
const oldest = this.events.keys().next().value;
|
|
537
|
+
if (oldest !== undefined)
|
|
538
|
+
this.events.delete(oldest);
|
|
539
|
+
}
|
|
356
540
|
return eventId;
|
|
357
541
|
}
|
|
358
542
|
async getStreamIdForEventId(eventId) {
|
|
@@ -382,20 +566,26 @@ function jsonRpcError(code, message, status) {
|
|
|
382
566
|
return Response.json({ jsonrpc: "2.0", error: { code, message }, id: null }, { status });
|
|
383
567
|
}
|
|
384
568
|
function createMcpHandler(config) {
|
|
569
|
+
if (Array.isArray(config.services)) {
|
|
570
|
+
validateMcpSchemas(config.services, config.onIncompatibleSchema, config.logger);
|
|
571
|
+
}
|
|
385
572
|
const eventStore = new InMemoryEventStore;
|
|
386
573
|
const sessions = new Map;
|
|
574
|
+
const closeTransport = (transport) => {
|
|
575
|
+
transport.close().catch((err) => {
|
|
576
|
+
console.error("[stitchkit] MCP transport close failed:", err);
|
|
577
|
+
});
|
|
578
|
+
};
|
|
387
579
|
setInterval(() => {
|
|
388
580
|
eventStore.cleanup();
|
|
389
581
|
const cutoff = Date.now() - SESSION_TTL_MS;
|
|
390
582
|
for (const [id, session] of sessions) {
|
|
391
583
|
if (session.lastSeen < cutoff) {
|
|
392
584
|
sessions.delete(id);
|
|
393
|
-
session.transport
|
|
394
|
-
return;
|
|
395
|
-
});
|
|
585
|
+
closeTransport(session.transport);
|
|
396
586
|
}
|
|
397
587
|
}
|
|
398
|
-
},
|
|
588
|
+
}, SWEEP_INTERVAL_MS).unref();
|
|
399
589
|
return async (req) => {
|
|
400
590
|
const auth = await config.auth(req);
|
|
401
591
|
if (!auth) {
|
|
@@ -413,6 +603,22 @@ function createMcpHandler(config) {
|
|
|
413
603
|
}
|
|
414
604
|
return existing.transport.handleRequest(req);
|
|
415
605
|
}
|
|
606
|
+
if (sessions.size >= MAX_SESSIONS) {
|
|
607
|
+
let oldestId;
|
|
608
|
+
let oldestSeen = Number.POSITIVE_INFINITY;
|
|
609
|
+
for (const [id, session] of sessions) {
|
|
610
|
+
if (session.lastSeen < oldestSeen) {
|
|
611
|
+
oldestSeen = session.lastSeen;
|
|
612
|
+
oldestId = id;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
if (oldestId !== undefined) {
|
|
616
|
+
const evicted = sessions.get(oldestId);
|
|
617
|
+
sessions.delete(oldestId);
|
|
618
|
+
if (evicted)
|
|
619
|
+
closeTransport(evicted.transport);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
416
622
|
const newSessionId = randomUUID();
|
|
417
623
|
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
418
624
|
sessionIdGenerator: () => newSessionId,
|
|
@@ -480,8 +686,10 @@ function implementRemote(contract, http, options) {
|
|
|
480
686
|
import { lookup } from "node:dns/promises";
|
|
481
687
|
import { readFile, stat } from "node:fs/promises";
|
|
482
688
|
import { isIP } from "node:net";
|
|
483
|
-
import { extname, resolve
|
|
484
|
-
import { z as
|
|
689
|
+
import { extname, resolve } from "node:path";
|
|
690
|
+
import { z as z7 } from "zod";
|
|
691
|
+
var NUMERIC_HOST = /^(0x[0-9a-f]+|[0-9.]+)$/i;
|
|
692
|
+
var MAX_REDIRECTS = 5;
|
|
485
693
|
var MAX_INLINE_BYTES = 20 * 1024 * 1024;
|
|
486
694
|
var EXT_MIME = {
|
|
487
695
|
".png": "image/png",
|
|
@@ -524,6 +732,9 @@ async function assertPublicUrl(url) {
|
|
|
524
732
|
throw new Error("refusing to fetch a private address");
|
|
525
733
|
return;
|
|
526
734
|
}
|
|
735
|
+
if (NUMERIC_HOST.test(host)) {
|
|
736
|
+
throw new Error("refusing to fetch a non-canonical numeric host");
|
|
737
|
+
}
|
|
527
738
|
if (host === "localhost" || host.endsWith(".local") || host.endsWith(".internal")) {
|
|
528
739
|
throw new Error("refusing to fetch an internal host");
|
|
529
740
|
}
|
|
@@ -534,6 +745,22 @@ async function assertPublicUrl(url) {
|
|
|
534
745
|
}
|
|
535
746
|
}
|
|
536
747
|
}
|
|
748
|
+
async function fetchGuarded(start, allowPrivate) {
|
|
749
|
+
let url = start;
|
|
750
|
+
for (let hop = 0;hop <= MAX_REDIRECTS; hop++) {
|
|
751
|
+
if (!allowPrivate)
|
|
752
|
+
await assertPublicUrl(url);
|
|
753
|
+
const res = await fetch(url, { redirect: "manual" });
|
|
754
|
+
if (res.status < 300 || res.status >= 400)
|
|
755
|
+
return res;
|
|
756
|
+
const location = res.headers.get("location");
|
|
757
|
+
if (!location)
|
|
758
|
+
return res;
|
|
759
|
+
await res.body?.cancel();
|
|
760
|
+
url = new URL(location, url);
|
|
761
|
+
}
|
|
762
|
+
throw new Error("too many redirects");
|
|
763
|
+
}
|
|
537
764
|
async function readCapped(res, max) {
|
|
538
765
|
const reader = res.body?.getReader();
|
|
539
766
|
if (!reader)
|
|
@@ -561,9 +788,7 @@ async function fetchSource(pathOrUrl, options) {
|
|
|
561
788
|
const extMime = EXT_MIME[extname(pathOrUrl).toLowerCase()];
|
|
562
789
|
if (pathOrUrl.startsWith("http://") || pathOrUrl.startsWith("https://")) {
|
|
563
790
|
const url = new URL(pathOrUrl);
|
|
564
|
-
|
|
565
|
-
await assertPublicUrl(url);
|
|
566
|
-
const res = await fetch(url);
|
|
791
|
+
const res = await fetchGuarded(url, options.allowPrivateHosts ?? false);
|
|
567
792
|
if (!res.ok)
|
|
568
793
|
throw new Error(`HTTP ${res.status}`);
|
|
569
794
|
const headerMime = (res.headers.get("content-type") ?? "").split(";")[0]?.trim() ?? "";
|
|
@@ -581,7 +806,7 @@ async function fetchSource(pathOrUrl, options) {
|
|
|
581
806
|
}
|
|
582
807
|
const root = resolve(options.baseDir);
|
|
583
808
|
const target = resolve(root, pathOrUrl);
|
|
584
|
-
if (
|
|
809
|
+
if (!isWithinDir(root, target)) {
|
|
585
810
|
throw new Error("path escapes the allowed directory");
|
|
586
811
|
}
|
|
587
812
|
const info = await stat(target).catch(() => null);
|
|
@@ -624,7 +849,7 @@ function mountViewFile(server, options = {}) {
|
|
|
624
849
|
server.registerTool("view_file", {
|
|
625
850
|
description: "View media (image, audio, video) by URL or local path — returns it as content you can SEE / HEAR. Pass several paths to view multiple files at once. Use it on a generation `output` url to inspect the result.",
|
|
626
851
|
inputSchema: {
|
|
627
|
-
paths:
|
|
852
|
+
paths: z7.union([z7.string(), z7.array(z7.string())]).describe("Media URL(s) or file path(s) to view")
|
|
628
853
|
}
|
|
629
854
|
}, async (args) => {
|
|
630
855
|
const list = Array.isArray(args.paths) ? args.paths : [args.paths];
|
|
@@ -643,12 +868,17 @@ function mountViewFile(server, options = {}) {
|
|
|
643
868
|
});
|
|
644
869
|
}
|
|
645
870
|
export {
|
|
871
|
+
validateMcpSchemas,
|
|
646
872
|
resolveMedia,
|
|
647
873
|
mountViewFile,
|
|
648
874
|
mountMcp,
|
|
649
875
|
mountAgent,
|
|
650
876
|
implementRemote,
|
|
877
|
+
flattenDiscriminatedUnion,
|
|
651
878
|
createStdioMcpServer,
|
|
652
879
|
createMcpHandler,
|
|
880
|
+
collectTools,
|
|
881
|
+
coerceJsonArgs,
|
|
882
|
+
buildToolManifest,
|
|
653
883
|
buildMcpServer
|
|
654
884
|
};
|