feat(mcp): update thread metadata#8690

merged5
t3code/codex-turn-mapping
agents/mcp-thread-metadata/update
openedAug 29, 2026byJJulius Marminge
View on GitHubTwo-way sync enabledPR last imported 4d ago

Problem

Provider agents can read thread history but cannot safely rename a thread, regenerate its title, or link and unlink a pull request through the app-owned MCP workflow.

Change

Add t3_thread_update with a provider-compatible object-root schema and typed metadata actions. A focused service keeps updates in the current project, uses V2 commands and idempotent receipts, and returns committed title, title-regeneration, and linked-PR state. Thread list/read expose the corresponding read path; docs and tool presentation cover the user-visible behavior.

Behavior

threadId defaults to the calling thread. Rename requires title; link requires repository, number, and URL; regenerate and unlink take no action-specific payload. Branch and workspace mutations remain outside this tool.

Validation

  • Focused contracts, toolkit schema, presentation, and real MCP/V2 integration tests (4 files, 14 tests)
  • Targeted lint for changed TypeScript files
  • Contracts, shared, and server package typechecks

Dependency

Depends on #8689, which fixes V2 linked-pull-request persistence. Native stack #8709: #8689 → #8690, rooted at agents/mcp-controls/base-490318a. It remains independent from thread organization stack #8708.

Implemented by GPT-5.6-Sol via Codex in T3 Code.

Note

Add t3_thread_update MCP tool for thread metadata actions

  • Introduces ThreadMetadataMcpService and the t3_thread_update orchestrator MCP tool supporting four actions: rename, title regeneration, pull-request link, and pull-request unlink
  • Defines validated contracts in threadMetadataMcp.ts requiring action-specific fields, HTTP(S)-only PR URLs, bounded client request IDs, and non-empty titles up to 512 chars
  • Updates thread.metadata.update command handling in Orchestrator.ts to persist or clear a linked pull request field in the resulting event
  • Thread list and detail responses now include nullable linked pull request and title-regeneration state; determinstic command IDs enable idempotent client-request retries
  • Behavioral Change: OrchestratorMcpThreadListItem and OrchestratorMcpThreadDetail schemas in orchestratorMcp.ts gain new nullable fields; existing MCP consumers that validate strictly against these schemas must accept the added fields

Macroscope summarized aeabb89.


Note

Medium Risk Introduces a new mutating MCP surface that dispatches durable orchestration commands and links external PR metadata; mistakes in scoping or validation could affect thread metadata across a project.

Overview Adds t3_thread_update so provider agents can mutate thread metadata through the orchestrator MCP toolkit: rename, regenerate title, link pull request, and unlink pull request. Workspace/branch changes stay out of scope.

A new ThreadMetadataMcpService enforces orchestration capability, resolves the target thread in the calling project (defaulting to the caller), builds stable thread.metadata.update commands with clientRequestId idempotency, and returns committed title, titleRegeneration, and linkedPullRequest state. OrchestratorMcpService thread list/detail responses now surface linkedPullRequest (and in-flight title regeneration on read).

Contracts live in threadMetadataMcp.ts with action-specific validation (HTTP(S) PR URLs, title length, surrogate-safe idempotency keys). Docs, UI tool presentation, and integration/unit tests cover the new tool and read path.

Reviewed by Cursor Bugbot for commit aeabb8905cb9f78653fc8841f269242411a059ed. Bugbot is set up for automated code reviews on this repo. Configure here.

Synced from GitHub

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 49ac51cb-2ccf-4931-b043-86d94725b410

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

MmacroscopeappBotreviewed · 1 inline threadAug 29, 2026
Synced from GitHub

