@tmcp/transport-cli 0.0.2 → 0.0.3

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 CHANGED
@@ -123,6 +123,7 @@ node my-cli.js get-user '{"id":"1"}' --output structured --fields user.name,user
123
123
  - Output is written to stdout.
124
124
  - JSON outputs are pretty-printed.
125
125
  - Errors are written to stderr and set `process.exitCode` to `1`.
126
+ - Running the CLI without a command prints help output.
126
127
  - The CLI initializes an MCP session, sends `notifications/initialized`, and paginates through `tools/list` automatically.
127
128
  - The program name shown in help output comes from `McpServer`'s `name`.
128
129
 
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@tmcp/transport-cli",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "CLI transport for TMCP with static JSON-first tool commands",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "types": "src/types/index.d.ts",
8
+ "engines": {
9
+ "node": ">=18.0.0"
10
+ },
8
11
  "files": [
9
12
  "src"
10
13
  ],
package/src/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  * @import { ListToolsResult, Tool } from "./internal.js";
4
4
  */
5
5
  import process from 'node:process';
6
+ import { randomUUID } from 'node:crypto';
6
7
  import { AsyncLocalStorage } from 'node:async_hooks';
7
8
  import sade from 'sade';
8
9
 
@@ -20,6 +21,7 @@ const CLIENT_INFO = {
20
21
  };
21
22
 
22
23
  const RESERVED_COMMANDS = new Set(['call', 'schema', 'tools']);
24
+ const UNSAFE_ALIAS_NAME = /[<>[\]\s]/;
23
25
 
24
26
  /**
25
27
  * @param {unknown} value
@@ -233,7 +235,11 @@ function filter_fields(value, paths) {
233
235
  if (segments.length === 0) {
234
236
  throw new Error('`--fields` only supports dot-separated paths');
235
237
  }
236
- set_path_value(result, segments, get_path_value(value, segments));
238
+ set_path_value(
239
+ result,
240
+ segments,
241
+ structuredClone(get_path_value(value, segments)),
242
+ );
237
243
  }
238
244
 
239
245
  return result;
@@ -301,6 +307,36 @@ function format_tool_result(result, options) {
301
307
  return format_json(filter_fields(selected, parse_fields(options.fields)));
302
308
  }
303
309
 
310
+ /**
311
+ * @param {Record<string, unknown>} result
312
+ * @returns {string}
313
+ */
314
+ function format_tool_error(result) {
315
+ const content = result.content;
316
+
317
+ if (Array.isArray(content)) {
318
+ const text_blocks = content
319
+ .map((item) => {
320
+ if (
321
+ is_record(item) &&
322
+ item.type === 'text' &&
323
+ typeof item.text === 'string'
324
+ ) {
325
+ return item.text;
326
+ }
327
+
328
+ return undefined;
329
+ })
330
+ .filter((item) => typeof item === 'string');
331
+
332
+ if (text_blocks.length > 0) {
333
+ return text_blocks.join('\n');
334
+ }
335
+ }
336
+
337
+ return JSON.stringify(result, null, 2);
338
+ }
339
+
304
340
  /**
305
341
  * @template {Record<string, unknown> | undefined} [TCustom=undefined]
306
342
  */
