caplets 0.9.0 → 0.11.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 +117 -5
- package/caplets/github-cli/CAPLET.md +41 -0
- package/caplets/repo-cli/CAPLET.md +37 -0
- package/dist/index.js +904 -81
- package/package.json +7 -2
- package/schemas/caplet.schema.json +139 -0
- package/schemas/caplets-config.schema.json +182 -0
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import minproc, { stdin, stdout, default as process$1 } from "node:process";
|
|
4
|
-
import { execFileSync } from "node:child_process";
|
|
5
|
-
import minpath, { basename, dirname, extname, isAbsolute, join, parse, posix, relative, resolve, win32 } from "node:path";
|
|
4
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
5
|
+
import minpath, { basename, delimiter, dirname, extname, isAbsolute, join, parse, posix, relative, resolve, win32 } from "node:path";
|
|
6
6
|
import { accessSync, chmodSync, constants, cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
|
|
7
7
|
import { createInterface } from "node:readline/promises";
|
|
8
8
|
import { createServer } from "node:http";
|
|
@@ -180,7 +180,7 @@ const allowsEval = /* @__PURE__ */ cached(() => {
|
|
|
180
180
|
return false;
|
|
181
181
|
}
|
|
182
182
|
});
|
|
183
|
-
function isPlainObject$
|
|
183
|
+
function isPlainObject$7(o) {
|
|
184
184
|
if (isObject(o) === false) return false;
|
|
185
185
|
const ctor = o.constructor;
|
|
186
186
|
if (ctor === void 0) return true;
|
|
@@ -191,7 +191,7 @@ function isPlainObject$5(o) {
|
|
|
191
191
|
return true;
|
|
192
192
|
}
|
|
193
193
|
function shallowClone(o) {
|
|
194
|
-
if (isPlainObject$
|
|
194
|
+
if (isPlainObject$7(o)) return { ...o };
|
|
195
195
|
if (Array.isArray(o)) return [...o];
|
|
196
196
|
if (o instanceof Map) return new Map(o);
|
|
197
197
|
if (o instanceof Set) return new Set(o);
|
|
@@ -274,7 +274,7 @@ function omit(schema, mask) {
|
|
|
274
274
|
}));
|
|
275
275
|
}
|
|
276
276
|
function extend(schema, shape) {
|
|
277
|
-
if (!isPlainObject$
|
|
277
|
+
if (!isPlainObject$7(shape)) throw new Error("Invalid input to extend: expected a plain object");
|
|
278
278
|
const checks = schema._zod.def.checks;
|
|
279
279
|
if (checks && checks.length > 0) {
|
|
280
280
|
const existingShape = schema._zod.def.shape;
|
|
@@ -290,7 +290,7 @@ function extend(schema, shape) {
|
|
|
290
290
|
} }));
|
|
291
291
|
}
|
|
292
292
|
function safeExtend(schema, shape) {
|
|
293
|
-
if (!isPlainObject$
|
|
293
|
+
if (!isPlainObject$7(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
|
|
294
294
|
return clone(schema, mergeDefs(schema._zod.def, { get shape() {
|
|
295
295
|
const _shape = {
|
|
296
296
|
...schema._zod.def.shape,
|
|
@@ -1904,7 +1904,7 @@ function mergeValues$1(a, b) {
|
|
|
1904
1904
|
valid: true,
|
|
1905
1905
|
data: a
|
|
1906
1906
|
};
|
|
1907
|
-
if (isPlainObject$
|
|
1907
|
+
if (isPlainObject$7(a) && isPlainObject$7(b)) {
|
|
1908
1908
|
const bKeys = Object.keys(b);
|
|
1909
1909
|
const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
|
|
1910
1910
|
const newObj = {
|
|
@@ -1980,7 +1980,7 @@ const $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
|
|
|
1980
1980
|
$ZodType.init(inst, def);
|
|
1981
1981
|
inst._zod.parse = (payload, ctx) => {
|
|
1982
1982
|
const input = payload.value;
|
|
1983
|
-
if (!isPlainObject$
|
|
1983
|
+
if (!isPlainObject$7(input)) {
|
|
1984
1984
|
payload.issues.push({
|
|
1985
1985
|
expected: "record",
|
|
1986
1986
|
code: "invalid_type",
|
|
@@ -9619,7 +9619,7 @@ const { program, createCommand, createArgument, createOption, CommanderError, In
|
|
|
9619
9619
|
})))(), 1)).default;
|
|
9620
9620
|
//#endregion
|
|
9621
9621
|
//#region package.json
|
|
9622
|
-
var version = "0.
|
|
9622
|
+
var version = "0.11.0";
|
|
9623
9623
|
//#endregion
|
|
9624
9624
|
//#region node_modules/.pnpm/pkce-challenge@5.0.1/node_modules/pkce-challenge/dist/index.node.js
|
|
9625
9625
|
let crypto;
|
|
@@ -19049,6 +19049,34 @@ const capletHttpApiSchema = object$1({
|
|
|
19049
19049
|
"headers"
|
|
19050
19050
|
]);
|
|
19051
19051
|
});
|
|
19052
|
+
const capletCliToolOutputSchema = object$1({ type: _enum(["text", "json"]).optional() }).strict();
|
|
19053
|
+
const capletCliToolAnnotationsSchema = object$1({
|
|
19054
|
+
readOnlyHint: boolean().optional(),
|
|
19055
|
+
destructiveHint: boolean().optional(),
|
|
19056
|
+
idempotentHint: boolean().optional(),
|
|
19057
|
+
openWorldHint: boolean().optional()
|
|
19058
|
+
}).strict();
|
|
19059
|
+
const capletCliToolActionSchema = object$1({
|
|
19060
|
+
description: string().min(1).optional().describe("Action capability description."),
|
|
19061
|
+
inputSchema: record(string(), unknown()).optional().describe("JSON Schema for call_tool arguments."),
|
|
19062
|
+
outputSchema: record(string(), unknown()).optional().describe("JSON Schema for structuredContent returned by this action."),
|
|
19063
|
+
command: string().min(1).describe("Executable command to spawn without a shell."),
|
|
19064
|
+
args: array(string()).optional().describe("Arguments passed to the command."),
|
|
19065
|
+
env: record(string(), string()).optional().describe("Additional environment variables."),
|
|
19066
|
+
cwd: string().min(1).optional().describe("Working directory for this action."),
|
|
19067
|
+
timeoutMs: number$1().int().positive().optional(),
|
|
19068
|
+
maxOutputBytes: number$1().int().positive().optional(),
|
|
19069
|
+
output: capletCliToolOutputSchema.optional(),
|
|
19070
|
+
annotations: capletCliToolAnnotationsSchema.optional()
|
|
19071
|
+
}).strict();
|
|
19072
|
+
const capletCliToolsSchema = object$1({
|
|
19073
|
+
actions: record(string().regex(SERVER_ID_PATTERN), capletCliToolActionSchema).refine((actions) => Object.keys(actions).length > 0, "CLI tools backend must define at least one action").describe("Configured CLI actions keyed by stable tool name."),
|
|
19074
|
+
cwd: string().min(1).optional().describe("Default working directory for CLI actions."),
|
|
19075
|
+
env: record(string(), string()).optional().describe("Default environment variables."),
|
|
19076
|
+
timeoutMs: number$1().int().positive().optional(),
|
|
19077
|
+
maxOutputBytes: number$1().int().positive().optional(),
|
|
19078
|
+
disabled: boolean().optional().describe("When true, omit this Caplet from discovery.")
|
|
19079
|
+
}).strict();
|
|
19052
19080
|
const capletFileSchema = object$1({
|
|
19053
19081
|
$schema: string().url().optional().describe("Optional JSON Schema URL for editor validation."),
|
|
19054
19082
|
name: string().trim().min(1).max(80).describe("Human-readable Caplet display name."),
|
|
@@ -19057,11 +19085,12 @@ const capletFileSchema = object$1({
|
|
|
19057
19085
|
mcpServer: capletMcpServerSchema.describe("MCP server backend configuration for this Caplet.").optional(),
|
|
19058
19086
|
openapiEndpoint: capletOpenApiEndpointSchema.describe("OpenAPI endpoint backend configuration for this Caplet.").optional(),
|
|
19059
19087
|
graphqlEndpoint: capletGraphQlEndpointSchema.describe("GraphQL endpoint backend configuration for this Caplet.").optional(),
|
|
19060
|
-
httpApi: capletHttpApiSchema.describe("HTTP API backend configuration for this Caplet.").optional()
|
|
19088
|
+
httpApi: capletHttpApiSchema.describe("HTTP API backend configuration for this Caplet.").optional(),
|
|
19089
|
+
cliTools: capletCliToolsSchema.describe("CLI tools backend configuration for this Caplet.").optional()
|
|
19061
19090
|
}).strict().superRefine((frontmatter, ctx) => {
|
|
19062
|
-
if (Number(Boolean(frontmatter.mcpServer)) + Number(Boolean(frontmatter.openapiEndpoint)) + Number(Boolean(frontmatter.graphqlEndpoint)) + Number(Boolean(frontmatter.httpApi)) !== 1) ctx.addIssue({
|
|
19091
|
+
if (Number(Boolean(frontmatter.mcpServer)) + Number(Boolean(frontmatter.openapiEndpoint)) + Number(Boolean(frontmatter.graphqlEndpoint)) + Number(Boolean(frontmatter.httpApi)) + Number(Boolean(frontmatter.cliTools)) !== 1) ctx.addIssue({
|
|
19063
19092
|
code: "custom",
|
|
19064
|
-
message: "Caplet file must define exactly one backend: mcpServer, openapiEndpoint, graphqlEndpoint, or
|
|
19093
|
+
message: "Caplet file must define exactly one backend: mcpServer, openapiEndpoint, graphqlEndpoint, httpApi, or cliTools"
|
|
19065
19094
|
});
|
|
19066
19095
|
});
|
|
19067
19096
|
function loadCapletFiles(root) {
|
|
@@ -19070,29 +19099,35 @@ function loadCapletFiles(root) {
|
|
|
19070
19099
|
const openapiEndpoints = {};
|
|
19071
19100
|
const graphqlEndpoints = {};
|
|
19072
19101
|
const httpApis = {};
|
|
19102
|
+
const cliTools = {};
|
|
19073
19103
|
for (const candidate of discoverCapletFiles(root)) {
|
|
19074
|
-
if (servers[candidate.id] || openapiEndpoints[candidate.id] || graphqlEndpoints[candidate.id] || httpApis[candidate.id]) throw new CapletsError("CONFIG_INVALID", `Duplicate Caplet ID ${candidate.id} under ${root}`);
|
|
19104
|
+
if (servers[candidate.id] || openapiEndpoints[candidate.id] || graphqlEndpoints[candidate.id] || httpApis[candidate.id] || cliTools[candidate.id]) throw new CapletsError("CONFIG_INVALID", `Duplicate Caplet ID ${candidate.id} under ${root}`);
|
|
19075
19105
|
const config = readCapletFile(candidate.path);
|
|
19076
|
-
if (isPlainObject$
|
|
19106
|
+
if (isPlainObject$6(config) && config.backend === "openapi") {
|
|
19077
19107
|
const { backend: _backend, ...endpoint } = config;
|
|
19078
19108
|
openapiEndpoints[candidate.id] = endpoint;
|
|
19079
|
-
} else if (isPlainObject$
|
|
19109
|
+
} else if (isPlainObject$6(config) && config.backend === "graphql") {
|
|
19080
19110
|
const { backend: _backend, ...endpoint } = config;
|
|
19081
19111
|
graphqlEndpoints[candidate.id] = endpoint;
|
|
19082
|
-
} else if (isPlainObject$
|
|
19112
|
+
} else if (isPlainObject$6(config) && config.backend === "http") {
|
|
19083
19113
|
const { backend: _backend, ...endpoint } = config;
|
|
19084
19114
|
httpApis[candidate.id] = endpoint;
|
|
19115
|
+
} else if (isPlainObject$6(config) && config.backend === "cli") {
|
|
19116
|
+
const { backend: _backend, ...endpoint } = config;
|
|
19117
|
+
cliTools[candidate.id] = endpoint;
|
|
19085
19118
|
} else servers[candidate.id] = config;
|
|
19086
19119
|
}
|
|
19087
19120
|
const hasServers = Object.keys(servers).length > 0;
|
|
19088
19121
|
const hasOpenApi = Object.keys(openapiEndpoints).length > 0;
|
|
19089
19122
|
const hasGraphQl = Object.keys(graphqlEndpoints).length > 0;
|
|
19090
19123
|
const hasHttpApis = Object.keys(httpApis).length > 0;
|
|
19091
|
-
|
|
19124
|
+
const hasCliTools = Object.keys(cliTools).length > 0;
|
|
19125
|
+
return hasServers || hasOpenApi || hasGraphQl || hasHttpApis || hasCliTools ? {
|
|
19092
19126
|
...hasServers ? { mcpServers: servers } : {},
|
|
19093
19127
|
...hasOpenApi ? { openapiEndpoints } : {},
|
|
19094
19128
|
...hasGraphQl ? { graphqlEndpoints } : {},
|
|
19095
|
-
...hasHttpApis ? { httpApis } : {}
|
|
19129
|
+
...hasHttpApis ? { httpApis } : {},
|
|
19130
|
+
...hasCliTools ? { cliTools } : {}
|
|
19096
19131
|
} : void 0;
|
|
19097
19132
|
}
|
|
19098
19133
|
function discoverCapletFiles(root) {
|
|
@@ -19158,6 +19193,16 @@ function capletToServerConfig(frontmatter, body, baseDir) {
|
|
|
19158
19193
|
...frontmatter.tags ? { tags: frontmatter.tags } : {},
|
|
19159
19194
|
body
|
|
19160
19195
|
};
|
|
19196
|
+
if (frontmatter.cliTools) return {
|
|
19197
|
+
...frontmatter.cliTools,
|
|
19198
|
+
cwd: normalizeLocalPath$1(frontmatter.cliTools.cwd, baseDir),
|
|
19199
|
+
actions: normalizeCliToolActions(frontmatter.cliTools.actions, baseDir),
|
|
19200
|
+
backend: "cli",
|
|
19201
|
+
name: frontmatter.name,
|
|
19202
|
+
description: frontmatter.description,
|
|
19203
|
+
...frontmatter.tags ? { tags: frontmatter.tags } : {},
|
|
19204
|
+
body
|
|
19205
|
+
};
|
|
19161
19206
|
return {
|
|
19162
19207
|
...frontmatter.mcpServer,
|
|
19163
19208
|
name: frontmatter.name,
|
|
@@ -19166,6 +19211,12 @@ function capletToServerConfig(frontmatter, body, baseDir) {
|
|
|
19166
19211
|
body
|
|
19167
19212
|
};
|
|
19168
19213
|
}
|
|
19214
|
+
function normalizeCliToolActions(actions, baseDir) {
|
|
19215
|
+
return Object.fromEntries(Object.entries(actions).map(([name, action]) => [name, {
|
|
19216
|
+
...action,
|
|
19217
|
+
cwd: normalizeLocalPath$1(action.cwd, baseDir)
|
|
19218
|
+
}]));
|
|
19219
|
+
}
|
|
19169
19220
|
function normalizeGraphQlOperations(operations, baseDir) {
|
|
19170
19221
|
if (!operations) return;
|
|
19171
19222
|
return Object.fromEntries(Object.entries(operations).map(([name, operation]) => [name, {
|
|
@@ -19200,7 +19251,7 @@ function parseFrontmatter(text, path) {
|
|
|
19200
19251
|
value: text
|
|
19201
19252
|
});
|
|
19202
19253
|
matter(file, { strip: true });
|
|
19203
|
-
if (!isPlainObject$
|
|
19254
|
+
if (!isPlainObject$6(file.data.matter) || Object.keys(file.data.matter).length === 0) throw new Error("empty frontmatter");
|
|
19204
19255
|
return {
|
|
19205
19256
|
frontmatter: file.data.matter,
|
|
19206
19257
|
body: String(file)
|
|
@@ -19209,7 +19260,7 @@ function parseFrontmatter(text, path) {
|
|
|
19209
19260
|
throw new CapletsError("CONFIG_INVALID", `Caplet file at ${path} has invalid YAML frontmatter`, redactSecrets(error));
|
|
19210
19261
|
}
|
|
19211
19262
|
}
|
|
19212
|
-
function isPlainObject$
|
|
19263
|
+
function isPlainObject$6(value) {
|
|
19213
19264
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
19214
19265
|
}
|
|
19215
19266
|
function validateCapletId(id, path) {
|
|
@@ -19386,6 +19437,7 @@ const httpActionSchema = object$1({
|
|
|
19386
19437
|
path: string().min(1).regex(/^\//, "HTTP action path must start with /").describe("URL path appended to the HTTP API baseUrl.").refine((value) => !value.startsWith("//"), "HTTP action path must not start with //").refine((value) => !isUrl(value), "HTTP action path must be a URL path, not a URL"),
|
|
19387
19438
|
description: string().min(1).optional().describe("Action capability description."),
|
|
19388
19439
|
inputSchema: record(string(), unknown()).optional().describe("JSON Schema for call_tool arguments."),
|
|
19440
|
+
outputSchema: record(string(), unknown()).optional().describe("JSON Schema for structuredContent returned by this action."),
|
|
19389
19441
|
query: httpScalarMappingSchema.optional().describe("Query parameter mapping."),
|
|
19390
19442
|
headers: httpScalarMappingSchema.optional().describe("Request header mapping."),
|
|
19391
19443
|
jsonBody: unknown().optional().describe("JSON request body mapping.")
|
|
@@ -19408,7 +19460,39 @@ const publicHttpApiSchema = object$1({
|
|
|
19408
19460
|
disabled: boolean().default(false).describe("When true, omit this HTTP API Caplet.")
|
|
19409
19461
|
}).strict();
|
|
19410
19462
|
const normalizedHttpApiSchema = publicHttpApiSchema.extend({ body: string().optional() });
|
|
19411
|
-
|
|
19463
|
+
const cliToolOutputSchema = object$1({ type: _enum(["text", "json"]).default("text").describe("How stdout should be represented in structuredContent.") }).strict();
|
|
19464
|
+
const cliToolAnnotationsSchema = object$1({
|
|
19465
|
+
readOnlyHint: boolean().optional(),
|
|
19466
|
+
destructiveHint: boolean().optional(),
|
|
19467
|
+
idempotentHint: boolean().optional(),
|
|
19468
|
+
openWorldHint: boolean().optional()
|
|
19469
|
+
}).strict();
|
|
19470
|
+
const cliToolActionSchema = object$1({
|
|
19471
|
+
description: string().min(1).optional().describe("Action capability description."),
|
|
19472
|
+
inputSchema: record(string(), unknown()).optional().describe("JSON Schema for call_tool arguments."),
|
|
19473
|
+
outputSchema: record(string(), unknown()).optional().describe("JSON Schema for structuredContent returned by this action."),
|
|
19474
|
+
command: string().min(1).describe("Executable command to spawn without a shell."),
|
|
19475
|
+
args: array(string()).optional().describe("Arguments passed to the command."),
|
|
19476
|
+
env: record(string(), string()).optional().describe("Additional environment variables for the command."),
|
|
19477
|
+
cwd: string().min(1).optional().describe("Working directory for this action."),
|
|
19478
|
+
timeoutMs: number$1().int().positive().optional().describe("Command timeout in milliseconds."),
|
|
19479
|
+
maxOutputBytes: number$1().int().positive().optional().describe("Maximum combined stdout and stderr bytes to keep."),
|
|
19480
|
+
output: cliToolOutputSchema.optional(),
|
|
19481
|
+
annotations: cliToolAnnotationsSchema.optional()
|
|
19482
|
+
}).strict();
|
|
19483
|
+
const publicCliToolsSchema = object$1({
|
|
19484
|
+
name: string().trim().min(1).max(80).describe("Human-readable CLI tools display name."),
|
|
19485
|
+
description: string().describe("Capability description shown to agents before CLI actions are disclosed.").refine((value) => value.trim().length >= 10, "description must contain at least 10 non-whitespace characters").refine((value) => value.length <= 1500, "description must be at most 1500 characters"),
|
|
19486
|
+
actions: record(string().regex(SERVER_ID_PATTERN), cliToolActionSchema).refine((actions) => Object.keys(actions).length > 0, "CLI tools backend must define at least one action").describe("Configured CLI actions keyed by stable tool name."),
|
|
19487
|
+
cwd: string().min(1).optional().describe("Default working directory for CLI actions."),
|
|
19488
|
+
env: record(string(), string()).optional().describe("Default environment variables for CLI actions."),
|
|
19489
|
+
tags: array(string().trim().min(1).max(80)).optional(),
|
|
19490
|
+
timeoutMs: number$1().int().positive().default(6e4).describe("Default timeout in milliseconds for CLI actions."),
|
|
19491
|
+
maxOutputBytes: number$1().int().positive().default(1e6).describe("Default maximum combined stdout and stderr bytes to keep."),
|
|
19492
|
+
disabled: boolean().default(false).describe("When true, omit this CLI tools Caplet.")
|
|
19493
|
+
}).strict();
|
|
19494
|
+
const normalizedCliToolsSchema = publicCliToolsSchema.extend({ body: string().optional() });
|
|
19495
|
+
function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, graphQlEndpointValueSchema, httpApiValueSchema, cliToolsValueSchema) {
|
|
19412
19496
|
return object$1({
|
|
19413
19497
|
$schema: string().url().optional().describe("Optional JSON Schema URL for editor validation."),
|
|
19414
19498
|
version: literal(1).default(1).describe("Caplets config schema version."),
|
|
@@ -19417,7 +19501,8 @@ function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, graphQlE
|
|
|
19417
19501
|
mcpServers: record(string().regex(SERVER_ID_PATTERN), serverValueSchema).default({}).describe("Downstream MCP servers keyed by stable server ID."),
|
|
19418
19502
|
openapiEndpoints: record(string().regex(SERVER_ID_PATTERN), openApiEndpointValueSchema).default({}).describe("OpenAPI endpoints keyed by stable Caplet ID."),
|
|
19419
19503
|
graphqlEndpoints: record(string().regex(SERVER_ID_PATTERN), graphQlEndpointValueSchema).default({}).describe("GraphQL endpoints keyed by stable Caplet ID."),
|
|
19420
|
-
httpApis: record(string().regex(SERVER_ID_PATTERN), httpApiValueSchema).default({}).describe("HTTP APIs keyed by stable Caplet ID.")
|
|
19504
|
+
httpApis: record(string().regex(SERVER_ID_PATTERN), httpApiValueSchema).default({}).describe("HTTP APIs keyed by stable Caplet ID."),
|
|
19505
|
+
cliTools: record(string().regex(SERVER_ID_PATTERN), cliToolsValueSchema).default({}).describe("CLI tools keyed by stable Caplet ID.")
|
|
19421
19506
|
}).strict().superRefine((config, ctx) => {
|
|
19422
19507
|
if (config.defaultSearchLimit > config.maxSearchLimit) ctx.addIssue({
|
|
19423
19508
|
code: "custom",
|
|
@@ -19606,10 +19691,34 @@ function configSchemaFor(serverValueSchema, openApiEndpointValueSchema, graphQlE
|
|
|
19606
19691
|
"headers"
|
|
19607
19692
|
]);
|
|
19608
19693
|
}
|
|
19694
|
+
for (const [server, rawValue] of Object.entries(config.cliTools)) {
|
|
19695
|
+
const raw = rawValue;
|
|
19696
|
+
const duplicateBackend = config.mcpServers[server] ? "mcpServers" : config.openapiEndpoints[server] ? "openapiEndpoints" : config.graphqlEndpoints[server] ? "graphqlEndpoints" : config.httpApis[server] ? "httpApis" : void 0;
|
|
19697
|
+
if (duplicateBackend) ctx.addIssue({
|
|
19698
|
+
code: "custom",
|
|
19699
|
+
path: ["cliTools", server],
|
|
19700
|
+
message: `Caplet ID ${server} is already used by ${duplicateBackend}`
|
|
19701
|
+
});
|
|
19702
|
+
if (!SERVER_ID_PATTERN.test(server)) ctx.addIssue({
|
|
19703
|
+
code: "custom",
|
|
19704
|
+
path: ["cliTools", server],
|
|
19705
|
+
message: "CLI tools ID must match ^[a-zA-Z0-9_-]{1,64}$"
|
|
19706
|
+
});
|
|
19707
|
+
for (const actionName of Object.keys(raw.actions)) if (!SERVER_ID_PATTERN.test(actionName)) ctx.addIssue({
|
|
19708
|
+
code: "custom",
|
|
19709
|
+
path: [
|
|
19710
|
+
"cliTools",
|
|
19711
|
+
server,
|
|
19712
|
+
"actions",
|
|
19713
|
+
actionName
|
|
19714
|
+
],
|
|
19715
|
+
message: "CLI action ID must match ^[a-zA-Z0-9_-]{1,64}$"
|
|
19716
|
+
});
|
|
19717
|
+
}
|
|
19609
19718
|
});
|
|
19610
19719
|
}
|
|
19611
|
-
const configFileSchema = configSchemaFor(publicServerSchema, publicOpenApiEndpointSchema, publicGraphQlEndpointSchema, publicHttpApiSchema);
|
|
19612
|
-
const normalizedConfigFileSchema = configSchemaFor(normalizedServerSchema, normalizedOpenApiEndpointSchema, normalizedGraphQlEndpointSchema, normalizedHttpApiSchema);
|
|
19720
|
+
const configFileSchema = configSchemaFor(publicServerSchema, publicOpenApiEndpointSchema, publicGraphQlEndpointSchema, publicHttpApiSchema, publicCliToolsSchema);
|
|
19721
|
+
const normalizedConfigFileSchema = configSchemaFor(normalizedServerSchema, normalizedOpenApiEndpointSchema, normalizedGraphQlEndpointSchema, normalizedHttpApiSchema, normalizedCliToolsSchema);
|
|
19613
19722
|
function loadConfig(path = resolveConfigPath(), projectPath = resolveProjectConfigPath()) {
|
|
19614
19723
|
const hasUserConfig = existsSync(path);
|
|
19615
19724
|
const hasProjectConfig = existsSync(projectPath);
|
|
@@ -19620,7 +19729,7 @@ function loadConfig(path = resolveConfigPath(), projectPath = resolveProjectConf
|
|
|
19620
19729
|
if (!hasUserConfig && !hasProjectConfig && !userCaplets && !projectCaplets) throw new CapletsError("CONFIG_NOT_FOUND", `Caplets config not found at ${path} or ${projectPath}`);
|
|
19621
19730
|
try {
|
|
19622
19731
|
const config = parseConfig(mergeConfigInputs(userConfig, userCaplets, projectConfig, projectCaplets));
|
|
19623
|
-
if (Object.keys(config.mcpServers).length === 0 && Object.keys(config.openapiEndpoints).length === 0 && Object.keys(config.graphqlEndpoints).length === 0 && Object.keys(config.httpApis).length === 0) throw new CapletsError("CONFIG_INVALID", "Caplets config must define at least one MCP server, OpenAPI endpoint, GraphQL endpoint, or
|
|
19732
|
+
if (Object.keys(config.mcpServers).length === 0 && Object.keys(config.openapiEndpoints).length === 0 && Object.keys(config.graphqlEndpoints).length === 0 && Object.keys(config.httpApis).length === 0 && Object.keys(config.cliTools).length === 0) throw new CapletsError("CONFIG_INVALID", "Caplets config must define at least one MCP server, OpenAPI endpoint, GraphQL endpoint, HTTP API, or CLI tools backend");
|
|
19624
19733
|
return config;
|
|
19625
19734
|
} catch (error) {
|
|
19626
19735
|
if (error instanceof CapletsError) throw error;
|
|
@@ -19645,12 +19754,13 @@ function normalizeLocalPaths(input, baseDir) {
|
|
|
19645
19754
|
return stripUndefined({
|
|
19646
19755
|
...input,
|
|
19647
19756
|
openapiEndpoints: normalizeEndpointPaths(input.openapiEndpoints, baseDir, normalizeOpenApiPath),
|
|
19648
|
-
graphqlEndpoints: normalizeEndpointPaths(input.graphqlEndpoints, baseDir, normalizeGraphQlPath)
|
|
19757
|
+
graphqlEndpoints: normalizeEndpointPaths(input.graphqlEndpoints, baseDir, normalizeGraphQlPath),
|
|
19758
|
+
cliTools: normalizeEndpointPaths(input.cliTools, baseDir, normalizeCliToolsPaths)
|
|
19649
19759
|
});
|
|
19650
19760
|
}
|
|
19651
19761
|
function normalizeEndpointPaths(endpoints, baseDir, normalize) {
|
|
19652
19762
|
if (!endpoints) return;
|
|
19653
|
-
return Object.fromEntries(Object.entries(endpoints).map(([id, endpoint]) => [id, isPlainObject$
|
|
19763
|
+
return Object.fromEntries(Object.entries(endpoints).map(([id, endpoint]) => [id, isPlainObject$5(endpoint) ? normalize(endpoint, baseDir) : endpoint]));
|
|
19654
19764
|
}
|
|
19655
19765
|
function normalizeOpenApiPath(endpoint, baseDir) {
|
|
19656
19766
|
return {
|
|
@@ -19659,7 +19769,7 @@ function normalizeOpenApiPath(endpoint, baseDir) {
|
|
|
19659
19769
|
};
|
|
19660
19770
|
}
|
|
19661
19771
|
function normalizeGraphQlPath(endpoint, baseDir) {
|
|
19662
|
-
const operations = isPlainObject$
|
|
19772
|
+
const operations = isPlainObject$5(endpoint.operations) ? Object.fromEntries(Object.entries(endpoint.operations).map(([name, operation]) => [name, isPlainObject$5(operation) ? {
|
|
19663
19773
|
...operation,
|
|
19664
19774
|
documentPath: normalizeLocalPath(operation.documentPath, baseDir)
|
|
19665
19775
|
} : operation])) : endpoint.operations;
|
|
@@ -19669,6 +19779,17 @@ function normalizeGraphQlPath(endpoint, baseDir) {
|
|
|
19669
19779
|
operations
|
|
19670
19780
|
};
|
|
19671
19781
|
}
|
|
19782
|
+
function normalizeCliToolsPaths(endpoint, baseDir) {
|
|
19783
|
+
const actions = isPlainObject$5(endpoint.actions) ? Object.fromEntries(Object.entries(endpoint.actions).map(([name, action]) => [name, isPlainObject$5(action) ? {
|
|
19784
|
+
...action,
|
|
19785
|
+
cwd: normalizeLocalPath(action.cwd, baseDir)
|
|
19786
|
+
} : action])) : endpoint.actions;
|
|
19787
|
+
return {
|
|
19788
|
+
...endpoint,
|
|
19789
|
+
cwd: normalizeLocalPath(endpoint.cwd, baseDir),
|
|
19790
|
+
actions
|
|
19791
|
+
};
|
|
19792
|
+
}
|
|
19672
19793
|
function normalizeLocalPath(value, baseDir) {
|
|
19673
19794
|
if (typeof value !== "string" || !value || isAbsolute(value) || hasEnvReference(value)) return value;
|
|
19674
19795
|
return join(baseDir, value);
|
|
@@ -19677,6 +19798,7 @@ function rejectUntrustedProjectExecutableBackends(input, path) {
|
|
|
19677
19798
|
if (input.openapiEndpoints && Object.keys(input.openapiEndpoints).length > 0) throw new CapletsError("CONFIG_INVALID", `Project config at ${path} cannot define openapiEndpoints; use trusted project Caplet files or user config`);
|
|
19678
19799
|
if (input.graphqlEndpoints && Object.keys(input.graphqlEndpoints).length > 0) throw new CapletsError("CONFIG_INVALID", `Project config at ${path} cannot define graphqlEndpoints; use trusted project Caplet files or user config`);
|
|
19679
19800
|
if (input.httpApis && Object.keys(input.httpApis).length > 0) throw new CapletsError("CONFIG_INVALID", `Project config at ${path} cannot define httpApis; use trusted project Caplet files or user config`);
|
|
19801
|
+
if (input.cliTools && Object.keys(input.cliTools).length > 0) throw new CapletsError("CONFIG_INVALID", `Project config at ${path} cannot define cliTools; use trusted project Caplet files or user config`);
|
|
19680
19802
|
return input;
|
|
19681
19803
|
}
|
|
19682
19804
|
function mergeConfigInputs(...inputs) {
|
|
@@ -19701,6 +19823,10 @@ function mergeConfigInputs(...inputs) {
|
|
|
19701
19823
|
httpApis: {
|
|
19702
19824
|
...merged?.httpApis,
|
|
19703
19825
|
...input.httpApis
|
|
19826
|
+
},
|
|
19827
|
+
cliTools: {
|
|
19828
|
+
...merged?.cliTools,
|
|
19829
|
+
...input.cliTools
|
|
19704
19830
|
}
|
|
19705
19831
|
};
|
|
19706
19832
|
}
|
|
@@ -19737,6 +19863,12 @@ function parseConfig(input) {
|
|
|
19737
19863
|
server,
|
|
19738
19864
|
backend: "http"
|
|
19739
19865
|
});
|
|
19866
|
+
const cliTools = {};
|
|
19867
|
+
for (const [server, raw] of Object.entries(parsed.data.cliTools)) cliTools[server] = stripUndefined({
|
|
19868
|
+
...raw,
|
|
19869
|
+
server,
|
|
19870
|
+
backend: "cli"
|
|
19871
|
+
});
|
|
19740
19872
|
return {
|
|
19741
19873
|
version: parsed.data.version,
|
|
19742
19874
|
options: {
|
|
@@ -19746,7 +19878,8 @@ function parseConfig(input) {
|
|
|
19746
19878
|
mcpServers: servers,
|
|
19747
19879
|
openapiEndpoints,
|
|
19748
19880
|
graphqlEndpoints,
|
|
19749
|
-
httpApis
|
|
19881
|
+
httpApis,
|
|
19882
|
+
cliTools
|
|
19750
19883
|
};
|
|
19751
19884
|
}
|
|
19752
19885
|
function validateEndpointAuthHeaders(auth, ctx, path) {
|
|
@@ -19775,10 +19908,10 @@ function interpolateConfig(value, path = []) {
|
|
|
19775
19908
|
return value;
|
|
19776
19909
|
}
|
|
19777
19910
|
function isPublicMetadataPath(path) {
|
|
19778
|
-
if (path.length < 3 || path[0] !== "mcpServers" && path[0] !== "openapiEndpoints" && path[0] !== "graphqlEndpoints" && path[0] !== "httpApis") return false;
|
|
19911
|
+
if (path.length < 3 || path[0] !== "mcpServers" && path[0] !== "openapiEndpoints" && path[0] !== "graphqlEndpoints" && path[0] !== "httpApis" && path[0] !== "cliTools") return false;
|
|
19779
19912
|
return NON_INTERPOLATED_SERVER_FIELDS.has(path[2] ?? "");
|
|
19780
19913
|
}
|
|
19781
|
-
function isPlainObject$
|
|
19914
|
+
function isPlainObject$5(value) {
|
|
19782
19915
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
19783
19916
|
}
|
|
19784
19917
|
function hasEnvReference(value) {
|
|
@@ -19867,6 +20000,194 @@ async function maybeReadManualInput() {
|
|
|
19867
20000
|
}
|
|
19868
20001
|
}
|
|
19869
20002
|
//#endregion
|
|
20003
|
+
//#region src/cli/author.ts
|
|
20004
|
+
function authorCliCaplet(id, options = {}) {
|
|
20005
|
+
const repo = resolve(options.repo ?? process.cwd());
|
|
20006
|
+
const include = options.include !== void 0 ? parseInclude(options.include) : options.command ? /* @__PURE__ */ new Set() : new Set([
|
|
20007
|
+
"git",
|
|
20008
|
+
"gh",
|
|
20009
|
+
"package"
|
|
20010
|
+
]);
|
|
20011
|
+
const actions = {};
|
|
20012
|
+
if (include.has("git") || options.command === "git") Object.assign(actions, gitActions(repo));
|
|
20013
|
+
if (include.has("gh") || options.command === "gh") Object.assign(actions, ghActions(repo));
|
|
20014
|
+
if (include.has("package") || isPackageCommand(options.command)) Object.assign(actions, packageActions(repo, options.command));
|
|
20015
|
+
if (options.command && Object.keys(actions).length === 0) actions[`${sanitizeId(options.command)}_version`] = {
|
|
20016
|
+
description: `Print ${options.command} version information.`,
|
|
20017
|
+
command: options.command,
|
|
20018
|
+
args: ["--version"],
|
|
20019
|
+
annotations: { readOnlyHint: true }
|
|
20020
|
+
};
|
|
20021
|
+
if (Object.keys(actions).length === 0) throw new Error("No CLI actions could be generated for the requested options");
|
|
20022
|
+
const text = renderCaplet({
|
|
20023
|
+
name: titleize(id),
|
|
20024
|
+
description: `Curated CLI tools for ${basename(repo)} workflows.`,
|
|
20025
|
+
cwd: repo,
|
|
20026
|
+
actions
|
|
20027
|
+
});
|
|
20028
|
+
const output = options.output ?? "-";
|
|
20029
|
+
if (output !== "-") {
|
|
20030
|
+
writeFileSync(output, text);
|
|
20031
|
+
return {
|
|
20032
|
+
path: output,
|
|
20033
|
+
text
|
|
20034
|
+
};
|
|
20035
|
+
}
|
|
20036
|
+
return { text };
|
|
20037
|
+
}
|
|
20038
|
+
function parseInclude(value) {
|
|
20039
|
+
if (!value) return /* @__PURE__ */ new Set();
|
|
20040
|
+
return new Set(value.split(",").map((entry) => entry.trim()).filter(Boolean));
|
|
20041
|
+
}
|
|
20042
|
+
function gitActions(repo) {
|
|
20043
|
+
return {
|
|
20044
|
+
git_status: {
|
|
20045
|
+
description: "Show concise Git working tree status.",
|
|
20046
|
+
command: "git",
|
|
20047
|
+
args: ["status", "--short"],
|
|
20048
|
+
cwd: repo,
|
|
20049
|
+
annotations: { readOnlyHint: true }
|
|
20050
|
+
},
|
|
20051
|
+
git_current_branch: {
|
|
20052
|
+
description: "Print the current Git branch name.",
|
|
20053
|
+
command: "git",
|
|
20054
|
+
args: ["branch", "--show-current"],
|
|
20055
|
+
cwd: repo,
|
|
20056
|
+
annotations: { readOnlyHint: true }
|
|
20057
|
+
},
|
|
20058
|
+
git_changed_files: {
|
|
20059
|
+
description: "List changed tracked and untracked files.",
|
|
20060
|
+
command: "git",
|
|
20061
|
+
args: [
|
|
20062
|
+
"status",
|
|
20063
|
+
"--porcelain=v1",
|
|
20064
|
+
"--untracked-files=all"
|
|
20065
|
+
],
|
|
20066
|
+
cwd: repo,
|
|
20067
|
+
annotations: { readOnlyHint: true }
|
|
20068
|
+
}
|
|
20069
|
+
};
|
|
20070
|
+
}
|
|
20071
|
+
function ghActions(repo) {
|
|
20072
|
+
return {
|
|
20073
|
+
gh_pr_status: {
|
|
20074
|
+
description: "Show pull request status for the current branch as JSON.",
|
|
20075
|
+
command: "gh",
|
|
20076
|
+
args: [
|
|
20077
|
+
"pr",
|
|
20078
|
+
"status",
|
|
20079
|
+
"--json",
|
|
20080
|
+
"currentBranch"
|
|
20081
|
+
],
|
|
20082
|
+
cwd: repo,
|
|
20083
|
+
output: { type: "json" },
|
|
20084
|
+
annotations: {
|
|
20085
|
+
readOnlyHint: true,
|
|
20086
|
+
openWorldHint: true
|
|
20087
|
+
}
|
|
20088
|
+
},
|
|
20089
|
+
gh_issue_list: {
|
|
20090
|
+
description: "List open GitHub issues as JSON.",
|
|
20091
|
+
command: "gh",
|
|
20092
|
+
args: [
|
|
20093
|
+
"issue",
|
|
20094
|
+
"list",
|
|
20095
|
+
"--json",
|
|
20096
|
+
"number,title,state,url"
|
|
20097
|
+
],
|
|
20098
|
+
cwd: repo,
|
|
20099
|
+
output: { type: "json" },
|
|
20100
|
+
annotations: {
|
|
20101
|
+
readOnlyHint: true,
|
|
20102
|
+
openWorldHint: true
|
|
20103
|
+
}
|
|
20104
|
+
}
|
|
20105
|
+
};
|
|
20106
|
+
}
|
|
20107
|
+
function packageActions(repo, explicitCommand) {
|
|
20108
|
+
const packageJsonPath = join(repo, "package.json");
|
|
20109
|
+
if (!existsSync(packageJsonPath)) return {};
|
|
20110
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
20111
|
+
const manager = explicitCommand && isPackageCommand(explicitCommand) ? explicitCommand : detectPackageManager(repo, packageJson);
|
|
20112
|
+
const scripts = packageJson.scripts ?? {};
|
|
20113
|
+
const actions = {};
|
|
20114
|
+
for (const script of [
|
|
20115
|
+
"test",
|
|
20116
|
+
"lint",
|
|
20117
|
+
"typecheck",
|
|
20118
|
+
"build",
|
|
20119
|
+
"verify"
|
|
20120
|
+
]) {
|
|
20121
|
+
if (!scripts[script]) continue;
|
|
20122
|
+
actions[`package_${script}`] = {
|
|
20123
|
+
description: `Run the package ${script} script.`,
|
|
20124
|
+
command: manager,
|
|
20125
|
+
args: ["run", script],
|
|
20126
|
+
cwd: repo,
|
|
20127
|
+
annotations: { readOnlyHint: false },
|
|
20128
|
+
...script === "test" || script === "verify" ? { timeoutMs: 12e4 } : {}
|
|
20129
|
+
};
|
|
20130
|
+
}
|
|
20131
|
+
return actions;
|
|
20132
|
+
}
|
|
20133
|
+
function detectPackageManager(repo, packageJson) {
|
|
20134
|
+
if (packageJson.packageManager?.startsWith("pnpm@")) return "pnpm";
|
|
20135
|
+
if (packageJson.packageManager?.startsWith("bun@")) return "bun";
|
|
20136
|
+
if (packageJson.packageManager?.startsWith("yarn@")) return "yarn";
|
|
20137
|
+
if (existsSync(join(repo, "pnpm-lock.yaml"))) return "pnpm";
|
|
20138
|
+
if (existsSync(join(repo, "bun.lockb")) || existsSync(join(repo, "bun.lock"))) return "bun";
|
|
20139
|
+
if (existsSync(join(repo, "yarn.lock"))) return "yarn";
|
|
20140
|
+
return "npm";
|
|
20141
|
+
}
|
|
20142
|
+
function isPackageCommand(command) {
|
|
20143
|
+
return command === "pnpm" || command === "npm" || command === "bun" || command === "yarn";
|
|
20144
|
+
}
|
|
20145
|
+
function renderCaplet(input) {
|
|
20146
|
+
const lines = [
|
|
20147
|
+
"---",
|
|
20148
|
+
"$schema: https://raw.githubusercontent.com/spiritledsoftware/caplets/main/schemas/caplet.schema.json",
|
|
20149
|
+
`name: ${yamlString(input.name)}`,
|
|
20150
|
+
`description: ${yamlString(input.description)}`,
|
|
20151
|
+
"tags:",
|
|
20152
|
+
" - cli",
|
|
20153
|
+
" - code",
|
|
20154
|
+
"cliTools:",
|
|
20155
|
+
` cwd: ${yamlString(input.cwd)}`,
|
|
20156
|
+
" actions:"
|
|
20157
|
+
];
|
|
20158
|
+
for (const [name, action] of Object.entries(input.actions).sort(([left], [right]) => left.localeCompare(right))) {
|
|
20159
|
+
lines.push(` ${name}:`);
|
|
20160
|
+
if (action.description) lines.push(` description: ${yamlString(action.description)}`);
|
|
20161
|
+
lines.push(` command: ${yamlString(action.command)}`);
|
|
20162
|
+
if (action.args?.length) {
|
|
20163
|
+
lines.push(" args:");
|
|
20164
|
+
for (const arg of action.args) lines.push(` - ${yamlString(arg)}`);
|
|
20165
|
+
}
|
|
20166
|
+
if (action.cwd && action.cwd !== input.cwd) lines.push(` cwd: ${yamlString(action.cwd)}`);
|
|
20167
|
+
if (action.timeoutMs) lines.push(` timeoutMs: ${action.timeoutMs}`);
|
|
20168
|
+
if (action.maxOutputBytes) lines.push(` maxOutputBytes: ${action.maxOutputBytes}`);
|
|
20169
|
+
if (action.output) {
|
|
20170
|
+
lines.push(" output:");
|
|
20171
|
+
lines.push(` type: ${action.output.type}`);
|
|
20172
|
+
}
|
|
20173
|
+
if (action.annotations) {
|
|
20174
|
+
lines.push(" annotations:");
|
|
20175
|
+
for (const [key, value] of Object.entries(action.annotations)) lines.push(` ${key}: ${value ? "true" : "false"}`);
|
|
20176
|
+
}
|
|
20177
|
+
}
|
|
20178
|
+
lines.push("---", "", `# ${input.name}`, "", input.description, "");
|
|
20179
|
+
return `${lines.join("\n")}`;
|
|
20180
|
+
}
|
|
20181
|
+
function yamlString(value) {
|
|
20182
|
+
return JSON.stringify(value);
|
|
20183
|
+
}
|
|
20184
|
+
function titleize(id) {
|
|
20185
|
+
return id.split(/[-_]/).filter(Boolean).map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join(" ");
|
|
20186
|
+
}
|
|
20187
|
+
function sanitizeId(value) {
|
|
20188
|
+
return value.replace(/[^a-zA-Z0-9_-]+/g, "_").slice(0, 48) || "cli";
|
|
20189
|
+
}
|
|
20190
|
+
//#endregion
|
|
19870
20191
|
//#region src/cli/init.ts
|
|
19871
20192
|
function initConfig(options = {}) {
|
|
19872
20193
|
const path = resolveConfigPath(options.path);
|
|
@@ -19918,7 +20239,9 @@ function allCaplets$1(config) {
|
|
|
19918
20239
|
return [
|
|
19919
20240
|
...Object.values(config.mcpServers),
|
|
19920
20241
|
...Object.values(config.openapiEndpoints),
|
|
19921
|
-
...Object.values(config.graphqlEndpoints)
|
|
20242
|
+
...Object.values(config.graphqlEndpoints),
|
|
20243
|
+
...Object.values(config.httpApis),
|
|
20244
|
+
...Object.values(config.cliTools)
|
|
19922
20245
|
];
|
|
19923
20246
|
}
|
|
19924
20247
|
function formatCapletList(rows) {
|
|
@@ -20158,6 +20481,14 @@ function createProgram(io = {}) {
|
|
|
20158
20481
|
});
|
|
20159
20482
|
for (const caplet of result.installed) writeOut(`Installed ${caplet.id} to ${caplet.destination}\n`);
|
|
20160
20483
|
});
|
|
20484
|
+
program.command("author").description("Generate reviewable Caplet files.").command("cli").description("Generate a CLI tools Caplet.").argument("<id>", "Caplet ID/display seed").option("--repo <path>", "repository path to inspect").option("--include <items>", "comma-separated generators to include: git,gh,package").option("--command <name>", "single CLI command template to generate").option("--output <path>", "output path, or - for stdout", "-").action((id, options) => {
|
|
20485
|
+
const result = authorCliCaplet(id, options);
|
|
20486
|
+
if (result.path) {
|
|
20487
|
+
writeOut(`Wrote CLI Caplet to ${result.path}\n`);
|
|
20488
|
+
return;
|
|
20489
|
+
}
|
|
20490
|
+
writeOut(result.text);
|
|
20491
|
+
});
|
|
20161
20492
|
const config = program.command("config").description("Inspect Caplets config locations.");
|
|
20162
20493
|
config.command("path").description("Print the effective user config path.").action(() => {
|
|
20163
20494
|
writeOut(`${resolveConfigPath(envConfigPath())}\n`);
|
|
@@ -25864,7 +26195,7 @@ var Protocol = class {
|
|
|
25864
26195
|
};
|
|
25865
26196
|
}
|
|
25866
26197
|
};
|
|
25867
|
-
function isPlainObject$
|
|
26198
|
+
function isPlainObject$4(value) {
|
|
25868
26199
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
25869
26200
|
}
|
|
25870
26201
|
function mergeCapabilities(base, additional) {
|
|
@@ -25874,7 +26205,7 @@ function mergeCapabilities(base, additional) {
|
|
|
25874
26205
|
const addValue = additional[k];
|
|
25875
26206
|
if (addValue === void 0) continue;
|
|
25876
26207
|
const baseValue = result[k];
|
|
25877
|
-
if (isPlainObject$
|
|
26208
|
+
if (isPlainObject$4(baseValue) && isPlainObject$4(addValue)) result[k] = {
|
|
25878
26209
|
...baseValue,
|
|
25879
26210
|
...addValue
|
|
25880
26211
|
};
|
|
@@ -33525,6 +33856,273 @@ const EMPTY_COMPLETION_RESULT = { completion: {
|
|
|
33525
33856
|
hasMore: false
|
|
33526
33857
|
} };
|
|
33527
33858
|
//#endregion
|
|
33859
|
+
//#region src/cli-tools.ts
|
|
33860
|
+
const DEFAULT_INPUT_SCHEMA$1 = {
|
|
33861
|
+
type: "object",
|
|
33862
|
+
additionalProperties: true
|
|
33863
|
+
};
|
|
33864
|
+
var CliToolsManager = class {
|
|
33865
|
+
registry;
|
|
33866
|
+
constructor(registry) {
|
|
33867
|
+
this.registry = registry;
|
|
33868
|
+
}
|
|
33869
|
+
updateRegistry(registry) {
|
|
33870
|
+
this.registry = registry;
|
|
33871
|
+
}
|
|
33872
|
+
invalidate(_serverId) {}
|
|
33873
|
+
async checkTools(config) {
|
|
33874
|
+
const startedAt = Date.now();
|
|
33875
|
+
try {
|
|
33876
|
+
for (const action of actionsFor(config)) {
|
|
33877
|
+
const cwdTemplate = action.cwd ?? config.cwd;
|
|
33878
|
+
if (cwdTemplate && !cwdTemplate.includes("$input")) {
|
|
33879
|
+
if (!existsSync(interpolateRequiredString(cwdTemplate, {}, "cwd"))) throw new CapletsError("CONFIG_INVALID", `CLI cwd does not exist for ${config.server}/${action.name}`);
|
|
33880
|
+
}
|
|
33881
|
+
if (!action.command.includes("$input")) resolveCommandPath(action.command);
|
|
33882
|
+
}
|
|
33883
|
+
this.registry.setStatus(config.server, "available");
|
|
33884
|
+
return {
|
|
33885
|
+
server: config.server,
|
|
33886
|
+
status: "available",
|
|
33887
|
+
toolCount: Object.keys(config.actions).length,
|
|
33888
|
+
elapsedMs: Date.now() - startedAt
|
|
33889
|
+
};
|
|
33890
|
+
} catch (error) {
|
|
33891
|
+
const safe = toSafeError(error, "SERVER_UNAVAILABLE");
|
|
33892
|
+
this.registry.setStatus(config.server, "unavailable", safe);
|
|
33893
|
+
return {
|
|
33894
|
+
server: config.server,
|
|
33895
|
+
status: "unavailable",
|
|
33896
|
+
elapsedMs: Date.now() - startedAt,
|
|
33897
|
+
error: safe
|
|
33898
|
+
};
|
|
33899
|
+
}
|
|
33900
|
+
}
|
|
33901
|
+
async listTools(config) {
|
|
33902
|
+
return actionsFor(config).map((action) => this.toTool(action));
|
|
33903
|
+
}
|
|
33904
|
+
async getTool(config, toolName) {
|
|
33905
|
+
return this.toTool(getAction(config, toolName));
|
|
33906
|
+
}
|
|
33907
|
+
async callTool(config, toolName, args) {
|
|
33908
|
+
const action = getAction(config, toolName);
|
|
33909
|
+
validateInput(action, args);
|
|
33910
|
+
const execution = resolveExecution(config, action, args);
|
|
33911
|
+
const startedAt = Date.now();
|
|
33912
|
+
const controller = new AbortController();
|
|
33913
|
+
const timeout = setTimeout(() => controller.abort(), execution.timeoutMs);
|
|
33914
|
+
try {
|
|
33915
|
+
const result = await spawnCommand(execution, controller.signal, () => Date.now() - startedAt);
|
|
33916
|
+
const structured = parseStructuredResult(action, result, result.exitCode !== 0);
|
|
33917
|
+
return {
|
|
33918
|
+
content: [{
|
|
33919
|
+
type: "text",
|
|
33920
|
+
text: JSON.stringify(structured, null, 2)
|
|
33921
|
+
}],
|
|
33922
|
+
structuredContent: structured,
|
|
33923
|
+
isError: result.exitCode !== 0
|
|
33924
|
+
};
|
|
33925
|
+
} catch (error) {
|
|
33926
|
+
if (isAbortError$1(error)) throw new CapletsError("TOOL_CALL_TIMEOUT", `CLI tool timed out for ${config.server}/${toolName}`);
|
|
33927
|
+
if (error instanceof CapletsError) throw error;
|
|
33928
|
+
throw new CapletsError("DOWNSTREAM_TOOL_ERROR", `CLI tool failed for ${config.server}/${toolName}`, toSafeError(error));
|
|
33929
|
+
} finally {
|
|
33930
|
+
clearTimeout(timeout);
|
|
33931
|
+
}
|
|
33932
|
+
}
|
|
33933
|
+
compact(config, tool) {
|
|
33934
|
+
return {
|
|
33935
|
+
server: config.server,
|
|
33936
|
+
tool: tool.name,
|
|
33937
|
+
...tool.description ? { description: tool.description } : {},
|
|
33938
|
+
...tool.annotations ? { annotations: tool.annotations } : {},
|
|
33939
|
+
hasInputSchema: Boolean(tool.inputSchema),
|
|
33940
|
+
hasOutputSchema: Boolean(tool.outputSchema)
|
|
33941
|
+
};
|
|
33942
|
+
}
|
|
33943
|
+
search(config, tools, query, limit) {
|
|
33944
|
+
const needle = query.toLocaleLowerCase();
|
|
33945
|
+
return tools.filter((tool) => `${tool.name}\n${tool.description ?? ""}`.toLocaleLowerCase().includes(needle)).sort((left, right) => left.name.localeCompare(right.name)).slice(0, limit).map((tool) => this.compact(config, tool));
|
|
33946
|
+
}
|
|
33947
|
+
toTool(action) {
|
|
33948
|
+
return {
|
|
33949
|
+
name: action.name,
|
|
33950
|
+
...action.description ? { description: action.description } : {},
|
|
33951
|
+
inputSchema: action.inputSchema ?? DEFAULT_INPUT_SCHEMA$1,
|
|
33952
|
+
...action.outputSchema ? { outputSchema: action.outputSchema } : {},
|
|
33953
|
+
...action.annotations ? { annotations: action.annotations } : {}
|
|
33954
|
+
};
|
|
33955
|
+
}
|
|
33956
|
+
};
|
|
33957
|
+
function actionsFor(config) {
|
|
33958
|
+
return Object.entries(config.actions).map(([name, action]) => ({
|
|
33959
|
+
name,
|
|
33960
|
+
...action
|
|
33961
|
+
})).sort((left, right) => left.name.localeCompare(right.name));
|
|
33962
|
+
}
|
|
33963
|
+
function getAction(config, toolName) {
|
|
33964
|
+
const actions = actionsFor(config);
|
|
33965
|
+
const action = actions.find((candidate) => candidate.name === toolName);
|
|
33966
|
+
if (!action) throw new CapletsError("TOOL_NOT_FOUND", `Tool ${toolName} was not found on ${config.server}`, {
|
|
33967
|
+
server: config.server,
|
|
33968
|
+
tool: toolName,
|
|
33969
|
+
suggestions: actions.map((candidate) => candidate.name).filter((name) => name.toLocaleLowerCase().includes(toolName.toLocaleLowerCase()[0] ?? "")).slice(0, 5)
|
|
33970
|
+
});
|
|
33971
|
+
return action;
|
|
33972
|
+
}
|
|
33973
|
+
function resolveExecution(config, action, input) {
|
|
33974
|
+
const cwd = interpolateString(action.cwd ?? config.cwd, input, "cwd");
|
|
33975
|
+
if (cwd && !existsSync(cwd)) throw new CapletsError("CONFIG_INVALID", `CLI cwd does not exist for ${config.server}/${action.name}`);
|
|
33976
|
+
const env = {
|
|
33977
|
+
...process.env,
|
|
33978
|
+
...resolveEnv(config.env, input),
|
|
33979
|
+
...resolveEnv(action.env, input)
|
|
33980
|
+
};
|
|
33981
|
+
return {
|
|
33982
|
+
command: interpolateString(action.command, input, "command") ?? action.command,
|
|
33983
|
+
args: (action.args ?? []).map((arg, index) => interpolateRequiredString(arg, input, `args.${index}`)),
|
|
33984
|
+
...cwd ? { cwd } : {},
|
|
33985
|
+
env,
|
|
33986
|
+
timeoutMs: action.timeoutMs ?? config.timeoutMs,
|
|
33987
|
+
maxOutputBytes: action.maxOutputBytes ?? config.maxOutputBytes
|
|
33988
|
+
};
|
|
33989
|
+
}
|
|
33990
|
+
function resolveEnv(env, input) {
|
|
33991
|
+
if (!env) return {};
|
|
33992
|
+
return Object.fromEntries(Object.entries(env).map(([key, value]) => [key, interpolateRequiredString(value, input, `env.${key}`)]));
|
|
33993
|
+
}
|
|
33994
|
+
function interpolateString(value, input, field) {
|
|
33995
|
+
return value === void 0 ? void 0 : interpolateRequiredString(value, input, field);
|
|
33996
|
+
}
|
|
33997
|
+
function interpolateRequiredString(value, input, field) {
|
|
33998
|
+
return value.replace(/\$input(?:\.([A-Za-z0-9_.-]+))?/g, (_match, path) => {
|
|
33999
|
+
if (!path) throw new CapletsError("REQUEST_INVALID", `CLI ${field} cannot interpolate $input directly`);
|
|
34000
|
+
const selected = valueAtPath$1(input, path);
|
|
34001
|
+
if (selected === void 0 || selected === null) throw new CapletsError("REQUEST_INVALID", `CLI ${field} references missing input ${path}`);
|
|
34002
|
+
if (typeof selected !== "string" && typeof selected !== "number" && typeof selected !== "boolean") throw new CapletsError("REQUEST_INVALID", `CLI ${field} input ${path} must be a string, number, or boolean`);
|
|
34003
|
+
return String(selected);
|
|
34004
|
+
});
|
|
34005
|
+
}
|
|
34006
|
+
function valueAtPath$1(input, path) {
|
|
34007
|
+
let current = input;
|
|
34008
|
+
for (const segment of path.split(".")) {
|
|
34009
|
+
if (!current || typeof current !== "object" || Array.isArray(current)) return;
|
|
34010
|
+
current = current[segment];
|
|
34011
|
+
}
|
|
34012
|
+
return current;
|
|
34013
|
+
}
|
|
34014
|
+
function validateInput(action, input) {
|
|
34015
|
+
const schema = action.inputSchema;
|
|
34016
|
+
if (!schema) return;
|
|
34017
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
34018
|
+
for (const key of required) if (typeof key === "string" && (input[key] === void 0 || input[key] === null)) throw new CapletsError("REQUEST_INVALID", `CLI tool ${action.name} requires input ${key}`);
|
|
34019
|
+
const properties = isPlainObject$3(schema.properties) ? schema.properties : {};
|
|
34020
|
+
for (const [key, property] of Object.entries(properties)) {
|
|
34021
|
+
if (input[key] === void 0 || !isPlainObject$3(property) || typeof property.type !== "string") continue;
|
|
34022
|
+
if (!matchesJsonType(input[key], property.type)) throw new CapletsError("REQUEST_INVALID", `CLI tool ${action.name} input ${key} must be ${property.type}`);
|
|
34023
|
+
}
|
|
34024
|
+
}
|
|
34025
|
+
function matchesJsonType(value, type) {
|
|
34026
|
+
switch (type) {
|
|
34027
|
+
case "string": return typeof value === "string";
|
|
34028
|
+
case "number":
|
|
34029
|
+
case "integer": return typeof value === "number" && (type === "number" || Number.isInteger(value));
|
|
34030
|
+
case "boolean": return typeof value === "boolean";
|
|
34031
|
+
case "object": return isPlainObject$3(value);
|
|
34032
|
+
case "array": return Array.isArray(value);
|
|
34033
|
+
case "null": return value === null;
|
|
34034
|
+
default: return true;
|
|
34035
|
+
}
|
|
34036
|
+
}
|
|
34037
|
+
function spawnCommand(execution, signal, elapsedMs) {
|
|
34038
|
+
return new Promise((resolve, reject) => {
|
|
34039
|
+
let stdout = "";
|
|
34040
|
+
let stderr = "";
|
|
34041
|
+
let outputBytes = 0;
|
|
34042
|
+
const child = spawn(execution.command, execution.args, {
|
|
34043
|
+
cwd: execution.cwd,
|
|
34044
|
+
env: execution.env,
|
|
34045
|
+
shell: false,
|
|
34046
|
+
signal,
|
|
34047
|
+
windowsHide: true
|
|
34048
|
+
});
|
|
34049
|
+
child.on("error", reject);
|
|
34050
|
+
const append = (stream, chunk) => {
|
|
34051
|
+
outputBytes += chunk.byteLength;
|
|
34052
|
+
if (outputBytes > execution.maxOutputBytes) {
|
|
34053
|
+
child.kill();
|
|
34054
|
+
reject(new CapletsError("DOWNSTREAM_TOOL_ERROR", "CLI tool output exceeded byte limit"));
|
|
34055
|
+
return;
|
|
34056
|
+
}
|
|
34057
|
+
if (stream === "stdout") stdout += chunk.toString("utf8");
|
|
34058
|
+
else stderr += chunk.toString("utf8");
|
|
34059
|
+
};
|
|
34060
|
+
child.stdout?.on("data", (chunk) => append("stdout", chunk));
|
|
34061
|
+
child.stderr?.on("data", (chunk) => append("stderr", chunk));
|
|
34062
|
+
child.on("close", (exitCode, childSignal) => {
|
|
34063
|
+
resolve({
|
|
34064
|
+
exitCode,
|
|
34065
|
+
signal: childSignal,
|
|
34066
|
+
stdout,
|
|
34067
|
+
stderr,
|
|
34068
|
+
elapsedMs: elapsedMs()
|
|
34069
|
+
});
|
|
34070
|
+
});
|
|
34071
|
+
});
|
|
34072
|
+
}
|
|
34073
|
+
function parseStructuredResult(action, result, tolerateInvalidJson = false) {
|
|
34074
|
+
const structured = {
|
|
34075
|
+
exitCode: result.exitCode,
|
|
34076
|
+
stdout: result.stdout,
|
|
34077
|
+
stderr: result.stderr,
|
|
34078
|
+
elapsedMs: result.elapsedMs,
|
|
34079
|
+
...result.signal ? { signal: result.signal } : {}
|
|
34080
|
+
};
|
|
34081
|
+
if (action.output?.type === "json" && result.stdout.trim()) try {
|
|
34082
|
+
structured.json = JSON.parse(result.stdout);
|
|
34083
|
+
} catch (error) {
|
|
34084
|
+
if (tolerateInvalidJson) {
|
|
34085
|
+
structured.jsonParseError = toSafeError(error);
|
|
34086
|
+
return structured;
|
|
34087
|
+
}
|
|
34088
|
+
throw new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", `CLI tool ${action.name} stdout was not valid JSON`, toSafeError(error));
|
|
34089
|
+
}
|
|
34090
|
+
return structured;
|
|
34091
|
+
}
|
|
34092
|
+
function resolveCommandPath(command) {
|
|
34093
|
+
if (isAbsolute(command) || /[\\/]/.test(command)) {
|
|
34094
|
+
assertExecutable(command);
|
|
34095
|
+
return command;
|
|
34096
|
+
}
|
|
34097
|
+
for (const directory of (process.env.PATH ?? "").split(delimiter)) {
|
|
34098
|
+
if (!directory) continue;
|
|
34099
|
+
const candidate = join(directory, command);
|
|
34100
|
+
if (isExecutable(candidate)) return candidate;
|
|
34101
|
+
if (process.platform === "win32") for (const ext of (process.env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";")) {
|
|
34102
|
+
const windowsCandidate = join(directory, `${command}${ext.toLowerCase()}`);
|
|
34103
|
+
if (isExecutable(windowsCandidate)) return windowsCandidate;
|
|
34104
|
+
}
|
|
34105
|
+
}
|
|
34106
|
+
throw new CapletsError("SERVER_UNAVAILABLE", `CLI command ${command} was not found on PATH`);
|
|
34107
|
+
}
|
|
34108
|
+
function assertExecutable(path) {
|
|
34109
|
+
if (!isExecutable(path)) throw new CapletsError("SERVER_UNAVAILABLE", `CLI command ${path} is not executable`);
|
|
34110
|
+
}
|
|
34111
|
+
function isExecutable(path) {
|
|
34112
|
+
try {
|
|
34113
|
+
accessSync(path, constants.X_OK);
|
|
34114
|
+
return true;
|
|
34115
|
+
} catch {
|
|
34116
|
+
return false;
|
|
34117
|
+
}
|
|
34118
|
+
}
|
|
34119
|
+
function isAbortError$1(error) {
|
|
34120
|
+
return error instanceof Error && error.name === "AbortError";
|
|
34121
|
+
}
|
|
34122
|
+
function isPlainObject$3(value) {
|
|
34123
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
34124
|
+
}
|
|
34125
|
+
//#endregion
|
|
33528
34126
|
//#region node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/client.js
|
|
33529
34127
|
/**
|
|
33530
34128
|
* Experimental client task features for MCP SDK.
|
|
@@ -35770,7 +36368,8 @@ var DownstreamManager = class {
|
|
|
35770
36368
|
tool: tool.name,
|
|
35771
36369
|
...tool.description ? { description: tool.description } : {},
|
|
35772
36370
|
...tool.annotations ? { annotations: tool.annotations } : {},
|
|
35773
|
-
hasInputSchema: Boolean(tool.inputSchema)
|
|
36371
|
+
hasInputSchema: Boolean(tool.inputSchema),
|
|
36372
|
+
hasOutputSchema: Boolean(tool.outputSchema)
|
|
35774
36373
|
};
|
|
35775
36374
|
}
|
|
35776
36375
|
search(server, tools, query, limit) {
|
|
@@ -50749,7 +51348,8 @@ var GraphQLManager = class {
|
|
|
50749
51348
|
tool: tool.name,
|
|
50750
51349
|
...tool.description ? { description: tool.description } : {},
|
|
50751
51350
|
...tool.annotations ? { annotations: tool.annotations } : {},
|
|
50752
|
-
hasInputSchema: Boolean(tool.inputSchema)
|
|
51351
|
+
hasInputSchema: Boolean(tool.inputSchema),
|
|
51352
|
+
hasOutputSchema: Boolean(tool.outputSchema)
|
|
50753
51353
|
};
|
|
50754
51354
|
}
|
|
50755
51355
|
search(endpoint, tools, query, limit) {
|
|
@@ -51147,7 +51747,8 @@ var HttpActionManager = class {
|
|
|
51147
51747
|
tool: tool.name,
|
|
51148
51748
|
...tool.description ? { description: tool.description } : {},
|
|
51149
51749
|
...tool.annotations ? { annotations: tool.annotations } : {},
|
|
51150
|
-
hasInputSchema: Boolean(tool.inputSchema)
|
|
51750
|
+
hasInputSchema: Boolean(tool.inputSchema),
|
|
51751
|
+
hasOutputSchema: Boolean(tool.outputSchema)
|
|
51151
51752
|
};
|
|
51152
51753
|
}
|
|
51153
51754
|
search(api, tools, query, limit) {
|
|
@@ -51159,6 +51760,7 @@ var HttpActionManager = class {
|
|
|
51159
51760
|
name: operation.name,
|
|
51160
51761
|
...operation.description ? { description: operation.description } : {},
|
|
51161
51762
|
inputSchema: operation.inputSchema ?? DEFAULT_INPUT_SCHEMA,
|
|
51763
|
+
...operation.outputSchema ? { outputSchema: operation.outputSchema } : {},
|
|
51162
51764
|
annotations: {
|
|
51163
51765
|
readOnlyHint: operation.method === "GET",
|
|
51164
51766
|
destructiveHint: operation.method === "DELETE"
|
|
@@ -51230,7 +51832,7 @@ function resolveMapping(mapping, input) {
|
|
|
51230
51832
|
function resolveMappingToRecord(mapping, input, name) {
|
|
51231
51833
|
if (mapping === void 0) return {};
|
|
51232
51834
|
const resolved = resolveMapping(mapping, input);
|
|
51233
|
-
if (!isPlainObject$
|
|
51835
|
+
if (!isPlainObject$2(resolved)) throw new CapletsError("REQUEST_INVALID", `HTTP action ${name} mapping must resolve to an object`);
|
|
51234
51836
|
return resolved;
|
|
51235
51837
|
}
|
|
51236
51838
|
function valueAtPath(input, path) {
|
|
@@ -51325,9 +51927,9 @@ function buildActionUrl(base, actionPath, options = {}) {
|
|
|
51325
51927
|
return baseUrl;
|
|
51326
51928
|
}
|
|
51327
51929
|
function asRecord$1(value) {
|
|
51328
|
-
return isPlainObject$
|
|
51930
|
+
return isPlainObject$2(value) ? value : {};
|
|
51329
51931
|
}
|
|
51330
|
-
function isPlainObject$
|
|
51932
|
+
function isPlainObject$2(value) {
|
|
51331
51933
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
51332
51934
|
}
|
|
51333
51935
|
//#endregion
|
|
@@ -60941,7 +61543,8 @@ var OpenApiManager = class {
|
|
|
60941
61543
|
tool: tool.name,
|
|
60942
61544
|
...tool.description ? { description: tool.description } : {},
|
|
60943
61545
|
...tool.annotations ? { annotations: tool.annotations } : {},
|
|
60944
|
-
hasInputSchema: Boolean(tool.inputSchema)
|
|
61546
|
+
hasInputSchema: Boolean(tool.inputSchema),
|
|
61547
|
+
hasOutputSchema: Boolean(tool.outputSchema)
|
|
60945
61548
|
};
|
|
60946
61549
|
}
|
|
60947
61550
|
search(endpoint, tools, query, limit) {
|
|
@@ -60984,6 +61587,7 @@ var OpenApiManager = class {
|
|
|
60984
61587
|
name: operation.name,
|
|
60985
61588
|
...operation.summary || operation.description ? { description: operation.summary ?? operation.description } : {},
|
|
60986
61589
|
inputSchema: operation.inputSchema,
|
|
61590
|
+
...operation.outputSchema ? { outputSchema: operation.outputSchema } : {},
|
|
60987
61591
|
annotations: {
|
|
60988
61592
|
readOnlyHint: operation.method === "get" || operation.method === "head",
|
|
60989
61593
|
destructiveHint: operation.method === "delete"
|
|
@@ -61019,6 +61623,7 @@ function extractOperations(endpoint, document) {
|
|
|
61019
61623
|
seen.add(name);
|
|
61020
61624
|
const parameters = [...inheritedParameters, ...Array.isArray(operation.parameters) ? operation.parameters : []];
|
|
61021
61625
|
const requestBody = requestBodyFor(operation);
|
|
61626
|
+
const outputSchema = outputSchemaFor(operation);
|
|
61022
61627
|
const baseUrl = endpoint.baseUrl ?? firstServerUrl(document);
|
|
61023
61628
|
validateOperationBaseUrl(endpoint, baseUrl);
|
|
61024
61629
|
operations.push({
|
|
@@ -61028,6 +61633,7 @@ function extractOperations(endpoint, document) {
|
|
|
61028
61633
|
...typeof operation.summary === "string" ? { summary: operation.summary } : {},
|
|
61029
61634
|
...typeof operation.description === "string" ? { description: operation.description } : {},
|
|
61030
61635
|
inputSchema: inputSchemaFor(parameters, requestBody),
|
|
61636
|
+
...outputSchema ? { outputSchema } : {},
|
|
61031
61637
|
...requestBody?.contentType ? { requestBodyContentType: requestBody.contentType } : {},
|
|
61032
61638
|
...baseUrl ? { baseUrl } : {}
|
|
61033
61639
|
});
|
|
@@ -61048,6 +61654,53 @@ function requestBodyFor(operation) {
|
|
|
61048
61654
|
contentType
|
|
61049
61655
|
};
|
|
61050
61656
|
}
|
|
61657
|
+
function outputSchemaFor(operation) {
|
|
61658
|
+
const responses = operation.responses;
|
|
61659
|
+
if (!responses || typeof responses !== "object") return;
|
|
61660
|
+
const schemas = [];
|
|
61661
|
+
for (const [status, response] of Object.entries(responses)) {
|
|
61662
|
+
if (!/^2\d\d$/.test(status) || !response || typeof response !== "object") continue;
|
|
61663
|
+
const content = response.content;
|
|
61664
|
+
if (!content || typeof content !== "object") continue;
|
|
61665
|
+
const contentType = JSON_CONTENT_TYPES.find((candidate) => content[candidate]);
|
|
61666
|
+
if (!contentType) continue;
|
|
61667
|
+
const schema = actualSchema(content[contentType]?.schema);
|
|
61668
|
+
if (!schema) continue;
|
|
61669
|
+
schemas.push(schema);
|
|
61670
|
+
}
|
|
61671
|
+
if (schemas.length === 0) return;
|
|
61672
|
+
const firstSchema = schemas[0];
|
|
61673
|
+
if (schemas.slice(1).some((schema) => JSON.stringify(schema) !== JSON.stringify(firstSchema))) return;
|
|
61674
|
+
return structuredOutputSchema(firstSchema);
|
|
61675
|
+
}
|
|
61676
|
+
function actualSchema(value) {
|
|
61677
|
+
rejectExternalRefs(value);
|
|
61678
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
61679
|
+
const schema = value;
|
|
61680
|
+
return typeof schema.$ref === "string" ? void 0 : schema;
|
|
61681
|
+
}
|
|
61682
|
+
function structuredOutputSchema(bodySchema) {
|
|
61683
|
+
return {
|
|
61684
|
+
type: "object",
|
|
61685
|
+
additionalProperties: false,
|
|
61686
|
+
required: [
|
|
61687
|
+
"status",
|
|
61688
|
+
"statusText",
|
|
61689
|
+
"headers"
|
|
61690
|
+
],
|
|
61691
|
+
properties: {
|
|
61692
|
+
status: { type: "number" },
|
|
61693
|
+
statusText: { type: "string" },
|
|
61694
|
+
headers: {
|
|
61695
|
+
type: "object",
|
|
61696
|
+
additionalProperties: false,
|
|
61697
|
+
required: ["content-type"],
|
|
61698
|
+
properties: { "content-type": { type: "string" } }
|
|
61699
|
+
},
|
|
61700
|
+
body: bodySchema
|
|
61701
|
+
}
|
|
61702
|
+
};
|
|
61703
|
+
}
|
|
61051
61704
|
function inputSchemaFor(parameters, requestBody) {
|
|
61052
61705
|
const schema = {
|
|
61053
61706
|
type: "object",
|
|
@@ -61207,6 +61860,26 @@ function openApiCacheKey(endpoint) {
|
|
|
61207
61860
|
});
|
|
61208
61861
|
}
|
|
61209
61862
|
//#endregion
|
|
61863
|
+
//#region src/capability-description.mjs
|
|
61864
|
+
function capabilityDescription(server) {
|
|
61865
|
+
const backendName = server.backend === "mcp" ? "MCP server" : server.backend === "openapi" ? "OpenAPI endpoint" : server.backend === "graphql" ? "GraphQL endpoint" : server.backend === "http" ? "HTTP API" : "CLI tools";
|
|
61866
|
+
const checkOperation = server.backend === "mcp" ? "check_mcp_server" : "check_backend";
|
|
61867
|
+
const hint = [
|
|
61868
|
+
`Use this Caplet to inspect and call tools from its ${backendName} backend.`,
|
|
61869
|
+
"",
|
|
61870
|
+
"Recommended flow:",
|
|
61871
|
+
"- Read the full Caplet card: {\"operation\":\"get_caplet\"}",
|
|
61872
|
+
`- Check the backend: {"operation":"${checkOperation}"}`,
|
|
61873
|
+
"- Discover tools: {\"operation\":\"list_tools\"} or {\"operation\":\"search_tools\",\"query\":\"<what you need>\"}",
|
|
61874
|
+
"- Read one tool schema: {\"operation\":\"get_tool\",\"tool\":\"<tool name>\"}",
|
|
61875
|
+
"- Invoke one downstream tool: {\"operation\":\"call_tool\",\"tool\":\"<tool name>\",\"arguments\":{...}}",
|
|
61876
|
+
"",
|
|
61877
|
+
"Important: Do not put downstream arguments at the top level; put them inside \"arguments\".",
|
|
61878
|
+
"After get_tool shows outputSchema (non-GraphQL), call_tool may use fields: [\"path.to.field\"]."
|
|
61879
|
+
].join("\n");
|
|
61880
|
+
return `${server.name}\n\n${server.description}\n\n${hint}`;
|
|
61881
|
+
}
|
|
61882
|
+
//#endregion
|
|
61210
61883
|
//#region src/registry.ts
|
|
61211
61884
|
var ServerRegistry = class {
|
|
61212
61885
|
config;
|
|
@@ -61219,7 +61892,7 @@ var ServerRegistry = class {
|
|
|
61219
61892
|
return this.allCaplets().filter((server) => !server.disabled);
|
|
61220
61893
|
}
|
|
61221
61894
|
get(serverId) {
|
|
61222
|
-
const server = this.config.mcpServers[serverId] ?? this.config.openapiEndpoints[serverId] ?? this.config.graphqlEndpoints[serverId] ?? this.config.httpApis[serverId];
|
|
61895
|
+
const server = this.config.mcpServers[serverId] ?? this.config.openapiEndpoints[serverId] ?? this.config.graphqlEndpoints[serverId] ?? this.config.httpApis[serverId] ?? this.config.cliTools[serverId];
|
|
61223
61896
|
return server?.disabled ? void 0 : server;
|
|
61224
61897
|
}
|
|
61225
61898
|
require(serverId) {
|
|
@@ -61270,27 +61943,11 @@ var ServerRegistry = class {
|
|
|
61270
61943
|
...Object.values(this.config.mcpServers),
|
|
61271
61944
|
...Object.values(this.config.openapiEndpoints),
|
|
61272
61945
|
...Object.values(this.config.graphqlEndpoints),
|
|
61273
|
-
...Object.values(this.config.httpApis)
|
|
61946
|
+
...Object.values(this.config.httpApis),
|
|
61947
|
+
...Object.values(this.config.cliTools)
|
|
61274
61948
|
];
|
|
61275
61949
|
}
|
|
61276
61950
|
};
|
|
61277
|
-
function capabilityDescription(server) {
|
|
61278
|
-
const backendName = server.backend === "mcp" ? "MCP server" : server.backend === "openapi" ? "OpenAPI endpoint" : server.backend === "graphql" ? "GraphQL endpoint" : "HTTP API";
|
|
61279
|
-
const checkOperation = server.backend === "mcp" ? "check_mcp_server" : "check_backend";
|
|
61280
|
-
const hint = [
|
|
61281
|
-
`Use this Caplet to inspect and call tools from its ${backendName} backend.`,
|
|
61282
|
-
"",
|
|
61283
|
-
"Recommended flow:",
|
|
61284
|
-
"- Read the full Caplet card: {\"operation\":\"get_caplet\"}",
|
|
61285
|
-
`- Check the backend: {"operation":"${checkOperation}"}`,
|
|
61286
|
-
"- Discover tools: {\"operation\":\"list_tools\"} or {\"operation\":\"search_tools\",\"query\":\"<what you need>\"}",
|
|
61287
|
-
"- Read one tool schema: {\"operation\":\"get_tool\",\"tool\":\"<tool name>\"}",
|
|
61288
|
-
"- Invoke one downstream tool: {\"operation\":\"call_tool\",\"tool\":\"<tool name>\",\"arguments\":{...}}",
|
|
61289
|
-
"",
|
|
61290
|
-
"Important: call_tool requires a top-level \"arguments\" JSON object containing the downstream tool inputs. Do not put downstream arguments at the top level of this wrapper request."
|
|
61291
|
-
].join("\n");
|
|
61292
|
-
return `${server.name}\n\n${server.description}\n\n${hint}`;
|
|
61293
|
-
}
|
|
61294
61951
|
function backendDetail(server) {
|
|
61295
61952
|
if (server.backend === "openapi") return {
|
|
61296
61953
|
type: "openapi",
|
|
@@ -61313,6 +61970,13 @@ function backendDetail(server) {
|
|
|
61313
61970
|
requestTimeoutMs: server.requestTimeoutMs,
|
|
61314
61971
|
configuredActions: Object.keys(server.actions).length
|
|
61315
61972
|
};
|
|
61973
|
+
if (server.backend === "cli") return {
|
|
61974
|
+
type: "cli",
|
|
61975
|
+
disabled: server.disabled,
|
|
61976
|
+
timeoutMs: server.timeoutMs,
|
|
61977
|
+
maxOutputBytes: server.maxOutputBytes,
|
|
61978
|
+
configuredActions: Object.keys(server.actions).length
|
|
61979
|
+
};
|
|
61316
61980
|
return {
|
|
61317
61981
|
type: "mcp",
|
|
61318
61982
|
transport: server.transport,
|
|
@@ -61328,7 +61992,110 @@ function graphQlSource(server) {
|
|
|
61328
61992
|
return "introspection";
|
|
61329
61993
|
}
|
|
61330
61994
|
//#endregion
|
|
61331
|
-
//#region src/
|
|
61995
|
+
//#region src/field-selection.ts
|
|
61996
|
+
function projectStructuredContent(value, outputSchema, fields) {
|
|
61997
|
+
validateFieldSelection(outputSchema, fields);
|
|
61998
|
+
if (!isPlainObject$1(value)) throwInvalid("Field selection requires object structured content");
|
|
61999
|
+
const result = createJsonObject();
|
|
62000
|
+
for (const field of fields) {
|
|
62001
|
+
const projected = projectPath(value, outputSchema, field.split("."));
|
|
62002
|
+
if (projected !== void 0) mergeValue(result, projected);
|
|
62003
|
+
}
|
|
62004
|
+
return result;
|
|
62005
|
+
}
|
|
62006
|
+
function validateFieldSelection(outputSchema, fields) {
|
|
62007
|
+
if (!isPlainObject$1(outputSchema)) throwInvalid("Field selection requires an output schema");
|
|
62008
|
+
if (!Array.isArray(fields) || fields.some((field) => typeof field !== "string")) throwInvalid("Field selection requires an array of field paths");
|
|
62009
|
+
for (const field of fields) validateSchemaPath(outputSchema, field.split("."), field);
|
|
62010
|
+
}
|
|
62011
|
+
function validateSchemaPath(schema, path, field) {
|
|
62012
|
+
let current = schema;
|
|
62013
|
+
for (const segment of path) {
|
|
62014
|
+
if (!isSupportedSegment(segment)) throwInvalid(`Unsupported field selection path: ${field}`);
|
|
62015
|
+
if (current?.type === "array") current = Array.isArray(current.items) ? void 0 : current.items;
|
|
62016
|
+
current = getOwnSchemaProperty(current?.properties, segment);
|
|
62017
|
+
if (!current) throwInvalid(`Field is not allowed by output schema: ${field}`);
|
|
62018
|
+
}
|
|
62019
|
+
}
|
|
62020
|
+
function getOwnSchemaProperty(properties, segment) {
|
|
62021
|
+
if (!properties || !Object.prototype.hasOwnProperty.call(properties, segment)) return;
|
|
62022
|
+
return properties[segment];
|
|
62023
|
+
}
|
|
62024
|
+
function projectPath(value, schema, path) {
|
|
62025
|
+
if (path.length === 0) return pruneToSchema(value, schema);
|
|
62026
|
+
if (Array.isArray(value)) {
|
|
62027
|
+
const itemSchema = arrayItemSchema(schema);
|
|
62028
|
+
return value.map((item) => projectPath(item, itemSchema, path) ?? {});
|
|
62029
|
+
}
|
|
62030
|
+
const segment = path[0];
|
|
62031
|
+
if (!isPlainObject$1(value) || !Object.prototype.hasOwnProperty.call(value, segment)) return;
|
|
62032
|
+
const rest = path.slice(1);
|
|
62033
|
+
const propertySchema = getSchemaProperty(schema, segment);
|
|
62034
|
+
const projected = projectPath(value[segment], propertySchema, rest);
|
|
62035
|
+
if (projected === void 0) return;
|
|
62036
|
+
return { [segment]: projected };
|
|
62037
|
+
}
|
|
62038
|
+
function pruneToSchema(value, schema) {
|
|
62039
|
+
if (Array.isArray(value)) {
|
|
62040
|
+
const itemSchema = arrayItemSchema(schema);
|
|
62041
|
+
return value.map((item) => pruneToSchema(item, itemSchema));
|
|
62042
|
+
}
|
|
62043
|
+
if (!isPlainObject$1(value)) return cloneJsonValue(value);
|
|
62044
|
+
const properties = isPlainObject$1(schema) ? schema.properties : void 0;
|
|
62045
|
+
if (!isPlainObject$1(properties)) return cloneJsonValue(value);
|
|
62046
|
+
const result = createJsonObject();
|
|
62047
|
+
for (const [key, nestedSchema] of Object.entries(properties)) if (isSupportedSegment(key) && Object.prototype.hasOwnProperty.call(value, key)) result[key] = pruneToSchema(value[key], nestedSchema);
|
|
62048
|
+
return result;
|
|
62049
|
+
}
|
|
62050
|
+
function getSchemaProperty(schema, segment) {
|
|
62051
|
+
const properties = isPlainObject$1(schema) ? schema.properties : void 0;
|
|
62052
|
+
if (!properties || !Object.prototype.hasOwnProperty.call(properties, segment)) return;
|
|
62053
|
+
return properties[segment];
|
|
62054
|
+
}
|
|
62055
|
+
function arrayItemSchema(schema) {
|
|
62056
|
+
if (!isPlainObject$1(schema) || Array.isArray(schema.items)) return;
|
|
62057
|
+
return schema.items;
|
|
62058
|
+
}
|
|
62059
|
+
function mergeValue(target, value) {
|
|
62060
|
+
if (!isPlainObject$1(value)) return;
|
|
62061
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
62062
|
+
if (!isSupportedSegment(key)) continue;
|
|
62063
|
+
target[key] = mergeNested(target[key], nested);
|
|
62064
|
+
}
|
|
62065
|
+
}
|
|
62066
|
+
function mergeNested(existing, next) {
|
|
62067
|
+
if (next === void 0) return existing;
|
|
62068
|
+
if (Array.isArray(existing) && Array.isArray(next)) return Array.from({ length: Math.max(existing.length, next.length) }, (_, index) => mergeNested(existing[index], next[index]));
|
|
62069
|
+
if (isPlainObject$1(existing) && isPlainObject$1(next)) {
|
|
62070
|
+
const merged = Object.assign(createJsonObject(), existing);
|
|
62071
|
+
mergeValue(merged, next);
|
|
62072
|
+
return merged;
|
|
62073
|
+
}
|
|
62074
|
+
return next;
|
|
62075
|
+
}
|
|
62076
|
+
function isSupportedSegment(segment) {
|
|
62077
|
+
return segment !== "" && segment !== "*" && segment !== "__proto__" && segment !== "prototype" && segment !== "constructor" && !/^\d+$/.test(segment);
|
|
62078
|
+
}
|
|
62079
|
+
function isPlainObject$1(value) {
|
|
62080
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
62081
|
+
}
|
|
62082
|
+
function createJsonObject() {
|
|
62083
|
+
return Object.create(null);
|
|
62084
|
+
}
|
|
62085
|
+
function cloneJsonValue(value) {
|
|
62086
|
+
if (Array.isArray(value)) return value.map(cloneJsonValue);
|
|
62087
|
+
if (isPlainObject$1(value)) {
|
|
62088
|
+
const result = createJsonObject();
|
|
62089
|
+
for (const [key, nested] of Object.entries(value)) if (isSupportedSegment(key)) result[key] = cloneJsonValue(nested);
|
|
62090
|
+
return result;
|
|
62091
|
+
}
|
|
62092
|
+
return value;
|
|
62093
|
+
}
|
|
62094
|
+
function throwInvalid(message) {
|
|
62095
|
+
throw new CapletsError("REQUEST_INVALID", message);
|
|
62096
|
+
}
|
|
62097
|
+
//#endregion
|
|
62098
|
+
//#region src/generated-tool-input-schema.mjs
|
|
61332
62099
|
const operations = [
|
|
61333
62100
|
"get_caplet",
|
|
61334
62101
|
"check_backend",
|
|
@@ -61338,27 +62105,36 @@ const operations = [
|
|
|
61338
62105
|
"get_tool",
|
|
61339
62106
|
"call_tool"
|
|
61340
62107
|
];
|
|
61341
|
-
const
|
|
61342
|
-
operation:
|
|
62108
|
+
const generatedToolInputDescriptions = {
|
|
62109
|
+
operation: [
|
|
61343
62110
|
"Caplets wrapper operation to perform for this configured Caplet backend.",
|
|
61344
|
-
"Use get_caplet to read the full Caplet card, check_backend to check any backend, check_mcp_server to check an MCP backend, list_tools or search_tools to discover downstream tools, get_tool to read a downstream input schema, and call_tool to run one downstream tool or
|
|
62111
|
+
"Use get_caplet to read the full Caplet card, check_backend to check any backend, check_mcp_server to check an MCP backend, list_tools or search_tools to discover downstream tools, get_tool to read a downstream input schema, and call_tool to run one downstream tool, operation, action, or CLI command.",
|
|
61345
62112
|
"For call_tool, pass downstream inputs only inside the top-level \"arguments\" object."
|
|
61346
|
-
].join(" ")
|
|
61347
|
-
query:
|
|
61348
|
-
limit:
|
|
61349
|
-
tool:
|
|
61350
|
-
arguments:
|
|
62113
|
+
].join(" "),
|
|
62114
|
+
query: "Required only for search_tools. Example: {\"operation\":\"search_tools\",\"query\":\"web search\",\"limit\":5}. Do not use query for call_tool; put downstream query values under arguments.query.",
|
|
62115
|
+
limit: "Optional only for search_tools; defaults to the configured search limit. For downstream result limits, use call_tool.arguments with the downstream schema field name.",
|
|
62116
|
+
tool: "Exact downstream tool name for get_tool or call_tool. Example: {\"operation\":\"get_tool\",\"tool\":\"web_search_exa\"} before calling it.",
|
|
62117
|
+
arguments: "Required JSON object only for call_tool. Put every downstream tool input inside this object. Example: {\"operation\":\"call_tool\",\"tool\":\"web_search_exa\",\"arguments\":{\"query\":\"latest MCP docs\",\"numResults\":3}}. Do not send downstream inputs as top-level query, limit, url, path, or other fields.",
|
|
62118
|
+
fields: "Optional for call_tool after get_tool shows outputSchema on a non-GraphQL tool. Example: fields: [\"path.to.field\"]."
|
|
62119
|
+
};
|
|
62120
|
+
const generatedToolInputSchema = object$1({
|
|
62121
|
+
operation: _enum(operations).describe(generatedToolInputDescriptions.operation),
|
|
62122
|
+
query: string().optional().describe(generatedToolInputDescriptions.query),
|
|
62123
|
+
limit: number$1().int().positive().optional().describe(generatedToolInputDescriptions.limit),
|
|
62124
|
+
tool: string().optional().describe(generatedToolInputDescriptions.tool),
|
|
62125
|
+
arguments: record(string(), unknown()).optional().describe(generatedToolInputDescriptions.arguments),
|
|
62126
|
+
fields: array(string().min(1)).min(1).optional().describe(generatedToolInputDescriptions.fields)
|
|
61351
62127
|
}).strict();
|
|
61352
|
-
async function handleServerTool(server, request, registry, downstream, openapi, graphql, http) {
|
|
62128
|
+
async function handleServerTool(server, request, registry, downstream, openapi, graphql, http, cli) {
|
|
61353
62129
|
const parsed = validateOperationRequest(request, registry.config.options.maxSearchLimit);
|
|
61354
62130
|
switch (parsed.operation) {
|
|
61355
62131
|
case "get_caplet": return jsonResult(registry.detail(server));
|
|
61356
|
-
case "check_backend": return jsonResult(await backendFor(server, downstream, openapi, graphql, http).check(server));
|
|
62132
|
+
case "check_backend": return jsonResult(await backendFor(server, downstream, openapi, graphql, http, cli).check(server));
|
|
61357
62133
|
case "check_mcp_server":
|
|
61358
62134
|
if (server.backend !== "mcp") throw new CapletsError("REQUEST_INVALID", "check_mcp_server is only valid for MCP-backed Caplets; use check_backend");
|
|
61359
62135
|
return jsonResult(await downstream.checkServer(server));
|
|
61360
62136
|
case "list_tools": {
|
|
61361
|
-
const backend = backendFor(server, downstream, openapi, graphql, http);
|
|
62137
|
+
const backend = backendFor(server, downstream, openapi, graphql, http, cli);
|
|
61362
62138
|
const tools = await backend.listTools(server);
|
|
61363
62139
|
return jsonResult({
|
|
61364
62140
|
server: server.server,
|
|
@@ -61366,7 +62142,7 @@ async function handleServerTool(server, request, registry, downstream, openapi,
|
|
|
61366
62142
|
});
|
|
61367
62143
|
}
|
|
61368
62144
|
case "search_tools": {
|
|
61369
|
-
const backend = backendFor(server, downstream, openapi, graphql, http);
|
|
62145
|
+
const backend = backendFor(server, downstream, openapi, graphql, http, cli);
|
|
61370
62146
|
const tools = await backend.listTools(server);
|
|
61371
62147
|
const limit = parsed.limit ?? registry.config.options.defaultSearchLimit;
|
|
61372
62148
|
return jsonResult({
|
|
@@ -61376,13 +62152,21 @@ async function handleServerTool(server, request, registry, downstream, openapi,
|
|
|
61376
62152
|
});
|
|
61377
62153
|
}
|
|
61378
62154
|
case "get_tool": {
|
|
61379
|
-
const tool = await backendFor(server, downstream, openapi, graphql, http).getTool(server, parsed.tool);
|
|
62155
|
+
const tool = await backendFor(server, downstream, openapi, graphql, http, cli).getTool(server, parsed.tool);
|
|
61380
62156
|
return jsonResult({
|
|
61381
62157
|
server: server.server,
|
|
61382
62158
|
tool
|
|
61383
62159
|
});
|
|
61384
62160
|
}
|
|
61385
|
-
case "call_tool":
|
|
62161
|
+
case "call_tool": {
|
|
62162
|
+
const backend = backendFor(server, downstream, openapi, graphql, http, cli);
|
|
62163
|
+
if (parsed.fields === void 0) return backend.callTool(server, parsed.tool, parsed.arguments);
|
|
62164
|
+
if (server.backend === "graphql") throw new CapletsError("REQUEST_INVALID", "call_tool.fields is not supported for GraphQL-backed Caplets; select fields in the GraphQL operation document instead");
|
|
62165
|
+
const tool = await backend.getTool(server, parsed.tool);
|
|
62166
|
+
if (!tool.outputSchema) throw new CapletsError("REQUEST_INVALID", "Field selection requires an output schema");
|
|
62167
|
+
validateFieldSelection(tool.outputSchema, parsed.fields);
|
|
62168
|
+
return projectCallToolResult(await backend.callTool(server, parsed.tool, parsed.arguments), tool.outputSchema, parsed.fields);
|
|
62169
|
+
}
|
|
61386
62170
|
}
|
|
61387
62171
|
}
|
|
61388
62172
|
function validateOperationRequest(request, maxSearchLimit) {
|
|
@@ -61423,13 +62207,22 @@ function validateOperationRequest(request, maxSearchLimit) {
|
|
|
61423
62207
|
tool: value.tool
|
|
61424
62208
|
};
|
|
61425
62209
|
case "call_tool":
|
|
61426
|
-
allowed([
|
|
62210
|
+
allowed([
|
|
62211
|
+
"tool",
|
|
62212
|
+
"arguments",
|
|
62213
|
+
"fields"
|
|
62214
|
+
]);
|
|
61427
62215
|
if (!value.tool) throw new CapletsError("REQUEST_INVALID", "call_tool requires tool");
|
|
61428
62216
|
if (!isPlainObject(value.arguments)) throw new CapletsError("REQUEST_INVALID", "call_tool.arguments must be a JSON object");
|
|
61429
|
-
return {
|
|
62217
|
+
return value.fields === void 0 ? {
|
|
61430
62218
|
operation: "call_tool",
|
|
61431
62219
|
tool: value.tool,
|
|
61432
62220
|
arguments: value.arguments
|
|
62221
|
+
} : {
|
|
62222
|
+
operation: "call_tool",
|
|
62223
|
+
tool: value.tool,
|
|
62224
|
+
arguments: value.arguments,
|
|
62225
|
+
fields: value.fields
|
|
61433
62226
|
};
|
|
61434
62227
|
}
|
|
61435
62228
|
}
|
|
@@ -61442,10 +62235,24 @@ function jsonResult(value) {
|
|
|
61442
62235
|
structuredContent: { result: value }
|
|
61443
62236
|
};
|
|
61444
62237
|
}
|
|
62238
|
+
function projectCallToolResult(result, outputSchema, fields) {
|
|
62239
|
+
if (result.isError === true) return result;
|
|
62240
|
+
const structuredContent = result.structuredContent;
|
|
62241
|
+
if (!isPlainObject(structuredContent)) throw new CapletsError("DOWNSTREAM_PROTOCOL_ERROR", "Field selection requires the downstream tool to return object structuredContent");
|
|
62242
|
+
const projected = projectStructuredContent(structuredContent, outputSchema, fields);
|
|
62243
|
+
return {
|
|
62244
|
+
...result,
|
|
62245
|
+
content: [{
|
|
62246
|
+
type: "text",
|
|
62247
|
+
text: JSON.stringify(projected, null, 2)
|
|
62248
|
+
}],
|
|
62249
|
+
structuredContent: projected
|
|
62250
|
+
};
|
|
62251
|
+
}
|
|
61445
62252
|
function isPlainObject(value) {
|
|
61446
62253
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
61447
62254
|
}
|
|
61448
|
-
function backendFor(server, downstream, openapi, graphql, http) {
|
|
62255
|
+
function backendFor(server, downstream, openapi, graphql, http, cli) {
|
|
61449
62256
|
if (server.backend === "mcp") return {
|
|
61450
62257
|
check: (...args) => downstream.checkServer(...args),
|
|
61451
62258
|
listTools: (...args) => downstream.listTools(...args),
|
|
@@ -61476,6 +62283,17 @@ function backendFor(server, downstream, openapi, graphql, http) {
|
|
|
61476
62283
|
search: (...args) => http.search(...args)
|
|
61477
62284
|
};
|
|
61478
62285
|
}
|
|
62286
|
+
if (server.backend === "cli") {
|
|
62287
|
+
if (!cli) throw new CapletsError("INTERNAL_ERROR", "CLI tools manager is not configured");
|
|
62288
|
+
return {
|
|
62289
|
+
check: (...args) => cli.checkTools(...args),
|
|
62290
|
+
listTools: (...args) => cli.listTools(...args),
|
|
62291
|
+
getTool: (...args) => cli.getTool(...args),
|
|
62292
|
+
callTool: (...args) => cli.callTool(...args),
|
|
62293
|
+
compact: (...args) => cli.compact(...args),
|
|
62294
|
+
search: (...args) => cli.search(...args)
|
|
62295
|
+
};
|
|
62296
|
+
}
|
|
61479
62297
|
if (!openapi) throw new CapletsError("INTERNAL_ERROR", "OpenAPI manager is not configured");
|
|
61480
62298
|
return {
|
|
61481
62299
|
check: (...args) => openapi.checkEndpoint(...args),
|
|
@@ -61495,6 +62313,7 @@ var CapletsRuntime = class {
|
|
|
61495
62313
|
openapi;
|
|
61496
62314
|
graphql;
|
|
61497
62315
|
http;
|
|
62316
|
+
cli;
|
|
61498
62317
|
tools = /* @__PURE__ */ new Map();
|
|
61499
62318
|
paths;
|
|
61500
62319
|
watchDebounceMs;
|
|
@@ -61516,6 +62335,7 @@ var CapletsRuntime = class {
|
|
|
61516
62335
|
this.openapi = new OpenApiManager(this.registry, selectAuthOptions(options.authDir));
|
|
61517
62336
|
this.graphql = new GraphQLManager(this.registry, selectAuthOptions(options.authDir));
|
|
61518
62337
|
this.http = new HttpActionManager(this.registry, selectAuthOptions(options.authDir));
|
|
62338
|
+
this.cli = new CliToolsManager(this.registry);
|
|
61519
62339
|
this.server = options.server ?? new McpServer({
|
|
61520
62340
|
name: "caplets",
|
|
61521
62341
|
version
|
|
@@ -61592,6 +62412,7 @@ var CapletsRuntime = class {
|
|
|
61592
62412
|
this.openapi.updateRegistry(nextRegistry);
|
|
61593
62413
|
this.graphql.updateRegistry(nextRegistry);
|
|
61594
62414
|
this.http.updateRegistry(nextRegistry);
|
|
62415
|
+
this.cli.updateRegistry(nextRegistry);
|
|
61595
62416
|
let invalidated = true;
|
|
61596
62417
|
try {
|
|
61597
62418
|
await this.invalidateChangedBackends(previousConfig, nextConfig);
|
|
@@ -61650,7 +62471,7 @@ var CapletsRuntime = class {
|
|
|
61650
62471
|
}
|
|
61651
62472
|
async handleTool(serverId, request) {
|
|
61652
62473
|
try {
|
|
61653
|
-
return await handleServerTool(this.registry.require(serverId), request, this.registry, this.downstream, this.openapi, this.graphql, this.http);
|
|
62474
|
+
return await handleServerTool(this.registry.require(serverId), request, this.registry, this.downstream, this.openapi, this.graphql, this.http, this.cli);
|
|
61654
62475
|
} catch (error) {
|
|
61655
62476
|
return errorResult(error);
|
|
61656
62477
|
}
|
|
@@ -61667,6 +62488,7 @@ var CapletsRuntime = class {
|
|
|
61667
62488
|
if (before?.backend === "openapi" || after?.backend === "openapi" || !after) this.openapi.invalidate(serverId);
|
|
61668
62489
|
if (before?.backend === "graphql" || after?.backend === "graphql" || !after) this.graphql.invalidate(serverId);
|
|
61669
62490
|
if (before?.backend === "http" || after?.backend === "http" || !after) this.http.invalidate(serverId);
|
|
62491
|
+
if (before?.backend === "cli" || after?.backend === "cli" || !after) this.cli.invalidate(serverId);
|
|
61670
62492
|
}
|
|
61671
62493
|
}
|
|
61672
62494
|
resetWatchers() {
|
|
@@ -61759,14 +62581,15 @@ function allCaplets(config) {
|
|
|
61759
62581
|
...Object.values(config.mcpServers),
|
|
61760
62582
|
...Object.values(config.openapiEndpoints),
|
|
61761
62583
|
...Object.values(config.graphqlEndpoints),
|
|
61762
|
-
...Object.values(config.httpApis)
|
|
62584
|
+
...Object.values(config.httpApis),
|
|
62585
|
+
...Object.values(config.cliTools)
|
|
61763
62586
|
];
|
|
61764
62587
|
}
|
|
61765
62588
|
function nextEnabledServers(config) {
|
|
61766
62589
|
return allCaplets(config).filter((server) => !server.disabled);
|
|
61767
62590
|
}
|
|
61768
62591
|
function capletById(config, serverId) {
|
|
61769
|
-
return config.mcpServers[serverId] ?? config.openapiEndpoints[serverId] ?? config.graphqlEndpoints[serverId] ?? config.httpApis[serverId];
|
|
62592
|
+
return config.mcpServers[serverId] ?? config.openapiEndpoints[serverId] ?? config.graphqlEndpoints[serverId] ?? config.httpApis[serverId] ?? config.cliTools[serverId];
|
|
61770
62593
|
}
|
|
61771
62594
|
function serializeCaplet(caplet) {
|
|
61772
62595
|
return JSON.stringify(caplet ?? null);
|