Effect service conventions: one finding in the new ThreadMetadataMcpService module. Everything else (namespace imports of effect/*, ThreadMetadataMcpService.layer used as a namespace member in McpHttpServer.ts, dependencies acquired via yield* in make, no ManagedRuntime/runPromise at the service boundary) matches the conventions.

Posted via Macroscope — Effect Service Conventions

apps/server/src/mcp/ThreadMetadataMcpService.tslines 23–33Outdated · cce20c4Open thread
……@@ -0,0 +1,220 @@
30+export class ThreadMetadataMcpService extends Context.Service<
31+ ThreadMetadataMcpService,
32+ ThreadMetadataMcpServiceShape
33+>()("t3/mcp/ThreadMetadataMcpService") {}
MmacroscopeappBoton lines 23–33Aug 29, 2026

New service modules should declare the interface inline in the Context.Service declaration rather than keeping a standalone FooServiceShape type; refer to the inferred interface as ThreadMetadataMcpService["Service"] if a name is needed. (ThreadMetadataMcpServiceShape has no other references.)

suggestion
1export class ThreadMetadataMcpService extends Context.Service<
2 ThreadMetadataMcpService,
3 {
4 readonly update: (
5 scope: McpInvocationScope,
6 input: ThreadMetadataMcpUpdateInput,
7 ) => Effect.Effect<ThreadMetadataMcpUpdateResult, OrchestratorMcpFailure>;
8 }
9>()("t3/mcp/ThreadMetadataMcpService") {}

Posted via Macroscope — Effect Service Conventions

Fixed in 6c22941568. The service now declares its interface inline in Context.Service; the standalone, otherwise-unused shape type is removed.

apps/server/src/mcp/toolkits/orchestrator/tools.tslines 211–211Outdated · cce20c4Open thread
……@@ -190,6 +197,19 @@ export const ThreadReadTool = Tool.make("t3_thread_read", {
208+})
209+ .annotate(Tool.Title, "Update T3 thread metadata")
210+ .annotate(Tool.Destructive, true)
211+ .annotate(Tool.Idempotent, true);
MmacroscopeappBoton lines 211–211Aug 29, 2026

🟡 Medium orchestrator/tools.ts:211

ThreadUpdateTool is published as idempotent, but repeating action: "regenerate_title" without clientRequestId generates a new command each time and schedules another title-generation effect. Marking the tool idempotent is therefore incorrect; set Tool.Idempotent to false unless idempotency is enforced for requests without clientRequestId.

suggestion
1 .annotate(Tool.Idempotent, false);
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
text
1In file @apps/server/src/mcp/toolkits/orchestrator/tools.ts around line 211:
2
3`ThreadUpdateTool` is published as idempotent, but repeating `action: "regenerate_title"` without `clientRequestId` generates a new command each time and schedules another title-generation effect. Marking the tool idempotent is therefore incorrect; set `Tool.Idempotent` to `false` unless idempotency is enforced for requests without `clientRequestId`.

Fixed in 6c22941568. The tool-level idempotency hint is now false because calls without clientRequestId create fresh commands; the description still documents clientRequestId-based retry idempotency, and the production MCP annotation assertion now verifies false.

Synced from GitHub

Thread transfer impact

⚠️ The latest CI run did not produce a thread transfer result for aeabb89.

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 production MCP tool that lets orchestration-authorized agents mutate durable thread metadata, link pull requests, and trigger asynchronous title generation. Its expanded capability surface and downstream side effects are broader than a small isolated change.

No code changes detected at 1d3256b. Prior analysis still applies.

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

docs/orchestration-v2/orchestrator-mcp-server.mdlines 393–393Outdated · 4736cd6Open thread
……@@ -376,7 +391,8 @@ orchestration_error
376391## Code Ownership
377392
378393- Shared schemas: `packages/contracts/src/orchestratorMcp.ts`
MmacroscopeappBoton lines 393–393Aug 30, 2026

🟢 Low orchestration-v2/orchestrator-mcp-server.md:393

The ownership list points maintainers only to packages/contracts/src/orchestratorMcp.ts, so they cannot find the ThreadMetadataMcpUpdateInput and result contract used by ThreadMetadataMcpService. Add packages/contracts/src/threadMetadataMcp.ts to the shared schemas entry.

No longer relevant as of a965fc4eda71a107953d2c01f013b2b6755b247d

apps/server/src/mcp/ThreadMetadataMcpService.tsline 153Outdated · 4736cd6Open thread
……@@ -0,0 +1,218 @@
150+ const parent = yield* threadManagement
151+ .getThreadProjection(scope.threadId)
152+ .pipe(
153+ Effect.mapError((error) =>
MmacroscopeappBoton line 153Aug 30, 2026

🟡 Medium mcp/ThreadMetadataMcpService.ts:153

When the calling thread no longer exists, getThreadProjection(scope.threadId) fails with OrchestratorProjectionError, but this mapper always returns orchestration_error. The update tool therefore reports a server failure instead of thread_not_found, so MCP clients cannot handle stale calling threads; map the unavailable-thread projection error to thread_not_found.

✅ Resolved in a965fc4eda71a107953d2c01f013b2b6755b247d

Fixed in a965fc4eda71a107953d2c01f013b2b6755b247d. The service now checks the typed ThreadManagement shell first: a null shell maps to thread_not_found, while a shell read failure or a subsequent Orchestrator projection/storage failure remains orchestration_error. Focused tests cover both the missing caller and an OrchestratorProjectionError so storage failures are not misclassified.

Added 5 commits

  • fix(mcp): validate thread metadata inputs05869dd
  • test(mcp): align metadata service imports1d3256b
  • feat(mcp): update thread metadata8003c31
  • fix(mcp): classify missing metadata callers948caad
  • fix(mcp): correct metadata tool annotationsb1a89b6