From bf0ec80fb5a1af9d03914287c90fd7915e368592 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Tue, 23 Dec 2025 15:45:48 +0700 Subject: [PATCH] Move agent-control MCP config into AgentManager --- .../server/src/server/agent/agent-manager.ts | 8 +++ .../src/server/agent/agent-sdk-types.ts | 6 +++ .../server/src/server/agent/mcp-server.ts | 12 +---- .../server/agent/providers/claude-agent.ts | 17 ++----- .../agent/providers/codex-agent.test.ts | 2 +- .../src/server/agent/providers/codex-agent.ts | 7 +-- packages/server/src/server/index.ts | 16 +++--- packages/server/src/server/messages.ts | 6 +++ packages/server/src/server/session.ts | 49 +++---------------- 9 files changed, 41 insertions(+), 82 deletions(-) diff --git a/packages/server/src/server/agent/agent-manager.ts b/packages/server/src/server/agent/agent-manager.ts index 3ae6bdf77..bc8275d7c 100644 --- a/packages/server/src/server/agent/agent-manager.ts +++ b/packages/server/src/server/agent/agent-manager.ts @@ -18,6 +18,7 @@ import type { AgentTimelineItem, AgentUsage, AgentRuntimeInfo, + AgentControlMcpConfig, ListPersistedAgentsOptions, PersistedAgentDescriptor, } from "./agent-sdk-types.js"; @@ -54,6 +55,7 @@ export type AgentManagerOptions = { maxTimelineItems?: number; idFactory?: () => string; registry?: AgentRegistry; + agentControlMcp?: AgentControlMcpConfig; }; export type WaitForAgentOptions = { @@ -178,12 +180,14 @@ export class AgentManager { private readonly idFactory: () => string; private readonly registry?: AgentRegistry; private readonly previousStatuses = new Map(); + private readonly agentControlMcp?: AgentControlMcpConfig; constructor(options?: AgentManagerOptions) { this.maxTimelineItems = options?.maxTimelineItems ?? DEFAULT_MAX_TIMELINE_ITEMS; this.idFactory = options?.idFactory ?? (() => randomUUID()); this.registry = options?.registry; + this.agentControlMcp = options?.agentControlMcp; if (options?.clients) { for (const [provider, client] of Object.entries(options.clients)) { if (client) { @@ -982,6 +986,10 @@ export class AgentManager { normalized.model = trimmed.length > 0 ? trimmed : undefined; } + if (!normalized.agentControlMcp && this.agentControlMcp) { + normalized.agentControlMcp = this.agentControlMcp; + } + return normalized; } diff --git a/packages/server/src/server/agent/agent-sdk-types.ts b/packages/server/src/server/agent/agent-sdk-types.ts index e29392282..39e9a9cc3 100644 --- a/packages/server/src/server/agent/agent-sdk-types.ts +++ b/packages/server/src/server/agent/agent-sdk-types.ts @@ -131,6 +131,11 @@ export type AgentRuntimeInfo = { extra?: Record; }; +export type AgentControlMcpConfig = { + url: string; + headers?: Record; +}; + export type ListPersistedAgentsOptions = { limit?: number; }; @@ -156,6 +161,7 @@ export type AgentSessionConfig = { networkAccess?: boolean; webSearch?: boolean; reasoningEffort?: string; + agentControlMcp?: AgentControlMcpConfig; extra?: { codex?: Record; claude?: Partial; diff --git a/packages/server/src/server/agent/mcp-server.ts b/packages/server/src/server/agent/mcp-server.ts index 5e39ba4e6..f538c9140 100644 --- a/packages/server/src/server/agent/mcp-server.ts +++ b/packages/server/src/server/agent/mcp-server.ts @@ -33,10 +33,6 @@ export interface AgentMcpServerOptions { * When set, create_agent will auto-inject this as parentAgentId. */ callerAgentId?: string; - /** - * Agent-control MCP URL to inject for Codex agents spawned via MCP. - */ - agentControlMcpUrl?: string; } const AgentProviderEnum = z.enum( @@ -175,8 +171,7 @@ async function serializeSnapshotWithMetadata( export async function createAgentMcpServer( options: AgentMcpServerOptions ): Promise { - const { agentManager, agentRegistry, callerAgentId, agentControlMcpUrl } = - options; + const { agentManager, agentRegistry, callerAgentId } = options; const waitTracker = new WaitForAgentTracker(); const server = new McpServer({ @@ -279,17 +274,12 @@ export async function createAgentMcpServer( const normalizedTitle = title?.trim() ?? null; // Use explicit parentAgentId if provided, otherwise default to caller agent ID const resolvedParentAgentId = parentAgentId ?? callerAgentId; - const codexExtra = - provider === "codex" && agentControlMcpUrl - ? { codex: { agentControlMcpUrl } } - : undefined; const snapshot = await agentManager.createAgent({ provider, cwd: resolvedCwd, modeId: initialMode, title: normalizedTitle ?? undefined, parentAgentId: resolvedParentAgentId, - extra: codexExtra, }); if (initialPrompt) { diff --git a/packages/server/src/server/agent/providers/claude-agent.ts b/packages/server/src/server/agent/providers/claude-agent.ts index a773d1586..7679e8aa8 100644 --- a/packages/server/src/server/agent/providers/claude-agent.ts +++ b/packages/server/src/server/agent/providers/claude-agent.ts @@ -30,6 +30,7 @@ import type { AgentPromptInput, AgentRunOptions, AgentRunResult, + AgentControlMcpConfig, AgentSession, AgentSessionConfig, AgentStreamEvent, @@ -83,20 +84,13 @@ type ClaudeAgentConfig = AgentSessionConfig & { provider: "claude" }; export type ClaudeContentChunk = { type: string; [key: string]: any }; -type AgentControlMcpConfig = { - url: string; - headers?: Record; -}; - type ClaudeAgentClientOptions = { defaults?: { agents?: Record }; - agentControlMcp?: AgentControlMcpConfig; }; type ClaudeAgentSessionOptions = { defaults?: { agents?: Record }; handle?: AgentPersistenceHandle; - agentControlMcp?: AgentControlMcpConfig; }; const DEFAULT_AGENT_CONTROL_MCP: AgentControlMcpConfig = { @@ -185,18 +179,15 @@ export class ClaudeAgentClient implements AgentClient { readonly capabilities = CLAUDE_CAPABILITIES; private readonly defaults?: { agents?: Record }; - private readonly agentControlMcp?: AgentControlMcpConfig; constructor(options?: ClaudeAgentClientOptions) { this.defaults = options?.defaults; - this.agentControlMcp = options?.agentControlMcp; } async createSession(config: AgentSessionConfig): Promise { const claudeConfig = this.assertConfig(config); return new ClaudeAgentSession(claudeConfig, { defaults: this.defaults, - agentControlMcp: this.agentControlMcp, }); } @@ -213,7 +204,6 @@ export class ClaudeAgentClient implements AgentClient { const claudeConfig = this.assertConfig(mergedConfig); return new ClaudeAgentSession(claudeConfig, { defaults: this.defaults, - agentControlMcp: this.agentControlMcp, handle, }); } @@ -254,7 +244,6 @@ class ClaudeAgentSession implements AgentSession { private readonly config: ClaudeAgentConfig; private readonly defaults?: { agents?: Record }; - private readonly agentControlMcp?: AgentControlMcpConfig; private query: Query | null = null; private input: Pushable | null = null; private claudeSessionId: string | null; @@ -283,7 +272,6 @@ class ClaudeAgentSession implements AgentSession { ) { this.config = config; this.defaults = options?.defaults; - this.agentControlMcp = options?.agentControlMcp; const handle = options?.handle; this.claudeSessionId = handle?.sessionId ?? handle?.nativeHandle ?? null; this.pendingLocalId = this.claudeSessionId ?? `claude-${randomUUID()}`; @@ -575,7 +563,8 @@ class ClaudeAgentSession implements AgentSession { }; // Always include the agent-control MCP server so agents can launch other agents - const agentControlConfig = this.agentControlMcp ?? DEFAULT_AGENT_CONTROL_MCP; + const agentControlConfig = + this.config.agentControlMcp ?? DEFAULT_AGENT_CONTROL_MCP; const agentControlUrl = this.managedAgentId ? appendCallerAgentId(agentControlConfig.url, this.managedAgentId) : agentControlConfig.url; diff --git a/packages/server/src/server/agent/providers/codex-agent.test.ts b/packages/server/src/server/agent/providers/codex-agent.test.ts index 73f5ba21d..d0447055f 100644 --- a/packages/server/src/server/agent/providers/codex-agent.test.ts +++ b/packages/server/src/server/agent/providers/codex-agent.test.ts @@ -843,7 +843,7 @@ describe("CodexAgentClient (SDK integration)", () => { provider: "codex", cwd, modeId: "full-access", - extra: { codex: { agentControlMcpUrl: mcpServer.url } }, + agentControlMcp: { url: mcpServer.url }, }; let session: Awaited> | null = null; try { diff --git a/packages/server/src/server/agent/providers/codex-agent.ts b/packages/server/src/server/agent/providers/codex-agent.ts index 0068c0063..d2dcfcd92 100644 --- a/packages/server/src/server/agent/providers/codex-agent.ts +++ b/packages/server/src/server/agent/providers/codex-agent.ts @@ -44,7 +44,6 @@ import { getOrchestratorModeInstructions } from "../orchestrator-instructions.js type CodexAgentConfig = AgentSessionConfig & { provider: "codex" }; type CodexExtraConfig = { - agentControlMcpUrl?: string; developerInstructions?: string; }; @@ -269,11 +268,7 @@ function detectSystemCodexPath(): string | undefined { } function resolveAgentControlMcpUrl(config: CodexAgentConfig): string | null { - const extras = config.extra?.codex as CodexExtraConfig | undefined; - if (!extras || typeof extras !== "object") { - return null; - } - const baseUrl = extras.agentControlMcpUrl; + const baseUrl = config.agentControlMcp?.url; if (!baseUrl || typeof baseUrl !== "string") { return null; } diff --git a/packages/server/src/server/index.ts b/packages/server/src/server/index.ts index 3f6cf4c1c..a7759811b 100644 --- a/packages/server/src/server/index.ts +++ b/packages/server/src/server/index.ts @@ -93,17 +93,16 @@ async function main() { const agentRegistry = new AgentRegistry(); const agentManager = new AgentManager({ clients: { - claude: new ClaudeAgentClient({ - agentControlMcp: { - url: agentMcpUrl, - ...(agentMcpAuthHeader - ? { headers: { Authorization: agentMcpAuthHeader } } - : {}), - }, - }), + claude: new ClaudeAgentClient(), codex: new CodexAgentClient(), }, registry: agentRegistry, + agentControlMcp: { + url: agentMcpUrl, + ...(agentMcpAuthHeader + ? { headers: { Authorization: agentMcpAuthHeader } } + : {}), + }, }); attachAgentRegistryPersistence(agentManager, agentRegistry); @@ -123,7 +122,6 @@ async function main() { agentManager, agentRegistry, callerAgentId, - agentControlMcpUrl: agentMcpUrl, }); const transport = new StreamableHTTPServerTransport({ diff --git a/packages/server/src/server/messages.ts b/packages/server/src/server/messages.ts index 12f9f91a7..e9b534b40 100644 --- a/packages/server/src/server/messages.ts +++ b/packages/server/src/server/messages.ts @@ -71,6 +71,12 @@ const AgentSessionConfigSchema = z.object({ networkAccess: z.boolean().optional(), webSearch: z.boolean().optional(), reasoningEffort: z.string().optional(), + agentControlMcp: z + .object({ + url: z.string(), + headers: z.record(z.string()).optional(), + }) + .optional(), extra: z .object({ codex: z.record(z.unknown()).optional(), diff --git a/packages/server/src/server/session.ts b/packages/server/src/server/session.ts index 9011c8d94..b82ba336e 100644 --- a/packages/server/src/server/session.ts +++ b/packages/server/src/server/session.ts @@ -1256,8 +1256,10 @@ export class Session { ); try { - const sessionConfig = this.withCodexAgentControl( - await this.buildAgentSessionConfig(config, git, worktreeName) + const sessionConfig = await this.buildAgentSessionConfig( + config, + git, + worktreeName ); const snapshot = await this.agentManager.createAgent(sessionConfig); this.setCachedTitle(snapshot.id, null); @@ -1352,13 +1354,9 @@ export class Session { `[Session ${this.clientId}] Resuming agent ${handle.sessionId} (${handle.provider})` ); try { - const normalizedOverrides = this.withCodexAgentControl( - overrides ?? {}, - handle.provider - ); const snapshot = await this.agentManager.resumeAgent( handle, - normalizedOverrides + overrides ); this.setCachedTitle(snapshot.id, null); await this.agentManager.primeAgentHistory(snapshot.id); @@ -1419,11 +1417,11 @@ export class Session { `Agent ${agentId} cannot be refreshed because it lacks persistence` ); } - const overrides = this.withCodexAgentControl( + snapshot = await this.agentManager.resumeAgent( + handle, buildConfigOverrides(record), - record.provider + agentId ); - snapshot = await this.agentManager.resumeAgent(handle, overrides, agentId); this.setCachedTitle(agentId, null); } await this.agentManager.primeAgentHistory(agentId); @@ -1534,37 +1532,6 @@ export class Session { }; } - private withCodexAgentControl>( - config: T, - providerOverride?: AgentProvider - ): T { - const provider = - providerOverride ?? (config as AgentSessionConfig).provider; - if (provider !== "codex") { - return config; - } - const agentControlMcpUrl = this.agentMcpConfig?.agentMcpUrl; - if (!agentControlMcpUrl) { - return config; - } - const extra = (config.extra ?? {}) as AgentSessionConfig["extra"]; - const codexExtra = - extra?.codex && typeof extra.codex === "object" - ? { ...extra.codex } - : {}; - if (typeof (codexExtra as Record).agentControlMcpUrl !== "string") { - (codexExtra as Record).agentControlMcpUrl = - agentControlMcpUrl; - } - return { - ...config, - extra: { - ...extra, - codex: codexExtra, - }, - }; - } - private async handleGitRepoInfoRequest( msg: Extract ): Promise {