@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 +1 -0
- package/package.json +4 -1
- package/src/index.js +111 -37
- package/src/types/index.d.ts.map +1 -1
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.
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
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
|
-
|
|
490
|
-
|
|
536
|
+
/** @type {Map<string, Tool>} */
|
|
537
|
+
const tool_map = new Map();
|
|
491
538
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
539
|
+
for (const tool of tools) {
|
|
540
|
+
tool_map.set(tool.name, tool);
|
|
541
|
+
}
|
|
495
542
|
|
|
496
|
-
|
|
497
|
-
|
|
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
|
-
|
|
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
|
-
|
|
530
|
-
|
|
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
|
-
|
|
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
|
-
|
|
535
|
-
|
|
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
|
}
|