feat(mcp): expose project thread launch service#10565

merged3
agents/mcp-thin/attachments
agents/mcp-thin/project-launches
openedSep 7, 2026byJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 5d ago

Part 15/16 of the shared-core and MCP stack. Based on #10564. Next: #10566.

Expose ThreadLaunchService through t3_thread_launch, including a registered project selector, workspace strategy, optional message and pending attachments.

Existing create_threads/t3_thread_start are untouched. Requires a full-access/default caller. Each call is a new launch; failures can leave an accepted partial thread. Preparation and workspace behavior remain in ThreadLaunchService. No new parent-run admission or retry journal.

Attachment claiming and launch execution now go through ThreadMessageIntake, shared with RPC. MCP retains caller defaults, pending-upload policy, and response mapping.

MCP-only rebuild of #8678, preserving attribution to Julius Marminge's original work. Original branches remain available for separate service follow-ups.

Validation: The composed stack passes 94 tests across 12 focused files, including shared core MCP and real MCP/V2 integration, attachment intake, project RPC/service contracts, and client model command selection. Server/contracts/shared/client-runtime typechecks and targeted format/lint/diff checks pass. New behavior coverage lives with the shared operation; no per-tool mock suite was added. Current-head CI is shown below.

Layer size: 3 files, +102/-1. No domain-service production implementation or documentation files change in this MCP layer.

Prepared with Codex in the OpenAI agent runtime.

Note

Add t3_thread_launch MCP tool and handler to ProjectToolkit

  • Adds the t3_thread_launch handler in project.ProjectHandlersLive, which validates a full-access caller, accepts pending-upload attachments, applies caller defaults for project/model/modes, and launches a thread through ThreadMessageIntake, returning thread ID and optional initial-run status.
  • Adds the project.ThreadLaunchTool schema with bounded message, up to eight attachments, and structured success/failure output; registers it as the first tool in ProjectToolkit.
  • Adds a display-name entry ("Launch a project thread") in T3_MCP_TOOLS.
  • Risk: launch preparation may continue after acceptance and can leave a created thread on failure; callers must supply only pending-upload attachments or receive an orchestration error.

Macroscope summarized 3492b15.

apps/server/src/mcp/toolkits/project/handlers.tslines 72–89Outdated Ā· 6e7c612Open thread
MmacroscopeappBoton lines 72–89Sep 7, 2026

🟔 Medium project/handlers.ts:72

A failed launch.launch leaves the thread-scoped attachment copies orphaned, so repeated failed launches accumulate files while the pending originals remain retryable. The handler maps the launch error without releasing claimed.claimedPaths; release those paths on launch failure before returning the error.

diff
1- .pipe(Effect.mapError(unavailable));
2+ .pipe(
3+ Effect.tapError(() => Claims.releaseClaimedAttachments(claimed.claimedPaths)),
4+ Effect.mapError(unavailable),
5+ );
šŸš€ Reply "fix it for me" or copy this AI Prompt for your agent:
text
1In file @apps/server/src/mcp/toolkits/project/handlers.ts around line 72:
2
3A failed `launch.launch` leaves the thread-scoped attachment copies orphaned, so repeated failed launches accumulate files while the pending originals remain retryable. The handler maps the launch error without releasing `claimed.claimedPaths`; release those paths on launch failure before returning the error.

The proposed blanket cleanup is unsafe at this boundary. In the unchanged ThreadLaunchService, message.dispatch commits the attachment references at lines 520-534, then getThreadProjection at lines 548-550 can still fail; the later preparation re-read can also fail. A launch error therefore does not prove those copies are unused. Releasing claimedPaths in tapError can delete bytes referenced by an accepted durable message. This wrapper deliberately retains copies on an uncertain outcome, as stated in the PR limits. Cleanup that distinguishes proven pre-acceptance failure from partial acceptance belongs in the shared launch service as a separate change; no unconditional release is being added here.

Synced from GitHub

Thread transfer impact

āš ļø The latest CI run did not produce a thread transfer result for 3492b15.

This comment will update automatically after the next completed run.

Synced from GitHub

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds a new destructive, open-world MCP operation that can create project threads, launch model runs, provision worktrees, and execute setup scripts, including asynchronous preparation after acceptance. Its broad runtime side effects make it unsuitable for automatic approval.

No code changes detected at 3492b15. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

