Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3bde28d
fix(server): preserve native subagent thread ownership
Nov 2, 2026
f72b76d
fix(server): nested native subagents keep a delegated task working
Nov 2, 2026
6895d93
fix(server): keep native caller waiters until every resolver finishes
Nov 2, 2026
9a586fe
fix(server): preserve execution context for native-owned tasks
Nov 2, 2026
acb2960
fix(server): retain native thread callers for the provider session
Nov 2, 2026
5ec2ada
fix(server): stop nested workflows through their execution thread
Nov 2, 2026
7acdf66
fix(server): bind native callers only for their originating MCP crede…
Nov 2, 2026
9be0d34
fix(server): stop remote tasks delegated by native callers
Nov 2, 2026
178b089
test(server): wait for caller resolution before advancing time
Nov 2, 2026
da2f59d
chore: merge main into native subagent ownership fix
maria-rcks Nov 2, 2026
72dea1e
fix(server): preflight native-owned descendants before cancellation
maria-rcks Nov 2, 2026
a3d1571
test(server): refuse native descendant cancellation before stopping
shivamhwp Nov 2, 2026
a312909
fix(server): cancelling a task keeps the caller's mode ceiling
shivamhwp Nov 3, 2026
f991e33
fix(server): remote task cancel keeps the caller's mode ceiling
shivamhwp Nov 3, 2026
960ee37
chore: merge main into native subagent ownership fix
shivamhwp Nov 3, 2026
0b5a692
fix(server): an owed remote stop keeps its cancel's mode ceiling
shivamhwp Nov 3, 2026
6c67aeb
chore: merge main 30b9c4a956 into native subagent ownership fix
shivamhwp Nov 3, 2026
9807227
chore: merge main e1e520c93c into native subagent ownership fix
shivamhwp Nov 3, 2026
baf7372
chore: merge main c0ec87a55b into native subagent ownership fix
shivamhwp Nov 3, 2026
2058aa6
chore: merge main e2bb9e63e1 into native subagent ownership fix
shivamhwp Nov 3, 2026
a3fc2ca
chore: merge main b183048a82 into native subagent ownership fix
shivamhwp Nov 3, 2026
22c6d37
chore: merge main 3368dd70f2 into native subagent ownership fix
shivamhwp Nov 4, 2026
0d0c067
fix(server): trace native Claude caller lookup
shivamhwp Nov 4, 2026
8da7ac4
fix(server): keep remote delegation within the root's modes
shivamhwp Nov 4, 2026
d786208
fix(server): keep native delegation within the caller's modes
shivamhwp Nov 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions apps/server/src/mcp/McpHttpServer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,43 @@ it.effect("admits provider and OAuth client credentials and points only clients
const deadProvider = yield* call("deadProviderToken");
expect(deadProvider.status).toBe(401);
expect(deadProvider.headers["www-authenticate"]).toBe('Bearer error="invalid_token"');

// Each call's own `_meta` names its agent; the session's initialize `_meta` never does.
seen.length = 0;
const headers = {
accept: "application/json, text/event-stream",
authorization: `Bearer ${providerToken}`,
};
const initialize = yield* httpClient.post("/mcp", {
headers,
body: HttpBody.text(
`{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"1.0.0"},"_meta":{"claudecode/toolUseId":"toolu-initialize"}}}`,
"application/json",
),
});
for (const [id, meta] of [
[2, `,"_meta":{"claudecode/toolUseId":"toolu-first"}`],
[3, `,"_meta":{"threadId":"codex-native-thread","callId":"call-1"}`],
[4, ""],
] as const) {
const called = yield* httpClient.post("/mcp", {
headers: {
...headers,
"mcp-session-id": initialize.headers["mcp-session-id"]!,
"mcp-protocol-version": "2025-06-18",
},
body: HttpBody.text(
`{"jsonrpc":"2.0","id":${id},"method":"tools/call","params":{"name":"probe","arguments":{}${meta}}}`,
"application/json",
),
});
expect(called.status).toBe(200);
}
expect(seen.map((scope) => scope.thread?.nativeCallId)).toEqual([
"claude-tool:toolu-first",
"codex-thread:codex-native-thread",
undefined,
]);
}),
).pipe(Effect.provide(NodeHttpServer.layerTest)),
);
Expand Down
52 changes: 51 additions & 1 deletion apps/server/src/mcp/McpHttpServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,54 @@ const withToolAnalytics =
return yield* registration.pipe(Effect.provideService(McpServer.McpServer, recordingServer));
});

