toolcraft 0.0.38 → 0.0.40
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 +41 -4
- package/dist/cli.js +40 -9
- package/dist/http-errors.d.ts +57 -0
- package/dist/http-errors.js +72 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +15 -0
- package/dist/mcp-proxy.js +16 -1
- package/dist/mcp.js +224 -12
- package/node_modules/@poe-code/frontmatter/README.md +35 -0
- package/node_modules/@poe-code/frontmatter/dist/fences.d.ts +8 -0
- package/node_modules/@poe-code/frontmatter/dist/fences.js +72 -0
- package/node_modules/@poe-code/frontmatter/dist/index.d.ts +3 -0
- package/node_modules/@poe-code/frontmatter/dist/index.js +3 -0
- package/node_modules/@poe-code/frontmatter/dist/parse.d.ts +20 -0
- package/node_modules/@poe-code/frontmatter/dist/parse.js +137 -0
- package/node_modules/@poe-code/frontmatter/dist/stringify.d.ts +1 -0
- package/node_modules/@poe-code/frontmatter/dist/stringify.js +35 -0
- package/node_modules/@poe-code/frontmatter/package.json +25 -0
- package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.d.ts +1 -0
- package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +25 -1
- package/node_modules/tiny-mcp-client/README.md +7 -0
- package/node_modules/tiny-mcp-client/dist/internal.d.ts +2 -0
- package/node_modules/tiny-mcp-client/dist/internal.js +3 -0
- package/node_modules/tiny-mcp-client/dist/mcp-tool-types.compile-check.js +10 -0
- package/node_modules/tiny-mcp-client/src/http-oauth.test.ts +2 -8
- package/node_modules/tiny-mcp-client/src/internal.ts +6 -0
- package/node_modules/tiny-mcp-client/src/mcp-tool-types.compile-check.ts +10 -0
- package/node_modules/toolcraft-design/README.md +12 -0
- package/node_modules/toolcraft-design/dist/prompts/index.d.ts +39 -14
- package/node_modules/toolcraft-design/dist/prompts/index.js +10 -6
- package/node_modules/toolcraft-design/dist/prompts/interactive/cancel-symbol.d.ts +2 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/cancel-symbol.js +4 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/confirm.d.ts +9 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/confirm.js +47 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/core.d.ts +55 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/core.js +274 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/glyphs.d.ts +20 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/glyphs.js +53 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/index.d.ts +6 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/index.js +6 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/keys.d.ts +2 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/keys.js +28 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/multiselect.d.ts +13 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/multiselect.js +131 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/pagination.d.ts +10 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/pagination.js +52 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/password.d.ts +10 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/password.js +55 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/select.d.ts +18 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/select.js +89 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/test-helpers.d.ts +21 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/test-helpers.js +32 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/text.d.ts +12 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/text.js +60 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/wrap.d.ts +4 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/wrap.js +18 -0
- package/node_modules/toolcraft-design/dist/prompts/primitives/cancel.d.ts +1 -1
- package/node_modules/toolcraft-design/dist/prompts/primitives/cancel.js +1 -1
- package/node_modules/toolcraft-design/dist/terminal-markdown/parser/frontmatter.js +20 -453
- package/node_modules/toolcraft-design/package.json +6 -3
- package/package.json +9 -6
package/README.md
CHANGED
|
@@ -61,6 +61,9 @@ export const greet = defineCommand({
|
|
|
61
61
|
name: S.String({ description: "Who to greet" }),
|
|
62
62
|
loud: S.Optional(S.Boolean({ default: false }))
|
|
63
63
|
}),
|
|
64
|
+
result: S.Object({
|
|
65
|
+
message: S.String()
|
|
66
|
+
}),
|
|
64
67
|
handler: async ({ params }) => {
|
|
65
68
|
const message = `Hello, ${params.name}`;
|
|
66
69
|
return { message: params.loud ? message.toUpperCase() : message };
|
|
@@ -180,6 +183,10 @@ The same `root` flows into all three. No duplication.
|
|
|
180
183
|
|
|
181
184
|
**CLI help**: group help lists visible child commands with their parameter tokens inline. Required options appear as `--name <type>`, optional options and defaults appear in brackets like `[--limit <number>]`, and positional parameters render as positional tokens like `<name>` or `[name]` depending on whether they are required. Command-specific `--help` still shows the detailed parameter table.
|
|
182
185
|
|
|
186
|
+
**Examples**: commands may declare `examples: Array<{ title, params }>` alongside their params.
|
|
187
|
+
CLI help renders an `Examples` section for the command, and MCP tool descriptions include the
|
|
188
|
+
same examples so agents see concrete argument shapes.
|
|
189
|
+
|
|
183
190
|
## Secrets
|
|
184
191
|
|
|
185
192
|
Declare env-backed secrets on a command or group. Toolcraft reads `process.env` at command-run time and passes the values to the handler:
|
|
@@ -250,11 +257,16 @@ Services are merged into the handler context alongside the built-ins (`fetch`, `
|
|
|
250
257
|
|
|
251
258
|
## Output rendering
|
|
252
259
|
|
|
253
|
-
Handlers return raw values. Add
|
|
260
|
+
Handlers return raw values. Add `result:` when a command returns structured data and may be exposed over MCP; Toolcraft turns that schema into MCP `outputSchema`, validates the returned object, returns it as `structuredContent`, and keeps a JSON text backstop for older MCP clients.
|
|
261
|
+
|
|
262
|
+
Add per-format renderers when you want richer CLI output:
|
|
254
263
|
|
|
255
264
|
```ts
|
|
256
265
|
defineCommand({
|
|
257
266
|
// ...
|
|
267
|
+
result: S.Object({
|
|
268
|
+
rows: S.Array(S.Object({ id: S.Number() }))
|
|
269
|
+
}),
|
|
258
270
|
handler: async () => ({ rows: [{ id: 1 }, { id: 2 }] }),
|
|
259
271
|
render: {
|
|
260
272
|
rich: (result, { renderTable }) =>
|
|
@@ -265,7 +277,27 @@ defineCommand({
|
|
|
265
277
|
});
|
|
266
278
|
```
|
|
267
279
|
|
|
268
|
-
|
|
280
|
+
## HTTP errors
|
|
281
|
+
|
|
282
|
+
Toolcraft exports a fixed HTTP error hierarchy for transports and generated API clients:
|
|
283
|
+
|
|
284
|
+
- `HttpError`
|
|
285
|
+
- `ClientError`
|
|
286
|
+
- `BadRequestError`
|
|
287
|
+
- `AuthenticationError`
|
|
288
|
+
- `PermissionDeniedError`
|
|
289
|
+
- `NotFoundError`
|
|
290
|
+
- `ConflictError`
|
|
291
|
+
- `UnprocessableEntityError`
|
|
292
|
+
- `RateLimitError`
|
|
293
|
+
- `ServerError`
|
|
294
|
+
- `InternalServerError`
|
|
295
|
+
- `ServiceUnavailableError`
|
|
296
|
+
|
|
297
|
+
Each error carries `status`, optional `code`, optional `requestId`, and serializable `request` and
|
|
298
|
+
`response` context.
|
|
299
|
+
|
|
300
|
+
CLI picks `rich` by default, `--json` switches to `json`. SDK calls return the raw handler value. MCP calls with `result:` return the handler value as `structuredContent` using the configured MCP casing; MCP calls without `result:` keep content-block behavior.
|
|
269
301
|
|
|
270
302
|
## MCP proxy: adopt an existing MCP server
|
|
271
303
|
|
|
@@ -428,6 +460,7 @@ If you have an existing MCP server you want to keep running, use the MCP proxy:
|
|
|
428
460
|
- `aliases?: string[]`
|
|
429
461
|
- `positional?: string[]` — parameter names mapped from CLI argv order.
|
|
430
462
|
- `params: S.Object(...)` — input schema from `toolcraft-schema`.
|
|
463
|
+
- `result?: S.Object(...)` — structured result schema for MCP `outputSchema`; must be a root object.
|
|
431
464
|
- `secrets?: Record<string, { env: string; description?: string; optional?: boolean }>`
|
|
432
465
|
- `scope?: Array<"cli" | "mcp" | "sdk">` — defaults to `["cli", "sdk"]`.
|
|
433
466
|
- `confirm?: boolean` — deprecated CLI-only TTY confirmation; use `humanInLoop` instead. Cannot be combined with `humanInLoop`.
|
|
@@ -436,6 +469,10 @@ If you have an existing MCP server you want to keep running, use the MCP proxy:
|
|
|
436
469
|
- `handler: (ctx) => Promise<unknown>`
|
|
437
470
|
- `render?: { rich?, markdown?, json? }` — per-format output renderers.
|
|
438
471
|
|
|
472
|
+
Parameter schemas may set `short: "x"` for a short CLI flag and `cliAliases: ["alias"]` for
|
|
473
|
+
additional long CLI flags. For example, `rawResponse` normally maps to `--raw-response`; adding
|
|
474
|
+
`cliAliases: ["raw"]` also accepts `--raw` while the SDK parameter remains `rawResponse`.
|
|
475
|
+
|
|
439
476
|
### `defineGroup(config)`
|
|
440
477
|
|
|
441
478
|
- `name: string`
|
|
@@ -473,7 +510,7 @@ If you have an existing MCP server you want to keep running, use the MCP proxy:
|
|
|
473
510
|
- `projectRoot?: string` — root used for MCP proxy cache files (`.toolcraft/mcp/*.json`).
|
|
474
511
|
- `tools?: string[]` — allowlist of MCP tool names or group prefixes. Tool names are `__`-joined snake_case path segments (`root__bot__create`); a prefix like `root__bot` includes every descendant tool.
|
|
475
512
|
- `omitRootToolNamePrefix?: boolean` — defaults to `false`. Set to `true` to omit the root group name from single-root MCP tool names (`bot__create`).
|
|
476
|
-
- `casing?: "snake" | "camel"` — affects MCP
|
|
513
|
+
- `casing?: "snake" | "camel"` — affects MCP input-schema property names, output-schema property names, and structured result keys. Tool names always stay `__`-joined snake_case.
|
|
477
514
|
|
|
478
515
|
### `HumanInLoopRuntimeOptions`
|
|
479
516
|
|
|
@@ -500,7 +537,7 @@ type HumanInLoopRuntimeOptions = {
|
|
|
500
537
|
|
|
501
538
|
- `defineCommand`, `defineGroup`
|
|
502
539
|
- `S`, `toJsonSchema`, type helpers — re-exported from `toolcraft-schema`
|
|
503
|
-
- `UserError`, `ApprovalDeclinedError`
|
|
540
|
+
- `UserError`, `ApprovalDeclinedError`, `HttpError` and the HTTP error subclasses.
|
|
504
541
|
- Type exports: `Command`, `Group`, `Scope`, `HandlerContext`, `HumanInLoopConfig`, `HumanInLoopPending`, `HumanInLoopRuntimeOptions`, schema types from `toolcraft-schema`.
|
|
505
542
|
|
|
506
543
|
Subpath imports:
|
package/dist/cli.js
CHANGED
|
@@ -175,6 +175,7 @@ function collectFields(schema, casing, globalLongOptionFlags, path = [], inherit
|
|
|
175
175
|
optionAttribute: toOptionAttribute([...nextPath, childSchema.discriminator], casing),
|
|
176
176
|
commanderOptionAttribute: toCommanderOptionAttribute([...nextPath, childSchema.discriminator], casing, globalLongOptionFlags),
|
|
177
177
|
optionFlag: toOptionFlag([...nextPath, childSchema.discriminator], casing),
|
|
178
|
+
longAliases: [],
|
|
178
179
|
shortFlag: undefined,
|
|
179
180
|
schema: createSyntheticEnumSchema(branchIds),
|
|
180
181
|
description: childSchema.description,
|
|
@@ -226,6 +227,7 @@ function collectFields(schema, casing, globalLongOptionFlags, path = [], inherit
|
|
|
226
227
|
optionAttribute: toOptionAttribute(controlPath, casing),
|
|
227
228
|
commanderOptionAttribute: toCommanderOptionAttribute(controlPath, casing, globalLongOptionFlags),
|
|
228
229
|
optionFlag: toOptionFlag(controlPath, casing),
|
|
230
|
+
longAliases: [],
|
|
229
231
|
shortFlag: undefined,
|
|
230
232
|
schema: createSyntheticEnumSchema(branchIds),
|
|
231
233
|
description: childSchema.description,
|
|
@@ -312,6 +314,7 @@ function collectFields(schema, casing, globalLongOptionFlags, path = [], inherit
|
|
|
312
314
|
optionAttribute: toOptionAttribute(nextPath, casing),
|
|
313
315
|
commanderOptionAttribute: toCommanderOptionAttribute(nextPath, casing, globalLongOptionFlags),
|
|
314
316
|
optionFlag: toOptionFlag(nextPath, casing),
|
|
317
|
+
longAliases: [...(childSchema.cliAliases ?? [])].map((alias) => alias.startsWith("--") ? alias : `--${alias}`),
|
|
315
318
|
shortFlag: childSchema.short,
|
|
316
319
|
schema: childSchema,
|
|
317
320
|
description: childSchema.description,
|
|
@@ -368,9 +371,9 @@ function formatOptionFlags(field, globalLongOptionFlags) {
|
|
|
368
371
|
return `-${field.shortFlag}`;
|
|
369
372
|
}
|
|
370
373
|
if (field.shortFlag === undefined) {
|
|
371
|
-
return field.optionFlag;
|
|
374
|
+
return [field.optionFlag, ...field.longAliases].join(", ");
|
|
372
375
|
}
|
|
373
|
-
return `-${field.shortFlag},
|
|
376
|
+
return [`-${field.shortFlag}`, field.optionFlag, ...field.longAliases].join(", ");
|
|
374
377
|
}
|
|
375
378
|
function formatPositionalToken(field) {
|
|
376
379
|
const optionalPositional = field.optional || field.hasDefault;
|
|
@@ -639,22 +642,30 @@ function getGlobalLongOptionFlags(presetsEnabled, versionEnabled, controls) {
|
|
|
639
642
|
}
|
|
640
643
|
return new Set(flags);
|
|
641
644
|
}
|
|
642
|
-
function validateUniqueOptionFlags(fields) {
|
|
645
|
+
function validateUniqueOptionFlags(fields, globalLongOptionFlags) {
|
|
643
646
|
const fieldsByFlag = new Map();
|
|
644
647
|
for (const field of fields) {
|
|
645
648
|
if (field.positionalIndex !== undefined) {
|
|
646
649
|
continue;
|
|
647
650
|
}
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
+
for (const flag of [field.optionFlag, ...field.longAliases]) {
|
|
652
|
+
if (globalLongOptionFlags.has(flag)) {
|
|
653
|
+
if (flag === field.optionFlag && field.shortFlag !== undefined) {
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
throw new UserError(`Parameter "${field.displayPath}" uses reserved CLI flag "${flag}". Add a short flag or rename the parameter.`);
|
|
657
|
+
}
|
|
658
|
+
const existing = fieldsByFlag.get(flag);
|
|
659
|
+
if (existing !== undefined) {
|
|
660
|
+
throw new UserError(`Parameters "${existing.displayPath}" and "${field.displayPath}" use conflicting CLI flag "${flag}".`);
|
|
661
|
+
}
|
|
662
|
+
fieldsByFlag.set(flag, field);
|
|
651
663
|
}
|
|
652
|
-
fieldsByFlag.set(field.optionFlag, field);
|
|
653
664
|
}
|
|
654
665
|
}
|
|
655
666
|
function createCommanderOption(flags, description, field) {
|
|
656
667
|
const option = new Option(flags, description);
|
|
657
|
-
if (field.commanderOptionAttribute !== field.optionAttribute) {
|
|
668
|
+
if (field.commanderOptionAttribute !== field.optionAttribute || field.longAliases.length > 0) {
|
|
658
669
|
option.attributeName = () => field.commanderOptionAttribute;
|
|
659
670
|
}
|
|
660
671
|
return option;
|
|
@@ -958,6 +969,23 @@ function formatSecretDescription(secret) {
|
|
|
958
969
|
}
|
|
959
970
|
return secret.optional === true ? "Optional secret" : "Required secret";
|
|
960
971
|
}
|
|
972
|
+
function formatExampleValue(value) {
|
|
973
|
+
if (typeof value === "string" && value.length > 0 && !value.includes(" ")) {
|
|
974
|
+
return value;
|
|
975
|
+
}
|
|
976
|
+
return JSON.stringify(value);
|
|
977
|
+
}
|
|
978
|
+
function formatExampleCommand(breadcrumb, rootUsageName, params) {
|
|
979
|
+
const commandPath = buildUsageLine(breadcrumb, rootUsageName, "");
|
|
980
|
+
const flags = Object.entries(params).map(([key, value]) => {
|
|
981
|
+
const flag = `--${key}`;
|
|
982
|
+
return typeof value === "boolean" ? (value ? flag : `--no-${key}`) : `${flag} ${formatExampleValue(value)}`;
|
|
983
|
+
});
|
|
984
|
+
return [commandPath, ...flags].filter((token) => token.length > 0).join(" ");
|
|
985
|
+
}
|
|
986
|
+
function formatExampleRows(examples, breadcrumb, rootUsageName) {
|
|
987
|
+
return examples.map((example) => `${example.title}\n ${formatExampleCommand(breadcrumb, rootUsageName, example.params)}`);
|
|
988
|
+
}
|
|
961
989
|
function wrapOptionalCommandParameterToken(token, optional) {
|
|
962
990
|
return optional ? `[${token}]` : token;
|
|
963
991
|
}
|
|
@@ -1098,6 +1126,9 @@ function renderLeafHelp(command, breadcrumb, casing, globalOptions, rootUsageNam
|
|
|
1098
1126
|
if (secretRows.length > 0) {
|
|
1099
1127
|
sections.push(`${text.sectionHeader("Secrets (environment)")}\n${formatHelpOptionList(secretRows)}`);
|
|
1100
1128
|
}
|
|
1129
|
+
if (command.examples.length > 0) {
|
|
1130
|
+
sections.push(`${text.sectionHeader("Examples")}\n${formatExampleRows(command.examples, breadcrumb, rootUsageName).join("\n")}`);
|
|
1131
|
+
}
|
|
1101
1132
|
const positionalFields = fields.filter((f) => f.positionalIndex !== undefined);
|
|
1102
1133
|
const usageSuffix = positionalFields.length > 0
|
|
1103
1134
|
? `[OPTIONS] ${positionalFields.map(formatPositionalToken).join(" ")}`
|
|
@@ -1162,7 +1193,7 @@ function createNodeCommand(node, casing, globalLongOptionFlags, execute, presets
|
|
|
1162
1193
|
const command = new CommanderCommand(node.name);
|
|
1163
1194
|
const collected = collectFields(node.params, casing, globalLongOptionFlags);
|
|
1164
1195
|
const fields = assignPositionals(collected.fields, node.positional);
|
|
1165
|
-
validateUniqueOptionFlags(fields);
|
|
1196
|
+
validateUniqueOptionFlags(fields, globalLongOptionFlags);
|
|
1166
1197
|
if (node.description !== undefined) {
|
|
1167
1198
|
command.description(node.description);
|
|
1168
1199
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export interface HttpErrorRequest {
|
|
2
|
+
method: string;
|
|
3
|
+
url: string;
|
|
4
|
+
headers: Record<string, string>;
|
|
5
|
+
body?: unknown;
|
|
6
|
+
}
|
|
7
|
+
export interface HttpErrorResponse {
|
|
8
|
+
status: number;
|
|
9
|
+
statusText: string;
|
|
10
|
+
headers: Record<string, string>;
|
|
11
|
+
body: unknown;
|
|
12
|
+
}
|
|
13
|
+
export declare class HttpError extends Error {
|
|
14
|
+
readonly status: number;
|
|
15
|
+
readonly statusText: string;
|
|
16
|
+
readonly code?: string;
|
|
17
|
+
readonly requestId?: string;
|
|
18
|
+
readonly request: HttpErrorRequest;
|
|
19
|
+
readonly response: HttpErrorResponse;
|
|
20
|
+
get body(): unknown;
|
|
21
|
+
constructor(args: {
|
|
22
|
+
request: HttpErrorRequest;
|
|
23
|
+
response: HttpErrorResponse;
|
|
24
|
+
code?: string;
|
|
25
|
+
requestId?: string;
|
|
26
|
+
message?: string;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export declare class ClientError extends HttpError {
|
|
30
|
+
}
|
|
31
|
+
export declare class BadRequestError extends ClientError {
|
|
32
|
+
}
|
|
33
|
+
export declare class AuthenticationError extends ClientError {
|
|
34
|
+
}
|
|
35
|
+
export declare class PermissionDeniedError extends ClientError {
|
|
36
|
+
}
|
|
37
|
+
export declare class NotFoundError extends ClientError {
|
|
38
|
+
}
|
|
39
|
+
export declare class ConflictError extends ClientError {
|
|
40
|
+
}
|
|
41
|
+
export declare class UnprocessableEntityError extends ClientError {
|
|
42
|
+
}
|
|
43
|
+
export declare class RateLimitError extends ClientError {
|
|
44
|
+
}
|
|
45
|
+
export declare class ServerError extends HttpError {
|
|
46
|
+
}
|
|
47
|
+
export declare class InternalServerError extends ServerError {
|
|
48
|
+
}
|
|
49
|
+
export declare class ServiceUnavailableError extends ServerError {
|
|
50
|
+
}
|
|
51
|
+
export declare function createHttpError(args: {
|
|
52
|
+
request: HttpErrorRequest;
|
|
53
|
+
response: HttpErrorResponse;
|
|
54
|
+
code?: string;
|
|
55
|
+
requestId?: string;
|
|
56
|
+
message?: string;
|
|
57
|
+
}): HttpError;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export class HttpError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
statusText;
|
|
4
|
+
code;
|
|
5
|
+
requestId;
|
|
6
|
+
request;
|
|
7
|
+
response;
|
|
8
|
+
get body() {
|
|
9
|
+
return this.response.body;
|
|
10
|
+
}
|
|
11
|
+
constructor(args) {
|
|
12
|
+
super(args.message ??
|
|
13
|
+
`${args.request.method} ${args.request.url} → ${args.response.status} ${args.response.statusText}`);
|
|
14
|
+
this.name = new.target.name;
|
|
15
|
+
this.status = args.response.status;
|
|
16
|
+
this.statusText = args.response.statusText;
|
|
17
|
+
this.code = args.code;
|
|
18
|
+
this.requestId = args.requestId;
|
|
19
|
+
this.request = args.request;
|
|
20
|
+
this.response = args.response;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export class ClientError extends HttpError {
|
|
24
|
+
}
|
|
25
|
+
export class BadRequestError extends ClientError {
|
|
26
|
+
}
|
|
27
|
+
export class AuthenticationError extends ClientError {
|
|
28
|
+
}
|
|
29
|
+
export class PermissionDeniedError extends ClientError {
|
|
30
|
+
}
|
|
31
|
+
export class NotFoundError extends ClientError {
|
|
32
|
+
}
|
|
33
|
+
export class ConflictError extends ClientError {
|
|
34
|
+
}
|
|
35
|
+
export class UnprocessableEntityError extends ClientError {
|
|
36
|
+
}
|
|
37
|
+
export class RateLimitError extends ClientError {
|
|
38
|
+
}
|
|
39
|
+
export class ServerError extends HttpError {
|
|
40
|
+
}
|
|
41
|
+
export class InternalServerError extends ServerError {
|
|
42
|
+
}
|
|
43
|
+
export class ServiceUnavailableError extends ServerError {
|
|
44
|
+
}
|
|
45
|
+
export function createHttpError(args) {
|
|
46
|
+
const ErrorClass = getHttpErrorClass(args.response.status);
|
|
47
|
+
return new ErrorClass(args);
|
|
48
|
+
}
|
|
49
|
+
function getHttpErrorClass(status) {
|
|
50
|
+
switch (status) {
|
|
51
|
+
case 400:
|
|
52
|
+
return BadRequestError;
|
|
53
|
+
case 401:
|
|
54
|
+
return AuthenticationError;
|
|
55
|
+
case 403:
|
|
56
|
+
return PermissionDeniedError;
|
|
57
|
+
case 404:
|
|
58
|
+
return NotFoundError;
|
|
59
|
+
case 409:
|
|
60
|
+
return ConflictError;
|
|
61
|
+
case 422:
|
|
62
|
+
return UnprocessableEntityError;
|
|
63
|
+
case 429:
|
|
64
|
+
return RateLimitError;
|
|
65
|
+
case 500:
|
|
66
|
+
return InternalServerError;
|
|
67
|
+
case 503:
|
|
68
|
+
return ServiceUnavailableError;
|
|
69
|
+
default:
|
|
70
|
+
return status >= 400 && status < 500 ? ClientError : status >= 500 ? ServerError : HttpError;
|
|
71
|
+
}
|
|
72
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -58,6 +58,10 @@ export interface CheckResult {
|
|
|
58
58
|
ok: boolean;
|
|
59
59
|
message?: string;
|
|
60
60
|
}
|
|
61
|
+
export interface CommandExample {
|
|
62
|
+
title: string;
|
|
63
|
+
params: Record<string, unknown>;
|
|
64
|
+
}
|
|
61
65
|
export type GroupCheckContext<TServices extends object = EmptyServices> = TServices & {
|
|
62
66
|
params?: unknown;
|
|
63
67
|
secrets?: Record<string, string | undefined>;
|
|
@@ -95,9 +99,11 @@ export type HandlerContext<TParamsSchema extends ObjectSchema<any> = AnyObjectSc
|
|
|
95
99
|
export interface CommandConfig<TServices extends object, TParamsSchema extends ObjectSchema<any>, TSecrets extends SecretDeclarations | undefined, TResult> {
|
|
96
100
|
name: string;
|
|
97
101
|
description?: string;
|
|
102
|
+
examples?: CommandExample[];
|
|
98
103
|
aliases?: string[];
|
|
99
104
|
positional?: string[];
|
|
100
105
|
params: TParamsSchema;
|
|
106
|
+
result?: ObjectSchema<any>;
|
|
101
107
|
secrets?: TSecrets;
|
|
102
108
|
scope?: Scope[];
|
|
103
109
|
confirm?: boolean;
|
|
@@ -110,9 +116,11 @@ export interface Command<TServices extends object = EmptyServices, TParamsSchema
|
|
|
110
116
|
kind: "command";
|
|
111
117
|
name: string;
|
|
112
118
|
description?: string;
|
|
119
|
+
examples: CommandExample[];
|
|
113
120
|
aliases: string[];
|
|
114
121
|
positional: string[];
|
|
115
122
|
params: TParamsSchema;
|
|
123
|
+
result?: ObjectSchema<any>;
|
|
116
124
|
secrets: SecretDeclarations;
|
|
117
125
|
scope: Scope[];
|
|
118
126
|
confirm: boolean;
|
|
@@ -187,6 +195,8 @@ export declare function defineGroup<TServices extends object = EmptyServices, TN
|
|
|
187
195
|
}): Group<TServices> & TypedGroupMetadata<TServices, TName, TChildren, TOwnScope, ResolveOwnHumanInLoopMode<TOwnHumanInLoop>>;
|
|
188
196
|
export declare function getCommandSourcePath(command: Command<any, any, any, any>): string | undefined;
|
|
189
197
|
export { S, toJsonSchema } from "toolcraft-schema";
|
|
198
|
+
export { AuthenticationError, BadRequestError, ClientError, ConflictError, HttpError, InternalServerError, NotFoundError, PermissionDeniedError, RateLimitError, ServerError, ServiceUnavailableError, UnprocessableEntityError, createHttpError } from "./http-errors.js";
|
|
199
|
+
export type { HttpErrorRequest, HttpErrorResponse } from "./http-errors.js";
|
|
190
200
|
export { ApprovalDeclinedError, ToolcraftBugError, UserError };
|
|
191
201
|
export { findPackageMetadata, packageMetadata } from "./package-metadata.js";
|
|
192
202
|
export type { PackageMetadata } from "./package-metadata.js";
|
package/dist/index.js
CHANGED
|
@@ -35,6 +35,12 @@ function cloneRequires(requires) {
|
|
|
35
35
|
function cloneStringArray(values) {
|
|
36
36
|
return values === undefined ? undefined : [...values];
|
|
37
37
|
}
|
|
38
|
+
function cloneCommandExamples(examples) {
|
|
39
|
+
return (examples ?? []).map((example) => ({
|
|
40
|
+
title: example.title,
|
|
41
|
+
params: { ...example.params }
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
38
44
|
function cloneStringRecord(values) {
|
|
39
45
|
return values === undefined ? undefined : { ...values };
|
|
40
46
|
}
|
|
@@ -286,9 +292,11 @@ function createBaseCommand(config) {
|
|
|
286
292
|
kind: "command",
|
|
287
293
|
name: config.name,
|
|
288
294
|
description: config.description,
|
|
295
|
+
examples: cloneCommandExamples(config.examples),
|
|
289
296
|
aliases: [...(config.aliases ?? [])],
|
|
290
297
|
positional: [...(config.positional ?? [])],
|
|
291
298
|
params: config.params,
|
|
299
|
+
result: config.result,
|
|
292
300
|
secrets: cloneSecrets(config.secrets),
|
|
293
301
|
scope: resolveCommandScope(config.scope, undefined),
|
|
294
302
|
confirm: config.confirm ?? false,
|
|
@@ -300,6 +308,8 @@ function createBaseCommand(config) {
|
|
|
300
308
|
Object.defineProperty(command, commandConfigSymbol, {
|
|
301
309
|
value: {
|
|
302
310
|
scope: cloneScope(config.scope),
|
|
311
|
+
examples: cloneCommandExamples(config.examples),
|
|
312
|
+
result: config.result,
|
|
303
313
|
humanInLoop: config.humanInLoop,
|
|
304
314
|
secrets: cloneSecrets(config.secrets),
|
|
305
315
|
requires: cloneRequires(config.requires),
|
|
@@ -348,9 +358,11 @@ function materializeCommand(command, inherited) {
|
|
|
348
358
|
kind: "command",
|
|
349
359
|
name: command.name,
|
|
350
360
|
description: command.description,
|
|
361
|
+
examples: cloneCommandExamples(internal.examples),
|
|
351
362
|
aliases: [...command.aliases],
|
|
352
363
|
positional: [...command.positional],
|
|
353
364
|
params: command.params,
|
|
365
|
+
result: internal.result,
|
|
354
366
|
secrets: mergeSecrets(inherited.secrets, internal.secrets),
|
|
355
367
|
scope: resolveCommandScope(internal.scope, inherited.scope),
|
|
356
368
|
confirm: command.confirm,
|
|
@@ -362,6 +374,8 @@ function materializeCommand(command, inherited) {
|
|
|
362
374
|
Object.defineProperty(materialized, commandConfigSymbol, {
|
|
363
375
|
value: {
|
|
364
376
|
scope: cloneScope(internal.scope),
|
|
377
|
+
examples: cloneCommandExamples(internal.examples),
|
|
378
|
+
result: internal.result,
|
|
365
379
|
humanInLoop: internal.humanInLoop,
|
|
366
380
|
secrets: cloneSecrets(internal.secrets),
|
|
367
381
|
requires: cloneRequires(internal.requires),
|
|
@@ -453,5 +467,6 @@ export function getCommandSourcePath(command) {
|
|
|
453
467
|
return command[commandSourcePathSymbol];
|
|
454
468
|
}
|
|
455
469
|
export { S, toJsonSchema } from "toolcraft-schema";
|
|
470
|
+
export { AuthenticationError, BadRequestError, ClientError, ConflictError, HttpError, InternalServerError, NotFoundError, PermissionDeniedError, RateLimitError, ServerError, ServiceUnavailableError, UnprocessableEntityError, createHttpError } from "./http-errors.js";
|
|
456
471
|
export { ApprovalDeclinedError, ToolcraftBugError, UserError };
|
|
457
472
|
export { findPackageMetadata, packageMetadata } from "./package-metadata.js";
|
package/dist/mcp-proxy.js
CHANGED
|
@@ -68,23 +68,38 @@ function createProxyCommand(parent, tool, commandName, connection) {
|
|
|
68
68
|
if (params.kind !== "object") {
|
|
69
69
|
throw new Error(`upstream tool "${tool.name}" must define an object input schema`);
|
|
70
70
|
}
|
|
71
|
+
const result = tool.outputSchema === undefined
|
|
72
|
+
? undefined
|
|
73
|
+
: convertJsonSchema(tool.outputSchema);
|
|
74
|
+
if (result !== undefined && result.kind !== "object") {
|
|
75
|
+
throw new Error(`upstream tool "${tool.name}" must define an object output schema`);
|
|
76
|
+
}
|
|
71
77
|
return markProxyNode({
|
|
72
78
|
kind: "command",
|
|
73
79
|
name: commandName,
|
|
74
80
|
description: tool.description,
|
|
81
|
+
examples: [],
|
|
75
82
|
aliases: [],
|
|
76
83
|
positional: [],
|
|
77
84
|
params,
|
|
85
|
+
...(result === undefined ? {} : { result }),
|
|
78
86
|
secrets: cloneSecrets(parent.secrets),
|
|
79
87
|
scope: cloneScope(parent.scope) ?? ["cli", "sdk"],
|
|
80
88
|
confirm: false,
|
|
81
89
|
requires: parent.requires,
|
|
82
90
|
handler: async (ctx) => {
|
|
83
91
|
const client = await ensureConnected(connection);
|
|
84
|
-
|
|
92
|
+
const toolResult = await client.callTool({
|
|
85
93
|
name: tool.name,
|
|
86
94
|
arguments: ctx.params,
|
|
87
95
|
});
|
|
96
|
+
if (result === undefined) {
|
|
97
|
+
return toolResult;
|
|
98
|
+
}
|
|
99
|
+
if (toolResult.structuredContent === undefined) {
|
|
100
|
+
throw new Error(`upstream tool "${tool.name}" declared outputSchema but returned no structuredContent`);
|
|
101
|
+
}
|
|
102
|
+
return toolResult.structuredContent;
|
|
88
103
|
},
|
|
89
104
|
render: undefined,
|
|
90
105
|
});
|