Keep Paseo MCP config out of agent storage

Runtime-injected MCP endpoints can change with daemon listen settings, so compute them for launches instead of persisting them.
This commit is contained in:
Mohamed Boudra
2026-06-07 23:31:03 +07:00
parent 893e3376b0
commit 06a8f952db
5 changed files with 255 additions and 59 deletions
@@ -542,7 +542,7 @@ test("createAgent injects daemon append system prompt at runtime only", async ()
expect(client.createdConfigs[0]?.systemPrompt).toBe("Agent instructions.");
expect(client.createdConfigs[0]?.daemonAppendSystemPrompt).toBe("Daemon instructions.");
expect(snapshot.config.daemonAppendSystemPrompt).toBe("Daemon instructions.");
expect(snapshot.config).not.toHaveProperty("daemonAppendSystemPrompt");
expect(record?.config?.systemPrompt).toBe("Agent instructions.");
expect(record?.config).not.toHaveProperty("daemonAppendSystemPrompt");
});
@@ -948,7 +948,7 @@ test("createAgent passes persistSession to provider create options", async () =>
rmSync(workdir, { recursive: true, force: true });
});
test("createAgent injects paseo MCP server when manager has an MCP base URL", async () => {
test("createAgent injects paseo MCP server only into provider launch config", async () => {
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
const storagePath = join(workdir, "agents");
const storage = new AgentStorage(storagePath, logger);
@@ -985,6 +985,12 @@ test("createAgent injects paseo MCP server when manager has an MCP base URL", as
});
expect(snapshot.config.mcpServers).toEqual({
custom: {
type: "stdio",
command: "custom-mcp",
},
});
expect(client.lastConfig?.mcpServers).toEqual({
paseo: {
type: "http",
url: `http://127.0.0.1:6767/mcp/agents?callerAgentId=${snapshot.id}`,
@@ -994,7 +1000,102 @@ test("createAgent injects paseo MCP server when manager has an MCP base URL", as
command: "custom-mcp",
},
});
expect(client.lastConfig?.mcpServers).toEqual(snapshot.config.mcpServers);
const stored = await storage.get(snapshot.id);
expect(stored?.config?.mcpServers).toEqual({
custom: {
type: "stdio",
command: "custom-mcp",
},
});
});
test("resumeAgentFromPersistence replaces stored internal paseo MCP with current runtime URL", async () => {
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
const storagePath = join(workdir, "agents");
const storage = new AgentStorage(storagePath, logger);
const client = new TestAgentClient();
const manager = new AgentManager({
clients: {
codex: client,
},
registry: storage,
logger,
mcpBaseUrl: "http://127.0.0.1:6768/mcp/agents",
idFactory: () => "00000000-0000-4000-8000-000000000105",
});
const handle: AgentPersistenceHandle = {
provider: "codex",
sessionId: "session-123",
metadata: {
cwd: workdir,
},
};
const snapshot = await manager.resumeAgentFromPersistence(handle, {
cwd: workdir,
mcpServers: {
paseo: {
type: "http",
url: "http://127.0.0.1:6767/mcp/agents?callerAgentId=stale-agent",
},
custom: {
type: "stdio",
command: "custom-mcp",
},
},
});
expect(client.resumeOverrides[0]?.mcpServers).toEqual({
paseo: {
type: "http",
url: `http://127.0.0.1:6768/mcp/agents?callerAgentId=${snapshot.id}`,
},
custom: {
type: "stdio",
command: "custom-mcp",
},
});
expect(snapshot.config.mcpServers).toEqual({
custom: {
type: "stdio",
command: "custom-mcp",
},
});
});
test("resumeAgentFromPersistence drops stored internal paseo MCP when runtime injection is disabled", async () => {
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
const storagePath = join(workdir, "agents");
const storage = new AgentStorage(storagePath, logger);
const client = new TestAgentClient();
const manager = new AgentManager({
clients: {
codex: client,
},
registry: storage,
logger,
});
const handle: AgentPersistenceHandle = {
provider: "codex",
sessionId: "session-123",
metadata: {
cwd: workdir,
},
};
const snapshot = await manager.resumeAgentFromPersistence(handle, {
cwd: workdir,
mcpServers: {
paseo: {
type: "http",
url: "http://127.0.0.1:6767/mcp/agents?callerAgentId=stale-agent",
},
},
});
expect(client.resumeOverrides[0]?.mcpServers).toBeUndefined();
expect(snapshot.config.mcpServers).toBeUndefined();
});
test("createAgent preserves a user-provided paseo MCP config", async () => {
@@ -57,6 +57,7 @@ import { getAgentProviderDefinition } from "@getpaseo/protocol/provider-manifest
import { IMPORTABLE_PROVIDERS } from "./provider-registry.js";
import { invokeRewindCapability, type RewindMode } from "./rewind/rewind.js";
import { isSystemInjectedEnvelope } from "./agent-prompt.js";
import { stripInternalPaseoMcpServer, withRuntimePaseoMcpServer } from "./runtime-mcp-config.js";
const RELOAD_SESSION_CLOSE_TIMEOUT_MS = 3_000;
const INTERRUPT_SESSION_TIMEOUT_MS = 2_000;
@@ -74,6 +75,11 @@ const STORED_AGENT_CAPABILITIES: AgentCapabilityFlags = {
type TimeoutResult = "completed" | "timed_out";
interface PreparedSessionConfig {
storedConfig: AgentSessionConfig;
launchConfig: AgentSessionConfig;
}
interface TimeoutOptions {
operation: Promise<void>;
timeoutMs: number;
@@ -105,7 +111,7 @@ function buildStoredAgentConfig(record: StoredAgentRecord): AgentSessionConfig {
config.systemPrompt = record.config.systemPrompt;
}
if (record.config.mcpServers != null) config.mcpServers = record.config.mcpServers;
return config;
return stripInternalPaseoMcpServer(config);
}
export { AGENT_LIFECYCLE_STATUSES, type AgentLifecycleStatus };
@@ -811,30 +817,15 @@ export class AgentManager {
},
): Promise<ManagedAgent> {
const resolvedAgentId = validateAgentId(agentId ?? this.idFactory(), "createAgent");
const injectedConfig =
this.mcpBaseUrl == null
? config
: {
...config,
mcpServers: {
paseo: {
type: "http" as const,
url: `${this.mcpBaseUrl}?callerAgentId=${resolvedAgentId}`,
},
...config.mcpServers,
},
};
this.requireEnabledProvider(injectedConfig.provider);
const normalizedConfig = this.applyDaemonAppendSystemPrompt(
await this.normalizeConfig(injectedConfig),
);
const { storedConfig, launchConfig } = await this.prepareSessionConfig(config, resolvedAgentId);
this.requireEnabledProvider(storedConfig.provider);
const launchContext = this.buildLaunchContext(resolvedAgentId, options?.env);
const client = await this.requireAvailableClient({
provider: normalizedConfig.provider,
provider: storedConfig.provider,
});
const createOptions = this.buildCreateSessionOptions(options);
const session = await client.createSession(normalizedConfig, launchContext, createOptions);
return this.registerSession(session, normalizedConfig, resolvedAgentId, {
const session = await client.createSession(launchConfig, launchContext, createOptions);
return this.registerSession(session, storedConfig, resolvedAgentId, {
labels: options?.labels,
workspaceId: options?.workspaceId,
initialTitle: options?.initialTitle,
@@ -872,26 +863,10 @@ export class AgentManager {
...overrides,
provider: handle.provider,
} as AgentSessionConfig;
const normalizedConfig = this.applyDaemonAppendSystemPrompt(
await this.normalizeConfig(mergedConfig),
const { storedConfig, launchConfig } = await this.prepareSessionConfig(
mergedConfig,
resolvedAgentId,
);
const resumeOverrides: Partial<AgentSessionConfig> = { ...overrides };
let hasResumeOverrides = overrides !== undefined;
if (normalizedConfig.model !== mergedConfig.model) {
resumeOverrides.model = normalizedConfig.model;
hasResumeOverrides = true;
}
if (normalizedConfig.modeId !== mergedConfig.modeId) {
resumeOverrides.modeId = normalizedConfig.modeId;
hasResumeOverrides = true;
}
if (metadata.daemonAppendSystemPrompt !== normalizedConfig.daemonAppendSystemPrompt) {
resumeOverrides.daemonAppendSystemPrompt = normalizedConfig.daemonAppendSystemPrompt;
hasResumeOverrides = true;
}
const launchContext = this.buildLaunchContext(resolvedAgentId);
const client = this.requireClient(handle.provider);
@@ -901,12 +876,8 @@ export class AgentManager {
`Provider '${handle.provider}' is not available. Please ensure the CLI is installed.`,
);
}
const session = await client.resumeSession(
handle,
hasResumeOverrides ? resumeOverrides : undefined,
launchContext,
);
return this.registerSession(session, normalizedConfig, resolvedAgentId, options);
const session = await client.resumeSession(handle, launchConfig, launchContext);
return this.registerSession(session, storedConfig, resolvedAgentId, options);
}
// Hot-reload an active agent session with config overrides. By default the
@@ -938,14 +909,12 @@ export class AgentManager {
...overrides,
provider,
} as AgentSessionConfig;
const normalizedConfig = this.applyDaemonAppendSystemPrompt(
await this.normalizeConfig(refreshConfig),
);
const { storedConfig, launchConfig } = await this.prepareSessionConfig(refreshConfig, agentId);
const launchContext = this.buildLaunchContext(agentId);
const session = handle
? await client.resumeSession(handle, normalizedConfig, launchContext)
: await client.createSession(normalizedConfig, launchContext);
? await client.resumeSession(handle, launchConfig, launchContext)
: await client.createSession(launchConfig, launchContext);
this.agentStreamCoalescer.flushAndDiscard(agentId);
// Remove the existing agent entry before swapping sessions
@@ -966,7 +935,7 @@ export class AgentManager {
}
// Preserve existing labels and timeline during reload.
return this.registerSession(session, normalizedConfig, agentId, {
return this.registerSession(session, storedConfig, agentId, {
labels: existing.labels,
createdAt: existing.createdAt,
updatedAt: existing.updatedAt,
@@ -3489,6 +3458,21 @@ export class AgentManager {
return normalized;
}
private async prepareSessionConfig(
config: AgentSessionConfig,
agentId: string,
): Promise<PreparedSessionConfig> {
const storedConfig = await this.normalizeConfig(stripInternalPaseoMcpServer(config));
const launchConfig = this.applyDaemonAppendSystemPrompt(
withRuntimePaseoMcpServer({
config: storedConfig,
agentId,
mcpBaseUrl: this.mcpBaseUrl,
}),
);
return { storedConfig, launchConfig };
}
private applyDaemonAppendSystemPrompt(config: AgentSessionConfig): AgentSessionConfig {
const daemonAppendSystemPrompt = this.appendSystemPrompt.trim();
const next = { ...config };
@@ -0,0 +1,61 @@
import type { AgentSessionConfig, McpServerConfig } from "./agent-sdk-types.js";
const PASEO_MCP_SERVER_NAME = "paseo";
const PASEO_MCP_PATHNAME = "/mcp/agents";
export function stripInternalPaseoMcpServer(config: AgentSessionConfig): AgentSessionConfig {
const mcpServers = config.mcpServers;
if (!mcpServers) {
return config;
}
const paseoServer = mcpServers[PASEO_MCP_SERVER_NAME];
if (!paseoServer || !isInternalPaseoMcpServer(paseoServer)) {
return config;
}
const nextMcpServers = { ...mcpServers };
delete nextMcpServers[PASEO_MCP_SERVER_NAME];
const next = { ...config };
if (Object.keys(nextMcpServers).length > 0) {
next.mcpServers = nextMcpServers;
} else {
delete next.mcpServers;
}
return next;
}
export function withRuntimePaseoMcpServer(params: {
config: AgentSessionConfig;
agentId: string;
mcpBaseUrl: string | null;
}): AgentSessionConfig {
const storedConfig = stripInternalPaseoMcpServer(params.config);
if (!params.mcpBaseUrl || storedConfig.mcpServers?.[PASEO_MCP_SERVER_NAME]) {
return storedConfig;
}
return {
...storedConfig,
mcpServers: {
[PASEO_MCP_SERVER_NAME]: {
type: "http",
url: `${params.mcpBaseUrl}?callerAgentId=${params.agentId}`,
},
...storedConfig.mcpServers,
},
};
}
function isInternalPaseoMcpServer(config: McpServerConfig): boolean {
if (config.type !== "http" && config.type !== "sse") {
return false;
}
try {
return new URL(config.url).pathname === PASEO_MCP_PATHNAME;
} catch {
return false;
}
}
@@ -95,6 +95,54 @@ describe("persistence hooks", () => {
});
});
test("buildConfigOverrides drops persisted internal paseo MCP server", () => {
const record = createRecord({
config: {
modeId: "default",
model: "gpt-5.4-mini",
mcpServers: {
paseo: {
type: "http",
url: "http://127.0.0.1:6767/mcp/agents?callerAgentId=stale-agent",
},
custom: {
type: "stdio",
command: "custom-mcp",
},
},
},
});
expect(buildConfigOverrides(record).mcpServers).toEqual({
custom: {
type: "stdio",
command: "custom-mcp",
},
});
});
test("buildConfigOverrides preserves user-provided paseo MCP server", () => {
const record = createRecord({
config: {
modeId: "default",
model: "gpt-5.4-mini",
mcpServers: {
paseo: {
type: "http",
url: "https://example.com/custom-paseo",
},
},
},
});
expect(buildConfigOverrides(record).mcpServers).toEqual({
paseo: {
type: "http",
url: "https://example.com/custom-paseo",
},
});
});
test("buildSessionConfig accepts providers from the canonical manifest", () => {
const record = createRecord({
provider: "claude",
@@ -1,4 +1,5 @@
import type { AgentManager } from "./agent/agent-manager.js";
import { stripInternalPaseoMcpServer } from "./agent/runtime-mcp-config.js";
import type {
AgentPersistenceHandle,
AgentProvider,
@@ -49,7 +50,8 @@ export function attachAgentStoragePersistence(
}
export function buildConfigOverrides(record: StoredAgentRecord): Partial<AgentSessionConfig> {
return {
return stripInternalPaseoMcpServer({
provider: record.provider,
cwd: record.cwd,
modeId: record.lastModeId ?? record.config?.modeId ?? undefined,
model: record.config?.model ?? undefined,
@@ -58,7 +60,7 @@ export function buildConfigOverrides(record: StoredAgentRecord): Partial<AgentSe
extra: record.config?.extra ?? undefined,
systemPrompt: record.config?.systemPrompt ?? undefined,
mcpServers: record.config?.mcpServers ?? undefined,
};
});
}
export function buildSessionConfig(
@@ -71,7 +73,7 @@ export function buildSessionConfig(
return null;
}
const overrides = buildConfigOverrides(record);
return {
return stripInternalPaseoMcpServer({
provider: record.provider,
cwd: record.cwd,
modeId: overrides.modeId,
@@ -81,7 +83,7 @@ export function buildSessionConfig(
extra: overrides.extra,
systemPrompt: overrides.systemPrompt,
mcpServers: overrides.mcpServers,
};
});
}
export function isStoredAgentProviderAvailable(