Content
# @locus-dev/mcp-server
> Give your AI coding agent the spec it should be reading before writing code.
MCP server for [Locus](https://locus.dev) — exposes your stories.yaml as structured resources and tools for Claude Code, Cursor, Windsurf, Zed, **OpenAI Codex, Continue.dev, GitHub Copilot**, and any MCP-compatible client.
stories.yaml is the single source of truth for your product requirements. It lives in your repo. No account required.
---
## Quick start — zero config
Add one entry to your Claude Code config (`~/.config/claude/claude_desktop_config.json`):
```json
{
"mcpServers": {
"locus": {
"command": "npx",
"args": ["-y", "@locus-dev/mcp-server"]
}
}
}
```
That's it. No API keys, no credentials, no account.
The server reads `stories.yaml` from your project directory automatically.
**Compatible with Claude Code, Codex, Cursor, Continue.dev, GitHub Copilot, and any MCP-compatible coding agent.**
> `stories.yaml` belongs to you. It lives in your repo.
> Switch AI tools anytime — your spec stays intact.
---
## How it works
1. Add a `stories.yaml` file to your project root (see [schema docs](https://locus.dev/spec/v1))
2. Add the MCP server to your AI client config (above)
3. Your AI coding assistant now sees what needs to be built, what the acceptance criteria are, and what's already done
The server finds `stories.yaml` automatically:
1. `LOCUS_STORIES_PATH` env var if set
2. `stories.yaml` in the current directory
3. Walks up to find `stories.yaml` at the repo root (stops at `.git`)
4. Also accepts `stories.yml` and `stories.json`
---
## Client setup guides
- [Claude Code](../../docs/integrations/claude-code.md)
- [Cursor](../../docs/integrations/cursor.md)
- [OpenAI Codex CLI](../../docs/integrations/codex.md)
- [Windsurf](../../docs/integrations/windsurf.md)
- [Zed](../../docs/integrations/zed.md)
- [Continue.dev](../../docs/integrations/continue.md)
- [GitHub Copilot (VS Code)](../../docs/integrations/copilot.md)
---
## Cloud mode (optional)
If you use the [Locus platform](https://locus.dev) for team sync, set these environment variables to enable cloud mode:
| Variable | Description |
|----------|-------------|
| `PROTOTYPER_API_KEY` | Supabase user JWT (from Locus web app → Settings → API Keys) |
| `PROTOTYPER_SUPABASE_URL` | Your Supabase project URL |
| `PROTOTYPER_PROJECT_ID` | Default project UUID (optional — avoids passing `project_id` to every call) |
Cloud mode config example:
```json
{
"mcpServers": {
"locus": {
"command": "npx",
"args": ["-y", "@locus-dev/mcp-server"],
"env": {
"PROTOTYPER_API_KEY": "your-jwt",
"PROTOTYPER_SUPABASE_URL": "https://your-project.supabase.co",
"PROTOTYPER_PROJECT_ID": "optional-project-uuid"
}
}
}
}
```
If `PROTOTYPER_API_KEY` and `PROTOTYPER_SUPABASE_URL` are both set, cloud mode activates automatically. Otherwise, file mode is used.
---
## Environment variables
| Variable | Mode | Description |
|----------|------|-------------|
| `LOCUS_STORIES_PATH` | File | Explicit path to `stories.yaml` (overrides auto-discovery) |
| `PROTOTYPER_API_KEY` | Cloud | Supabase user JWT |
| `PROTOTYPER_SUPABASE_URL` | Cloud | Supabase project URL |
| `PROTOTYPER_PROJECT_ID` | Cloud | Default project UUID |
---
## Tools
### `list_stories`
List user stories with optional filters.
```
list_stories(status="not-implemented", section="Leave Management")
```
**Parameters:**
- `project_id` (optional) — file mode: always `local`; cloud mode: defaults to `PROTOTYPER_PROJECT_ID`
- `status` — `not-implemented` | `partial` | `implemented` | `stale` | `deprecated` | `all` (default: `all`)
- `section` — case-insensitive partial match on story section
---
### `get_story`
Get full detail for a single story.
```
get_story(story_id="US-04")
```
**Parameters:**
- `story_id` (required) — e.g. `US-01`, `AUTH-03`
- `project_id` (optional)
**Returns:** Full story YAML including description and acceptance criteria.
---
### `get_active_story`
Get the story currently being implemented — `partial` status first, then first `not-implemented`.
Used by [Story Guard](#story-guard) to check whether an agent action is in scope.
```
get_active_story()
```
---
### `mark_story_status`
Update the implementation status of a story.
In file mode, writes directly to `stories.yaml`. In cloud mode, syncs to the Locus platform.
```
mark_story_status(story_id="US-01", status="implemented", notes="PR #47 — LeaveRequestForm")
```
**Parameters:**
- `story_id` (required)
- `status` (required) — `not-implemented` | `partial` | `implemented` | `stale` | `deprecated`
- `notes` (optional) — PR number, component name, known limitations
---
### `find_stories`
Full-text search across titles, descriptions, and acceptance criteria.
```
find_stories(query="authentication")
```
---
### `get_coverage`
Implementation coverage summary — total stories, counts by status, overall percentage.
```
get_coverage()
```
---
### `create_story`
Add a new story to `stories.yaml`. Use when you discover behaviour in the codebase with no story covering it, or when specifying a new feature before implementation begins.
In file mode, writes directly to `stories.yaml` with validation. Auto-generates a unique story ID if one is not provided.
```
create_story(
title="User can export transactions as CSV",
description="Users can download their transaction history as a CSV file for external reporting.",
section="Transactions",
acceptance_criteria=[
"Export button visible on /transactions page",
"CSV includes date, amount, and description columns",
"File downloads as transactions-YYYY-MM-DD.csv"
]
)
```
**Parameters:**
- `title` (required) — verb-first: "User can X"
- `description` (required) — what the user can do and why
- `section` (required) — feature area; if omitted, existing sections are listed to guide your choice
- `id` (optional) — e.g. `US-07`; auto-generated from existing prefix if omitted
- `status` (optional) — `not-implemented` | `partial` | `implemented` (default: `not-implemented`)
- `acceptance_criteria` (optional) — list of testable conditions; at least 2 recommended
- `depends_on` (optional) — story IDs that must be implemented first; validated against existing IDs
**Validation:**
- Duplicate ID prevention with next-available-ID suggestion
- ID format enforced: `PREFIX-NUMBER` (e.g. `US-01`, `BT-12`)
- `depends_on` cross-reference check — errors if referenced IDs don't exist
- Advisory warning if fewer than 2 acceptance criteria are provided
---
### `suggest_stories`
Analyse a git diff or a set of source files and suggest user stories that are missing from `stories.yaml`. Use before implementing a feature, when reviewing a PR, or when auditing coverage.
```
suggest_stories(
diff="git diff HEAD~1..HEAD -- src/auth/",
context="This diff adds OAuth2 login via Google"
)
```
```
suggest_stories(
files=["src/payments/checkout.ts", "src/payments/invoice.ts"]
)
```
**Parameters:**
- `diff` (optional) — a git diff string to analyse
- `files` (optional) — list of source file paths to read and analyse
- `context` (optional) — additional context to guide story generation
- `project_id` (optional)
**Returns:** 3–5 draft stories with titles, descriptions, sections, and acceptance criteria in YAML. Pass each suggestion directly to `create_story` to add them to `stories.yaml`.
---
### `link_pr`
Associate a pull request with the stories it implements or references. Writes `pr_refs` to each story in `stories.yaml`, creating an audit trail between PRs and stories.
Call this when opening or merging a PR with `story_ids` set to all stories the PR contributes to.
```
link_pr(
pr_url="https://github.com/org/repo/pull/142",
pr_number=142,
pr_title="feat: broadcast pre-peg-in transaction",
story_ids=["BT-07", "BT-08"],
link_type="implements",
state="merged"
)
```
**Parameters:**
- `pr_url` (required) — full GitHub PR URL
- `pr_number` (required) — PR number
- `story_ids` (required) — array of story IDs this PR implements or references
- `pr_title` (optional) — PR title
- `link_type` (optional) — `implements` | `partial` | `refs` (default: `implements`)
- `state` (optional) — `open` | `merged` | `closed` (default: `open`)
- `merged_at` (optional) — ISO 8601 timestamp
- `repo` (optional) — `owner/repo`; auto-extracted from `pr_url` if omitted
**Result in `stories.yaml`:**
```yaml
stories:
- id: BT-07
title: Broadcast pre-peg-in transaction
# ...
pr_refs:
- pr_url: https://github.com/org/repo/pull/142
pr_number: 142
pr_title: 'feat: broadcast pre-peg-in transaction'
link_type: implements
state: merged
```
Re-running with the same `pr_url` updates the existing entry rather than duplicating it.
---
## Story Guard
Story Guard fires after every agent action to check whether the action is within the active story's acceptance criteria.
### Setup
Create `.claude/settings.json` in your project root:
```json
{
"hooks": {
"PostToolUse": [
{
"matcher": ".*",
"hooks": [
{
"type": "mcp_tool",
"tool": "locus/get_active_story",
"description": "Story Guard: check action scope against active story acceptance criteria"
}
]
}
]
}
}
```
This fires `get_active_story()` after every tool call — file writes, bash commands, and external API calls. The agent sees the active story's acceptance criteria and can determine whether its last action was in scope.
### Why it matters
In April 2026, a Cursor AI coding agent deleted a production database and all its backups in 9 seconds — because it had no story contract to check its own actions against. Story Guard is the structural answer: the intent contract lives upstream, before execution, where the agent can reason about its own scope.
---
## Typical session flow
1. Developer starts a Claude Code session with Locus MCP connected
2. Developer says: _"Implement the Leave Management stories"_
3. Claude calls `list_stories(status="not-implemented", section="Leave Management")` — sees the specs from `stories.yaml`
4. Claude implements each story, calls `get_story` for detail when needed
5. When done, calls `mark_story_status(story_id="US-03", status="implemented", notes="PR #52")` — writes back to `stories.yaml`
6. The spec and its status are now both in the repo
---
## Story schema
Stories follow the [Locus open schema](https://locus.dev/spec/v1) (YAML, v1.1).
Minimal `stories.yaml`:
```yaml
version: "1.1"
project:
id: my-project
name: My Project
stories:
- id: US-01
title: User can sign up
description: New users can create an account with email and password
section: Authentication
status: not-implemented
acceptance_criteria:
- Email field validates format before submission
- Password must be at least 8 characters
- Successful signup redirects to /dashboard
```
---
## License
MIT © Jony Bursztyn
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
everything-claude-code
Complete Claude Code configuration collection - agents, skills, hooks,...
markitdown
MarkItDown-MCP is a lightweight server for converting URIs to Markdown.
cc-switch
All-in-One Assistant for Claude Code, Codex & Gemini CLI across platforms.
servers
Model Context Protocol Servers
servers
Model Context Protocol Servers
Time
A Model Context Protocol server for time and timezone conversions.