An MCP server for orchestrating AI coding agents. One driver (e.g. Cursor) directs multiple workers (Claude Code, Codex, Gemini CLI) through shared tasks, messaging, plans, and progress monitoring — all from a single Go binary.
curl -fsSL https://raw.githubusercontent.com/jaakkos/stringwork/main/scripts/install.sh | shSupports macOS (arm64, amd64) and Linux (amd64, arm64). Pass --version v0.1.0 for a specific release or --dir /usr/local/bin to change the install location.
Or build from source:
go build -o mcp-stringwork ./cmd/mcp-serverStringwork uses a driver/worker model:
Driver (Cursor)
|
|-- creates tasks, monitors progress, cancels stuck workers
|
+-- Worker 1 (Claude Code) -- claims tasks, reports progress, sends findings
+-- Worker 2 (Codex) -- claims tasks, reports progress, sends findings
+-- Worker 3 (Gemini CLI) -- claims tasks, reports progress, sends findings
+-- Worker N (any agent) -- ...
- The driver creates tasks (with
assigned_to='any'for auto-assignment), monitors workers viaworker_status, and cancels stuck agents withcancel_agent. - Workers are spawned automatically by the server when there's pending work. They claim tasks, report progress every 2-3 minutes, and communicate findings back via messages.
- All agents share state through a single SQLite file (
~/.config/stringwork/state.sqlite).
The server provides only coordination tools. Each agent uses its own native capabilities for file editing, search, git, and terminal.
- Driver/worker orchestration -- one driver, N workers with automatic spawning, SLA monitoring, and cancellation
- Task management -- create, assign, track, and auto-notify on task lifecycle events
- Messaging -- inter-agent messages with urgency, piggyback notifications on every tool call
- Shared planning -- collaborative plans with items, acceptance criteria, and progress tracking
- Progress monitoring -- mandatory heartbeats and progress reports; escalating alerts (3 min warning, 5 min critical, 10 min auto-recovery)
- Session continuation -- workers report CLI session IDs via heartbeat; restarted workers resume their previous CLI conversation context automatically
- File locks -- prevent simultaneous edits across agents
- Web dashboard -- real-time view of tasks, workers, messages, and plans (URL logged on startup)
- Auto-respond -- server spawns agents when they have unread messages, no external daemon needed
- Git worktree isolation -- optional per-worker checkouts to prevent file conflicts
- Dynamic workspace -- switch projects at runtime via
set_presence workspace='...' - Custom agents -- register any MCP client as a participant via
register_agent
Add to .cursor/mcp.json in your project:
{
"mcpServers": {
"stringwork": {
"command": "mcp-stringwork",
"env": { "MCP_CONFIG": "/path/to/config.yaml" }
}
}
}Cursor spawns the server as a subprocess via stdio. With daemon mode enabled (recommended), the first Cursor window starts a background daemon and subsequent windows share it automatically.
Copy mcp/config.yaml and customize. Minimal example:
workspace_root: "/path/to/your/project"
enabled_tools: ["*"]
# Daemon mode: multiple Cursor windows share one server.
daemon:
enabled: true
# Fixed port for a stable dashboard URL. Use 0 for auto-assign.
http_port: 8943
orchestration:
driver: cursor
workers:
- type: claude-code
instances: 1
command: ["claude", "-p", "You are claude-code. Steps: 1) set_presence 2) read_messages 3) list_tasks 4) Do the work 5) report_progress 6) send_message with findings.", "--dangerously-skip-permissions"]
timeout_seconds: 600Open Cursor -- the server starts automatically. Create tasks, workers get spawned:
# Driver (Cursor) creates a task
create_task title='Add auth middleware' assigned_to='any' created_by='cursor'
# Server spawns a worker, which claims and works on it
# Driver monitors via:
worker_status
Enable daemon mode so multiple Cursor windows share a single server process:
daemon:
enabled: true
grace_period_seconds: 10The first Cursor window starts a background daemon. Subsequent windows connect to it as lightweight proxies. Workers, notifier, and watchdog run once -- no duplicates. The HTTP port and dashboard URL stay stable across reconnects. When the last window closes, the daemon waits for the grace period then shuts down.
Use --standalone to bypass daemon mode.
Without daemon mode, each Cursor window spawns its own server. With http_port: 0 (default), each gets an auto-assigned port so they don't conflict. All instances share the same SQLite state file, so tasks and messages are visible across all windows.
orchestration:
driver: cursor # which agent is the driver
assignment_strategy: least_loaded # or capability_match
heartbeat_interval_seconds: 30
worker_timeout_seconds: 120
worktrees:
enabled: false # git worktree isolation per worker
workers:
- type: claude-code
instances: 2 # run up to 2 Claude Code workers
command: ["claude", "-p", "...", "--dangerously-skip-permissions"]
cooldown_seconds: 30
timeout_seconds: 600
max_retries: 2
env:
GH_TOKEN: "${GH_TOKEN}" # ${VAR} expands from server env
SSH_AUTH_SOCK: "${SSH_AUTH_SOCK}"
# inherit_env: ["HOME", "PATH", "GH_*", "SSH_*"] # restrict inherited env
- type: codex
instances: 1
command: ["codex", "exec", "--sandbox", "danger-full-access", "--skip-git-repo-check", "..."]
- type: gemini
instances: 1
command: ["gemini", "--yolo", "--prompt", "..."]
env:
GOOGLE_API_KEY: "${GOOGLE_API_KEY}"See mcp/config.yaml for a fully annotated example.
| Tool | Description |
|---|---|
get_session_context |
Full session context (messages, tasks, presence, plans) |
set_presence |
Update status and workspace; dynamically changes server's project context |
append_session_note |
Add shared note or decision |
| Tool | Description |
|---|---|
send_message |
Message an agent (optional title, urgency) |
read_messages |
Read and mark messages as read |
| Tool | Description |
|---|---|
create_task |
Create task with optional work context (relevant_files, background, constraints) |
list_tasks |
List tasks with filters |
update_task |
Update status, assignment, priority; auto-notifies on completion |
| Tool | Description |
|---|---|
create_plan |
Create shared plan |
get_plan |
View plan(s); omit ID to list all |
update_plan |
Add or update plan items with acceptance criteria |
| Tool | Description |
|---|---|
handoff |
Hand off work with summary and next steps |
claim_next |
Claim next task (dry_run to peek) |
request_review |
Request code review from an agent |
| Tool | Description |
|---|---|
worker_status |
Live view of workers: progress, SLA status, process activity |
heartbeat |
Signal liveness every 60-90s with progress info. Include session_id on first call for session resume on restart |
report_progress |
Structured progress: description, percent complete, ETA |
cancel_agent |
Cancel a worker's tasks, send STOP signal, kill process |
get_work_context |
Get task context (files, background, constraints, notes) |
update_work_context |
Add shared notes to a task's work context |
| Tool | Description |
|---|---|
lock_file |
Lock, unlock, check, or list file locks |
register_agent |
Register a custom agent for collaboration |
list_agents |
List all available agents (built-in and registered) |
Claude Code's CLAUDE.md instructions are wrapped in a "may or may not be relevant" framing that weakens compliance. Stringwork ships hooks that bypass this limitation:
./scripts/install-claude-hooks.sh # install hooks
./scripts/uninstall-claude-hooks.sh # clean removalHooks are installed at user level (~/.claude/settings.json) so they work across all projects. They inject progress reporting rules as clean system-reminder messages — no disclaimer, survives context compaction. Scripts have a guard and do nothing in non-Stringwork projects.
See Claude Code config for details.
mcp-stringwork # start server (auto-detects daemon/proxy/standalone)
mcp-stringwork --daemon # force daemon mode
mcp-stringwork --standalone # force standalone mode (no daemon)
mcp-stringwork --version # print version
mcp-stringwork status claude-code # check unread/pending counts for an agentWhen the worker pool gets stuck (zombie workers, stale presence rows, ballooning
AgentInstance count), use the admin subcommands instead of editing
state.sqlite by hand. They operate directly on the SQLite store, so they work
whether or not the daemon is running.
# Inspect pool: total/active/offline counts, oldest stale rows, in-flight tasks.
mcp-stringwork admin pool-status
# Garbage-collect both presence and instance rows using policy defaults.
mcp-stringwork admin prune
# Preview what `admin prune` would remove without writing.
mcp-stringwork admin prune --dry-run
# Just presence rows, with an explicit retention window.
mcp-stringwork admin prune --presence --older-than 3d
# Aggressively clean up task-bound worker rows (e.g. after a server crash
# left zombies behind) without touching the static pool.
mcp-stringwork admin prune --instances --task-bound-older-than 1hThe same pruning runs continuously inside the daemon as part of the watchdog
loop; the CLI exists so you don't need it to be running to recover from a bad
state. Defaults come from config.yaml (presence_retention_days,
instance_retention_days, task_bound_instance_retention_hours).
The same admin actions are available from the web dashboard
(http://localhost:<http_port>/) so you don't have to drop to a shell when a
worker is misbehaving. Each one POSTs JSON; CORS is permissive so you can also
hit them directly from curl/fetch.
| Method | Path | Purpose |
|---|---|---|
POST |
/api/cancel-agent |
Cancel a stuck worker. Body: {agent, cancelled_by, reason}. Returns the synthetic recovery message if cancel_agent emitted one. |
POST |
/api/prune |
Wraps admin prune. Body: {presence?, instances?, older_than?, task_bound_older_than?, dry_run?}. Defaults to dry-run; flip dry_run:false to commit. |
GET |
/api/pool-status |
Same payload as mcp-stringwork admin pool-status rendered as JSON. |
POST |
/api/send-message |
Driver-only escape hatch. Body: {from, to, content} — from must equal the configured driver to prevent UI-side worker impersonation. |
The dashboard surfaces these as a per-worker Cancel… menu, a Prune… modal in
the toolbar, an auto-polling Pool status panel, and a Send Message form in
the messages card.
.
├── cmd/mcp-server/ # Server entrypoint, daemon, proxy, CLI
├── internal/
│ ├── domain/ # Core entities (Message, Task, Plan, AgentInstance, ...)
│ ├── app/ # Application services (CollabService, WorkerManager, Watchdog, Orchestrator)
│ ├── repository/sqlite/ # State persistence (SQLite)
│ ├── policy/ # Workspace validation, config, safety policy
│ ├── dashboard/ # Web dashboard (HTML + REST API)
│ ├── worktree/ # Git worktree manager for worker isolation
│ └── tools/collab/ # 23 MCP tool handlers
├── chrome-extension/ # Chrome extension for toolbar monitoring (alpha)
├── cursor-plugin/ # Cursor IDE plugin (rules, skills, agents, commands, hooks)
├── mcp/ # Configuration files
├── scripts/ # Install, dev-install, hook install/uninstall scripts
├── docs/ # Documentation
├── .github/workflows/ # CI and release automation
├── AGENTS.md # Cursor agent instructions
└── CLAUDE.md # Claude Code agent instructions
Stringwork ships a Cursor plugin that provides workflow rules, skills, agents, and commands for the driver role. The plugin structure is in cursor-plugin/ and ready for marketplace submission when the time comes.
- Install the binary (required):
curl -fsSL https://raw.githubusercontent.com/jaakkos/stringwork/main/scripts/install.sh | sh- Add the MCP server to Cursor — click the deeplink or add manually:
Install Stringwork MCP Server
Or add to your project's .cursor/mcp.json:
{
"mcpServers": {
"stringwork": {
"command": "mcp-stringwork"
}
}
}- Install the plugin (rules, skills, agents, commands) — globally for all projects:
git clone https://github.com/jaakkos/stringwork.git /tmp/stringwork
/tmp/stringwork/scripts/install-cursor-plugin.shTo uninstall: ./scripts/uninstall-cursor-plugin.sh
For project-scoped installation instead of global, copy the plugin dirs into your project:
cp -r /tmp/stringwork/.cursor-plugin /tmp/stringwork/cursor-plugin your-project/- Configure orchestration (optional — for spawning workers):
mkdir -p ~/.config/stringwork
cp /tmp/stringwork/mcp/config.yaml ~/.config/stringwork/config.yaml
# Edit workers section, then start with: MCP_CONFIG=~/.config/stringwork/config.yaml| Component | Description |
|---|---|
| MCP server | Auto-configured mcp-stringwork connection |
| Rules (4) | Driver workflow, progress reporting, STOP signals, worker communication |
| Skills (4) | Setup guide, code review, task creation, worker configuration |
| Agents (2) | Pair programming driver, code review coordinator |
| Commands (1) | /pair-respond for processing worker messages |
| Hooks (1) | Binary existence check on session start |
The plugin is purely additive — it does not modify any existing project files.
A browser extension that brings Stringwork monitoring to your toolbar -- see worker status, task progress, and receive desktop notifications without switching to the dashboard.
What it does:
- Badge counter on the toolbar icon shows attention-needed items (blocked tasks, offline workers, SLA violations, unread messages)
- Popup dashboard with live worker status, active tasks with progress bars, and recent messages -- polls every 5 seconds while open
- Desktop notifications when workers go offline, tasks get blocked, SLAs are exceeded, or progress stalls
- Quick actions -- restart workers (with confirmation) and jump to the full dashboard
Architecture: The extension is built on Manifest V3 with a service worker that polls /api/state. All API access is centralized in the service worker; the popup uses chrome.runtime.connect for live state pushes (the service worker pushes a fresh snapshot on every poll while the port is open) and falls back to one-shot chrome.runtime.sendMessage calls (getState, restartWorkers, openDashboard, settingsUpdated) for everything else. State is persisted in chrome.storage.local to survive MV3 service worker termination. Background polling uses chrome.alarms (1-minute minimum); active polling at 5-second intervals only runs while the popup is open.
Destructive operations (cancel a worker, prune state, send a message as the driver) live in the web dashboard, not the extension — see Dashboard operations. The extension is intentionally a passive monitor.
Install (unpacked, developer mode):
-
Set a fixed HTTP port in your config (required -- auto-assigned ports change on restart):
# ~/.config/stringwork/config.yaml http_port: 8943
-
Load the extension in Chrome:
- Navigate to
chrome://extensions/ - Enable Developer mode (toggle in top-right)
- Click Load unpacked and select the
chrome-extension/directory from this repo
- Navigate to
-
Click the Stringwork icon in your toolbar. Configure the server URL in the extension options if needed (default:
http://localhost:8943).
Note: This is alpha software. The extension is functional but has not been published to the Chrome Web Store. After pulling code changes, reload the extension from
chrome://extensions/to pick them up.
- Setup Guide -- installation, client config, orchestration setup
- Workflow -- collaboration patterns and best practices
- Quick Reference -- tool usage examples
- Architecture -- clean architecture overview
- Client Configs -- Cursor and Claude Code specifics