1# Orchestrator MCP Server
2
3## Purpose
4
5T3 exposes V2 orchestration through its app-owned MCP endpoint. A provider
6agent can use this endpoint to:
7
8- create an app-owned sub-agent on any supported provider instance;
9- wait for or poll the sub-agent's durable result;
10- cancel an active delegated task; and
11- create one or more ordinary top-level T3 threads;
12- list a project's threads and incrementally read any thread;
13- rename threads, regenerate titles, and link or unlink pull requests;
14- send or steer follow-up messages; and
15- wait for or interrupt ordinary thread runs.
16
17These are T3 orchestration operations, not provider-native sub-agent APIs.
18Delegated tasks always create a T3 child thread and run. The child receives
19only the supplied task prompt, plus an optional role instruction supplied in
20the same tool call. Parent conversation history is not copied into the child.
21
22`ThreadManagementService` is the shared server application boundary for V2
23WebSocket commands and MCP. It owns thread lookup, listing, send-mode
24selection, durable send postconditions, wait polling, and interrupt selection;
25`OrchestratorV2` remains the lower-level command/event processor. Transport
26adapters only authenticate, resolve transport-specific inputs, and shape
27responses.
28
29## Transport And Authentication
30
31The orchestration tools share the existing authenticated HTTP MCP endpoint:
32
33```text
34http://127.0.0.1:<server-port>/mcp
35```
36
37The provider-visible server key is `t3-code`. The endpoint registers both the
38preview toolkit and the orchestration toolkit.
39
40Before `ProviderSessionManager` opens a new V2 provider session, it asks
41`McpSessionRegistry` for a credential scoped to:
42
43- the T3 environment;
44- the parent T3 thread;
45- the concrete provider instance; and
46- the provider session.
47
48The credential grants `preview` and `orchestration` capabilities. Credentials
49expire after a maximum lifetime, expire when idle, and are revoked when the
50provider session is released. The raw token is not persisted in orchestration
51state.
52
53The MCP HTTP server resolves the bearer token and supplies the resulting
54`McpInvocationScope` to tool handlers. Orchestration handlers additionally
55check the `orchestration` capability before reading or mutating state.
56
57## Provider Injection
58
59### Codex V2
60
61Codex app-server receives the remote MCP server through command-line config
62overrides:
63
64```text
65-c mcp_servers.t3-code.url=http://127.0.0.1:<port>/mcp
66-c mcp_servers.t3-code.bearer_token_env_var="T3_MCP_BEARER_TOKEN"
67```
68
69The provider-session token is placed in `T3_MCP_BEARER_TOKEN`. Both the
70production Codex launcher and the injectable test launcher use the same
71projection helper.
72
73### Claude Agent SDK V2
74
75Claude receives an HTTP MCP server in its query options:
76
77```ts
78{
79 mcpServers: {
80 "t3-code": {
81 type: "http",
82 url: "http://127.0.0.1:<port>/mcp",
83 headers: {
84 Authorization: "Bearer <provider-session-token>",
85 },
86 },
87 },
88 allowedTools: [
89 // existing allowed tools
90 "mcp__t3-code__*",
91 ],
92}
93```
94
95The adapter logs only whether MCP configuration exists; it does not log the
96server headers or token.
97
98### Cursor Agent SDK V2
99
100Cursor receives the same authenticated HTTP MCP endpoint through the SDK's
101`mcpServers` agent and send options. The adapter passes the authorization header
102to the SDK but projects only redacted option metadata into protocol diagnostics.
103
104### Grok ACP V2
105
106Grok receives the authenticated HTTP MCP endpoint through the ACP
107`session/new`, `session/load`, and `session/fork` `mcpServers` field. The shared
108ACP adapter owns standard protocol behavior; the Grok flavor adds xAI extension
109requests such as structured user questions.
110
111ACP does not define native subagents or active steering. Grok therefore uses
112orchestrator-owned child threads and implements steering through
113cancel-and-restart. Its current driver also lacks `session/fork`, so app forks
114use portable context transfer. These are orchestrator policies, not
115provider-specific MCP tools.
116
117### ACP Registry V2
118
119The `acpRegistry` driver is the generic flavor of the same shared ACP adapter.
120Each provider instance names an agent from the official ACP Registry. Settings
121searches the registry through the connected server, then prepares a compatible
122distribution before persisting the provider instance. Binary distributions use
123a managed, versioned cache; declared checksums are verified when present.
124Version-pinned `npx` packages install globally through `npm`; `uvx` packages use
125`uv tool install`. ACP launches the resulting global command directly, so the
126same command is available for terminal authentication. A local executable may
127override the installed command without changing the registry-declared arguments
128or environment.
129
130Search, preparation, provider status, and session startup share one
131server-scoped catalog service. This keeps platform selection and registry
132validation identical across settings and runtime use. Catalog inspection never
133starts an ACP process, probes models, or performs authentication. The managed
134provider snapshot creates a disposable `session/new` through the normal provider
135refresh lifecycle, using success as the authentication-readiness proof and
136projecting advertised models into the snapshot. Terminal-only login remains a
137manual operation on the connected server.
138
139Capabilities such as session loading, session forking, models, modes, and MCP
140transport are enabled only when the selected agent advertises them. Missing
141features degrade through V2 policy: steering uses interrupt-and-restart,
142forking uses portable context when native `session/fork` is unavailable, and
143subagents use orchestrator-owned child threads. Registry agents do not receive
144provider-specific extensions; those remain in flavors such as Grok.
145
146### Pi V2
147
148T3 keeps a provider-session HTTP bridge even when Pi supports native MCP.
149`mcp.json` entries override native registrations, and native MCP's default
15060-second request timeout can interrupt long-running T3 tools. The bridge owns
151the injected endpoint and credential and forwards Pi's cancellation signal.
152
153When a provider session credential exists, the adapter writes a T3-owned
154extension into the server cache and spawns
155`pi --mode rpc --extension <cache>/pi-t3-mcp-extension.ts` with:
156
157```text
158T3_MCP_URL=http://127.0.0.1:<port>/mcp
159T3_MCP_BEARER_TOKEN=<provider-session-token>
160```
161
162The extension preserves public names under `mcp__t3-code__` for saved loadouts
163and tool selectors. Modern Pi also receives hidden `mcp__t3_code__` aliases,
164which reserve the normalized namespace against configured MCP servers without
165adding declarations or search results. On Pi 0.99+,
166`orchestrator_capabilities`, `delegate_task`, and `task_status` remain directly
167available; optional tools are discovered through Pi's builtin `tool_search`.
168On older Pi or without builtin search, all tools remain directly available.
169The bridge calls the original MCP tool name over HTTP. Follow-up requests send
170`mcp-protocol-version: 2025-06-18`; Effect's MCP transport returns 400
171without it. The first turn of a session also receives the shared T3
172orchestration instructions.
173
174Pi keeps ownership of native extension discovery. T3 does not replace Pi's
175`subagent` tool or reproduce Pi's package and project-trust loader. Durable
176delegation goes through the namespaced T3 MCP `delegate_task` tool and the
177shared orchestration child-thread lifecycle. When Pi's example `subagent`
178extension is installed, the adapter observes its documented `details.results`
179shape and projects task cards with no child thread id. Unknown result shapes
180remain ordinary dynamic tool output.
181
182### Provider Support
183
184A provider instance can run child tasks when its live `ProviderInstance`
185exposes a V2 `orchestrationAdapter` — the same registration the orchestrator
186resolves when a `delegated_task.request` executes. That covers Codex, Claude
187Agent SDK, Cursor Agent SDK, Grok, generic registry agents over ACP, OpenCode,
188OpenCode 2, Pi, Antigravity, and any future driver that builds an adapter.
189Capability discovery still reports other registered provider instances, but
190marks them unavailable for orchestration when no adapter resolves. This keeps
191provider selection model-visible without allowing a request that cannot run.
192
193## Tool Surface
194
195The server exposes eleven orchestration tools.
196
197### `orchestrator_capabilities`
198
199Returns:
200
201- the inherited provider instance and model;
202- the parent runtime and interaction modes;
203- registered provider instances and advertised models;
204- whether each provider can run a child task; and
205- feature flags for polling, cancellation, and batch thread creation.
206
207Unavailable providers include model-visible constraints such as missing V2
208adapter support, disabled state, missing executable, or missing authentication.
209
210### `delegate_task`
211
212Creates a T3-owned child thread and immediately dispatches the supplied task
213prompt.
214
215```ts
216type DelegateTaskInput = {
217 task: string;
218 target?: {
219 providerInstanceId?: string;
220 driverKind?: string;
221 model?: string;
222 };
223 title?: string;
224 role?: "implementation" | "research" | "review" | "design" | "test" | "general";
225 mode?: "async" | "wait";
226 timeoutMs?: number;
227 clientRequestId?: string;
228 runtimeMode?: "inherit" | "approval-required" | "auto-accept-edits" | "full-access";
229 interactionMode?: "inherit" | "plan" | "default";
230};
231```
232
233Provider, model, runtime mode, and interaction mode inherit from the parent
234when omitted. A driver-only target inherits the parent's provider instance
235when it can run child tasks, and otherwise selects an available instance of
236that driver; an explicit `providerInstanceId` is honored exactly and fails
237when unavailable. Selecting a different provider without a model uses that
238provider's first advertised model.
239
240A provider runs a runtime mode it does not offer as `approval-required`, so a
241child would ask for approval on every command. `orchestrator_capabilities`
242lists `runtimeModes` only for a provider that offers some of the modes, and
243omits it for a provider that offers all of them. When the target lists
244`runtimeModes`, an explicit `runtimeMode` outside that list is refused, and an
245inherited mode steps down to the broadest mode the target offers.
246
247Each delegated review round uses a new `delegate_task` call with the original brief,
248prior findings, responses, and unresolved objections. Track each round by its own `taskId` and use
249a distinct `clientRequestId` per round, stable across retries of that round.
250`childThreadId` is backing storage, not a target for another review round through
251`t3_thread_send`. Ordinary thread messaging remains available for user-requested
252conversations; it does not reopen a completed task. There is no task-level follow-up
253API for preserving the same reviewer session.
254
255Delegation requires an active parent run owned by the MCP credential's
256provider session. The request becomes the V2 command
257`delegated_task.request`.
258
259`mode: "async"` returns the current durable state immediately.
260`mode: "wait"` waits for the task result, including nested work and completion follow-ups, or until
261the timeout expires. A wait timeout does not cancel the child; the result sets
262`waitTimedOut: true`, and the caller can continue with `task_status`. A pending blocking
263question also ends the wait with `hasPendingQuestion: true` and `waitTimedOut: false`.
264If `hasPendingQuestion`, pass `threadId=childThreadId` to `t3_pending_request_list`,
265`t3_pending_request_read` (with `requestId`) and `t3_pending_request_respond`, or ask
266the user; the task's completion still wakes this thread. Async questions and approvals
267do not end the wait.
268
269```ts
270type DelegateTaskResult = {
271 taskId: string;
272 childThreadId: string;
273 childRunId: string | null;
274 childNodeId: string;
275 status: "queued" | "running" | "waiting" | "completed" | "failed" | "cancelled" | "interrupted";
276 workState: "working" | "waiting_for_children" | "result_available";
277 hasPendingChildRuns: boolean;
278 providerInstanceId: string;
279 model: string | null;
280 summary: string | null;
281 resultContextTransferId: string | null;
282 latestTerminalRunId: string | null;
283 latestTerminalStatus: "completed" | "failed" | "cancelled" | "interrupted" | null;
284 latestTerminalSummary: string | null;
285 latestTerminalResultContextTransferId: string | null;
286 hasPendingQuestion: boolean;
287 waitTimedOut: boolean;
288};
289```
290
291### `task_status`
292
293Reads a delegated task from the parent thread's durable projection. A task ID
294from another parent thread is rejected. `childRunId` identifies the original
295run. `workState` distinguishes active work, a finished turn waiting for children,
296and an available result. The task remains nonterminal until its known work
297finishes. Its published `summary` and result transfer then remain stable across
298later follow-ups. `hasPendingChildRuns` reports later queued or executing turns;
299`latestTerminal*` exposes later executed, non-monitor results without replacing
300the published task result.
301
302### `task_cancel`
303
304Stops the child thread with the internal `thread.stop` command, then stops every
305task the child delegated, and disposes automatic parent delivery. Like a user Stop,
306`thread.stop` interrupts the running turn, holds queued turns, and ends pull request
307watches. A nonterminal task with no interruptible run is rejected. A terminal task
308returns its existing status, and its child thread still stops, including later
309runs and watch wakes. Published task results remain available. It accepts an
310optional cancellation reason.
311
312### `create_threads`
313
314Creates between one and twenty ordinary top-level T3 threads:
315
316```ts
317type CreateThreadsInput = {
318 threads: Array<{
319 prompt?: string;
320 title?: string;
321 target?: {
322 providerInstanceId?: string;
323 driverKind?: string;
324 model?: string;
325 };
326 runtimeMode?: "inherit" | "approval-required" | "auto-accept-edits" | "full-access";
327 interactionMode?: "inherit" | "plan" | "default";
328 }>;
329 clientRequestId?: string;
330};
331```
332
333Each entry independently resolves provider, model, and modes. The new threads
334inherit the parent's project, branch, and worktree path, but they have no
335sub-agent lineage. Entries with a prompt immediately dispatch a run; entries
336without a prompt remain idle.
337
338### `t3_thread_launch`
339
340Launches one ordinary top-level thread through the app's launch service. Use an
341explicit `workspaceStrategy` to create a new worktree (`worktree` with `baseRef`),
342attach an existing checkout (`existing_worktree` with `worktreePath`), or use the
343project root (`root`, also the default). The thread is bound to that workspace
344before the agent starts. Creating a worktree in the task prompt does not update
345this binding.
346
347Pass the task in `message`. Project, model, and modes inherit when omitted;
348workspace does not. `scratch: true` launches without a project, in a folder of
349its own under the environment's Scratch project. For stacked PRs, use the parent branch as `baseRef` with
350`startFromOrigin: false`. The new thread may not run with broader runtime or
351interaction modes than the caller. Launch has no retry key, so inspect existing threads after a failed or lost response before
352launching again. `create_threads` remains the batch option for a shared checkout.
353
354### `t3_thread_list`
355
356Lists durable thread shells in one project, newest first: `projectId` when
357given, else the calling thread's project. Callers can filter by title, run
358status, and whether app-owned sub-agent threads are included. Results are
359bounded and offset-paginated. Deleted threads are never listed.
360
361### `t3_thread_read`
362
363Reads the durable state, recent runs, and visible timeline of any thread in
364the environment by thread ID. A deleted thread returns `thread_not_found`. The
365default `messages` view returns user messages, assistant
366messages, and proposed plans. The `activity` view also returns summarized tool,
367reasoning, checkpoint, handoff, and runtime-request items. Large item text is
368bounded and reports whether it was truncated. `afterPosition` and
369`nextPosition` support incremental reads.
370
371Thread and message results include required `createdBy` and `creationSource`
372provenance. MCP-created threads and user-role messages use `createdBy: "agent"`
373and `creationSource: "mcp"`; provider output uses `creationSource: "provider"`.
374Actor and ingress are separate so agent-authored user-role messages remain
375distinguishable from human-authored messages.
376
377Agents mention another thread as `[title](t3-thread://v1/<threadId>)`. The
378link carries only the id, which resolves in the environment of the message that
379holds it. Clients show the thread's current title rather than the label, so a
380rename never leaves a stale link.
381
382List and read results report `snoozed` and `snoozedUntil`, and
383`t3_thread_list` filters on `snoozed`. The server's `isSnoozed` follows the
384client's `effectiveSnoozed`, so agents and the sidebar agree: a snoozed thread
385wakes early when it has a pending request, fails, or completes after the snooze.
386
387### `t3_thread_update`
388
389Updates metadata for the calling thread or any other thread in the environment.
390The typed actions are `rename`, `regenerate_title`, `link_pull_request`, and
391`unlink_pull_request`. A link input supplies the repository, number, and URL;
392the server records the target thread's project ID. Branch and workspace changes
393are outside this tool.
394
395The result includes the command ID and durable event sequence together with the
396resultant title, title-regeneration marker, and linked pull request. Reusing a
397`clientRequestId` for the same action and thread replays the same command
398receipt. Thread list and read results expose the linked pull request, and thread
399detail also exposes an in-flight title regeneration.
400
401### `t3_thread_send`
402
403Sends a message to any ordinary or delegated thread in the environment:
404
405- `auto` starts an idle thread, steers a fully active turn, or queues behind a
406 turn that is not yet steerable;
407- `queue` creates a separate follow-up run after active work;
408- `steer` requires a steerable active provider turn; and
409- `restart` requires an active provider turn and uses the orchestrator's
410 interrupt-and-restart path.
411
412The target runtime and interaction modes may not be broader than the caller's.
413Stable command and message IDs are derived from `clientRequestId` for
414idempotent retries.
415
416### `t3_thread_wait`
417
418Waits for a selected run to become `completed`, `failed`, `cancelled`,
419`interrupted`, or `rolled_back`. Without `runId`, it pins the latest run at call
420time; an idle thread returns immediately. A timeout reports the latest durable
421status and does not cancel work.
422
423### `t3_thread_interrupt`
424
425Interrupts a selected active run through the normal V2 `run.interrupt` command.
426Without `runId`, it selects the newest interruptible run. A terminal run is
427returned unchanged, and a thread with no active provider turn returns
428`no_active_run`.
429
430## Delegated Task Lifecycle
431
432The MCP server is a command ingress into V2. It does not call provider adapters
433directly.
434
435```text
436provider model
437 -> MCP tools/call delegate_task
438 -> authenticated OrchestratorMcpService
439 -> shared ThreadManagementService
440 -> V2 delegated_task.request command
441 -> child thread + child run
442 -> parent app_owned subagent projection
443 -> parent/child execution nodes
444 -> consumed subagent_spawn context transfer
445 -> normal provider effect and runtime ingestion
446 -> child run reaches a terminal state
447 -> parent subagent/node/turn item finalized
448 -> consumed subagent_result context transfer
449 -> wait result or later task_status result
450```
451
452The child thread has lineage relationship `subagent` and points back to the
453parent node. The parent gets an `app_owned` sub-agent projection and a
454sub-agent turn item so the existing debug UI can render progress.
455
456Terminal provider events trigger finalization. The event stream first replays
457persisted events and then follows live events, so finalization also runs after
458a server restart. An existing `subagent_result` transfer makes finalization
459idempotent.
460
461Users rarely watch child threads, so a child question would otherwise go
462unanswered. When a child opens a pending `user_input` request, the parent gets
463a notification message telling it to answer with the pending request tools or
464ask the user. The message says whether the child is blocked or, for a Codex
465async question, still working. If the question is answered or dismissed before
466the notification starts a turn, that queued turn is cancelled. Approvals are not
467routed, because those tools cannot answer them.
468A parent still blocked in a `delegate_task` wait gets the blocking question
469through that wait instead.
470If the provider cuts that wait short, a still-pending live question wakes the
471parent when its launching run completes, fails, or waits for post-turn work.
472Stopping or interrupting the parent prevents this catch-up.
473
474A failed run exposes its provider error before any progress text. Successful
475results use the latest assistant content from the final work turn.
476
477## Policy And Idempotency
478
479- A child runtime mode may stay equal to or become narrower than the parent
480 mode. It may not escalate privileges.
481- A child interaction mode may stay equal to or narrow from `default` to
482 `plan`. It may not escalate from `plan` to `default`.
483- Thread tools take any thread in the environment as a target. For a thread
484 caller, list and search cover one project: its own unless `projectId` is given.
485- A tool that changes another thread needs the calling thread's live run, and
486 the target's runtime and interaction modes may not be broader than the
487 caller's. This is the same privilege ceiling as child creation.
488- Provider instances must be enabled, installed, available, authenticated, and
489 backed by a V2 adapter.
490- A requested model must be advertised by the selected provider when the
491 provider publishes a model list.
492- `clientRequestId` derives stable command, thread, and message IDs within the
493 provider session. Retrying the same call returns the same durable work.
494- Calls without `clientRequestId` receive a generated request key and create
495 new work.
496
497Expected denials use the typed `OrchestratorMcpFailure` result:
498
499```text
500capability_denied
501parent_not_active
502provider_unavailable
503model_unavailable
504runtime_mode_escalation_denied
505interaction_mode_escalation_denied
506task_not_found
507task_not_cancellable
508thread_not_found
509run_not_found
510thread_not_sendable
511thread_not_interruptible
512invalid_request
513orchestration_error
514```
515
516## Code Ownership
517
518- Shared schemas: `packages/contracts/src/orchestratorMcp.ts` and
519 `packages/contracts/src/threadMetadataMcp.ts`
520- MCP services: `apps/server/src/mcp/OrchestratorMcpService.ts` and the focused
521 `apps/server/src/mcp/ThreadMetadataMcpService.ts`
522- Tool definitions and handlers:
523 `apps/server/src/mcp/toolkits/orchestrator/`
524- HTTP registration and authentication:
525 `apps/server/src/mcp/McpHttpServer.ts`
526- Credential lifecycle: `apps/server/src/mcp/McpSessionRegistry.ts`
527- Provider injection:
528 `apps/server/src/orchestration-v2/ProviderSessionManager.ts` and V2 adapters
529- Durable delegated-task command and finalization:
530 `apps/server/src/orchestration-v2/Orchestrator.ts`
531
532## Verification
533
534The integration test uses the real MCP toolkit registration, V2 orchestrator,
535SQL persistence, event ingestion, projections, and checkpoints. Only the
536external provider adapters are deterministic test implementations.
537
538Coverage includes:
539
540- capability discovery;
541- cross-provider delegated completion;
542- prompt-only child context;
543- parent and child lineage projections;
544- spawn and result context transfers;
545- async status polling;
546- cancellation;
547- batch ordinary-thread creation;
548- thread listing and timeline reads, including another project's threads;
549- ordinary-thread send, wait, steering, and interruption;
550- inheritance and per-thread provider overrides; and
551- idempotent retries.
552
553Provider adapter tests separately verify Codex, Claude, Cursor, Grok, and ACP
554Registry behavior and MCP injection. The provider-session manager test verifies
555that credentials exist before an adapter opens and are revoked when it closes.