Move agent-control MCP config into AgentManager

This commit is contained in:
Mohamed Boudra
2025-12-23 15:45:48 +07:00
parent dc90f57567
commit bf0ec80fb5
9 changed files with 41 additions and 82 deletions

View File

@@ -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<string, AgentLifecycleStatus>();
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;
}

View File

@@ -131,6 +131,11 @@ export type AgentRuntimeInfo = {
extra?: Record<string, unknown>;
};
export type AgentControlMcpConfig = {
url: string;
headers?: Record<string, string>;
};
export type ListPersistedAgentsOptions = {
limit?: number;
};
@@ -156,6 +161,7 @@ export type AgentSessionConfig = {
networkAccess?: boolean;
webSearch?: boolean;
reasoningEffort?: string;
agentControlMcp?: AgentControlMcpConfig;
extra?: {
codex?: Record<string, unknown>;
claude?: Partial<ClaudeAgentOptions>;

View File

@@ -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<McpServer> {
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) {

View File

@@ -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<string, string>;
};
type ClaudeAgentClientOptions = {
defaults?: { agents?: Record<string, AgentDefinition> };
agentControlMcp?: AgentControlMcpConfig;
};
type ClaudeAgentSessionOptions = {
defaults?: { agents?: Record<string, AgentDefinition> };
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<string, AgentDefinition> };
private readonly agentControlMcp?: AgentControlMcpConfig;
constructor(options?: ClaudeAgentClientOptions) {
this.defaults = options?.defaults;
this.agentControlMcp = options?.agentControlMcp;
}
async createSession(config: AgentSessionConfig): Promise<AgentSession> {
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<string, AgentDefinition> };
private readonly agentControlMcp?: AgentControlMcpConfig;
private query: Query | null = null;
private input: Pushable<SDKUserMessage> | 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;

View File

@@ -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<ReturnType<typeof client.createSession>> | null = null;
try {

View File

@@ -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;
}

View File

@@ -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({

View File

@@ -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(),

View File

@@ -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<T extends Partial<AgentSessionConfig>>(
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<string, unknown>).agentControlMcpUrl !== "string") {
(codexExtra as Record<string, unknown>).agentControlMcpUrl =
agentControlMcpUrl;
}
return {
...config,
extra: {
...extra,
codex: codexExtra,
},
};
}
private async handleGitRepoInfoRequest(
msg: Extract<SessionInboundMessage, { type: "git_repo_info_request" }>
): Promise<void> {