spike(the-framework): MCP-tools bind for topics (#1121) - #1130
Closed
suleimansh wants to merge 1 commit into
Closed
Conversation
Give a project-less topic run (#1120) two MCP tools, list_projects and create_project, so its agent binds to a project by calling a tool rather than parking on a gate. create_project registers the project and signals the run over the control channel; the run folds the bind onto RunMeta.boundProjectId. The worktree re-home / respawn is out of scope (#1122). Spike for #1121, part of the #1129 decision.
This was referenced Jul 24, 2026
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Spike for #1121, part of the #1129 decision. Draft — not for merge. Evaluates the MCP-tools variant of the topic-to-project bind against the await-gate variant (spiked separately).
What it does
A project-less "topic" run (#1120) now gets two MCP tools, so its agent binds to a project by calling a tool instead of parking on a gate:
list_projects— returns the registered projects (reads the registry).create_project— takes a path, registers it (addProject), and binds this run to it.The bind is signalled back to the run over the existing control channel:
create_projectappends a new{ kind: 'bind', projectId }control entry to the run'scontrol.jsonl, the run's watcher folds it to abindevent, and that lands onRunMeta.boundProjectId. Same file-is-the-seam design the dashboard already steers with, so there is no new run<->subprocess IPC.The worktree re-home / respawn that a real bind implies is out of scope — that is #1122; a comment marks where it goes.
Wiring
src/projects-mcp.ts(new) — the server + tools (authored on@gemstack/mcpdirectly),projectsMcpServersspawn spec, andwithProjectsMcp(mirrorswithBrowser).src/cli.ts— hiddenmcp-projectssubcommand (the spawnable server, reads the run cwd fromFRAMEWORK_RUN_CWD); folds the server into topic runs'claudeOpts.mcpServers; handles thebindcontrol entry.src/control.ts/src/events.ts/src/store/run-store.ts/src/terminal.ts— thebindcontrol entry, thebindevent,RunMeta.boundProjectId, and the terminal line.No prompt change was needed — MCP advertises the tools itself.
Proof
node dist/bin.js mcp-projects):tools/listreturnslist_projects, create_project;create_projectreturns the record withbound: true; the run'scontrol.jsonlgets{"kind":"bind","projectId":"smoke-142xwjl"}.How it felt (verdict)
Clean:
--browserprecedent (withBrowser->withProjectsMcp, onemcpServersentry). The MCP plumbing (--mcp-configtemp file, merge-not-replace) already existed, so wiring a second server was trivial.handoff-armedalready lives.Awkward:
@gemstack/mcp-connectorsdefineConnector) kebab-cases and namespaces tool names, and its id regex rejects_, so it cannot producecreate_projectverbatim (best it does isprojects_create-project). I dropped to@gemstack/mcpMcpTooldirectly to honor the names, which costs a small factory-closure-per-tool. Worth flagging for the decision: if we acceptcreate-project, connectors is even less code.create_projectcall returns into a turn that is about to be torn down — there is nothing to continue into mid-turn. The tool's return value is effectively discarded; the side effect (the bind) is what matters. This is a slightly unnatural fit for "the agent calls a tool and uses the result": here the agent calls a tool and then its whole process is replaced. A gate that the orchestrator resolves by respawning models that reality more directly. The tools approach still works (the bind is recorded, Topics: re-home a run's cwd/worktree on bind #1122 picks it up on respawn), but the "tool returns a value the agent reasons about" mental model does not hold at the bind moment.Recommendation: The MCP-tools path is clean to build and gives the nicest discovery story (no prompt, self-describing, list + create in one surface). Its weak spot is exactly the bind semantics: the respawn means the tool return is throwaway, so we get the ergonomics of "call a tool" without the payoff of "use its result." If the #1129 decision weights agent-facing discoverability, pick tools. If it weights modeling the respawn honestly, the gate is the more faithful shape. My lean: tools for discovery, but only if we are fine treating create_project as fire-and-effect (bind recorded, run respawned by #1122) rather than a value-returning call. Keep the tool return minimal (it will be discarded) and let #1122 own the respawn.
Codex note (not wired here)
On Codex the same server would ride the driver's
extraArgshatch (src/driver/codex.ts):codex exectakes-cconfig overrides, so a topic run would append-c mcp_servers.projects.command=<node>-c mcp_servers.projects.args=["<bin>","mcp-projects"](plus theFRAMEWORK_RUN_CWD/XDG_CONFIG_HOMEenv). The server binary is agent-agnostic — only the "how the CLI is told about it" differs (Claude's--mcp-configfile vs Codex's-c mcp_servers.*). Not built here to keep the spike to one driver.