Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ Example: `mcpc @apify logging-set-level debug`

## Common Implementation Patterns

After making any code changes, always run `npm run lint` and fix **all** errors before committing. Do not skip or ignore lint failures. The lint command checks both ESLint rules and Prettier formatting. To auto-fix issues, run `npm run lint:fix`. If auto-fix doesn't resolve everything, manually fix the remaining errors. Never commit code that fails `npm run lint`.
After making any code changes, always run `npm run lint` and fix **all** errors before committing. Do not skip or ignore lint failures. The lint command checks both ESLint rules and Prettier formatting. To auto-fix issues, run `npm run lint:fix`. If auto-fix doesn't resolve everything, manually fix the remaining errors. Never commit code that fails `npm run lint`. **As the very last step of every task**, run `npm run lint` once more and fix any remaining issues before considering the work done.

After lint passes, run `npm test` (unit tests) and fix any failures before committing. If a test fails due to your changes, update the test or fix the code so all tests pass. Never commit code that fails unit tests.

Expand Down
4 changes: 2 additions & 2 deletions scripts/demo-output.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,15 @@ console.log(prompt('mcpc'));
console.log('MCP sessions:');
console.log(sessionLine('@playwright', 'npx @playwright/mcp@latest', 'stdio', 'live'));
console.log(sessionLine('@fs', 'npx -y @modelcontextprotocol/server-filesystem /Users/bob', 'stdio', 'live'));
console.log(sessionLine('@apify', 'https://mcp.apify.com', `HTTP, OAuth: ${chalk.magenta('default')}, MCP: 2025-11-25`, 'live'));
console.log(sessionLine('@apify', 'https://mcp.apify.com', `HTTP, OAuth: ${chalk.magenta('default')}`, 'live'));
console.log();
console.log('Available OAuth profiles:');
console.log(` mcp.apify.com / ${chalk.magenta('default')}, created 35m ago`);
console.log();

console.log(comment('List MCP server tools'));
console.log(prompt('mcpc @apify tools-list'));
// console.log(`[${chalk.cyan('@apify')} → https://mcp.apify.com ${chalk.dim(`(HTTP, OAuth: ${chalk.magenta('default')}, MCP: 2025-11-25)`)}]`);
// console.log(`[${chalk.cyan('@apify')} → https://mcp.apify.com ${chalk.dim(`(HTTP, OAuth: ${chalk.magenta('default')})`)}]`);
console.log();
console.log('Available tools (3):');
console.log(toolLine('search-actors', ['read-only', 'idempotent']));
Expand Down
26 changes: 20 additions & 6 deletions src/bridge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
cleanupOrphanedLogFiles,
isSessionExpiredError,
} from '../lib/index.js';
import { ClientError, NetworkError, isAuthenticationError } from '../lib/index.js';
import { ClientError, NetworkError, AuthError, isAuthenticationError } from '../lib/index.js';
import { getSession, loadSessions, updateSession } from '../lib/sessions.js';
import type { AuthCredentials, X402WalletCredentials } from '../lib/types.js';
import { OAuthTokenManager } from '../lib/auth/oauth-token-manager.js';
Expand Down Expand Up @@ -394,16 +394,23 @@ class BridgeProcess {
);
this.mcpClientReadyRejecter(error as Error);

