--- name: MCP Server for Workflow API --- # Spike 1: MCP Server for Workflow API ## Question Can we expose the Story Kit workflow API as MCP tools so that agents call enforced endpoints instead of manipulating files directly? ## Hypothesis A thin stdio MCP server that proxies to the existing Rust HTTP API will let Claude Code agents use `create_story`, `validate_stories`, `record_tests`, and `ensure_acceptance` as native tools — with zero changes to the existing server. ## Timebox 2 hours ## Investigation Plan 1. Understand the MCP stdio protocol (JSON-RPC over stdin/stdout) 2. Identify which workflow endpoints should become MCP tools 3. Determine the best language/approach for the MCP server (Rust binary vs Node script vs Rust integrated into existing server) 4. Prototype a minimal MCP server with one tool (`create_story`) and test it with `claude mcp add` 5. Verify spawned agents (via `claude -p`) inherit MCP tools 6. Evaluate whether we can restrict agents from writing to `.story_kit/stories/` directly ## Findings ### 1. MCP stdio protocol is simple JSON-RPC 2.0 over stdin/stdout. Three-phase: initialize handshake → tools/list → tools/call. A minimal server needs to handle ~3 message types. No HTTP, no sockets. ### 2. The `rmcp` Rust crate makes this trivial The official Rust SDK (`rmcp` 0.3) provides `#[tool]` and `#[tool_router]` macros that eliminate boilerplate. A tool is just an async function with typed parameters: ```rust #[derive(Debug, Deserialize, schemars::JsonSchema)] pub struct CreateStoryRequest { #[schemars(description = "Human-readable story name")] pub name: String, #[schemars(description = "User story text")] pub user_story: Option, #[schemars(description = "List of acceptance criteria")] pub acceptance_criteria: Option>, } #[tool(description = "Create a new story with correct front matter in upcoming/")] async fn create_story( &self, Parameters(req): Parameters, ) -> Result { let resp = self.client.post(&format!("{}/workflow/stories/create", self.api_url)) .json(&req).send().await...; Ok(CallToolResult::success(vec![Content::text(resp.story_id)])) } ``` Dependencies needed: `rmcp` (server, transport-io), `schemars`, `reqwest`, `tokio`, `serde`. We already use most of these in the existing server. ### 3. Architecture: separate binary, same workspace Best approach is a new binary crate (`story-kit-mcp`) in the workspace that: - Reads the API URL from env or CLI arg (default `http://localhost:3000/api`) - Proxies each MCP tool call to the corresponding HTTP endpoint - Returns the API response as tool output This keeps the MCP layer thin and the enforcement logic in the existing server. No code duplication — the MCP binary is just a translation layer. ### 4. Which endpoints become tools | MCP Tool | HTTP Endpoint | Why | |---|---|---| | `create_story` | POST /workflow/stories/create | Enforce front matter | | `validate_stories` | GET /workflow/stories/validate | Check all stories | | `record_tests` | POST /workflow/tests/record | Record test results | | `ensure_acceptance` | POST /workflow/acceptance/ensure | Gate story acceptance | | `collect_coverage` | POST /workflow/coverage/collect | Run + record coverage | | `get_story_todos` | GET /workflow/todos | See remaining work | | `list_upcoming` | GET /workflow/upcoming | See backlog | ### 5. Configuration via `.mcp.json` (project-scoped) ```json { "mcpServers": { "story-kit": { "type": "stdio", "command": "./target/release/story-kit-mcp", "args": ["--api-url", "http://localhost:${STORYKIT_PORT:-3000}/api"] } } } ``` This gets checked into the repo. Every Claude Code session and every spawned agent inherits it automatically. ### 6. Agent restrictions Claude Code's `.claude/settings.local.json` can restrict which tools agents have access to. We could: - Give agents the MCP tools (`story-kit:create_story`, etc.) - Restrict or remove Write access to `.story_kit/stories/` paths - This forces agents through the API for all workflow actions Caveat: tool restrictions are advisory in `settings.local.json` — agents with Bash access could still `echo > file`. Full enforcement requires removing Bash or scoping it (which is story 35's problem). ### 7. Effort estimate The MCP binary itself is ~200-300 lines of Rust. One afternoon of work. Most of the time would be testing the integration with agent spawning and worktrees. ## Recommendation **Proceed with a story.** The spike confirms this is straightforward and high-value. The `rmcp` crate handles the protocol complexity, and our existing HTTP API already does the enforcement. The MCP server is just plumbing. Suggested story scope: 1. New `story-kit-mcp` binary crate in the workspace 2. Expose the 7 tools listed above 3. Add `.mcp.json` to the project 4. Update agent spawn to ensure MCP tools are available in worktrees 5. Test: spawn agent, verify it uses MCP tools instead of file writes