@apify/mcpc 0.2.4 → 0.2.6

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 (87) hide show
  1. package/CHANGELOG.md +34 -1
  2. package/README.md +65 -26
  3. package/dist/bridge/index.js +26 -4
  4. package/dist/bridge/index.js.map +1 -1
  5. package/dist/cli/commands/auth.d.ts +1 -0
  6. package/dist/cli/commands/auth.d.ts.map +1 -1
  7. package/dist/cli/commands/auth.js +7 -0
  8. package/dist/cli/commands/auth.js.map +1 -1
  9. package/dist/cli/commands/clean.d.ts.map +1 -1
  10. package/dist/cli/commands/clean.js +13 -2
  11. package/dist/cli/commands/clean.js.map +1 -1
  12. package/dist/cli/commands/grep.d.ts.map +1 -1
  13. package/dist/cli/commands/grep.js +35 -4
  14. package/dist/cli/commands/grep.js.map +1 -1
  15. package/dist/cli/commands/prompts.d.ts.map +1 -1
  16. package/dist/cli/commands/prompts.js +7 -26
  17. package/dist/cli/commands/prompts.js.map +1 -1
  18. package/dist/cli/commands/resources.d.ts.map +1 -1
  19. package/dist/cli/commands/resources.js +9 -3
  20. package/dist/cli/commands/resources.js.map +1 -1
  21. package/dist/cli/commands/sessions.d.ts +25 -1
  22. package/dist/cli/commands/sessions.d.ts.map +1 -1
  23. package/dist/cli/commands/sessions.js +177 -11
  24. package/dist/cli/commands/sessions.js.map +1 -1
  25. package/dist/cli/commands/tasks.d.ts +1 -0
  26. package/dist/cli/commands/tasks.d.ts.map +1 -1
  27. package/dist/cli/commands/tasks.js +11 -0
  28. package/dist/cli/commands/tasks.js.map +1 -1
  29. package/dist/cli/commands/tools.d.ts +6 -1
  30. package/dist/cli/commands/tools.d.ts.map +1 -1
  31. package/dist/cli/commands/tools.js +39 -11
  32. package/dist/cli/commands/tools.js.map +1 -1
  33. package/dist/cli/commands/x402.js +1 -1
  34. package/dist/cli/commands/x402.js.map +1 -1
  35. package/dist/cli/index.js +290 -88
  36. package/dist/cli/index.js.map +1 -1
  37. package/dist/cli/output.d.ts +5 -0
  38. package/dist/cli/output.d.ts.map +1 -1
  39. package/dist/cli/output.js +103 -16
  40. package/dist/cli/output.js.map +1 -1
  41. package/dist/cli/parser.d.ts +4 -0
  42. package/dist/cli/parser.d.ts.map +1 -1
  43. package/dist/cli/parser.js +50 -16
  44. package/dist/cli/parser.js.map +1 -1
  45. package/dist/cli/shell.d.ts.map +1 -1
  46. package/dist/cli/shell.js +26 -3
  47. package/dist/cli/shell.js.map +1 -1
  48. package/dist/core/mcp-client.d.ts +1 -0
  49. package/dist/core/mcp-client.d.ts.map +1 -1
  50. package/dist/core/mcp-client.js +14 -0
  51. package/dist/core/mcp-client.js.map +1 -1
  52. package/dist/lib/auth/oauth-flow.d.ts +1 -0
  53. package/dist/lib/auth/oauth-flow.d.ts.map +1 -1
  54. package/dist/lib/auth/oauth-flow.js +33 -9
  55. package/dist/lib/auth/oauth-flow.js.map +1 -1
  56. package/dist/lib/auth/oauth-provider.d.ts +2 -0
  57. package/dist/lib/auth/oauth-provider.d.ts.map +1 -1
  58. package/dist/lib/auth/oauth-provider.js +4 -0
  59. package/dist/lib/auth/oauth-provider.js.map +1 -1
  60. package/dist/lib/bridge-manager.d.ts.map +1 -1
  61. package/dist/lib/bridge-manager.js +22 -19
  62. package/dist/lib/bridge-manager.js.map +1 -1
  63. package/dist/lib/cleanup.d.ts +5 -0
  64. package/dist/lib/cleanup.d.ts.map +1 -1
  65. package/dist/lib/cleanup.js +38 -1
  66. package/dist/lib/cleanup.js.map +1 -1
  67. package/dist/lib/config.d.ts.map +1 -1
  68. package/dist/lib/config.js +5 -1
  69. package/dist/lib/config.js.map +1 -1
  70. package/dist/lib/errors.d.ts.map +1 -1
  71. package/dist/lib/errors.js +1 -1
  72. package/dist/lib/errors.js.map +1 -1
  73. package/dist/lib/session-client.d.ts +1 -0
  74. package/dist/lib/session-client.d.ts.map +1 -1
  75. package/dist/lib/session-client.js +7 -4
  76. package/dist/lib/session-client.js.map +1 -1
  77. package/dist/lib/sessions.d.ts.map +1 -1
  78. package/dist/lib/sessions.js +13 -5
  79. package/dist/lib/sessions.js.map +1 -1
  80. package/dist/lib/types.d.ts +2 -0
  81. package/dist/lib/types.d.ts.map +1 -1
  82. package/dist/lib/utils.d.ts +15 -2
  83. package/dist/lib/utils.d.ts.map +1 -1
  84. package/dist/lib/utils.js +91 -7
  85. package/dist/lib/utils.js.map +1 -1
  86. package/docs/TODOs.md +5 -0
  87. package/package.json +6 -6
