feat: add MCP config and system prompt support for Codex local mode

Implements MCP server configuration and developer instructions support in
Codex local mode, bringing it to parity with remote mode.

Key additions:
- buildMcpServerConfigArgs() and buildDeveloperInstructionsArg() utilities
  to construct -c config arguments for passing MCP servers and instructions
  to the Codex CLI at runtime
- TITLE_INSTRUCTION for Codex to call hapi__change_title to update chat
  session titles dynamically
- Codex local mode now starts hapi MCP bridge server and passes both MCP
  configuration and developer instructions to Claude, enabling full feature
  parity with remote mode

Files changed:
- New: codexMcpConfig.ts (utilities), systemPrompt.ts (prompt definition),
  codexMcpConfig.test.ts (comprehensive tests)
- Updated: codexLocal.ts (accepts mcpServers, builds config args),
  codexLocalLauncher.ts (starts hapi server, passes config),
  codexStartConfig.ts (imports TITLE_INSTRUCTION)
This commit is contained in:
weishu
2026-01-05 18:38:58 +08:00
parent 45822afa49
commit c50621e85a
8 changed files with 214 additions and 7 deletions
+17 -1
View File
@@ -5,12 +5,25 @@ import { Future } from '@/utils/future';
import { createCodexSessionScanner } from './utils/codexSessionScanner';
import { convertCodexEvent } from './utils/codexEventConverter';
import { getLocalLaunchExitReason } from '@/agent/localLaunchPolicy';
import { startHappyServer } from '@/claude/utils/startHappyServer';
import { getHappyCliCommand } from '@/utils/spawnHappyCLI';
export async function codexLocalLauncher(session: CodexSession): Promise<'switch' | 'exit'> {
let exitReason: 'switch' | 'exit' | null = null;
const processAbortController = new AbortController();
const exitFuture = new Future<void>();
// Start hapi server for MCP bridge (same as remote mode)
const happyServer = await startHappyServer(session.client);
const bridgeCommand = getHappyCliCommand(['mcp', '--url', happyServer.url]);
const mcpServers = {
hapi: {
command: bridgeCommand.command,
args: bridgeCommand.args
}
};
logger.debug(`[codex-local]: Started hapi MCP bridge server at ${happyServer.url}`);
const handleSessionMatchFailed = (message: string) => {
logger.warn(`[codex-local]: ${message}`);
session.sendSessionEvent({ type: 'message', message });
@@ -100,7 +113,8 @@ export async function codexLocalLauncher(session: CodexSession): Promise<'switch
sessionId: session.sessionId,
onSessionFound: handleSessionFound,
abort: processAbortController.signal,
codexArgs: session.codexArgs
codexArgs: session.codexArgs,
mcpServers
});
if (!exitReason) {
@@ -131,6 +145,8 @@ export async function codexLocalLauncher(session: CodexSession): Promise<'switch
session.client.rpcHandlerManager.registerHandler('switch', async () => {});
session.queue.setOnMessage(null);
await scanner.cleanup();
happyServer.stop();
logger.debug('[codex-local]: Stopped hapi MCP bridge server');
}
return exitReason || 'exit';