Files
storkit/.story_kit/spikes/archive/spike-2-mcp-workflow-tools.md
Dave 45f1234a06 Accept spike 2: MCP HTTP endpoint for workflow and agent tools
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>
2026-02-19 19:34:03 +00:00

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

  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:

#[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:

  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