package/dist/cli/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { initProxy } from '../lib/proxy.js';
3
- import { Command, Help } from 'commander';
3
+ import { Command, CommanderError, Help } from 'commander';
4
4
  import { setVerbose, setJsonMode, closeFileLogger } from '../lib/index.js';
5
5
  import { isMcpError, formatHumanError, ClientError } from '../lib/index.js';
6
6
  import chalk from 'chalk';
@@ -16,7 +16,7 @@ import * as tasks from './commands/tasks.js';
16
16
  import * as grepCmd from './commands/grep.js';
17
17
  import { handleX402Command } from './commands/x402.js';
18
18
  import { clean } from './commands/clean.js';
19
- import { extractOptions, getVerboseFromEnv, getJsonFromEnv, validateOptions, validateArgValues, parseServerArg, hasSubcommand, optionTakesValue, KNOWN_COMMANDS, KNOWN_SESSION_COMMANDS, } from './parser.js';
19
+ import { extractOptions, getVerboseFromEnv, getJsonFromEnv, validateOptions, validateArgValues, parseServerArg, hasSubcommand, optionTakesValue, suggestCommand, KNOWN_COMMANDS, KNOWN_SESSION_COMMANDS, } from './parser.js';
20
20
  import { createRequire } from 'module';
21
21
  const { version: mcpcVersion } = createRequire(import.meta.url)('../../package.json');
22
22
  {
@@ -64,8 +64,21 @@ function getOptionsFromCommand(command) {
64
64
  }
65
65
  if (opts.full)
66
66
  options.full = opts.full;
67
+ if (opts.maxChars) {
68
+ const maxChars = parseInt(opts.maxChars, 10);
69
+ if (isNaN(maxChars) || maxChars <= 0) {
70
+ throw new Error(`Invalid --max-chars value: "${opts.maxChars}". Must be a positive number (characters).`);
71
+ }
72
+ options.maxChars = maxChars;
73
+ }
67
74
  return options;
68
75
  }
