toolcraft 0.0.39 → 0.0.41

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.
Files changed (70) hide show
  1. package/README.md +41 -4
  2. package/dist/cli.js +40 -9
  3. package/dist/http-errors.d.ts +57 -0
  4. package/dist/http-errors.js +72 -0
  5. package/dist/index.d.ts +10 -0
  6. package/dist/index.js +15 -0
  7. package/dist/mcp-proxy.js +16 -1
  8. package/dist/mcp.js +224 -12
  9. package/dist/number-schema.js +9 -2
  10. package/node_modules/@poe-code/agent-defs/dist/agents/claude-code.js +1 -1
  11. package/node_modules/@poe-code/agent-defs/dist/agents/claude-desktop.js +6 -1
  12. package/node_modules/@poe-code/agent-defs/dist/agents/cursor.js +1 -1
  13. package/node_modules/@poe-code/agent-defs/dist/agents/kimi.js +1 -1
  14. package/node_modules/@poe-code/agent-defs/dist/agents/opencode.js +1 -1
  15. package/node_modules/@poe-code/agent-defs/dist/specifier.js +15 -6
  16. package/node_modules/@poe-code/agent-defs/dist/types.d.ts +5 -0
  17. package/node_modules/@poe-code/agent-mcp-config/dist/apply.js +36 -3
  18. package/node_modules/@poe-code/frontmatter/README.md +35 -0
  19. package/node_modules/@poe-code/frontmatter/dist/fences.d.ts +8 -0
  20. package/node_modules/@poe-code/frontmatter/dist/fences.js +72 -0
  21. package/node_modules/@poe-code/frontmatter/dist/index.d.ts +3 -0
  22. package/node_modules/@poe-code/frontmatter/dist/index.js +3 -0
  23. package/node_modules/@poe-code/frontmatter/dist/parse.d.ts +20 -0
  24. package/node_modules/@poe-code/frontmatter/dist/parse.js +137 -0
  25. package/node_modules/@poe-code/frontmatter/dist/stringify.d.ts +1 -0
  26. package/node_modules/@poe-code/frontmatter/dist/stringify.js +35 -0
  27. package/node_modules/@poe-code/frontmatter/package.json +25 -0
  28. package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.d.ts +1 -0
  29. package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +25 -1
  30. package/node_modules/tiny-mcp-client/README.md +7 -0
  31. package/node_modules/tiny-mcp-client/dist/internal.d.ts +2 -0
  32. package/node_modules/tiny-mcp-client/dist/internal.js +3 -0
  33. package/node_modules/tiny-mcp-client/dist/mcp-tool-types.compile-check.js +10 -0
  34. package/node_modules/tiny-mcp-client/src/http-oauth.test.ts +2 -8
  35. package/node_modules/tiny-mcp-client/src/internal.ts +6 -0
  36. package/node_modules/tiny-mcp-client/src/mcp-tool-types.compile-check.ts +10 -0
  37. package/node_modules/toolcraft-design/README.md +12 -0
  38. package/node_modules/toolcraft-design/dist/prompts/index.d.ts +39 -14
  39. package/node_modules/toolcraft-design/dist/prompts/index.js +10 -6
  40. package/node_modules/toolcraft-design/dist/prompts/interactive/cancel-symbol.d.ts +2 -0
  41. package/node_modules/toolcraft-design/dist/prompts/interactive/cancel-symbol.js +4 -0
  42. package/node_modules/toolcraft-design/dist/prompts/interactive/confirm.d.ts +9 -0
  43. package/node_modules/toolcraft-design/dist/prompts/interactive/confirm.js +47 -0
  44. package/node_modules/toolcraft-design/dist/prompts/interactive/core.d.ts +55 -0
  45. package/node_modules/toolcraft-design/dist/prompts/interactive/core.js +274 -0
  46. package/node_modules/toolcraft-design/dist/prompts/interactive/glyphs.d.ts +20 -0
  47. package/node_modules/toolcraft-design/dist/prompts/interactive/glyphs.js +53 -0
  48. package/node_modules/toolcraft-design/dist/prompts/interactive/index.d.ts +6 -0
  49. package/node_modules/toolcraft-design/dist/prompts/interactive/index.js +6 -0
  50. package/node_modules/toolcraft-design/dist/prompts/interactive/keys.d.ts +2 -0
  51. package/node_modules/toolcraft-design/dist/prompts/interactive/keys.js +28 -0
  52. package/node_modules/toolcraft-design/dist/prompts/interactive/multiselect.d.ts +13 -0
  53. package/node_modules/toolcraft-design/dist/prompts/interactive/multiselect.js +131 -0
  54. package/node_modules/toolcraft-design/dist/prompts/interactive/pagination.d.ts +10 -0
  55. package/node_modules/toolcraft-design/dist/prompts/interactive/pagination.js +52 -0
  56. package/node_modules/toolcraft-design/dist/prompts/interactive/password.d.ts +10 -0
  57. package/node_modules/toolcraft-design/dist/prompts/interactive/password.js +55 -0
  58. package/node_modules/toolcraft-design/dist/prompts/interactive/select.d.ts +18 -0
  59. package/node_modules/toolcraft-design/dist/prompts/interactive/select.js +89 -0
  60. package/node_modules/toolcraft-design/dist/prompts/interactive/test-helpers.d.ts +21 -0
  61. package/node_modules/toolcraft-design/dist/prompts/interactive/test-helpers.js +32 -0
  62. package/node_modules/toolcraft-design/dist/prompts/interactive/text.d.ts +12 -0
  63. package/node_modules/toolcraft-design/dist/prompts/interactive/text.js +60 -0
  64. package/node_modules/toolcraft-design/dist/prompts/interactive/wrap.d.ts +4 -0
  65. package/node_modules/toolcraft-design/dist/prompts/interactive/wrap.js +18 -0
  66. package/node_modules/toolcraft-design/dist/prompts/primitives/cancel.d.ts +1 -1
  67. package/node_modules/toolcraft-design/dist/prompts/primitives/cancel.js +1 -1
  68. package/node_modules/toolcraft-design/dist/terminal-markdown/parser/frontmatter.js +20 -453
  69. package/node_modules/toolcraft-design/package.json +6 -3
  70. 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 per-format renderers when you want richer CLI output:
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
- CLI picks `rich` by default, `--json` switches to `json`. SDK and MCP always return the raw handler value.
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 **input-schema property names** only. Tool names always stay `__`-joined snake_case.
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}, ${field.optionFlag}`;
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 existing = fieldsByFlag.get(field.optionFlag);
649
- if (existing !== undefined) {
650
- throw new UserError(`Parameters "${existing.displayPath}" and "${field.displayPath}" use conflicting CLI flag "${field.optionFlag}".`);
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
- return client.callTool({
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
  });