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
@@ -18,6 +18,7 @@ import type {
AgentTimelineItem, AgentTimelineItem,
AgentUsage, AgentUsage,
AgentRuntimeInfo, AgentRuntimeInfo,
AgentControlMcpConfig,
ListPersistedAgentsOptions, ListPersistedAgentsOptions,
PersistedAgentDescriptor, PersistedAgentDescriptor,
} from "./agent-sdk-types.js"; } from "./agent-sdk-types.js";
@@ -54,6 +55,7 @@ export type AgentManagerOptions = {
maxTimelineItems?: number; maxTimelineItems?: number;
idFactory?: () => string; idFactory?: () => string;
registry?: AgentRegistry; registry?: AgentRegistry;
agentControlMcp?: AgentControlMcpConfig;
}; };
export type WaitForAgentOptions = { export type WaitForAgentOptions = {
@@ -178,12 +180,14 @@ export class AgentManager {
private readonly idFactory: () => string; private readonly idFactory: () => string;
private readonly registry?: AgentRegistry; private readonly registry?: AgentRegistry;
private readonly previousStatuses = new Map<string, AgentLifecycleStatus>(); private readonly previousStatuses = new Map<string, AgentLifecycleStatus>();
private readonly agentControlMcp?: AgentControlMcpConfig;
constructor(options?: AgentManagerOptions) { constructor(options?: AgentManagerOptions) {
this.maxTimelineItems = this.maxTimelineItems =
options?.maxTimelineItems ?? DEFAULT_MAX_TIMELINE_ITEMS; options?.maxTimelineItems ?? DEFAULT_MAX_TIMELINE_ITEMS;
this.idFactory = options?.idFactory ?? (() => randomUUID()); this.idFactory = options?.idFactory ?? (() => randomUUID());
this.registry = options?.registry; this.registry = options?.registry;
this.agentControlMcp = options?.agentControlMcp;
if (options?.clients) { if (options?.clients) {
for (const [provider, client] of Object.entries(options.clients)) { for (const [provider, client] of Object.entries(options.clients)) {
if (client) { if (client) {
@@ -982,6 +986,10 @@ export class AgentManager {
normalized.model = trimmed.length > 0 ? trimmed : undefined; normalized.model = trimmed.length > 0 ? trimmed : undefined;
} }
if (!normalized.agentControlMcp && this.agentControlMcp) {
normalized.agentControlMcp = this.agentControlMcp;
}
return normalized; return normalized;
} }
@@ -131,6 +131,11 @@ export type AgentRuntimeInfo = {
extra?: Record<string, unknown>; extra?: Record<string, unknown>;
}; };
export type AgentControlMcpConfig = {
url: string;
headers?: Record<string, string>;
};
export type ListPersistedAgentsOptions = { export type ListPersistedAgentsOptions = {
limit?: number; limit?: number;
}; };
@@ -156,6 +161,7 @@ export type AgentSessionConfig = {
networkAccess?: boolean; networkAccess?: boolean;
webSearch?: boolean; webSearch?: boolean;
reasoningEffort?: string; reasoningEffort?: string;
agentControlMcp?: AgentControlMcpConfig;
extra?: { extra?: {
codex?: Record<string, unknown>; codex?: Record<string, unknown>;
claude?: Partial<ClaudeAgentOptions>; claude?: Partial<ClaudeAgentOptions>;
+1 -11
View File
@@ -33,10 +33,6 @@ export interface AgentMcpServerOptions {
* When set, create_agent will auto-inject this as parentAgentId. * When set, create_agent will auto-inject this as parentAgentId.
*/ */
callerAgentId?: string; callerAgentId?: string;
/**
* Agent-control MCP URL to inject for Codex agents spawned via MCP.
*/
agentControlMcpUrl?: string;
} }
const AgentProviderEnum = z.enum( const AgentProviderEnum = z.enum(
@@ -175,8 +171,7 @@ async function serializeSnapshotWithMetadata(
export async function createAgentMcpServer( export async function createAgentMcpServer(
options: AgentMcpServerOptions options: AgentMcpServerOptions
): Promise<McpServer> { ): Promise<McpServer> {
const { agentManager, agentRegistry, callerAgentId, agentControlMcpUrl } = const { agentManager, agentRegistry, callerAgentId } = options;
options;
const waitTracker = new WaitForAgentTracker(); const waitTracker = new WaitForAgentTracker();
const server = new McpServer({ const server = new McpServer({
@@ -279,17 +274,12 @@ export async function createAgentMcpServer(
const normalizedTitle = title?.trim() ?? null; const normalizedTitle = title?.trim() ?? null;
// Use explicit parentAgentId if provided, otherwise default to caller agent ID // Use explicit parentAgentId if provided, otherwise default to caller agent ID
const resolvedParentAgentId = parentAgentId ?? callerAgentId; const resolvedParentAgentId = parentAgentId ?? callerAgentId;
const codexExtra =
provider === "codex" && agentControlMcpUrl
? { codex: { agentControlMcpUrl } }
: undefined;
const snapshot = await agentManager.createAgent({ const snapshot = await agentManager.createAgent({
provider, provider,
cwd: resolvedCwd, cwd: resolvedCwd,
modeId: initialMode, modeId: initialMode,
title: normalizedTitle ?? undefined, title: normalizedTitle ?? undefined,
parentAgentId: resolvedParentAgentId, parentAgentId: resolvedParentAgentId,
extra: codexExtra,
}); });
if (initialPrompt) { if (initialPrompt) {
@@ -30,6 +30,7 @@ import type {
AgentPromptInput, AgentPromptInput,
AgentRunOptions, AgentRunOptions,
AgentRunResult, AgentRunResult,
AgentControlMcpConfig,
AgentSession, AgentSession,
AgentSessionConfig, AgentSessionConfig,
AgentStreamEvent, AgentStreamEvent,
@@ -83,20 +84,13 @@ type ClaudeAgentConfig = AgentSessionConfig & { provider: "claude" };
export type ClaudeContentChunk = { type: string; [key: string]: any }; export type ClaudeContentChunk = { type: string; [key: string]: any };
type AgentControlMcpConfig = {
url: string;
headers?: Record<string, string>;
};
type ClaudeAgentClientOptions = { type ClaudeAgentClientOptions = {
defaults?: { agents?: Record<string, AgentDefinition> }; defaults?: { agents?: Record<string, AgentDefinition> };
agentControlMcp?: AgentControlMcpConfig;
}; };
type ClaudeAgentSessionOptions = { type ClaudeAgentSessionOptions = {
defaults?: { agents?: Record<string, AgentDefinition> }; defaults?: { agents?: Record<string, AgentDefinition> };
handle?: AgentPersistenceHandle; handle?: AgentPersistenceHandle;
agentControlMcp?: AgentControlMcpConfig;
}; };
const DEFAULT_AGENT_CONTROL_MCP: AgentControlMcpConfig = { const DEFAULT_AGENT_CONTROL_MCP: AgentControlMcpConfig = {
@@ -185,18 +179,15 @@ export class ClaudeAgentClient implements AgentClient {
readonly capabilities = CLAUDE_CAPABILITIES; readonly capabilities = CLAUDE_CAPABILITIES;
private readonly defaults?: { agents?: Record<string, AgentDefinition> }; private readonly defaults?: { agents?: Record<string, AgentDefinition> };
private readonly agentControlMcp?: AgentControlMcpConfig;
constructor(options?: ClaudeAgentClientOptions) { constructor(options?: ClaudeAgentClientOptions) {
this.defaults = options?.defaults; this.defaults = options?.defaults;
this.agentControlMcp = options?.agentControlMcp;
} }
async createSession(config: AgentSessionConfig): Promise<AgentSession> { async createSession(config: AgentSessionConfig): Promise<AgentSession> {
const claudeConfig = this.assertConfig(config); const claudeConfig = this.assertConfig(config);
return new ClaudeAgentSession(claudeConfig, { return new ClaudeAgentSession(claudeConfig, {
defaults: this.defaults, defaults: this.defaults,
agentControlMcp: this.agentControlMcp,
}); });
} }
@@ -213,7 +204,6 @@ export class ClaudeAgentClient implements AgentClient {
const claudeConfig = this.assertConfig(mergedConfig); const claudeConfig = this.assertConfig(mergedConfig);
return new ClaudeAgentSession(claudeConfig, { return new ClaudeAgentSession(claudeConfig, {
defaults: this.defaults, defaults: this.defaults,
agentControlMcp: this.agentControlMcp,
handle, handle,
}); });
} }
@@ -254,7 +244,6 @@ class ClaudeAgentSession implements AgentSession {
private readonly config: ClaudeAgentConfig; private readonly config: ClaudeAgentConfig;
private readonly defaults?: { agents?: Record<string, AgentDefinition> }; private readonly defaults?: { agents?: Record<string, AgentDefinition> };
private readonly agentControlMcp?: AgentControlMcpConfig;
private query: Query | null = null; private query: Query | null = null;
private input: Pushable<SDKUserMessage> | null = null; private input: Pushable<SDKUserMessage> | null = null;
private claudeSessionId: string | null; private claudeSessionId: string | null;
@@ -283,7 +272,6 @@ class ClaudeAgentSession implements AgentSession {
) { ) {
this.config = config; this.config = config;
this.defaults = options?.defaults; this.defaults = options?.defaults;
this.agentControlMcp = options?.agentControlMcp;
const handle = options?.handle; const handle = options?.handle;
this.claudeSessionId = handle?.sessionId ?? handle?.nativeHandle ?? null; this.claudeSessionId = handle?.sessionId ?? handle?.nativeHandle ?? null;
this.pendingLocalId = this.claudeSessionId ?? `claude-${randomUUID()}`; 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 // 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 const agentControlUrl = this.managedAgentId
? appendCallerAgentId(agentControlConfig.url, this.managedAgentId) ? appendCallerAgentId(agentControlConfig.url, this.managedAgentId)
: agentControlConfig.url; : agentControlConfig.url;
@@ -843,7 +843,7 @@ describe("CodexAgentClient (SDK integration)", () => {
provider: "codex", provider: "codex",
cwd, cwd,
modeId: "full-access", modeId: "full-access",
extra: { codex: { agentControlMcpUrl: mcpServer.url } }, agentControlMcp: { url: mcpServer.url },
}; };
let session: Awaited<ReturnType<typeof client.createSession>> | null = null; let session: Awaited<ReturnType<typeof client.createSession>> | null = null;
try { try {
@@ -44,7 +44,6 @@ import { getOrchestratorModeInstructions } from "../orchestrator-instructions.js
type CodexAgentConfig = AgentSessionConfig & { provider: "codex" }; type CodexAgentConfig = AgentSessionConfig & { provider: "codex" };
type CodexExtraConfig = { type CodexExtraConfig = {
agentControlMcpUrl?: string;
developerInstructions?: string; developerInstructions?: string;
}; };
@@ -269,11 +268,7 @@ function detectSystemCodexPath(): string | undefined {
} }
function resolveAgentControlMcpUrl(config: CodexAgentConfig): string | null { function resolveAgentControlMcpUrl(config: CodexAgentConfig): string | null {
const extras = config.extra?.codex as CodexExtraConfig | undefined; const baseUrl = config.agentControlMcp?.url;
if (!extras || typeof extras !== "object") {
return null;
}
const baseUrl = extras.agentControlMcpUrl;
if (!baseUrl || typeof baseUrl !== "string") { if (!baseUrl || typeof baseUrl !== "string") {
return null; return null;
} }
+7 -9
View File
@@ -93,17 +93,16 @@ async function main() {
const agentRegistry = new AgentRegistry(); const agentRegistry = new AgentRegistry();
const agentManager = new AgentManager({ const agentManager = new AgentManager({
clients: { clients: {
claude: new ClaudeAgentClient({ claude: new ClaudeAgentClient(),
agentControlMcp: {
url: agentMcpUrl,
...(agentMcpAuthHeader
? { headers: { Authorization: agentMcpAuthHeader } }
: {}),
},
}),
codex: new CodexAgentClient(), codex: new CodexAgentClient(),
}, },
registry: agentRegistry, registry: agentRegistry,
agentControlMcp: {
url: agentMcpUrl,
...(agentMcpAuthHeader
? { headers: { Authorization: agentMcpAuthHeader } }
: {}),
},
}); });
attachAgentRegistryPersistence(agentManager, agentRegistry); attachAgentRegistryPersistence(agentManager, agentRegistry);
@@ -123,7 +122,6 @@ async function main() {
agentManager, agentManager,
agentRegistry, agentRegistry,
callerAgentId, callerAgentId,
agentControlMcpUrl: agentMcpUrl,
}); });
const transport = new StreamableHTTPServerTransport({ const transport = new StreamableHTTPServerTransport({
+6
View File
@@ -71,6 +71,12 @@ const AgentSessionConfigSchema = z.object({
networkAccess: z.boolean().optional(), networkAccess: z.boolean().optional(),
webSearch: z.boolean().optional(), webSearch: z.boolean().optional(),
reasoningEffort: z.string().optional(), reasoningEffort: z.string().optional(),
agentControlMcp: z
.object({
url: z.string(),
headers: z.record(z.string()).optional(),
})
.optional(),
extra: z extra: z
.object({ .object({
codex: z.record(z.unknown()).optional(), codex: z.record(z.unknown()).optional(),
+8 -41
View File
@@ -1256,8 +1256,10 @@ export class Session {
); );
try { try {
const sessionConfig = this.withCodexAgentControl( const sessionConfig = await this.buildAgentSessionConfig(
await this.buildAgentSessionConfig(config, git, worktreeName) config,
git,
worktreeName
); );
const snapshot = await this.agentManager.createAgent(sessionConfig); const snapshot = await this.agentManager.createAgent(sessionConfig);
this.setCachedTitle(snapshot.id, null); this.setCachedTitle(snapshot.id, null);
@@ -1352,13 +1354,9 @@ export class Session {
`[Session ${this.clientId}] Resuming agent ${handle.sessionId} (${handle.provider})` `[Session ${this.clientId}] Resuming agent ${handle.sessionId} (${handle.provider})`
); );
try { try {
const normalizedOverrides = this.withCodexAgentControl(
overrides ?? {},
handle.provider
);
const snapshot = await this.agentManager.resumeAgent( const snapshot = await this.agentManager.resumeAgent(
handle, handle,
normalizedOverrides overrides
); );
this.setCachedTitle(snapshot.id, null); this.setCachedTitle(snapshot.id, null);
await this.agentManager.primeAgentHistory(snapshot.id); await this.agentManager.primeAgentHistory(snapshot.id);
@@ -1419,11 +1417,11 @@ export class Session {
`Agent ${agentId} cannot be refreshed because it lacks persistence` `Agent ${agentId} cannot be refreshed because it lacks persistence`
); );
} }
const overrides = this.withCodexAgentControl( snapshot = await this.agentManager.resumeAgent(
handle,
buildConfigOverrides(record), buildConfigOverrides(record),
record.provider agentId
); );
snapshot = await this.agentManager.resumeAgent(handle, overrides, agentId);
this.setCachedTitle(agentId, null); this.setCachedTitle(agentId, null);
} }
await this.agentManager.primeAgentHistory(agentId); 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( private async handleGitRepoInfoRequest(
msg: Extract<SessionInboundMessage, { type: "git_repo_info_request" }> msg: Extract<SessionInboundMessage, { type: "git_repo_info_request" }>
): Promise<void> { ): Promise<void> {