diff --git a/packages/agent/src/adapters/claude/claude-agent.refresh.test.ts b/packages/agent/src/adapters/claude/claude-agent.refresh.test.ts index e27994923..61d5eeca9 100644 --- a/packages/agent/src/adapters/claude/claude-agent.refresh.test.ts +++ b/packages/agent/src/adapters/claude/claude-agent.refresh.test.ts @@ -70,13 +70,14 @@ function makeAgent(): Agent { function installFakeSession( agent: Agent, sessionId: string, - overrides: Partial<{ modelId: string }> = {}, + overrides: Partial<{ modelId: string; localToolsMcpServer: unknown }> = {}, ) { const oldQuery = makeQueryHandle(); const input = new Pushable(); const endSpy = vi.spyOn(input, "end"); const abortController = new AbortController(); + const localToolsMcpServer = overrides.localToolsMcpServer; const session = { query: oldQuery, queryOptions: { @@ -105,6 +106,7 @@ function installFakeSession( notificationHistory: [{ foo: "bar" }], taskRunId: "run-1", modelId: overrides.modelId, + localToolsMcpServer, } as unknown as Parameters[0]; (agent as unknown as { session: unknown }).session = session; @@ -278,6 +280,43 @@ describe("ClaudeAcpAgent.extMethod refresh_session", () => { ); }); + it("re-attaches the in-process posthog-local MCP server", async () => { + const agent = makeAgent(); + const localServer = { + type: "sdk", + name: "posthog-local", + instance: { fakeServer: true }, + }; + installFakeSession(agent, "s-local-tools", { + localToolsMcpServer: localServer, + }); + + await agent.extMethod(POSTHOG_METHODS.REFRESH_SESSION, { + mcpServers: freshMcpServers, + }); + + expect(lastQueryCall.options?.mcpServers).toMatchObject({ + posthog: { type: "http", url: "https://fresh" }, + "posthog-local": localServer, + }); + }); + + it("omits posthog-local when none was registered at session creation", async () => { + const agent = makeAgent(); + installFakeSession(agent, "s-no-local-tools"); + + await agent.extMethod(POSTHOG_METHODS.REFRESH_SESSION, { + mcpServers: freshMcpServers, + }); + + const servers = lastQueryCall.options?.mcpServers as Record< + string, + unknown + >; + expect(servers).toBeDefined(); + expect(servers["posthog-local"]).toBeUndefined(); + }); + it("re-fetches MCP tool metadata for the new query", async () => { const agent = makeAgent(); installFakeSession(agent, "s-metadata"); diff --git a/packages/agent/src/adapters/claude/claude-agent.ts b/packages/agent/src/adapters/claude/claude-agent.ts index cb712669c..93def6d11 100644 --- a/packages/agent/src/adapters/claude/claude-agent.ts +++ b/packages/agent/src/adapters/claude/claude-agent.ts @@ -889,12 +889,19 @@ export class ClaudeAcpAgent extends BaseAcpAgent { // Reuse every option from the running session; swap mcpServers, re-root // identity on `resume` instead of `sessionId`, and give the new Query a - // fresh AbortController. + // fresh AbortController. Re-attach the in-process local-tools MCP server — + // `mcpServers` from the caller only carries external servers, so without + // this the rebuilt session would lose `git_signed_commit` and any future + // local tools. const newAbortController = new AbortController(); const { sessionId: _drop, ...rest } = prev.queryOptions; + const mergedMcpServers: Record = { ...mcpServers }; + if (prev.localToolsMcpServer) { + mergedMcpServers[LOCAL_TOOLS_MCP_NAME] = prev.localToolsMcpServer; + } const newOptions: Options = { ...rest, - mcpServers, + mcpServers: mergedMcpServers, resume: this.sessionId, forkSession: false, abortController: newAbortController, @@ -1226,6 +1233,7 @@ export class ClaudeAcpAgent extends BaseAcpAgent { cwd, notificationHistory: [], taskRunId: meta?.taskRunId, + localToolsMcpServer: localToolsServer, }; this.session = session; this.sessionId = sessionId; diff --git a/packages/agent/src/adapters/claude/types.ts b/packages/agent/src/adapters/claude/types.ts index da75bb62d..cf59d6437 100644 --- a/packages/agent/src/adapters/claude/types.ts +++ b/packages/agent/src/adapters/claude/types.ts @@ -4,6 +4,7 @@ import type { TerminalOutputResponse, } from "@agentclientprotocol/sdk"; import type { + McpSdkServerConfigWithInstance, Options, Query, SDKUserMessage, @@ -65,6 +66,13 @@ export type Session = BaseSession & { pendingMessages: Map; nextPendingOrder: number; emitRawSDKMessages: boolean | SDKMessageFilter[]; + /** + * In-process MCP server hosting PostHog-local tools (e.g. `git_signed_commit`). + * Stashed here so `refreshSession` can re-merge it into the rebuilt options — + * otherwise the cloud agent-server's pre-prompt TTL refresh would drop it and + * the model would lose access to tools registered at session creation. + */ + localToolsMcpServer?: McpSdkServerConfigWithInstance; }; export type ToolUseCache = {