diff --git a/CLAUDE.md b/CLAUDE.md index 71df4c3..9bd69af 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -612,17 +612,18 @@ When opening a pull request, always reference the originating issue or PR in the When implementing features: 1. **Self-documenting CLI** - All features, options, and usage patterns must be documented in command `--help` output (Commander.js `.description()` and `.addHelpText()`), not just in the README. AI agents discover how to use mcpc purely by running `mcpc --help` and `mcpc --help`, so help text is the primary documentation surface. Include examples in help text for non-obvious commands. The README can provide additional context but must not be the only place a feature is documented. -2. **Keep core runtime-agnostic** - Use native APIs, avoid runtime-specific dependencies -3. **Error handling** - Provide clear, actionable error messages; use appropriate exit codes -4. **Retry logic** - Use exponential backoff for network operations (3 attempts for requests, 1s→30s for streams) -5. **Concurrent safety** - Use file locking for shared state (`sessions.json`) -6. **Security** - Never log credentials (log `present`/`MISSING` instead); use OS keychain; enforce HTTPS; use `execFile()` not `exec()`; escape HTML output; validate Host headers on local servers; send secrets via IPC not CLI args; see "Security Considerations" section for full guidelines -7. **Output formatting** - Support both human-readable (default) and JSON (`--json`) modes -8. **Protocol compliance** - Follow MCP specification strictly; handle all notification types -9. **Session management** - Always clean up resources; handle orphaned processes; provide reconnection -10. **Hyphenated commands** - All MCP commands use hyphens: `tools-list`, `resources-read`, `prompts-list` -11. **Command-first syntax** - Top-level commands come first (`connect`, `login`, `clean`); MCP operations always go through a named session (`mcpc @session `) -12. **JSON field naming** - Use consistent field names in JSON output: +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. +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) +6. **Concurrent safety** - Use file locking for shared state (`sessions.json`) +7. **Security** - Never log credentials (log `present`/`MISSING` instead); use OS keychain; enforce HTTPS; use `execFile()` not `exec()`; escape HTML output; validate Host headers on local servers; send secrets via IPC not CLI args; see "Security Considerations" section for full guidelines +8. **Output formatting** - Support both human-readable (default) and JSON (`--json`) modes +9. **Protocol compliance** - Follow MCP specification strictly; handle all notification types +10. **Session management** - Always clean up resources; handle orphaned processes; provide reconnection +11. **Hyphenated commands** - All MCP commands use hyphens: `tools-list`, `resources-read`, `prompts-list` +12. **Command-first syntax** - Top-level commands come first (`connect`, `login`, `clean`); MCP operations always go through a named session (`mcpc @session `) +13. **JSON field naming** - Use consistent field names in JSON output: - `sessionName` (not `name`) for session identifiers - `server` (not `target`) for server URLs/addresses - No `success` wrapper - indicate errors via exit codes diff --git a/README.md b/README.md index 5c2aceb..1a89089 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,8 @@ many AI agents on the same machine. Authenticate once, reuse everywhere. ## Install +Requires a JavaScript runtime — install [Node.js](https://nodejs.org/en/download) (≥ 18) or [Bun](https://bun.sh) (≥ 1) if you don't have one yet. + ```bash npm install -g @apify/mcpc diff --git a/src/cli/commands/sessions.ts b/src/cli/commands/sessions.ts index 142083b..c80c610 100644 --- a/src/cli/commands/sessions.ts +++ b/src/cli/commands/sessions.ts @@ -673,8 +673,12 @@ export function formatTimeAgo(isoDate: string | undefined): string { if (diffHours < 24) return `${diffHours}h ago`; if (diffDays === 1) return 'yesterday'; if (diffDays < 7) return `${diffDays} days ago`; - if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`; - return `${Math.floor(diffDays / 30)} months ago`; + if (diffDays < 30) { + const weeks = Math.floor(diffDays / 7); + return `${weeks} ${weeks === 1 ? 'week' : 'weeks'} ago`; + } + const months = Math.floor(diffDays / 30); + return `${months} ${months === 1 ? 'month' : 'months'} ago`; } /** @@ -683,7 +687,7 @@ export function formatTimeAgo(isoDate: string | undefined): string { */ export async function listSessionsAndAuthProfiles(options: { outputMode: OutputMode; -}): Promise { +}): Promise<{ hasSessions: boolean }> { // Consolidate sessions first (cleans up crashed bridges, removes expired sessions) const consolidateResult = await consolidateSessions(false); const sessions = Object.values(consolidateResult.sessions); @@ -772,6 +776,8 @@ export async function listSessionsAndAuthProfiles(options: { } } } + + return { hasSessions: sessions.length > 0 }; } /** @@ -1411,11 +1417,14 @@ export async function connectAllFromStandardConfigs(options: BulkConnectOptions) } if (skippedStdio.length > 0) { parts.push( - `skipped ${skippedStdio.length} stdio server${skippedStdio.length === 1 ? '' : 's'}, pass --stdio to include` + `skipped ${skippedStdio.length} stdio server${skippedStdio.length === 1 ? '' : 's'}` ); } if (parts.length > 0) { console.log(theme.cyan(`\n${parts.join('. ')}.`)); + if (skippedStdio.length > 0) { + console.log(chalk.dim(' ↳ run: mcpc connect --stdio')); + } } } diff --git a/src/cli/index.ts b/src/cli/index.ts index 72efb0a..c844163 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -234,9 +234,16 @@ async function main(): Promise { if (!firstNonOption) { const { json } = extractOptions(args); if (json) setJsonMode(true); - await sessions.listSessionsAndAuthProfiles({ outputMode: json ? 'json' : 'human' }); + const { hasSessions } = await sessions.listSessionsAndAuthProfiles({ + outputMode: json ? 'json' : 'human', + }); if (!json) { - console.log('\nRun "mcpc --help" for usage information.\n'); + console.log(''); + if (hasSessions) { + console.log('To view server capabilities and tools, run: mcpc @session'); + } + console.log('For usage information, run: mcpc --help'); + console.log(''); } await closeFileLogger(); return; @@ -824,9 +831,16 @@ ${jsonHelp('`[{ sessionName, tools?: Tool[], resources?: Resource[], prompts?: P const opts = program.opts(); const json = opts.json || getJsonFromEnv(); if (json) setJsonMode(true); - await sessions.listSessionsAndAuthProfiles({ outputMode: json ? 'json' : 'human' }); + const { hasSessions } = await sessions.listSessionsAndAuthProfiles({ + outputMode: json ? 'json' : 'human', + }); if (!json) { - console.log('\nRun "mcpc --help" for usage information.\n'); + console.log(''); + if (hasSessions) { + console.log('To view server capabilities and tools, run: mcpc @session'); + } + console.log('For usage information, run: mcpc --help'); + console.log(''); } });