@@ -323,7 +359,7 @@ export class CliTransport {
323
359
  /**
324
360
  * @type {string}
325
361
  */
326
- #session_id = crypto.randomUUID();
362
+ #session_id = randomUUID();
327
363
 
328
364
  /**
329
365
  * @param {McpServer<any, TCustom>} server
@@ -332,6 +368,10 @@ export class CliTransport {
332
368
  this.#server = server;
333
369
  }
334
370
 
371
+ #exit() {
372
+ process.exit(process.exitCode ?? 0);
373
+ }
374
+
335
375
  /**
336
376
  * @param {string} method
337
377
  * @param {Record<string, unknown>} [params]
@@ -473,6 +513,12 @@ export class CliTransport {
473
513
  const args = await resolve_tool_input(input);
474
514
  const result = await this.#call_tool(tool.name, args, ctx);
475
515
 
516
+ if (result?.isError) {
517
+ process.stderr.write(`Error: ${format_tool_error(result)}\n`);
518
+ process.exitCode = 1;
519
+ return;
520
+ }
521
+
476
522
  process.stdout.write(format_tool_result(result, options));
477
523
  }
478
524
 
@@ -481,39 +527,25 @@ export class CliTransport {
481
527
  * @param {Array<string>} [argv]
482
528
  */
483
529
  async run(ctx, argv) {
484
- const init_result = await this.#initialize(ctx);
485
- const tools = await this.#list_tools(ctx);
486
- const script_name = init_result?.serverInfo?.name ?? 'tmcp';
487
- const prog = sade(script_name);
530
+ try {
531
+ const init_result = await this.#initialize(ctx);
532
+ const tools = await this.#list_tools(ctx);
533
+ const script_name = init_result?.serverInfo?.name ?? 'tmcp';
534
+ const prog = sade(script_name);
488
535
 
489
- /** @type {Map<string, Tool>} */
490
- const tool_map = new Map();
536
+ /** @type {Map<string, Tool>} */
537
+ const tool_map = new Map();
491
538
 
492
- for (const tool of tools) {
493
- tool_map.set(tool.name, tool);
494
- }
539
+ for (const tool of tools) {
540
+ tool_map.set(tool.name, tool);
541
+ }
495
542
 
496
- prog.command('tools', 'List available tools').action(() => {});
497
- prog.command('schema <tool>', 'Print a tool schema').action(() => {});
498
-
499
- const call_command = prog
500
- .command('call <tool> [input]', 'Call a tool with JSON input')
501
- .option(
502
- '--output',
503
- 'Select full, structured, content, or text output',
504
- 'full',
505
- )
506
- .option(
507
- '--fields',
508
- 'Select comma-separated dot paths from the chosen output',
543
+ prog.command('tools', 'List available tools').action(() => {});
544
+ prog.command('schema <tool>', 'Print a tool schema').action(
545
+ () => {},
509
546
  );
510
547
 
511
- call_command.action(() => {});
512
-
513
- for (const tool of tools) {
514
- if (RESERVED_COMMANDS.has(tool.name)) continue;
515
-
516
- prog.command(`${tool.name} [input]`, tool.description ?? '')
548
+ prog.command('call <tool> [input]', 'Call a tool with JSON input')
517
549
  .option(
518
550
  '--output',
519
551
  'Select full, structured, content, or text output',
@@ -524,19 +556,54 @@ export class CliTransport {
524
556
  'Select comma-separated dot paths from the chosen output',
525
557
  )
526
558
  .action(() => {});
527
- }
528
559
 
529
- const args = argv ? ['node', script_name, ...argv] : process.argv;
530
- const parsed = prog.parse(args, { lazy: true });
560
+ for (const tool of tools) {
561
+ if (RESERVED_COMMANDS.has(tool.name)) {
562
+ process.stderr.write(
563
+ `Warning: skipping bare alias for tool "${tool.name}" because its name conflicts with a built-in command. Use \`call ${tool.name}\` instead.\n`,
564
+ );
565
+ continue;
566
+ }
567
+
568
+ if (UNSAFE_ALIAS_NAME.test(tool.name)) {
569
+ process.stderr.write(
570
+ `Warning: skipping bare alias for tool "${tool.name}" because its name contains CLI syntax characters. Use \`call ${tool.name}\` instead.\n`,
571
+ );
572
+ continue;
573
+ }
531
574
 
532
- if (!parsed) return;
575
+ prog.command(`${tool.name} [input]`, tool.description ?? '')
576
+ .option(
577
+ '--output',
578
+ 'Select full, structured, content, or text output',
579
+ 'full',
580
+ )
581
+ .option(
582
+ '--fields',
583
+ 'Select comma-separated dot paths from the chosen output',
584
+ )
585
+ .action(() => {});
586
+ }
533
587
 
534
- const { name, args: handler_args } = parsed;
535
- const { positionals, options } = extract_command_args(handler_args);
588
+ const command_args = argv ?? process.argv.slice(2);
589
+ const args = [
590
+ 'node',
591
+ script_name,
592
+ ...(command_args.length === 0 ? ['--help'] : command_args),
593
+ ];
594
+ const parsed = prog.parse(args, { lazy: true });
595
+
596
+ if (!parsed) {
597
+ this.#exit();
598
+ return;
599
+ }
600
+
601
+ const { name, args: handler_args } = parsed;
602
+ const { positionals, options } = extract_command_args(handler_args);
536
603
 
537
- try {
538
604
  if (name === 'tools') {
539
605
  process.stdout.write(format_json(tools));
606
+ this.#exit();
540
607
  return;
541
608
  }
542
609
 
@@ -551,6 +618,7 @@ export class CliTransport {
551
618
  process.stdout.write(
552
619
  format_json(this.#get_tool(tool_map, tool_name)),
553
620
  );
621
+ this.#exit();
554
622
  return;
555
623
  }
556
624
 
@@ -573,6 +641,7 @@ export class CliTransport {
573
641
  normalize_tool_options(options),
574
642
  ctx,
575
643
  );
644
+ this.#exit();
576
645
  return;
577
646
  }
578
647
 
@@ -587,12 +656,17 @@ export class CliTransport {
587
656
  normalize_tool_options(options),
588
657
  ctx,
589
658
  );
659
+ this.#exit();
660
+ return;
590
661
  }
662
+
663
+ this.#exit();
591
664
  } catch (err) {
592
665
  process.stderr.write(
593
666
  `Error: ${err instanceof Error ? err.message : String(err)}\n`,
594
667
  );
595
668
  process.exitCode = 1;
669
+ this.#exit();
596
670
  }
597
671
  }
598
672
  }
@@ -12,6 +12,6 @@
12
12
  "sourcesContent": [
13
13
  null
14
14
  ],
15
- "mappings": ";;cAkTaA,YAAYA;;;;;;;aAzSgCC,UAAUA;aAIZC,WAAWA",
15
+ "mappings": ";;cAsVaA,YAAYA;;;;;;;aA5UgCC,UAAUA;aAIZC,WAAWA",
16
16
  "ignoreList": []
17
17
  }