From 9401bebe727a4addf40ae1a8ce4f0e75fe37ab6a Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 08:13:26 +0000 Subject: [PATCH 1/3] Show server description and website URL in session info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A server can advertise `description` and `websiteUrl` in its `serverInfo`, and they are the only human-readable statement of what it is and where it is documented — but they were a `--json`-only detail. Print them under the server name in `mcpc @session` and `server-discover`. Also, while looking at the same screens: - `mcpc help tools/list` (and any other MCP method name) now resolves the alias instead of reporting an unknown command - trimmed the `server-discover` help notes and un-indented the `ping` / `server-discover` footers, which read as part of the block above them Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HYhygKZPPuddYpG67y7c1N --- CHANGELOG.md | 8 ++++ src/cli/commands/utilities.ts | 4 +- src/cli/index.ts | 20 ++++++--- src/cli/output.ts | 39 ++++++++++++++--- test/e2e/server/index-v2.ts | 2 + test/e2e/server/index.ts | 2 + test/e2e/suites/basic/help.test.sh | 10 +++++ test/e2e/suites/basic/human-output.test.sh | 7 +++ test/unit/cli/output.test.ts | 51 ++++++++++++++++++++++ 9 files changed, 127 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd7174e0..a156e908 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- `mcpc @session` and `server-discover` now show the description and website URL a server advertises about itself, right below its name. + +### Fixed + +- `mcpc help tools/list` and other MCP method names now show the command's help instead of failing with "Unknown command" — they already worked as aliases everywhere else. + ## [0.6.0] - 2026-08-02 ### Added diff --git a/src/cli/commands/utilities.ts b/src/cli/commands/utilities.ts index 9eef7bea..632d7fc9 100644 --- a/src/cli/commands/utilities.ts +++ b/src/cli/commands/utilities.ts @@ -28,9 +28,9 @@ export async function ping(target: string, options: CommandOptions): Promise chalk.gray(line))); + } + + if (serverInfo.websiteUrl) { + lines.push(theme.cyan(serverInfo.websiteUrl)); + } + + return lines; +} + /** * Format the result of a live `server/discover` request (2026-07-28+). * @@ -1692,9 +1719,7 @@ export function formatDiscoverResult( const serverInfo = result._meta?.[SERVER_INFO_META_KEY] as Implementation | undefined; if (serverInfo) { - lines.push( - chalk.bold('Server:') + ` ${serverInfo.name} (version: ${serverInfo.version || 'N/A'})` - ); + lines.push(...formatServerIdentity(serverInfo)); lines.push(''); } @@ -1730,7 +1755,9 @@ export function formatDiscoverResult( lines.push(''); } - lines.push(chalk.dim(` ↳ session info and available commands: mcpc ${target}`)); + // Plain footer rather than an indented "↳" hint: this block ends with the server's + // instructions, which can run for pages, so an indented arrow reads as part of them. + lines.push(chalk.dim(`For session info and available commands, run: mcpc ${target}`)); return lines.join('\n'); } @@ -1769,9 +1796,7 @@ export function formatServerDetails( // Server info if (serverInfo) { - lines.push( - chalk.bold('Server:') + ` ${serverInfo.name} (version: ${serverInfo.version || 'N/A'})` - ); + lines.push(...formatServerIdentity(serverInfo)); lines.push(''); } diff --git a/test/e2e/server/index-v2.ts b/test/e2e/server/index-v2.ts index 141fd942..a2255399 100644 --- a/test/e2e/server/index-v2.ts +++ b/test/e2e/server/index-v2.ts @@ -114,6 +114,8 @@ function createTestServer(): Server { { name: 'e2e-test-server', version: '2.0.0', + description: 'A fake MCP server that exists only to exercise the mcpc CLI.', + websiteUrl: 'https://example.com/e2e-test-server', }, { capabilities, diff --git a/test/e2e/server/index.ts b/test/e2e/server/index.ts index 20a242e3..ad33863d 100644 --- a/test/e2e/server/index.ts +++ b/test/e2e/server/index.ts @@ -182,6 +182,8 @@ function createMcpServer(): Server { { name: 'e2e-test-server', version: '1.0.0', + description: 'A fake MCP server that exists only to exercise the mcpc CLI.', + websiteUrl: 'https://example.com/e2e-test-server', }, { capabilities, diff --git a/test/e2e/suites/basic/help.test.sh b/test/e2e/suites/basic/help.test.sh index bee7b1c3..081e5f2b 100755 --- a/test/e2e/suites/basic/help.test.sh +++ b/test/e2e/suites/basic/help.test.sh @@ -61,6 +61,16 @@ assert_not_contains "$STDOUT" "Mental model" assert_contains "$STDERR" "takes no command name" test_pass +# Test: mcpc help accepts raw MCP method names, like the command slot itself does +test_case "help resolves slash-style method names" +run_mcpc help server/discover +assert_success +assert_contains "$STDOUT" "mcpc <@session> server-discover" +run_mcpc help tools/list +assert_success +assert_contains "$STDOUT" "mcpc <@session> tools-list" +test_pass + # Test: --version shows version test_case "--version shows version" run_mcpc --version diff --git a/test/e2e/suites/basic/human-output.test.sh b/test/e2e/suites/basic/human-output.test.sh index b4590f6f..43e7626a 100755 --- a/test/e2e/suites/basic/human-output.test.sh +++ b/test/e2e/suites/basic/human-output.test.sh @@ -180,6 +180,13 @@ assert_success assert_contains "$STDOUT" "e2e-test-server" test_pass +test_case "server info contains the advertised description and website" +run_mcpc "$SESSION" +assert_success +assert_contains "$STDOUT" "A fake MCP server that exists only to exercise the mcpc CLI." +assert_contains "$STDOUT" "https://example.com/e2e-test-server" +test_pass + test_case "server info contains capabilities" run_mcpc "$SESSION" assert_success diff --git a/test/unit/cli/output.test.ts b/test/unit/cli/output.test.ts index 5c268582..dee35e83 100644 --- a/test/unit/cli/output.test.ts +++ b/test/unit/cli/output.test.ts @@ -1061,6 +1061,38 @@ describe('formatServerDetails', () => { expect(output).toContain('This is the server instructions.'); }); + it('shows the description and website URL a server advertises in serverInfo', () => { + const details: ServerDetails = { + protocolVersion: '2025-11-25', + capabilities: { tools: {} }, + serverInfo: { + name: 'Notion MCP', + version: '1.2.0', + description: "Notion's official MCP server.\nUse your workspace as a system of record.", + websiteUrl: 'https://developers.notion.com/docs/mcp', + }, + }; + + const output = formatServerDetails(details, '@notion'); + + expect(output).toContain('Notion MCP (version: 1.2.0)'); + expect(output).toContain("Notion's official MCP server."); + expect(output).toContain('Use your workspace as a system of record.'); + expect(output).toContain('https://developers.notion.com/docs/mcp'); + }); + + it('keeps the server line alone when no description or website URL is advertised', () => { + const details: ServerDetails = { + protocolVersion: '2025-11-25', + capabilities: { tools: {} }, + serverInfo: { name: 'Bare Server', version: '1.0.0' }, + }; + + const output = formatServerDetails(details, '@bare'); + + expect(output).toContain('Server: Bare Server (version: 1.0.0)\n\nCapabilities:'); + }); + it('shows the MCP version with the transport and its connection mode', () => { const details: ServerDetails = { protocolVersion: '2026-07-28', @@ -1438,6 +1470,25 @@ describe('formatDiscoverResult', () => { expect(output).toContain('mcpc @m'); }); + it('shows the description and website URL from the advertised identity', () => { + const described = { + ...result, + _meta: { + 'io.modelcontextprotocol/serverInfo': { + name: 'Modern Server', + version: '2.0.0', + description: 'A server that describes itself.', + websiteUrl: 'https://example.com/docs', + }, + }, + } as Parameters[0]; + + const output = formatDiscoverResult(described, '@m', '2026-07-28'); + + expect(output).toContain('A server that describes itself.'); + expect(output).toContain('https://example.com/docs'); + }); + it('marks the version this session negotiated', () => { const output = formatDiscoverResult(result, '@m', '2026-07-28'); expect(output).toMatch(/2026-07-28 \(negotiated\)/); From 24d6d0ee566d57dc27482e4e3b51e5acae19baba Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 09:23:32 +0000 Subject: [PATCH 2/3] =?UTF-8?q?Reserve=20the=20"=E2=86=B3"=20hint=20for=20?= =?UTF-8?q?one-line=20situations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The arrow means "this points at the line above", so it only works where there is a single line to point at — a session line in a list, a one-line status or empty state. Used as a footer under a multi-line block it reads as part of the block instead, which is what made the `ping` and `server-discover` footers look wrong. Hints closing a block are now plain unindented sentences ("To check sync status, run: ..."), and standalone arrow hints sit at the column of the line they follow; hints under list items keep their indent. CLAUDE.md documents the two shapes. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HYhygKZPPuddYpG67y7c1N --- CLAUDE.md | 6 +++++- src/cli/commands/grep.ts | 2 +- src/cli/commands/resources.ts | 10 ++++++---- src/cli/commands/sessions.ts | 4 ++-- src/cli/commands/x402.ts | 4 ++-- src/cli/output.ts | 2 +- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 48406a73..e21a82c0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -654,7 +654,11 @@ When implementing features: - **One line per description** - Every `.description()` and `.option()` description must fit on a single line of help output. Help width is 100 columns and the term column eats 30–40 of them, so keep descriptions under ~55 characters; anything longer wraps and makes the command list unreadable. Caveats, deprecations, protocol-era limits, defaults, and examples belong in a titled `.addHelpText('after', ...)` section (`Notes:`, `Deprecated:`, `Examples:`), or point at one with `(see below)`. Never restate a default Commander already prints: `.option('--scheme ', 'desc', 'auto')` appends `(default: "auto")` on its own. Enforced — together with the JSON-output rule below — by `test/e2e/suites/basic/help.test.sh`, which walks the whole help surface; run it after touching help text. - **Document the JSON output** - Every command that prints JSON must describe its `--json` shape with `jsonHelp()` in its help text, including the session program itself (`mcpc @session --help`, which documents the no-command server-info output). A command whose help has no `JSON output (--json):` section is a bug. - **Set the help width** - Any new Commander program must `configureOutput({ getOutHelpWidth: () => 100, getErrHelpWidth: () => 100 })` like the existing ones, otherwise it wraps at the default 80 columns. -2. **Next-step hints** - Every command's human-mode output should make it clear what the user or agent might want to do next. After listing items or finishing an action, print a dim hint suggesting the next likely command using the format `chalk.dim(' ↳ : mcpc ')` with the `↳` arrow prefix. Examples: after `mcpc` lists sessions, hint how to view details (`↳ view a session: mcpc @sessionname`); after `mcpc connect` skips stdio servers, hint how to include them (`↳ run: mcpc connect --stdio`); after recoverable session states, hint the recovery command (`↳ run: mcpc @sessionname restart`). The goal is that any user or agent can chain commands without consulting `--help`. Do not emit hints in `--json` mode — JSON output stays strictly machine-readable. +2. **Next-step hints** - Every command's human-mode output should make it clear what the user or agent might want to do next. After listing items or finishing an action, print a dim hint suggesting the next likely command, so that any user or agent can chain commands without consulting `--help`. Two shapes, picked by what the hint hangs off: + - **`↳` arrow, for one-line situations only** - when the hint belongs to a single line right above it: a session line in a list, a one-line status, an empty state. Indent it under that line when the line is itself an item in a list (`chalk.dim(' ↳ run: mcpc @sessionname restart')` under a session in `mcpc`), otherwise keep it at the same column as the line it follows (`chalk.dim('↳ save to a file: ...')` under `(binary content not shown)`). The arrow means "this points at the line above" — never use it as a footer for a whole screen, where there is no single line to point at. + - **Plain sentence, for everything else** - a hint closing a multi-line block or a whole screen reads as part of that block when arrowed and indented. Use an unindented dim sentence instead: `chalk.dim('For session logs, run: mcpc @sessionname logs')`, `chalk.dim('To stop syncing (keeps the file), run: ...')`. + + Do not emit hints in `--json` mode — JSON output stays strictly machine-readable. 3. **Keep core runtime-agnostic** - Use native APIs, avoid runtime-specific dependencies 4. **Error handling** - Provide clear, actionable error messages; use appropriate exit codes 5. **Retry logic** - Use exponential backoff for network operations (3 attempts for requests, 1s→30s for streams) diff --git a/src/cli/commands/grep.ts b/src/cli/commands/grep.ts index 29216315..5b1c9362 100644 --- a/src/cli/commands/grep.ts +++ b/src/cli/commands/grep.ts @@ -577,7 +577,7 @@ export async function grepAllSessions(pattern: string, options: GrepOptions): Pr ); } else { console.log(chalk.bold('No active sessions.')); - console.log(chalk.dim(' \u21B3 run: mcpc connect mcp.example.com @test')); + console.log(chalk.dim('\u21B3 run: mcpc connect mcp.example.com @test')); } return 1; } diff --git a/src/cli/commands/resources.ts b/src/cli/commands/resources.ts index af2b7020..790bb0d4 100644 --- a/src/cli/commands/resources.ts +++ b/src/cli/commands/resources.ts @@ -164,11 +164,13 @@ export async function subscribeResource( if (options.outputMode === 'human') { const mimeSuffix = result.mimeType ? `, ${result.mimeType}` : ''; console.log(formatSuccess(`Subscribed to resource: ${uri}`)); - console.log(` Synced to ${result.file} (${result.bytes} bytes${mimeSuffix})`); - console.log(chalk.dim(' The file is updated automatically while the session is connected.')); - console.log(chalk.dim(` ↳ check sync status: mcpc ${target}`)); + console.log(`Synced to ${result.file} (${result.bytes} bytes${mimeSuffix})`); + console.log(chalk.dim('The file is updated automatically while the session is connected.')); + console.log(chalk.dim(`To check sync status, run: mcpc ${target}`)); console.log( - chalk.dim(` ↳ stop syncing (keeps the file): mcpc ${target} resources-unsubscribe ${uri}`) + chalk.dim( + `To stop syncing (keeps the file), run: mcpc ${target} resources-unsubscribe ${uri}` + ) ); } else { console.log(formatOutput({ subscribed: true, ...result }, 'json')); diff --git a/src/cli/commands/sessions.ts b/src/cli/commands/sessions.ts index 63d451b6..96cd022e 100644 --- a/src/cli/commands/sessions.ts +++ b/src/cli/commands/sessions.ts @@ -161,7 +161,7 @@ export async function listSessionsAndAuthProfiles(options: { // Display sessions if (sessions.length === 0) { console.log(chalk.bold('No active MCP sessions.')); - console.log(chalk.dim(' ↳ run: mcpc connect mcp.example.com @test')); + console.log(chalk.dim('↳ run: mcpc connect mcp.example.com @test')); } else { console.log(chalk.bold('MCP sessions:')); for (const session of sessions) { @@ -202,7 +202,7 @@ export async function listSessionsAndAuthProfiles(options: { console.log(''); if (profiles.length === 0) { console.log(chalk.bold('No OAuth profiles.')); - console.log(chalk.dim(' ↳ run: mcpc login mcp.example.com')); + console.log(chalk.dim('↳ run: mcpc login mcp.example.com')); } else { console.log(chalk.bold('Saved OAuth profiles:')); for (const profile of profiles) { diff --git a/src/cli/commands/x402.ts b/src/cli/commands/x402.ts index 97fda9ee..019c9362 100644 --- a/src/cli/commands/x402.ts +++ b/src/cli/commands/x402.ts @@ -178,7 +178,7 @@ async function walletInfo(options: { } else { console.log(formatInfo('No wallet configured. Create one with: mcpc x402 init')); if (options.showUsageHint) { - console.log(chalk.dim(' For usage information, run: mcpc help x402')); + console.log(chalk.dim('For usage information, run: mcpc help x402')); } } return; @@ -237,7 +237,7 @@ async function walletInfo(options: { await printAddressQrCode(wallet.address); if (options.showUsageHint) { console.log(''); - console.log(chalk.dim(' For usage information, run: mcpc help x402')); + console.log(chalk.dim('For usage information, run: mcpc help x402')); } } diff --git a/src/cli/output.ts b/src/cli/output.ts index b51d687d..2a635336 100644 --- a/src/cli/output.ts +++ b/src/cli/output.ts @@ -897,7 +897,7 @@ export function formatResourceContents( lines.push(chalk.gray('(binary content not shown)')); lines.push( chalk.dim( - ` ↳ save to a file: mcpc ${target} resources-read ${item.uri || requestedUri} -o ` + `↳ save to a file: mcpc ${target} resources-read ${item.uri || requestedUri} -o ` ) ); } else { From 6637683fd7f54352136f2a0c7982837cadb1612b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 2 Aug 2026 11:18:33 +0000 Subject: [PATCH 3/3] Regenerate REFERENCE.md after the server-discover help trim Refs #361 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HYhygKZPPuddYpG67y7c1N --- REFERENCE.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/REFERENCE.md b/REFERENCE.md index dbe45e1a..6b2b2feb 100644 --- a/REFERENCE.md +++ b/REFERENCE.md @@ -958,13 +958,8 @@ Options: --json Output in JSON format Notes: - Sends `server/discover` and reports the answer: every protocol version the - server supports, its capabilities, instructions, and `_meta`. Unlike - `mcpc @`, which shows what the connection settled on at connect time, - this is a live request. - MCP 2026-07-28 introduced the method, so the command fails on 2025-11-25 (and - older) connections, where `initialize` carries the same data — run - `mcpc @` there instead. + A live `server/discover` request; `mcpc @` shows the cached connect-time + answer instead — use it on 2025-11-25 (and older) connections, where this fails. JSON output (--json): `DiscoverResult` object, verbatim: