feat(codex): support user-configured MCP servers in remote sessions (#1789)

* feat(codex): load user MCP servers in HAPI sessions

* feat(codex): proxy Windows MCP command shims

* fix(codex): address MCP review feedback

* fix(codex): use HAPI-owned MCP proxy

* fix(codex): preserve Windows MCP argv boundaries

* fix(codex): keep remote MCP placement unchanged
This commit is contained in:
Ananovo
2026-09-09 09:26:43 +08:00
committed by GitHub
parent 66ea35963e
commit 5c5c8b3a9f
18 changed files with 941 additions and 13 deletions
+34 -1
View File
@@ -21,6 +21,12 @@ import {
buildTurnStartParams,
type CodexContextManagementConfig
} from './utils/appServerConfig';
import {
extractCodexMcpServers,
mergeCodexMcpServers,
type CodexMcpServersConfig
} from './utils/codexMcpServers';
import { prepareCodexMcpServers } from './utils/codexMcpProxy';
import type { SkillMetadata, ThreadGoal, ThreadGoalStatus } from './appServerTypes';
import { shouldIgnoreTerminalEvent } from './utils/terminalEventGuard';
import { parseCodexSpecialCommand } from './codexSpecialCommands';
@@ -232,6 +238,7 @@ class CodexRemoteLauncher extends RemoteLauncherBase {
private reasoningProcessor: ReasoningProcessor | null = null;
private diffProcessor: DiffProcessor | null = null;
private happyServer: HappyServer | null = null;
private mcpProxyCleanup: (() => Promise<void>) | null = null;
private abortController: AbortController = new AbortController();
/** Invalidates queued-message steer handlers after abort or cleanup. */
private steerEpoch = 0;
@@ -3528,7 +3535,7 @@ class CodexRemoteLauncher extends RemoteLauncherBase {
failPendingAgentStartsForSpawnArgumentError(spawnAgentError);
});
const { server: happyServer, mcpServers } = await buildHapiMcpBridge(session.client, {
const { server: happyServer, mcpServers: hapiMcpServers } = await buildHapiMcpBridge(session.client, {
// In app-server/collab mode, child agents share this MCP bridge.
// If the MCP handler writes the title directly, child title calls
// leak into the parent HAPI session. Defer the side effect until
@@ -3537,6 +3544,7 @@ class CodexRemoteLauncher extends RemoteLauncherBase {
emitTitleSummary: false
});
this.happyServer = happyServer;
let mcpServers: CodexMcpServersConfig = hapiMcpServers;
this.setupAbortHandlers(session.client.rpcHandlerManager, {
onAbort: () => this.handleAbort(),
@@ -3576,6 +3584,22 @@ class CodexRemoteLauncher extends RemoteLauncherBase {
cwd: session.path,
includeLayers: false
})).config;
try {
const userMcpServers = extractCodexMcpServers(effectiveConfig);
const preparedMcpServers = await prepareCodexMcpServers(userMcpServers);
this.mcpProxyCleanup = preparedMcpServers.cleanup;
mcpServers = mergeCodexMcpServers(preparedMcpServers.servers, hapiMcpServers);
if (Object.keys(userMcpServers).length > 0) {
logger.debug(`[Codex] Loaded ${Object.keys(userMcpServers).length} user MCP server(s)`);
}
if (preparedMcpServers.proxiedServerNames.length > 0) {
logger.debug(
`[Codex] Added stdio compatibility proxy for ${preparedMcpServers.proxiedServerNames.length} Windows MCP server(s)`
);
}
} catch (error) {
logger.warn(`[Codex] Failed to merge user MCP servers; using HAPI bridge only: ${errorMessage(error)}`);
}
const modelContextWindow = effectiveConfig.model_context_window;
const modelAutoCompactTokenLimit = effectiveConfig.model_auto_compact_token_limit;
contextManagementConfig = {
@@ -4332,6 +4356,15 @@ class CodexRemoteLauncher extends RemoteLauncherBase {
this.happyServer = null;
}
if (this.mcpProxyCleanup) {
try {
await this.mcpProxyCleanup();
} catch (error) {
logger.debug('[codex-remote]: Error cleaning MCP proxy specs', error);
}
this.mcpProxyCleanup = null;
}
this.permissionHandler?.reset();
this.reasoningProcessor?.abort();
this.diffProcessor?.reset();