MCP
gaffer mcp is a Model Context Protocol server that exposes gaffer’s projection lifecycle and debugging surface to any MCP-aware AI assistant.
The quickest way in is to let an assistant set it up. Paste this into Claude Code, Cursor, or any MCP client:
Install the gaffer CLI and register its MCP server, following https://gaffer.kurrent.io/install.txtThat page carries the CLI install and the registration steps for every client. To wire it up by hand instead, see Manual setup.
Try it
Section titled “Try it”Once connected, you can drive gaffer from natural language:
- “Scaffold a projection that counts OrderPlaced events per stream.”
- “Run order-count against the happy fixture and show me the final state.”
- “Update order-count to also track OrderShipped events.”
- “Why isn’t my projection handling OrderShipped events?”
- “Set a breakpoint on the OrderPlaced handler, step through the next event, and tell me what the state looks like.”
The write-projection and fix-projection prompts wrap the most common flows.
What’s exposed
Section titled “What’s exposed”init: create agaffer.tomlto start a new project when there isn’t one yet.scaffold: create a new projection at an explicit path and register it ingaffer.toml. Accepts anengine_versionargument (1or2, defaults to2).validate: check a projection for compile errors and runtime gotchas. Reportsvalid: falsefor a source that fails to compile or carries an error-severity diagnostic (a feature the server rejects, such astrack_emitted_streamsonengine_version 2), with the diagnostic inlastError.run/stop: run a projection against a fixture or live stream, and stop a running session. A liverunaccepts anenvargument to choose the environment; omit it for the default.get_state/get_step/get_history/get_timeline: inspect projection state at any point.get_step,get_history, andget_timelinealso surface the runtime quirks that fired on each step, so the assistant can spot one and cross-reference its code against thegaffer://docs/quirksresource.get_projection_info: return a projection’s parsed structure, including sources, partition mode, whether it emits events, and effective engine version.list_projections/list_events: workspace navigation.list_eventssamples from a live connection and accepts anenvargument to choose the environment.debug_continue/debug_step_over/debug_step_into/debug_step_out/evaluate: drive the DAP debugger from natural language.deploy_status/deploy_plan/deploy_history: read-only visibility into deployed environments.deploy_statusshows each projection’s runtime state and drift verdict, plus any[database_config]divergence, mirroringgaffer status --json.deploy_planpreviews what a deploy would change without applying anything, mirroringgaffer deploy --dry-run --json.deploy_historyreads a projection’s per-deploy audit log, pageable withbefore, mirroringgaffer history --json. All three accept anenvargument; omit it for the default environment.deploy_statusanddeploy_planalso echo the resolved target and whether it is a production target (declared by the server itself, or byproduction = trueon the environment), so an assistant can tell it is pointed at a production database before deciding anything.deploy_apply: deploy fromgaffer.toml, mirroringgaffer deploy: the same compile-and-diagnostics preflight (no bypass), the same per-item results asgaffer deploy --json, and every write ledger-stamped. A production deploy asks you to confirm through your MCP client via elicitation, with the changed projections and cautions named in the prompt. A plan containingresetOnLogicChangerebuilds destroys state, so it always asks; on production that confirmation requires typing the environment name.deploy_pause/deploy_resume/deploy_abort/deploy_recreate/deploy_rollback/deploy_delete: manage a deployed projection’s lifecycle, mirroringgaffer disable/enable/disable --abort/recreate/rollback/delete. Abort skips the final checkpoint a pause writes. Recreate rebuilds from local source, which must compile and pass the diagnostics preflight (no bypass exists). Rollback applies in place, so a target differing in engine version or emitted-stream tracking is refused. Delete acceptsdeleteEmitted, like recreate. Writes are gated with a human in the loop via MCP elicitation. A write against a production target (a server that declares itself production, or an environment withproduction = true) asks you to confirm through your MCP client, mid-call; the assistant cannot answer for you.deploy_recreateanddeploy_deletedestroy state with no undo, so they ask every time, production or not. A client that doesn’t support elicitation cannot perform gated writes; the refusal names the CLI command to run instead. Recreate and rollback stamp the deploy ledger (operation: recreate/rollback) like their CLI counterparts.get_version: report the gaffer CLI version backing the server.
Resources
Section titled “Resources”- The full projection API reference.
- Worked examples covering counters, partitioned state,
emit, biState, and the rest. - Common gotchas across the projection API.
- V1 vs V2 engine differences.
- Known engine quirks by KurrentDB version.
- The current
gaffer.toml, exposed so the assistant can reason about projection registration. - Telemetry disclosure, so the assistant can answer questions about what gaffer collects.
Prompts
Section titled “Prompts”The two most common workflows come as registered prompts, which most clients surface as slash commands or a prompt picker.
write-projection: draft a projection from a natural-language description.fix-projection: diagnose and rewrite a broken projection.
Production databases
Section titled “Production databases”A database can declare itself production, and the write tools check that declaration on every deploy and operate call. The declaration is a production: true flag in the database’s own $server-info system stream, not a gaffer.toml label or a tool argument. Because it lives in the database, it travels with the cluster no matter who connects or what they call the environment locally. Operators set it from Navigator or the database’s embedded web UI (Admin > Database Info); it is absent on Kurrent Cloud and most databases today. An environment can add the tier with production = true, and the signals only ever add, so config can never disarm a server-declared production database. Production safety guards covers the model in full.
For an assistant driving the write tools, the guard is a contract rather than advice:
- A production write always asks a human. Any write against a production target is confirmed through MCP elicitation, mid-call, answered in the client’s UI. The assistant cannot answer it, and a declined or cancelled prompt refuses without writing anything. A write with no undo (
deploy_delete,deploy_recreate, a deploy plan that rebuilds) asks everywhere, production or not. - No argument bypasses the guard. The tools have no
--yesequivalent: nothing in a tool call’s arguments can pre-approve a write. The only permit is the human’s answer to that call’s confirmation. - Production escalates the ask. The confirmation front-loads the risk (
PRODUCTION [env.prod]: ...), and a no-undo write requires the human to type the projection name (the environment name fordeploy_apply) instead of a one-key accept. - No elicitation, no gated writes. A client that can’t elicit gets a refusal naming the CLI command for the human to run; the tool never falls back to applying silently.
Manual setup
Section titled “Manual setup”gaffer mcp is a local stdio server. No auth, no remote endpoint - your MCP client launches it as a subprocess and talks to it over stdin/stdout.
The server starts whether or not its working directory is a gaffer project, so it is safe to register globally. Without a project, the documentation resources and get_version are available immediately, and the projection tools return an error pointing you at the init tool. They start working as soon as a gaffer.toml exists, whether created by init, created by gaffer init, or already present in the working directory or a parent. No restart needed.
When the launch directory is not your project (common for a globally registered server), pass --project <dir> or set GAFFER_PROJECT. The flag takes precedence over the variable, and both override the working-directory search.
The generic connection shape is command: gaffer, args: [mcp]. Client-specific configs:
VS Code
Section titled “VS Code”The KurrentDB Gaffer extension auto-registers gaffer’s MCP server with VS Code’s MCP framework. Install the extension and Copilot Chat (or any MCP-aware VS Code client) picks it up.
Claude Code
Section titled “Claude Code”claude mcp add gaffer -- gaffer mcpThen /mcp inside Claude Code to confirm gaffer is connected. When registering from outside your project, pin it with claude mcp add gaffer -- gaffer mcp --project /path/to/project.
Cursor
Section titled “Cursor”Add to .cursor/mcp.json in your workspace (or ~/.cursor/mcp.json for global):
{ "mcpServers": { "gaffer": { "command": "gaffer", "args": ["mcp"] } }}Claude Desktop
Section titled “Claude Desktop”Edit the config file (platform-specific):
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%AppData%\Claude\claude_desktop_config.json
Add:
{ "mcpServers": { "gaffer": { "command": "gaffer", "args": ["mcp"] } }}Restart Claude Desktop. Look for the slider icon in the chat input to confirm tools are connected.
Other MCP clients
Section titled “Other MCP clients”Any MCP-aware client that supports stdio servers works. Point it at:
- Command:
gaffer - Args:
["mcp"](add["mcp", "--project", "/path/to/project"], or setGAFFER_PROJECT, when the launch directory is not your project) - Working directory: any directory. The projection tools need a
gaffer.tomlin the working directory or a parent, or a--project/GAFFER_PROJECToverride; the documentation resources andget_versionwork anywhere.
Most clients accept a JSON entry shaped like Cursor’s above. Consult your client’s MCP setup docs.
Telemetry
Section titled “Telemetry”The MCP server emits anonymous usage telemetry under the same opt-out cascade as the CLI. See the telemetry notice for what’s collected, or run gaffer config telemetry off to disable.