/** The provider's per-call agent id. Claude names the tool call; Codex names its thread. */
const nativeCallIdFromMetadata = (metadata: unknown): string | undefined => {
if (typeof metadata !== "object" || metadata === null) return undefined;
const fields = metadata as Readonly<Record<string, unknown>>;
const toolUseId = fields["claudecode/toolUseId"];
if (typeof toolUseId === "string" && toolUseId.length > 0) return `claude-tool:${toolUseId}`;
const threadId = fields.threadId;
if (typeof threadId === "string" && threadId.length > 0) return `codex-thread:${threadId}`;
return undefined;
};

/**
* Stateful MCP sessions give `McpRequestContext` the initialize `_meta`; each call's own `_meta`
* is on `McpServerClient`. The call id rides on the thread caller for delegation to verify.
*/
const withNativeCallIds = Effect.fn("McpHttpServer.withNativeCallIds")(function* <A, E, R>(
registration: Effect.Effect<A, E, R>,
) {
const server = yield* McpServer.McpServer;
const identifyingServer = McpServer.McpServer.of({
...server,
addTool: Effect.fn("McpServer.addTool")(function (options) {
return server.addTool({
...options,
handle: (payload) =>
Effect.withFiber((fiber) => {
const invocation = Context.getOrUndefined(
fiber.context,
McpInvocationContext.McpInvocationContext,
);
const nativeCallId = nativeCallIdFromMetadata(
Context.getOrUndefined(fiber.context, McpSchema.McpServerClient)?.requestMetadata,
);
return nativeCallId === undefined || invocation?.thread === undefined
? options.handle(payload)
: options.handle(payload).pipe(
Effect.provideService(McpInvocationContext.McpInvocationContext, {
...invocation,
thread: { ...invocation.thread, nativeCallId },
}),
);
}),
});
}),
});
return yield* registration.pipe(Effect.provideService(McpServer.McpServer, identifyingServer));
});

/** `McpServer.registerToolkit` advertises the full codec schema; agents get the compact one. */
const withAdvertisedSchemas = Effect.fn("McpHttpServer.withAdvertisedSchemas")(function* <
Tools extends Record<string, Tool.Any>,
Expand Down Expand Up @@ -1074,7 +1122,9 @@ export const toolkitRegistration = <Tools extends Record<string, Tool.Any>, EX,
) =>
Layer.effectDiscard(
withToolAnalytics(
withAdvertisedSchemas(toolkit, McpServer.registerToolkit(decodeAsAdvertised(toolkit))),
withNativeCallIds(
withAdvertisedSchemas(toolkit, McpServer.registerToolkit(decodeAsAdvertised(toolkit))),
),
),
).pipe(
Layer.provide(
Expand Down
5 changes: 5 additions & 0 deletions apps/server/src/mcp/McpInvocationContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export interface McpThreadCaller {
readonly providerInstanceId: ProviderInstanceId;
/** A delegated child task; its parent owns the work it hands back. */
readonly subagent?: boolean;
/**
* The provider's id for this call's agent (a Claude tool call, a Codex thread), from the
* call's `_meta`. Only an adapter binding for this credential turns it into a native caller.
*/
readonly nativeCallId?: string;
}

export interface McpClientCaller {
Expand Down
Loading
Loading