Skip to content

Commit b8024d5

Browse files
authored
Implement MCP server features and enhance documentation (#56)
* Implement MCP server with `raid context serve` command and update documentation * Enhance MCP toolset: implement read-only handlers for profiles and repositories, update documentation, and add tests for new functionality * Refactor mutating tool handlers to capture output and ensure thread safety; update documentation and tests for new functionality * feat: implement cross-process mutation locking using flock - Added a new locking mechanism using flock to serialize mutating operations across processes. - Introduced WithMutationLock function to wrap user-visible mutating operations, ensuring they are executed atomically. - Updated various commands (install, env switch, run task, profile add/remove) to utilize the new locking mechanism. - Refactored tests to isolate state files and prevent interference during concurrent test runs. - Added tests for the new locking functionality to ensure correct behavior under concurrent access. - Updated documentation to reflect changes in how mutating operations are handled. * feat: enhance `raid context serve` command help text with executable path snippets for MCP host configuration * feat: update `raid context` command description to include MCP server functionality * feat version bump * feat: update Go version to 1.25.5 in workflow files * feat: add tests for recent command status and duration, enhance profile YAML parsing tests * feat: implement syncBuffer for goroutine-safe output capture and update RemoveProfileCmd to handle lock acquisition errors
1 parent d469089 commit b8024d5

38 files changed

Lines changed: 1826 additions & 85 deletions

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
- name: Set up Go
6161
uses: actions/setup-go@v6.3.0
6262
with:
63-
go-version: '1.24.4'
63+
go-version: '1.25.5'
6464
- name: Build
6565
run: go build -v ./...
6666
- name: Test

.github/workflows/codecov.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
- name: Set up Go
2121
uses: actions/setup-go@v6.3.0
2222
with:
23-
go-version: '1.24.4'
23+
go-version: '1.25.5'
2424
- name: Run Tests
2525
run: go test -coverprofile=coverage.txt -covermode=atomic ./src/...
2626
- uses: codecov/codecov-action@v5.5.2

.github/workflows/deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ jobs:
8888
8989
- uses: actions/setup-go@v6.3.0
9090
with:
91-
go-version: "1.24.4"
91+
go-version: "1.25.5"
9292

9393
- uses: goreleaser/goreleaser-action@v7.0.0
9494
with:

.github/workflows/preview.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ jobs:
7070
7171
- uses: actions/setup-go@v6.3.0
7272
with:
73-
go-version: "1.24.4"
73+
go-version: "1.25.5"
7474

7575
- uses: goreleaser/goreleaser-action@v7.0.0
7676
with:

CLAUDE.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
Go 1.24.4 CLI. Cobra+Viper. yaml.v3 parsing, jsonschema/v6 validation. GoReleaser (.goreleaser.yaml stable, .goreleaser.preview.yaml preview).
1+
Go 1.25.5 CLI. Cobra+Viper. yaml.v3 parsing, jsonschema/v6 validation. mark3labs/mcp-go for the MCP server (`raid context serve`). GoReleaser (.goreleaser.yaml stable, .goreleaser.preview.yaml preview).
22

33
Build: `go build -o raid .` Test: `go test ./...` Run: `go run . <cmd>`
44

5-
Layout: main.go→src/cmd. src/cmd/raid.go=root cmd+subcommand registration+version check. Reserved built-in subcmds: doctor/, env/, install/, profile/ (user cmds w/ same name ignored w/ warning). src/raid/=core domain (profile loading, env resolution, cmd execution). src/internal/=lib/ (shared types), sys/ (OS helpers, GitHub release checks), utils/. schemas/=JSON schemas (raid-repo.schema.json, raid-profile.schema.json, raid-defs.schema.json). src/resources/=embedded assets (app.properties, profile-template, repo-template) via go:embed; resources.go exposes them. site/=Docusaurus source (merged from docsite-source 2026-04-10); builds to gh-pages via .github/workflows/docs.yml on site/** changes.
5+
Layout: main.go→src/cmd. src/cmd/raid.go=root cmd+subcommand registration+version check. Reserved built-in subcmds: context/, doctor/, env/, install/, profile/ (user cmds w/ same name ignored w/ warning). context/ has subcmd serve (MCP stdio server). src/raid/=core domain (profile loading, env resolution, cmd execution). src/internal/=lib/ (shared types), sys/ (OS helpers, GitHub release checks), utils/. schemas/=JSON schemas (raid-repo.schema.json, raid-profile.schema.json, raid-defs.schema.json). src/resources/=embedded assets (app.properties, profile-template, repo-template) via go:embed; resources.go exposes them. site/=Docusaurus source (merged from docsite-source 2026-04-10); builds to gh-pages via .github/workflows/docs.yml on site/** changes.
66

77
Config: raid.yaml=per-repo (environments+tasks: Shell|Script|HTTP|Wait|Template|Group|Git|Prompt|Confirm|Set|Print). profile.raid.yml=user profile (tracked repos, global settings).
88

@@ -13,6 +13,10 @@ Non-obvious:
1313
- Info-cmd fast path: QuietGetCommands() does read-only load (no config creation/warnings) so --help works without valid config
1414
- User commands registered at runtime from config via registerUserCommands; not in source
1515
- WriteProfileFile and CreateRepoConfigs prepend embedded templates (src/resources/profile-template, repo-template) to new files; schema URL constants live in the templates, not in Go code
16+
- src/cmd/context (Go package literally named `context`) imports stdlib context as `stdctx` and the raid wrapper as `rctx` to avoid the package-vs-import name collision
17+
- `raid context serve` blocks on stdin (stdio MCP transport); BuildServer() in src/cmd/context/serve.go is exported so tests can introspect the server without driving stdio.
18+
- Mutating tools (raid_install, raid_env_switch, raid_run_task) route command output through raid.SetCommandOutput / lib.commandStdout because os.Stdout is reserved for JSON-RPC framing in the MCP server. Any new lib code that writes user-facing progress should use commandStdout/commandStderr (not fmt.Printf or os.Stdout) so it's captured cleanly.
19+
- Cross-process mutation lock at ~/.raid/.lock via gofrs/flock. raid.WithMutationLock(fn) wraps the lock+release; every mutating cobra entry point and every MCP mutating handler must call it so CLI usage and the MCP server serialize against each other. Read paths don't acquire the lock. Tests must redirect lib.LockPathOverride (alongside RecentPathOverride) in any setup helper that exercises a mutating path; cmd/context tests use a TestMain to do this once for the whole package.
1620

1721
CI: .github/workflows/ — build.yml (build+test), deploy.yml (release), preview.yml (preview releases), codecov.yml (coverage), docs.yml (deploy Pages from site/), docs-build.yml (PR build check for site/)
1822

go.mod

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
module github.com/8bitalex/raid
22

3-
go 1.24.4
3+
go 1.25.5
44

55
require (
66
github.com/joho/godotenv v1.5.1
7+
github.com/mark3labs/mcp-go v0.49.0
78
github.com/mitchellh/go-homedir v1.1.0
89
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
910
github.com/spf13/cobra v1.10.2
@@ -14,6 +15,9 @@ require (
1415
require (
1516
github.com/fsnotify/fsnotify v1.9.0 // indirect
1617
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
18+
github.com/gofrs/flock v0.13.0 // indirect
19+
github.com/google/jsonschema-go v0.4.2 // indirect
20+
github.com/google/uuid v1.6.0 // indirect
1721
github.com/inconshreveable/mousetrap v1.1.0 // indirect
1822
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
1923
github.com/sagikazarmark/locafero v0.11.0 // indirect
@@ -22,7 +26,8 @@ require (
2226
github.com/spf13/cast v1.10.0 // indirect
2327
github.com/spf13/pflag v1.0.10 // indirect
2428
github.com/subosito/gotenv v1.6.0 // indirect
29+
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
2530
go.yaml.in/yaml/v3 v3.0.4 // indirect
26-
golang.org/x/sys v0.35.0 // indirect
31+
golang.org/x/sys v0.37.0 // indirect
2732
golang.org/x/text v0.28.0 // indirect
2833
)

go.sum

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
99
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
1010
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
1111
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
12-
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
13-
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
12+
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
13+
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
14+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
15+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
16+
github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
17+
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
18+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
19+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
1420
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
1521
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
1622
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
@@ -19,6 +25,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
1925
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
2026
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
2127
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
28+
github.com/mark3labs/mcp-go v0.49.0 h1:7Ssx4d7/T86qnWoJIdye7wEEvUzv39UIbnZb/FqUZMY=
29+
github.com/mark3labs/mcp-go v0.49.0/go.mod h1:BflTAZAzXlrTpiO44gmjMu89n2FO56rJ9m31fp4zd5k=
2230
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
2331
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
2432
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
@@ -49,14 +57,19 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
4957
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
5058
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
5159
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
60+
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
61+
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
5262
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
5363
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
5464
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
5565
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
66+
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
67+
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
5668
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
5769
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
5870
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
5971
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
6072
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
73+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
6174
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
6275
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

profile.raid.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,39 @@ commands:
2525
message: "==> Testing raid app"
2626
color: cyan
2727
- type: Shell
28+
name: Test raid app
2829
cmd: go test ./...
2930
path: ~/Developer/raid
3031
- type: Print
3132
message: "==> Building docsite"
3233
color: cyan
3334
- type: Shell
35+
name: Build docsite
3436
cmd: npm install && npm run build
3537
path: ~/Developer/raid/site
3638
- type: Print
3739
message: "==> Testing docsite"
3840
color: cyan
3941
- type: Shell
42+
name: Test docsite
4043
cmd: npm test
4144
path: ~/Developer/raid/site
4245
- type: Print
4346
message: "==> Type-checking docsite"
4447
color: cyan
4548
- type: Shell
49+
name: Type-check docsite
4650
cmd: npm run typecheck
4751
path: ~/Developer/raid/site
52+
- type: Print
53+
message: "==> Coverage report"
54+
color: cyan
55+
- type: Shell
56+
name: Test coverage report
57+
literal: true
58+
cmd: |-
59+
go test -coverpkg=./src/... -coverprofile=/tmp/raid-coverage.out ./... > /dev/null && go tool cover -func=/tmp/raid-coverage.out | awk 'END {print "Total coverage: " $NF}'
60+
path: ~/Developer/raid
4861
- type: Print
4962
message: "==> Tests completed"
5063
color: green

site/docs/references/commands.mdx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,15 @@ raid context --json # machine-readable JSON
107107

108108
Output is intentionally token-efficient and bounded — useful for piping into a chat agent or another tool.
109109

110-
For more details, see [Context](/docs/usage/context).
110+
### raid context serve
111+
112+
Run a Model Context Protocol server (stdio transport) that exposes the active workspace as resources and the raid agent toolkit as tools. Intended to be launched by an MCP host such as Claude Code or Cursor; the process blocks on stdin until the host disconnects.
113+
114+
```bash
115+
raid context serve
116+
```
117+
118+
For more details on either subcommand, see [Context](/docs/usage/context).
111119

112120
---
113121

site/docs/usage/context.mdx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,65 @@ raid context | pbcopy
164164
# Feed JSON into another tool
165165
raid context --json | jq '.workspace.repos[] | select(.dirty)'
166166
```
167+
168+
## MCP server (`raid context serve`)
169+
170+
The same data the snapshot exposes statically is also available as a live [Model Context Protocol](https://modelcontextprotocol.io) server, suitable for wiring into Claude Code, Cursor, Claude.ai, Cline, or any MCP-compatible host. The server speaks JSON-RPC 2.0 over stdio.
171+
172+
```bash
173+
raid context serve # blocks; intended to be launched by an MCP host
174+
```
175+
176+
### What's exposed
177+
178+
**Resources** — five `raid://workspace/*` URIs that mirror the static snapshot's `workspace` object. Each is fetched live on demand:
179+
180+
| URI | MIME | Contents |
181+
|---|---|---|
182+
| `raid://workspace/profile` | `text/plain` | Active profile name |
183+
| `raid://workspace/env` | `text/plain` | Active environment name |
184+
| `raid://workspace/repos` | `application/json` | Repositories with current git state |
185+
| `raid://workspace/commands` | `application/json` | User-defined raid commands |
186+
| `raid://workspace/recent` | `application/json` | Recent command invocations |
187+
188+
**Tools** — the canonical raid agent toolkit defined in [issue #45](https://github.com/8bitalex/raid/issues/45):
189+
190+
| Tool | Purpose |
191+
|---|---|
192+
| `raid_list_profiles` | List configured profiles, with the active one flagged |
193+
| `raid_list_repos` | List repos in the active profile with URL + live git state |
194+
| `raid_describe_repo` | Return the parsed `raid.yaml` for a repo (by name or path) as structured JSON |
195+
| `raid_install` | Clone repositories and run install tasks. Optional `repo` argument limits to a single repo |
196+
| `raid_env_switch` | Switch the active environment, write `.env` files into every repo, and run env tasks |
197+
| `raid_run_task` | Run a user-defined `raid <command>` from the active profile |
198+
199+
Each tool calls straight into the existing raid library and returns its result as a JSON-structured tool result. Mutating tools (`raid_install`, `raid_env_switch`, `raid_run_task`) are serialised behind a process-wide [flock](https://en.wikipedia.org/wiki/File_locking) at `~/.raid/.lock` — that same lock is acquired by every mutating raid CLI invocation (`raid install`, `raid env <name>`, `raid <command>`, `raid profile add/remove/use`). Concurrent mutations from any combination of CLI usage and the MCP server's tools therefore wait for one another instead of racing on `~/.raid/config.toml` or repository state. The kernel releases the lock automatically if the holding process exits unexpectedly.
200+
201+
`raid_list_repos` accepts an optional `profile` argument; in this release it must match the active profile or the call returns an error explaining the limitation. Multi-profile lookups are tracked separately.
202+
203+
:::note Output capture for mutating tools
204+
`raid_install`, `raid_env_switch`, and `raid_run_task` route the command output (git clone progress, shell task stdout/stderr, env-setup messages) through an internal buffer instead of letting it leak onto stdout — stdout is reserved for the JSON-RPC framing. The captured text is appended to the tool result so the agent can still inspect what happened.
205+
:::
206+
207+
### Wiring it into an MCP host
208+
209+
**Claude Code** ([docs](https://docs.claude.com/en/docs/claude-code/mcp)):
210+
211+
```bash
212+
claude mcp add raid -- raid context serve
213+
```
214+
215+
**Cursor** — add to your project's or user-level `mcp.json`:
216+
217+
```json
218+
{
219+
"mcpServers": {
220+
"raid": {
221+
"command": "raid",
222+
"args": ["context", "serve"]
223+
}
224+
}
225+
}
226+
```
227+
228+
Once wired, the host's `initialize` handshake reports raid's name and version, `resources/list` returns the five URIs above, and `tools/list` returns the six-tool catalog.

0 commit comments

Comments
 (0)