299 lines
8.8 KiB
Markdown
299 lines
8.8 KiB
Markdown
# beads-mcp
|
|
|
|
MCP server for [beads](https://github.com/steveyegge/beads) issue tracker and agentic memory system.
|
|
Enables AI agents to manage tasks using bd CLI through Model Context Protocol.
|
|
|
|
> **Note:** For environments with shell access (Claude Code, Cursor, Windsurf), the **CLI + hooks approach is recommended** over MCP. It uses ~1-2k tokens vs 10-50k for MCP schemas, resulting in lower compute cost and latency. See the [main README](../../README.md) for CLI setup.
|
|
>
|
|
> **Use this MCP server** for MCP-only environments like Claude Desktop where CLI access is unavailable.
|
|
|
|
## Installing
|
|
|
|
Install from PyPI:
|
|
|
|
```bash
|
|
# Using uv (recommended)
|
|
uv tool install beads-mcp
|
|
|
|
# Or using pip
|
|
pip install beads-mcp
|
|
```
|
|
|
|
Add to your Claude Desktop config:
|
|
|
|
```json
|
|
{
|
|
"mcpServers": {
|
|
"beads": {
|
|
"command": "beads-mcp"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Development Installation
|
|
|
|
For development, clone the repository:
|
|
|
|
```bash
|
|
git clone https://github.com/steveyegge/beads
|
|
cd beads/integrations/beads-mcp
|
|
uv sync
|
|
```
|
|
|
|
Then use in Claude Desktop config:
|
|
|
|
```json
|
|
{
|
|
"mcpServers": {
|
|
"beads": {
|
|
"command": "uv",
|
|
"args": [
|
|
"--directory",
|
|
"/path/to/beads-mcp",
|
|
"run",
|
|
"beads-mcp"
|
|
]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Environment Variables** (all optional):
|
|
- `BEADS_USE_DAEMON` - Use daemon RPC instead of CLI (default: `1`, set to `0` to disable)
|
|
- `BEADS_PATH` - Path to bd executable (default: `~/.local/bin/bd`)
|
|
- `BEADS_DB` - Path to beads database file (default: auto-discover from cwd)
|
|
- `BEADS_WORKING_DIR` - Working directory for bd commands (default: `$PWD` or current directory). Used for multi-repo setups - see below
|
|
- `BEADS_ACTOR` - Actor name for audit trail (default: `$USER`)
|
|
- `BEADS_NO_AUTO_FLUSH` - Disable automatic JSONL sync (default: `false`)
|
|
- `BEADS_NO_AUTO_IMPORT` - Disable automatic JSONL import (default: `false`)
|
|
|
|
## Multi-Repository Setup
|
|
|
|
**Recommended:** Use a single MCP server instance for all beads projects - it automatically routes to per-project local daemons.
|
|
|
|
### Single MCP Server (Recommended)
|
|
|
|
**Simple config - works for all projects:**
|
|
```json
|
|
{
|
|
"mcpServers": {
|
|
"beads": {
|
|
"command": "beads-mcp"
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**How it works (LSP model):**
|
|
1. MCP server checks for local daemon socket (`.beads/bd.sock`) in your current workspace
|
|
2. Routes requests to the **per-project daemon** based on working directory
|
|
3. Auto-starts the local daemon if not running
|
|
4. **Each project gets its own isolated daemon** serving only its database
|
|
|
|
**Architecture:**
|
|
```
|
|
MCP Server (one instance)
|
|
↓
|
|
Per-Project Daemons (one per workspace)
|
|
↓
|
|
SQLite Databases (complete isolation)
|
|
```
|
|
|
|
**Why per-project daemons?**
|
|
- ✅ Complete database isolation between projects
|
|
- ✅ No cross-project pollution or git worktree conflicts
|
|
- ✅ Simpler mental model: one project = one database = one daemon
|
|
- ✅ Follows LSP (Language Server Protocol) architecture
|
|
- ✅ One MCP config works for unlimited projects
|
|
|
|
**Note:** Global daemon support was removed in v0.16.0 to prevent cross-project database pollution.
|
|
|
|
### Alternative: Per-Project MCP Instances (Not Recommended)
|
|
|
|
Configure separate MCP servers for specific projects using `BEADS_WORKING_DIR`:
|
|
|
|
```json
|
|
{
|
|
"mcpServers": {
|
|
"beads-webapp": {
|
|
"command": "beads-mcp",
|
|
"env": {
|
|
"BEADS_WORKING_DIR": "/Users/yourname/projects/webapp"
|
|
}
|
|
},
|
|
"beads-api": {
|
|
"command": "beads-mcp",
|
|
"env": {
|
|
"BEADS_WORKING_DIR": "/Users/yourname/projects/api"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
⚠️ **Problem**: AI may select the wrong MCP server for your workspace, causing commands to operate on the wrong database. Use single MCP server instead.
|
|
|
|
## Multi-Project Support
|
|
|
|
The MCP server supports managing multiple beads projects in a single session using per-request workspace routing.
|
|
|
|
### Using `workspace_root` Parameter
|
|
|
|
Every tool accepts an optional `workspace_root` parameter for explicit project targeting:
|
|
|
|
```python
|
|
# Query issues from different projects concurrently
|
|
results = await asyncio.gather(
|
|
beads_ready_work(workspace_root="/Users/you/project-a"),
|
|
beads_ready_work(workspace_root="/Users/you/project-b"),
|
|
)
|
|
|
|
# Create issue in specific project
|
|
await beads_create_issue(
|
|
title="Fix auth bug",
|
|
priority=1,
|
|
workspace_root="/Users/you/project-a"
|
|
)
|
|
```
|
|
|
|
### Architecture
|
|
|
|
**Connection Pool**: The MCP server maintains a connection pool keyed by canonical workspace path:
|
|
- Each workspace gets its own daemon socket connection
|
|
- Paths are canonicalized (symlinks resolved, git toplevel detected)
|
|
- Concurrent requests use `asyncio.Lock` to prevent race conditions
|
|
- No LRU eviction (keeps all connections open for session)
|
|
|
|
**ContextVar Routing**: Per-request workspace context is managed via Python's `ContextVar`:
|
|
- Each tool call sets the workspace for its duration
|
|
- Properly isolated for concurrent calls (no cross-contamination)
|
|
- Falls back to `BEADS_WORKING_DIR` if `workspace_root` not provided
|
|
|
|
**Path Canonicalization**:
|
|
- Symlinks are resolved to physical paths (prevents duplicate connections)
|
|
- Git submodules with `.beads` directories use local context
|
|
- Git toplevel is used for non-initialized directories
|
|
- Results are cached for performance
|
|
|
|
### Backward Compatibility
|
|
|
|
The `set_context()` tool still works and sets a default workspace:
|
|
|
|
```python
|
|
# Old way (still supported)
|
|
await set_context(workspace_root="/Users/you/project-a")
|
|
await beads_ready_work() # Uses project-a
|
|
|
|
# New way (more flexible)
|
|
await beads_ready_work(workspace_root="/Users/you/project-a")
|
|
```
|
|
|
|
### Concurrency Gotchas
|
|
|
|
⚠️ **IMPORTANT**: Tool implementations must NOT spawn background tasks using `asyncio.create_task()`.
|
|
|
|
**Why?** ContextVar doesn't propagate to spawned tasks, which can cause cross-project data leakage.
|
|
|
|
**Solution**: Keep all tool logic synchronous or use sequential `await` calls.
|
|
|
|
### Troubleshooting
|
|
|
|
**Symlink aliasing**: Different paths to same project are deduplicated automatically via `realpath`.
|
|
|
|
**Submodule handling**: Submodules with their own `.beads` directory are treated as separate projects.
|
|
|
|
**Stale sockets**: Currently no health checks. Phase 2 will add retry-on-failure if monitoring shows need.
|
|
|
|
**Version mismatches**: Daemon version is auto-checked since v0.16.0. Mismatched daemons are automatically restarted.
|
|
|
|
## Features
|
|
|
|
**Resource:**
|
|
- `beads://quickstart` - Quickstart guide for using beads
|
|
|
|
**Tools (all support `workspace_root` parameter):**
|
|
- `init` - Initialize bd in current directory
|
|
- `create` - Create new issue (bug, feature, task, epic, chore)
|
|
- `list` - List issues with filters (status, priority, type, assignee)
|
|
- `ready` - Find tasks with no blockers ready to work on
|
|
- `show` - Show detailed issue info including dependencies
|
|
- `update` - Update issue (status, priority, design, notes, etc). Note: `status="closed"` or `status="open"` automatically route to `close` or `reopen` tools to respect approval workflows
|
|
- `close` - Close completed issue
|
|
- `dep` - Add dependency (blocks, related, parent-child, discovered-from)
|
|
- `blocked` - Get blocked issues
|
|
- `stats` - Get project statistics
|
|
- `reopen` - Reopen a closed issue with optional reason
|
|
- `set_context` - Set default workspace for subsequent calls (backward compatibility)
|
|
|
|
## Known Issues
|
|
|
|
### ~~MCP Tools Not Loading in Claude Code~~ (Issue [#346](https://github.com/steveyegge/beads/issues/346)) - RESOLVED
|
|
|
|
**Status:** ✅ Fixed in v0.24.0+
|
|
|
|
This issue affected versions prior to v0.24.0. The problem was caused by self-referential Pydantic models (`Issue` with `dependencies: list["Issue"]`) generating invalid MCP schemas with `$ref` at root level.
|
|
|
|
**Solution:** The issue was fixed in commit f3a678f by refactoring the data models:
|
|
- Created `IssueBase` with common fields
|
|
- Created `LinkedIssue(IssueBase)` for dependency references
|
|
- Changed `Issue` to use `list[LinkedIssue]` instead of `list["Issue"]`
|
|
|
|
This breaks the circular reference and ensures all tool outputSchemas have `type: object` at root level.
|
|
|
|
**Upgrade:** If you're running beads-mcp < 0.24.0:
|
|
```bash
|
|
pip install --upgrade beads-mcp
|
|
```
|
|
|
|
All MCP tools now load correctly in Claude Code with v0.24.0+.
|
|
|
|
|
|
## Development
|
|
|
|
Run MCP inspector:
|
|
```bash
|
|
# inside beads-mcp dir
|
|
uv run fastmcp dev src/beads_mcp/server.py
|
|
```
|
|
|
|
Type checking:
|
|
```bash
|
|
uv run mypy src/beads_mcp
|
|
```
|
|
|
|
Linting and formatting:
|
|
```bash
|
|
uv run ruff check src/beads_mcp
|
|
uv run ruff format src/beads_mcp
|
|
```
|
|
|
|
## Testing
|
|
|
|
Run all tests:
|
|
```bash
|
|
uv run pytest
|
|
```
|
|
|
|
With coverage:
|
|
```bash
|
|
uv run pytest --cov=beads_mcp tests/
|
|
```
|
|
|
|
Test suite includes both mocked unit tests and integration tests with real `bd` CLI.
|
|
|
|
### Multi-Repo Integration Test
|
|
|
|
Test daemon RPC with multiple repositories:
|
|
```bash
|
|
# Start the daemon first
|
|
cd /path/to/beads
|
|
./bd daemon start
|
|
|
|
# Run multi-repo test
|
|
cd integrations/beads-mcp
|
|
uv run python test_multi_repo.py
|
|
```
|
|
|
|
This test verifies that the daemon can handle operations across multiple repositories simultaneously using per-request context routing.
|