76
+ function jsonHelp(description, shape, schemaUrl) {
77
+ const line = shape ? ` ${description}:\n ${shape}` : ` ${description}`;
78
+ const link = schemaUrl ? `\n Schema: ${schemaUrl}` : '';
79
+ return `\n${chalk.bold('JSON output (--json):')}\n${line}${link}\n`;
80
+ }
81
+ const SCHEMA_BASE = 'https://modelcontextprotocol.io/specification/2025-11-25/schema';
69
82
  async function main() {
70
83
  const args = process.argv.slice(2);
71
84
  const handleExit = () => {
@@ -90,16 +103,26 @@ async function main() {
90
103
  return;
91
104
  }
92
105
  if (args.includes('--help') || args.includes('-h')) {
93
- if (args.includes('x402')) {
106
+ const hasSessionArg = args.some((a) => a.startsWith('@') && !a.startsWith('--'));
107
+ if (hasSessionArg) {
108
+ }
109
+ else if (args.includes('x402')) {
94
110
  const x402Index = args.indexOf('x402');
95
111
  const x402Args = args.slice(x402Index + 1);
96
112
  await handleX402Command(x402Args);
97
113
  await closeFileLogger();
98
114
  return;
99
115
  }
100
- const program = createTopLevelProgram();
101
- await program.parseAsync(process.argv);
102
- return;
116
+ else {
117
+ const helpTarget = args.find((a) => a !== '--help' && a !== '-h' && !a.startsWith('-') && !a.startsWith('@'));
118
+ if (helpTarget && KNOWN_SESSION_COMMANDS.includes(helpTarget)) {
119
+ showSessionCommandHelp(helpTarget);
120
+ return;
121
+ }
122
+ const program = createTopLevelProgram();
123
+ await program.parseAsync(process.argv);
124
+ return;
125
+ }
103
126
  }
104
127
  try {
105
128
  validateOptions(args);
@@ -210,11 +233,20 @@ async function main() {
210
233
  }
211
234
  }
212
235
  else {
236
+ const suggestion = suggestCommand(firstNonOption, allCommands);
213
237
  if (outputMode === 'json') {
214
238
  console.error(formatJsonError(new Error(`Unknown command: ${firstNonOption}`), 1));
215
239
  }
216
240
  else {
217
241
  console.error(`Error: Unknown command: ${firstNonOption}`);
242
+ if (suggestion) {
243
+ if (KNOWN_SESSION_COMMANDS.includes(suggestion)) {
244
+ console.error(`\nDid you mean: mcpc <@session> ${suggestion}`);
245
+ }
246
+ else {
247
+ console.error(`\nDid you mean: mcpc ${suggestion}`);
248
+ }
249
+ }
218
250
  console.error(`Run "mcpc --help" for usage information.\n`);
219
251
  }
220
252
  }
@@ -255,18 +287,17 @@ function createTopLevelProgram() {
255
287
  .name('mcpc')
256
288
  .description(`${rainbow('Universal')} command-line client for the Model Context Protocol (MCP).`)
257
289
  .usage('[<@session>] [<command>] [options]')
258
- .option('-j, --json', 'Output in JSON format for scripting')
290
+ .option('--json', 'Output in JSON format for scripting')
259
291
  .option('--verbose', 'Enable debug logging')
260
292
  .option('--profile <name>', 'OAuth profile for the server ("default" if not provided)')
261
- .option('--schema <file>', 'Validate tool/prompt schema against expected schema')
262
- .option('--schema-mode <mode>', 'Schema validation mode: strict, compatible (default), ignore')
263
293
  .option('--timeout <seconds>', 'Request timeout in seconds (default: 300)')
294
+ .option('--max-chars <n>', 'Truncate output to n characters (ignored in --json mode)')
264
295
  .option('--insecure', 'Skip TLS certificate verification (for self-signed certs)')
265
296
  .version(mcpcVersion, '-v, --version', 'Output the version number')
266
297
  .helpOption('-h, --help', 'Display help');
267
298
  program.addHelpText('after', `
268
299
  ${chalk.bold('MCP session commands (after connecting):')}
269
- <@session> Show MCP server info, capabilities, and tools
300
+ <@session> Show MCP server info, capabilities, and tools overview
270
301
  <@session> ${chalk.cyan('grep')} <pattern> Search tools and instructions
271
302
  <@session> ${chalk.cyan('tools-list')} List all server tools
272
303
  <@session> ${chalk.cyan('tools-get')} <name> Get tool details and schema
@@ -280,6 +311,7 @@ ${chalk.bold('MCP session commands (after connecting):')}
280
311
  <@session> ${chalk.cyan('resources-templates-list')}
281
312
  <@session> ${chalk.cyan('tasks-list')}
282
313
  <@session> ${chalk.cyan('tasks-get')} <taskId>
314
+ <@session> ${chalk.cyan('tasks-result')} <taskId>
283
315
  <@session> ${chalk.cyan('tasks-cancel')} <taskId>
284
316
  <@session> ${chalk.cyan('logging-set-level')} <level>
285
317
  <@session> ${chalk.cyan('ping')}
@@ -289,8 +321,8 @@ Run "mcpc" without arguments to show active sessions and OAuth profiles.
289
321
  Full docs: ${docsUrl}`);
290
322
  program
291
323
  .command('connect [server] [@session]')
292
- .usage('<server> <@session>')
293
- .description('Connect to an MCP server and start a new named @session')
324
+ .usage('<server> [@session]')
325
+ .description('Connect to an MCP server and start a named @session')
294
326
  .option('-H, --header <header>', 'HTTP header (can be repeated)')
295
327
  .option('--profile <name>', 'OAuth profile to use ("default" if skipped)')
296
328
  .option('--no-profile', 'Skip OAuth profile (connect anonymously)')
@@ -301,14 +333,20 @@ Full docs: ${docsUrl}`);
301
333
  ${chalk.bold('Server formats:')}
302
334
  mcp.apify.com Remote HTTP server (https:// added automatically)
303
335
  ~/.vscode/mcp.json:puppeteer Config file entry (file:entry)
304
- `)
336
+ ~/.vscode/mcp.json Config file — connect all servers in the file
337
+
338
+ ${chalk.bold('Session name:')}
339
+ If @session is omitted, a name is auto-generated from the server hostname
340
+ (e.g. mcp.apify.com → @apify) or config entry name. If a matching session
341
+ already exists (same server URL, OAuth profile, and HTTP header names), it
342
+ is reused (restarted if not live). Header values are not compared — they
343
+ are stored securely in OS keychain.
344
+ When connecting all servers from a config file, @session cannot be specified.
345
+ ${jsonHelp('`InitializeResult` object extended with `toolNames` and `_mcpc` metadata', '`{ protocolVersion, capabilities, serverInfo, instructions?, toolNames?, _mcpc }`', `${SCHEMA_BASE}#initializeresult`)}`)
305
346
  .action(async (server, sessionName, opts, command) => {
306
347
  if (!server) {
307
348
  throw new ClientError('Missing required argument: server\n\nExample: mcpc connect mcp.apify.com @myapp');
308
349
  }
309
- if (!sessionName) {
310
- throw new ClientError('Missing required argument: @session\n\nExample: mcpc connect mcp.apify.com @myapp');
311
- }
312
350
  const globalOpts = getOptionsFromCommand(command);
313
351
  const parsed = parseServerArg(server);
314
352
  const headers = opts.header
@@ -320,6 +358,29 @@ ${chalk.bold('Server formats:')}
320
358
  throw new ClientError(`Invalid server: "${server}"\n\n` +
321
359
  `Expected a URL (e.g. mcp.apify.com) or a config file entry (e.g. ~/.vscode/mcp.json:filesystem)`);
322
360
  }
361
+ if (parsed.type === 'config-file') {
362
+ if (sessionName) {
363
+ throw new ClientError(`Cannot specify @session name when connecting all servers from a config file.\n` +
364
+ `To connect a specific entry, use: mcpc connect ${server}:<entry> ${sessionName}`);
365
+ }
366
+ await sessions.connectAllFromConfig(parsed.file, {
367
+ ...globalOpts,
368
+ ...(headers && { headers }),
369
+ ...(opts.proxy && { proxy: opts.proxy }),
370
+ ...(opts.proxyBearerToken && { proxyBearerToken: opts.proxyBearerToken }),
371
+ ...(opts.x402 && { x402: opts.x402 }),
372
+ ...(globalOpts.insecure && { insecure: true }),
373
+ });
374
+ return;
375
+ }
376
+ if (!sessionName) {
377
+ sessionName = await sessions.resolveSessionName(parsed, {
378
+ outputMode: globalOpts.outputMode,
379
+ ...(globalOpts.profile && { profile: globalOpts.profile }),
380
+ ...(headers && { headers }),
381
+ ...(globalOpts.noProfile && { noProfile: globalOpts.noProfile }),
382
+ });
383
+ }
323
384
  if (parsed.type === 'config') {
324
385
  await sessions.connectSession(parsed.entry, sessionName, {
325
386
  ...globalOpts,
@@ -346,6 +407,7 @@ ${chalk.bold('Server formats:')}
346
407
  .command('close [@session]')
347
408
  .usage('<@session>')
348
409
  .description('Close a session')
410
+ .addHelpText('after', jsonHelp('`{ sessionName, closed: true }`'))
349
411
  .action(async (sessionName, _opts, command) => {
350
412
  if (!sessionName) {
351
413
  throw new ClientError('Missing required argument: @session\n\nExample: mcpc close @myapp');
@@ -377,9 +439,24 @@ ${chalk.bold('Server formats:')}
377
439
  .usage('<server>')
378
440
  .description('Interactively login to a server using OAuth and save profile')
379
441
  .option('--profile <name>', 'Profile name (default: "default")')
380
- .option('--scope <scopes>', 'OAuth scopes to request, quoted and space-separated (e.g. --scope "read write")')
381
- .option('--client-id <id>', 'OAuth client ID (for servers without dynamic client registration)')
382
- .option('--client-secret <secret>', 'OAuth client secret (for servers without dynamic client registration)')
442
+ .option('--scope <scopes>', 'OAuth scopes to request (e.g. --scope "read write")')
443
+ .option('--client-id <id>', 'Pre-registered OAuth client ID (skips CIMD and DCR)')
444
+ .option('--client-secret <secret>', 'Pre-registered OAuth client secret (requires --client-id)')
445
+ .option('--client-metadata-url <url>', 'HTTPS URL of an OAuth CIMD to use as the Client ID')
446
+ .addHelpText('after', `
447
+ ${chalk.bold('OAuth client registration approaches:')}
448
+
449
+ 1. Pre-registration: --client-id (and optionally --client-secret).
450
+ 2. Client ID Metadata Documents (CIMD): --client-metadata-url <https-url>.
451
+ Used when the authorization server advertises
452
+ "client_id_metadata_document_supported: true".
453
+ 3. Dynamic Client Registration (DCR): default fallback when the server
454
+ exposes a "registration_endpoint". No flags required.
455
+
456
+ See https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization
457
+
458
+ ${jsonHelp('Interactive prompts are written to stderr, stdout contains a clean JSON object', '`{ profile, serverUrl, scopes }`')}
459
+ `)
383
460
  .action(async (server, opts, command) => {
384
461
  if (!server) {
385
462
  throw new ClientError('Missing required argument: server\n\nExample: mcpc login mcp.apify.com');
@@ -389,6 +466,7 @@ ${chalk.bold('Server formats:')}
389
466
  scope: opts.scope,
390
467
  clientId: opts.clientId,
391
468
  clientSecret: opts.clientSecret,
469
+ clientMetadataUrl: opts.clientMetadataUrl,
392
470
  ...getOptionsFromCommand(command),
393
471
  });
394
472
  });
@@ -397,6 +475,7 @@ ${chalk.bold('Server formats:')}
397
475
  .usage('<server>')
398
476
  .description('Delete an OAuth profile for a server')
399
477
  .option('--profile <name>', 'Profile name (default: "default")')
478
+ .addHelpText('after', jsonHelp('`{ profile, serverUrl, deleted: true, affectedSessions }`'))
400
479
  .action(async (server, opts, command) => {
401
480
  if (!server) {
402
481
  throw new ClientError('Missing required argument: server\n\nExample: mcpc logout mcp.apify.com');
@@ -417,7 +496,7 @@ ${chalk.bold('Resources:')}
417
496
  all Remove all of the above
418
497
 
419
498
  Without arguments, performs safe cleanup of stale data only.
420
- `)
499
+ ${jsonHelp('`{ crashedBridges, expiredSessions, orphanedBridgeLogs, sessions, profiles, logs }`')}`)
421
500
  .action(async (resources, _opts, command) => {
422
501
  const globalOpts = getOptionsFromCommand(command);
423
502
  const VALID_CLEAN_TYPES = ['sessions', 'profiles', 'logs', 'all'];
@@ -458,7 +537,7 @@ ${chalk.bold('Examples:')}
458
537
  mcpc @apify grep "actor" Search within a single session
459
538
  mcpc grep "file" --json JSON output for scripting
460
539
  mcpc grep "actor" -m 5 Show at most 5 results
461
- `)
540
+ ${jsonHelp('`[{ sessionName, tools?: Tool[], resources?: Resource[], prompts?: Prompt[], instructions?: string[] }]`')}`)
462
541
  .action(async (pattern, opts, command) => {
463
542
  if (!pattern) {
464
543
  throw new ClientError('Missing required argument: pattern\n\nUsage: mcpc grep <pattern>\n\nExample: mcpc grep "search"');
@@ -496,17 +575,12 @@ ${chalk.bold('Examples:')}
496
575
  }
497
576
  const topLevelCmd = program.commands.find((c) => c.name() === cmdName || c.aliases().includes(cmdName));
498
577
  if (topLevelCmd) {
578
+ tuneCommandHelp(topLevelCmd);
499
579
  topLevelCmd.outputHelp();
500
580
  return;
501
581
  }
502
- const dummyProgram = new Command();
503
- dummyProgram.name('mcpc <@session>');
504
- registerSessionCommands(dummyProgram, '@dummy');
505
- const sessionCmd = dummyProgram.commands.find((c) => c.name() === cmdName || c.aliases().includes(cmdName));
506
- if (sessionCmd) {
507
- sessionCmd.outputHelp();
582
+ if (showSessionCommandHelp(cmdName))
508
583
  return;
509
- }
510
584
  console.error(`Unknown command: ${cmdName}`);
511
585
  console.error(`Run "mcpc --help" for usage information.`);
512
586
  process.exit(1);
@@ -523,57 +597,150 @@ ${chalk.bold('Examples:')}
523
597
  });
524
598
  return program;
525
599
  }
600
+ const NO_JSON_COMMANDS = new Set(['shell']);
601
+ function tuneCommandHelp(cmd) {
602
+ if (!NO_JSON_COMMANDS.has(cmd.name()) && !cmd.options.some((o) => o.long === '--json')) {
603
+ cmd.option('--json', 'Output in JSON format');
604
+ }
605
+ cmd.helpOption('-h, --help', 'Display help');
606
+ const helpOpt = cmd._getHelpOption?.();
607
+ if (helpOpt)
608
+ helpOpt.hidden = true;
609
+ }
610
+ function showSessionCommandHelp(cmdName) {
611
+ const dummyProgram = createSessionProgram();
612
+ registerSessionCommands(dummyProgram, '<@session>');
613
+ for (const cmd of dummyProgram.commands) {
614
+ tuneCommandHelp(cmd);
615
+ }
616
+ const sessionCmd = dummyProgram.commands.find((c) => c.name() === cmdName || c.aliases().includes(cmdName));
617
+ if (sessionCmd) {
618
+ sessionCmd.outputHelp();
619
+ return true;
620
+ }
621
+ return false;
622
+ }
526
623
  function registerSessionCommands(program, session) {
527
624
  program
528
- .command('help')
529
- .description('Show server instructions and available capabilities')
530
- .action(async (_options, command) => {
531
- await sessions.showHelp(session, getOptionsFromCommand(command));
625
+ .command('help', { hidden: true })
626
+ .description('Show available commands and options.')
627
+ .action((_options, command) => {
628
+ command.parent.outputHelp();
532
629
  });
533
630
  program
534
631
  .command('shell')
535
- .description('Interactive shell for the session')
632
+ .description('Launch interactive MCP shell.')
536
633
  .action(async () => {
537
634
  await sessions.openShell(session);
538
635
  });
539
636
  program
540
- .command('close', { hidden: true })
541
- .description('Close the session')
637
+ .command('close')
638
+ .description('Close MCP session.')
542
639
  .action(async (_options, command) => {
543
640
  await sessions.closeSession(session, getOptionsFromCommand(command));
544
641
  });
545
642
  program
546
643
  .command('restart')
547
- .description('Restart the session (stop and start the bridge)')
644
+ .description('Restart MCP session (losing all state).')
548
645
  .action(async (_options, command) => {
549
646
  await sessions.restartSession(session, getOptionsFromCommand(command));
550
647
  });
648
+ program
649
+ .command('grep <pattern>')
650
+ .usage('<pattern> [options]')
651
+ .description('Search MCP session objects.')
652
+ .option('--tools', 'Search tools')
653
+ .option('--resources', 'Search resources')
654
+ .option('--prompts', 'Search prompts')
655
+ .option('--instructions', 'Search server instructions')
656
+ .option('-E, --regex', 'Treat pattern as a regular expression')
657
+ .option('-s, --case-sensitive', 'Case-sensitive matching')
658
+ .option('-m, --max-results <n>', 'Limit the number of results')
659
+ .addHelpText('after', `
660
+ ${chalk.bold('Type filters:')}
661
+ By default, tools and instructions are searched. Use --resources or --prompts
662
+ to search those instead. Combine flags to search multiple types.
663
+
664
+ ${chalk.bold('Examples:')}
665
+ mcpc ${session} grep "search" Search tools and instructions
666
+ mcpc ${session} grep "search" --resources Search resources only
667
+ mcpc ${session} grep "search|find" -E Regex search
668
+ ${jsonHelp('`{ tools?: Tool[], resources?: Resource[], prompts?: Prompt[], instructions?: string[] }`')}`)
669
+ .action(async (pattern, opts, command) => {
670
+ const globalOpts = getOptionsFromCommand(command);
671
+ const maxResults = opts.maxResults ? parseInt(opts.maxResults, 10) : undefined;
672
+ const exitCode = await grepCmd.grepSession(session, pattern, {
673
+ tools: opts.tools,
674
+ resources: opts.resources,
675
+ prompts: opts.prompts,
676
+ instructions: opts.instructions,
677
+ regex: opts.regex,
678
+ caseSensitive: opts.caseSensitive,
679
+ maxResults,
680
+ ...globalOpts,
681
+ });
682
+ process.exit(exitCode);
683
+ });
551
684
  program
552
685
  .command('tools')
553
- .description('List available tools (shorthand for tools-list)')
554
- .option('--full', 'Show full tool details including complete input schema')
686
+ .description('List MCP tools (shorthand for tools-list).')
687
+ .option('--full', 'Show full tool details including schema')
688
+ .addHelpText('after', jsonHelp('Array of `Tool` objects', '`[{ name, description?, inputSchema, annotations? }, ...]`', `${SCHEMA_BASE}#tool`))
555
689
  .action(async (_options, command) => {
556
690
  await tools.listTools(session, getOptionsFromCommand(command));
557
691
  });
558
692
  program
559
693
  .command('tools-list')
560
- .description('List available tools')
561
- .option('--full', 'Show full tool details including complete input schema')
694
+ .description('List all MCP tools.')
695
+ .option('--full', 'Show full tool details including schema')
696
+ .addHelpText('after', jsonHelp('Array of `Tool` objects', '`[{ name, description?, inputSchema, annotations? }, ...]`', `${SCHEMA_BASE}#tool`))
562
697
  .action(async (_options, command) => {
563
698
  await tools.listTools(session, getOptionsFromCommand(command));
564
699
  });
565
700
  program
566
701
  .command('tools-get <name>')
567
- .description('Get information about a specific tool')
702
+ .description('Get details and schema for an MCP tool.')
703
+ .option('--schema <file>', 'Validate tool schema against expected schema')
704
+ .option('--schema-mode <mode>', 'Schema validation mode: strict, compatible (default), ignore')
705
+ .addHelpText('after', `
706
+ ${chalk.bold('Schema validation:')}
707
+ --schema <file> Validate against expected schema (save with tools-get --json)
708
+ --schema-mode <mode> strict | compatible (default) | ignore
709
+ ${jsonHelp('`Tool` object', '`{ name, description?, inputSchema, annotations? }`', `${SCHEMA_BASE}#tool`)}`)
568
710
  .action(async (name, _options, command) => {
569
711
  await tools.getTool(session, name, getOptionsFromCommand(command));
570
712
  });
713
+ const toolsCallJsonHelp = jsonHelp('`CallToolResult` object', '`{ content: [{ type, text?, ... }], isError?, structuredContent?: { ... } }`', `${SCHEMA_BASE}#calltoolresult`);
571
714
  program
572
715
  .command('tools-call <name> [args...]')
573
- .description('Call a tool with arguments (key:=value pairs or JSON)')
574
- .option('--task', 'Use task execution (experimental)')
716
+ .description('Call an MCP tool with arguments.')
717
+ .helpOption(false)
718
+ .option('--task', 'Use async task execution (experimental)')
575
719
  .option('--detach', 'Start task and return immediately with task ID (implies --task)')
720
+ .option('--schema <file>', 'Validate tool schema against expected schema before calling')
721
+ .option('--schema-mode <mode>', 'Schema validation mode: strict, compatible (default), ignore')
722
+ .addHelpText('after', `
723
+ ${chalk.bold('Arguments:')}
724
+ key:=value pairs mcpc ${session} tools-call search query:=hello limit:=10
725
+ Inline JSON mcpc ${session} tools-call search '{"query":"hello"}'
726
+ Stdin pipe echo '{"query":"hello"}' | mcpc ${session} tools-call search
727
+
728
+ Values are auto-parsed: strings, numbers, booleans, JSON objects/arrays.
729
+ To force a string, wrap in quotes: id:='"123"'
730
+
731
+ ${chalk.bold('Schema validation:')}
732
+ --schema <file> Validate tool schema before calling (save with tools-get --json)
733
+ --schema-mode <mode> strict | compatible (default) | ignore
734
+ ${toolsCallJsonHelp}`)
576
735
  .action(async (name, args, options, command) => {
736
+ if (name === '--help' || name === '-h') {
737
+ command.help();
738
+ return;
739
+ }
740
+ if (args.includes('--help') || args.includes('-h')) {
741
+ await tools.getTool(session, name, getOptionsFromCommand(command));
742
+ return;
743
+ }
577
744
  await tools.callTool(session, name, {
578
745
  args,
579
746
  task: options.task,
@@ -583,39 +750,52 @@ function registerSessionCommands(program, session) {
583
750
  });
584
751
  program
585
752
  .command('tasks-list')
586
- .description('List active tasks')
753
+ .description('List all MCP tasks.')
754
+ .addHelpText('after', jsonHelp('`{ tasks: Task[] }`', '`{ tasks: [{ taskId, status, ttl, createdAt, lastUpdatedAt, statusMessage?, pollInterval? }] }`', `${SCHEMA_BASE}#task`))
587
755
  .action(async (_options, command) => {
588
756
  await tasks.listTasks(session, getOptionsFromCommand(command));
589
757
  });
590
758
  program
591
759
  .command('tasks-get <taskId>')
592
- .description('Get status of a specific task')
760
+ .description('Get MCP task status.')
761
+ .addHelpText('after', jsonHelp('`Task` object', '`{ taskId, status, ttl, createdAt, lastUpdatedAt, statusMessage?, pollInterval? }`', `${SCHEMA_BASE}#task`))
593
762
  .action(async (taskId, _options, command) => {
594
763
  await tasks.getTask(session, taskId, getOptionsFromCommand(command));
595
764
  });
765
+ program
766
+ .command('tasks-result <taskId>')
767
+ .description('Get MCP task final result (blocks until task reaches a terminal state).')
768
+ .addHelpText('after', toolsCallJsonHelp)
769
+ .action(async (taskId, _options, command) => {
770
+ await tasks.getTaskResult(session, taskId, getOptionsFromCommand(command));
771
+ });
596
772
  program
597
773
  .command('tasks-cancel <taskId>')
598
- .description('Cancel a running task')
774
+ .description('Cancel an MCP task.')
775
+ .addHelpText('after', jsonHelp('`Task` object', '`{ taskId, status, ttl, createdAt, lastUpdatedAt, statusMessage?, pollInterval? }`', `${SCHEMA_BASE}#task`))
599
776
  .action(async (taskId, _options, command) => {
600
777
  await tasks.cancelTask(session, taskId, getOptionsFromCommand(command));
601
778
  });
602
779
  program
603
780
  .command('resources')
604
- .description('List available resources (shorthand for resources-list)')
781
+ .description('List MCP resources (shorthand for resources-list).')
782
+ .addHelpText('after', jsonHelp('Array of `Resource` objects', '`[{ uri, name?, description?, mimeType? }, ...]`', `${SCHEMA_BASE}#resource`))
605
783
  .action(async (_options, command) => {
606
784
  await resources.listResources(session, getOptionsFromCommand(command));
607
785
  });
608
786
  program
609
787
  .command('resources-list')
610
- .description('List available resources')
788
+ .description('List all MCP resources.')
789
+ .addHelpText('after', jsonHelp('Array of `Resource` objects', '`[{ uri, name?, description?, mimeType? }, ...]`', `${SCHEMA_BASE}#resource`))
611
790
  .action(async (_options, command) => {
612
791
  await resources.listResources(session, getOptionsFromCommand(command));
613
792
  });
614
793
  program
615
794
  .command('resources-read <uri>')
616
- .description('Get a resource by URI')
795
+ .description('Read an MCP resource by URI.')
617
796
  .option('-o, --output <file>', 'Write resource to file')
618
797
  .option('--max-size <bytes>', 'Maximum resource size in bytes')
798
+ .addHelpText('after', jsonHelp('`ReadResourceResult` object', '`{ contents: [{ uri, mimeType?, text? | blob? }] }`', `${SCHEMA_BASE}#readresourceresult`))
619
799
  .action(async (uri, options, command) => {
620
800
  await resources.getResource(session, uri, {
621
801
  output: options.output,
@@ -625,37 +805,51 @@ function registerSessionCommands(program, session) {
625
805
  });
626
806
  program
627
807
  .command('resources-subscribe <uri>')
628
- .description('Subscribe to resource updates')
808
+ .description('Subscribe to MCP resource updates.')
809
+ .addHelpText('after', jsonHelp('`{ subscribed: true, uri: string }`'))
629
810
  .action(async (uri, _options, command) => {
630
811
  await resources.subscribeResource(session, uri, getOptionsFromCommand(command));
631
812
  });
632
813
  program
633
814
  .command('resources-unsubscribe <uri>')
634
- .description('Unsubscribe from resource updates')
815
+ .description('Unsubscribe from MCP resource updates.')
816
+ .addHelpText('after', jsonHelp('`{ unsubscribed: true, uri: string }`'))
635
817
  .action(async (uri, _options, command) => {
636
818
  await resources.unsubscribeResource(session, uri, getOptionsFromCommand(command));
637
819
  });
638
820
  program
639
821
  .command('resources-templates-list')
640
- .description('List available resource templates')
822
+ .description('List MCP resource templates.')
823
+ .addHelpText('after', jsonHelp('Array of `ResourceTemplate` objects', '`[{ uriTemplate, name?, description?, mimeType? }, ...]`', `${SCHEMA_BASE}#resourcetemplate`))
641
824
  .action(async (_options, command) => {
642
825
  await resources.listResourceTemplates(session, getOptionsFromCommand(command));
643
826
  });
644
827
  program
645
828
  .command('prompts')
646
- .description('List available prompts (shorthand for prompts-list)')
829
+ .description('List MCP prompts (shorthand for prompts-list).')
830
+ .addHelpText('after', jsonHelp('Array of `Prompt` objects', '`[{ name, description?, arguments?: [{ name, required? }] }, ...]`', `${SCHEMA_BASE}#prompt`))
647
831
  .action(async (_options, command) => {
648
832
  await prompts.listPrompts(session, getOptionsFromCommand(command));
649
833
  });
650
834
  program
651
835
  .command('prompts-list')
652
- .description('List available prompts')
836
+ .description('List all MCP prompts.')
837
+ .addHelpText('after', jsonHelp('Array of `Prompt` objects', '`[{ name, description?, arguments?: [{ name, required? }] }, ...]`', `${SCHEMA_BASE}#prompt`))
653
838
  .action(async (_options, command) => {
654
839
  await prompts.listPrompts(session, getOptionsFromCommand(command));
655
840
  });
656
841
  program
657
842
  .command('prompts-get <name> [args...]')
658
- .description('Get a prompt by name with arguments (key:=value pairs or JSON)')
843
+ .description('Get an MCP prompt with arguments.')
844
+ .addHelpText('after', `
845
+ ${chalk.bold('Arguments:')}
846
+ key:=value pairs mcpc ${session} prompts-get summarize style:=brief lang:=en
847
+ Inline JSON mcpc ${session} prompts-get summarize '{"style":"brief"}'
848
+ Stdin pipe echo '{"style":"brief"}' | mcpc ${session} prompts-get summarize
849
+
850
+ Values are auto-parsed: strings, numbers, booleans, JSON objects/arrays.
851
+ To force a string, wrap in quotes: id:='"123"'
852
+ ${jsonHelp('`GetPromptResult` object', '`{ description?, messages: [{ role, content: { type, text?, ... } }] }`', `${SCHEMA_BASE}#getpromptresult`)}`)
659
853
  .action(async (name, args, _options, command) => {
660
854
  await prompts.getPrompt(session, name, {
661
855
  args,
@@ -664,63 +858,47 @@ function registerSessionCommands(program, session) {
664
858
  });
665
859
  program
666
860
  .command('logging-set-level <level>')
667
- .description('Set server logging level (debug, info, notice, warning, error, critical, alert, emergency)')
861
+ .description('Set MCP server logging level.')
862
+ .addHelpText('after', jsonHelp('`{ level: string }`'))
668
863
  .action(async (level, _options, command) => {
669
864
  await logging.setLogLevel(session, level, getOptionsFromCommand(command));
670
865
  });
671
866
  program
672
867
  .command('ping')
673
- .description('Ping the MCP server to check if it is alive')
868
+ .description('Ping the MCP server.')
869
+ .addHelpText('after', jsonHelp('`{ success: true, durationMs: number }`'))
674
870
  .action(async (_options, command) => {
675
871
  await utilities.ping(session, getOptionsFromCommand(command));
676
872
  });
677
- program
678
- .command('grep <pattern>')
679
- .description('Search tools and instructions')
680
- .option('--tools', 'Search tools')
681
- .option('--resources', 'Search resources')
682
- .option('--prompts', 'Search prompts')
683
- .option('--instructions', 'Search server instructions')
684
- .option('-E, --regex', 'Treat pattern as a regular expression')
685
- .option('-s, --case-sensitive', 'Case-sensitive matching')
686
- .option('-m, --max-results <n>', 'Limit the number of results')
687
- .action(async (pattern, opts, command) => {
688
- const globalOpts = getOptionsFromCommand(command);
689
- const maxResults = opts.maxResults ? parseInt(opts.maxResults, 10) : undefined;
690
- const exitCode = await grepCmd.grepSession(session, pattern, {
691
- tools: opts.tools,
692
- resources: opts.resources,
693
- prompts: opts.prompts,
694
- instructions: opts.instructions,
695
- regex: opts.regex,
696
- caseSensitive: opts.caseSensitive,
697
- maxResults,
698
- ...globalOpts,
699
- });
700
- process.exit(exitCode);
701
- });
702
873
  }
703
874
  function createSessionProgram() {
704
875
  const program = new Command();
705
876
  program.configureOutput({
706
- outputError: (str, write) => write(str),
877
+ outputError: () => { },
707
878
  getOutHelpWidth: () => 100,
708
879
  getErrHelpWidth: () => 100,
709
880
  });
881
+ program.configureHelp({
882
+ subcommandTerm: (cmd) => `${cmd.name()} ${cmd.usage()}`.replace(/^\[options\]\s*|\s*\[options\]/g, '').trim(),
883
+ styleTitle: (str) => chalk.bold(str),
884
+ styleSubcommandText: (str) => chalk.cyan(str),
885
+ });
710
886
  program
711
887
  .name('mcpc <@session>')
888
+ .description('Execute MCP commands on a connected session.')
712
889
  .helpOption('-h, --help', 'Display help')
713
- .option('-j, --json', 'Output in JSON format for scripting and code mode')
890
+ .option('--json', 'Output in JSON format for scripting and code mode')
714
891
  .option('--verbose', 'Enable debug logging')
715
892
  .option('--profile <name>', 'OAuth profile override')
716
- .option('--schema <file>', 'Validate tool/prompt schema against expected schema')
717
- .option('--schema-mode <mode>', 'Schema validation mode: strict, compatible (default), ignore')
718
893
  .option('--timeout <seconds>', 'Request timeout in seconds (default: 300)')
719
- .option('--insecure', 'Skip TLS certificate verification (for self-signed certs)');
894
+ .option('--max-chars <n>', 'Truncate output to n characters (ignored in --json mode)')
895
+ .option('--insecure', 'Skip TLS certificate verification (for self-signed certs)')
896
+ .addHelpText('after', `\nWhen no command is given, shows server info, capabilities, and tools.\n`);
720
897
  return program;
721
898
  }
722
899
  async function handleSessionCommands(session, args) {
723
- if (!hasSubcommand(args)) {
900
+ const argsSlice = args.slice(2);
901
+ if (!hasSubcommand(args) && !argsSlice.includes('--help') && !argsSlice.includes('-h')) {
724
902
  const options = extractOptions(args);
725
903
  if (options.verbose)
726
904
  setVerbose(true);
@@ -734,13 +912,37 @@ async function handleSessionCommands(session, args) {
734
912
  return;
735
913
  }
736
914
  const program = createSessionProgram();
915
+ program.exitOverride();
737
916
  registerSessionCommands(program, session);
917
+ for (const cmd of program.commands) {
918
+ tuneCommandHelp(cmd);
919
+ }
738
920
  try {
739
921
  await program.parseAsync(args);
740
922
  }
741
923
  catch (error) {
742
924
  const opts = program.opts();
743
925
  const outputMode = opts.json ? 'json' : 'human';
926
+ if (error instanceof CommanderError && error.code === 'commander.unknownCommand') {
927
+ const unknownCmd = args.find((a, i) => i >= 2 && !a.startsWith('-') && !KNOWN_SESSION_COMMANDS.includes(a));
928
+ if (unknownCmd) {
929
+ const suggestion = suggestCommand(unknownCmd, KNOWN_SESSION_COMMANDS);
930
+ if (outputMode === 'json') {
931
+ console.error(formatJsonError(new Error(`Unknown command: ${unknownCmd}`), 1));
932
+ }
933
+ else {
934
+ console.error(`Error: Unknown command: ${unknownCmd}`);
935
+ if (suggestion) {
936
+ console.error(`\nDid you mean: mcpc ${session} ${suggestion}`);
937
+ }
938
+ console.error(`Run "mcpc ${session} --help" for available commands.\n`);
939
+ }
940
+ process.exit(1);
941
+ }
942
+ }
943
+ if (error instanceof CommanderError && error.code === 'commander.helpDisplayed') {
944
+ process.exit(0);
945
+ }
744
946
  if (isMcpError(error)) {
745
947
  if (outputMode === 'json') {
746
948
  console.error(formatJsonError(error, error.code));