apps/server/src/mcp/toolkits/project/handlers.tslines 29–46Outdated Ā· 4ae96cbOpen thread
……@@ -24,6 +26,60 @@ const mutation = Effect.gen(function* () {
43+ return yield* new OrchestratorMcpFailure({
44+ code: "invalid_request",
45+ message: "A new thread accepts only pending attachment uploads.",
46+ });
MmacroscopeappBoton lines 29–46Sep 7, 2026

This handler introduces new backend behavior that nothing exercises: the full-access/default capability gate, the pending-upload-only attachment rejection, the caller inheritance of projectId/modelSelection/runtimeMode/interactionMode, and the runId/status lookup keyed by messageId. Consider adding a focused test (e.g. toolkits/project/handlers.test.ts, in the style of toolkits/preview/handlers.test.ts / toolkits/worktree/registration.test.ts) that provides a test layer for ThreadLaunchService only and asserts the denied-caller path, the non-pending attachment rejection, and the inherited defaults reaching launch.

Posted via Macroscope — Effect Service Conventions

The maintainer explicitly requested shared MCP tests and existing service tests instead of per-tool suites. This wrapper delegates execution/provisioning to ThreadLaunchService, uses the shared live caller helper, restricts project launch to full-access/default, and only accepts pending uploads for a new thread. Default inheritance is explicit nullish forwarding; the returned run is found by the exact generated initial message ID, never by the latest run. These lines have been source-reviewed; no concrete faulty behavior was identified. Launch execution/partial acceptance remain the service responsibility and its tests; the MCP core/real integration coverage stays shared.

apps/server/src/mcp/toolkits/project/handlers.tslines 29–46Outdated Ā· 4ae96cbOpen thread
……@@ -24,6 +26,60 @@ const mutation = Effect.gen(function* () {
43+ return yield* new OrchestratorMcpFailure({
44+ code: "invalid_request",
45+ message: "A new thread accepts only pending attachment uploads.",
46+ });
MmacroscopeappBoton lines 29–46Sep 7, 2026

This handler adds backend behavior that lives only here and not in ThreadLaunchService: the full-access/default capability gate, the pending-upload-only attachment rule, and the caller-inheritance defaults for projectId/modelSelection/runtimeMode/interactionMode/workspaceStrategy. None of it is covered by a test — consider a focused test alongside toolkits/core.test.ts, which already exercises a toolkit handler through Toolkit.toLayer with a test layer for ThreadManagementService, so the launch service can be substituted the same way without mocking the launch logic itself.

Posted via Macroscope — Effect Service Conventions

The maintainer explicitly requested shared MCP tests and existing service tests instead of per-tool suites. This wrapper delegates execution/provisioning to ThreadLaunchService, uses the shared live caller helper, restricts project launch to full-access/default, and only accepts pending uploads for a new thread. Default inheritance is explicit nullish forwarding; the returned run is found by the exact generated initial message ID, never by the latest run. These lines have been source-reviewed; no concrete faulty behavior was identified. Launch execution/partial acceptance remain the service responsibility and its tests; the MCP core/real integration coverage stays shared.

The delegation boundary is clear, but the capability gate, pending-upload rejection, and inheritance are handler-owned. Current shared integration coverage does not exercise uploads or inherited defaults, so the test-coverage concern remains.

apps/server/src/mcp/toolkits/project/handlers.tslines 58–60Outdated Ā· 6c9a47bOpen thread
……@@ -35,6 +37,60 @@ const mutation = Effect.gen(function* () {
57+ });
58+ const claimed = yield* Claims.claimPendingAttachments({ threadId, attachments }).pipe(
59+ Effect.mapError(unavailable),
60+ );
MmacroscopeappBoton lines 58–60Sep 7, 2026

unavailable discards the structured AttachmentClaimError, so caller-actionable claim reasons ("attachment not found", "stored size does not match", "attachment type does not match the upload") collapse into "The operation could not be completed." The sibling claim site (toolkits/attachment/handlers.ts) and ws.ts launchThread both preserve error.message; consider mapping this failure where its context is known rather than through the generic helper.

suggestion
1 const claimed = yield* Claims.claimPendingAttachments({ threadId, attachments }).pipe(
2 Effect.mapError(
3 (error) =>
4 new OrchestratorMcpFailure({ code: "orchestration_error", message: error.message }),
5 ),
6 );

Posted via Macroscope — Effect Service Conventions

Fixed in 530b178333. The launch wrapper now preserves AttachmentClaimError.message, matching the send wrapper and existing WS boundary, while excluding its raw cause. The public code remains orchestration_error because this typed channel also covers operational copy/stat failures. Final shared core + real MCP integration 7/7, server typecheck and targeted lint/fmt/diff pass. No claim or launch service behavior changed.

Added 3 commits

  • refactor(mcp): use shared attachment launch intake3492b15
  • feat(mcp): expose project thread launch service5d6a48f
  • fix(mcp): preserve safe launch attachment errorsf3bb499