Adds POST /mcp endpoint speaking MCP Streamable HTTP (JSON-RPC 2.0) with 12 tools for workflow management and agent orchestration. Supports both JSON and SSE response modes. Includes real-time agent output streaming over SSE, Content-Type validation, and 15 integration tests (134 total). Tools: create_story, validate_stories, list_upcoming, get_story_todos, record_tests, ensure_acceptance, start_agent, stop_agent, list_agents, get_agent_config, reload_agent_config, get_agent_output. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
4.9 KiB
name
| 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
- Understand the MCP stdio protocol (JSON-RPC over stdin/stdout)
- Identify which workflow endpoints should become MCP tools
- Determine the best language/approach for the MCP server (Rust binary vs Node script vs Rust integrated into existing server)
- Prototype a minimal MCP server with one tool (
create_story) and test it withclaude mcp add - Verify spawned agents (via
claude -p) inherit MCP tools - 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:
#[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<String>,
#[schemars(description = "List of acceptance criteria")]
pub acceptance_criteria: Option<Vec<String>>,
}
#[tool(description = "Create a new story with correct front matter in upcoming/")]
async fn create_story(
&self,
Parameters(req): Parameters<CreateStoryRequest>,
) -> Result<CallToolResult, McpError> {
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)
{
"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:
- New
story-kit-mcpbinary crate in the workspace - Expose the 7 tools listed above
- Add
.mcp.jsonto the project - Update agent spawn to ensure MCP tools are available in worktrees
- Test: spawn agent, verify it uses MCP tools instead of file writes