// If the error was due to session ID rejection or auth failure, mark session as expired
// User must explicitly use 'mcpc @session restart' to start a new session
// If the error was due to session ID rejection or auth failure, mark session status
// User must explicitly use 'mcpc @session restart' or 'mcpc login' to recover
const errorMsg = (error as Error).message || '';
if (isSessionExpiredError(errorMsg) || isAuthenticationError(errorMsg)) {
logger.warn('Session rejected by server (expired or auth failure), marking as expired');
if (isSessionExpiredError(errorMsg)) {
logger.warn('Session rejected by server (expired session ID), marking as expired');
try {
await updateSession(this.options.sessionName, { status: 'expired' });
} catch (updateError) {
logger.error('Failed to mark session as expired:', updateError);
}
} else if (isAuthenticationError(errorMsg)) {
logger.warn('Server requires authentication, marking as unauthorized');
try {
await updateSession(this.options.sessionName, { status: 'unauthorized' });
} catch (updateError) {
logger.error('Failed to mark session as unauthorized:', updateError);
}
}

// Set up signal handlers so we can be killed
Expand Down Expand Up @@ -1239,7 +1246,14 @@ class BridgeProcess {
const message: IpcMessage = {
type: 'response',
error: {
code: error instanceof ClientError ? 1 : error instanceof NetworkError ? 3 : 2,
code:
error instanceof ClientError
? 1
: error instanceof NetworkError
? 3
: error instanceof AuthError
? 4
: 2,
message: error.message,
},
};
Expand Down
9 changes: 5 additions & 4 deletions src/cli/commands/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,16 +294,17 @@ export async function connectSession(
console.log(formatSuccess(`Session ${name} ${isReconnect ? 'reconnected' : 'created'}`));
}

// Display server info via the new session (best-effort)
// If bridge is still initializing, don't fail the connect — the session is already created.
// The next command (e.g. ping, tools-list) will wait for the bridge to be ready.
// Display server info via the new session (best-effort).
// showServerDetails blocks until the bridge is connected (via health check),
// so by the time it returns or throws, we have definitive bridge status.
// Re-throw auth errors (real failures requiring user action), but swallow others
// (TLS errors, timeouts, etc.) since the session was created and can be used later.
try {
await showServerDetails(name, {
...options,
hideTarget: false, // Show session info prefix
});
} catch (detailsError) {
// Re-throw auth errors — these are real failures, not timing issues
if (detailsError instanceof AuthError) {
throw detailsError;
}
Expand Down
13 changes: 1 addition & 12 deletions src/cli/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -924,11 +924,6 @@ export function formatSessionLine(session: SessionData): string {
}
}

// Add MCP protocol version if available
if (session.protocolVersion) {
parts.push('MCP: ' + session.protocolVersion);
}

const infoStr = chalk.dim('(') + chalk.dim(parts.join(', ')) + chalk.dim(')');

// Add proxy info separately (not dimmed, for visibility)
Expand All @@ -952,7 +947,6 @@ export interface LogTargetOptions {
hide?: boolean | undefined;
profileName?: string | undefined; // Auth profile being used (for http targets)
serverConfig?: ServerConfig | undefined; // Resolved transport config (for non-session targets)
protocolVersion?: string | undefined; // MCP protocol version (for direct connections)
}

/**
Expand All @@ -977,16 +971,14 @@ export async function logTarget(target: string, options: LogTargetOptions): Prom

// For direct connections, use transportConfig if available
const tc = options.serverConfig;
const mcpVersionStr = options.protocolVersion ? `, MCP: ${options.protocolVersion}` : '';

if (tc?.command) {
// Stdio transport: show command + args
let targetStr = tc.command;
if (tc.args && tc.args.length > 0) {
targetStr += ' ' + tc.args.join(' ');
}
targetStr = truncateWithEllipsis(targetStr, 80);
console.log(`[→ ${targetStr} ${chalk.dim(`(stdio${mcpVersionStr})`)}]`);
console.log(`[→ ${targetStr} ${chalk.dim('(stdio)')}]`);
return;
}

Expand All @@ -996,9 +988,6 @@ export async function logTarget(target: string, options: LogTargetOptions): Prom
if (options.profileName) {
parts.push('OAuth: ' + chalk.magenta(options.profileName));
}
if (options.protocolVersion) {
parts.push('MCP: ' + options.protocolVersion);
}
console.log(`[→ ${serverStr} ${chalk.dim('(' + parts.join(', ') + ')')}]\n`);
}

Expand Down
2 changes: 1 addition & 1 deletion test/unit/cli/output.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1293,7 +1293,7 @@ describe('formatSessionLine', () => {
expect(output).toContain('HTTP');
expect(output).toContain('OAuth');
expect(output).toContain('default');
expect(output).toContain('MCP: 2025-11-25');
expect(output).not.toContain('MCP:');
});

it('should format stdio session', () => {
Expand Down
Loading