@korso/shepherd 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +336 -336
- package/dist/inboxHook.js +0 -0
- package/dist/index.js +247 -98
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,336 +1,336 @@
|
|
|
1
|
-
# @korso/shepherd — Shepherd MCP Server
|
|
2
|
-
|
|
3
|
-
Shepherd's stdio MCP server. Gives any MCP-capable agent (Claude Code, Codex, etc.) four advisory coordination tools backed by the shared hub: `work`, `done`, `announce`, and `sync`. The agent **joins the workspace automatically** on startup (no `join` tool), and the server ships standing instructions so the agent self-coordinates without the user prompting it.
|
|
4
|
-
|
|
5
|
-
> **New here?** The [developer quickstart](https://github.com/Korsoai/shepherd/blob/main/docs/shepherd-mcp-quickstart.md) is the fastest path. TL;DR: `npx -y @korso/shepherd` with the env vars below.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## CRITICAL: WORKSPACE must match the hub exactly
|
|
10
|
-
|
|
11
|
-
> **`WORKSPACE` defaults to `default`. If you override it, the value must equal the hub's `ALLOWED_WORKSPACE` env var exactly.**
|
|
12
|
-
|
|
13
|
-
The server fires an automatic `join` call to the hub at startup. If the workspace it sends does not match the hub's `ALLOWED_WORKSPACE`, that call returns HTTP 400 and coordination degrades: every tool reports "session not ready … proceeding uncoordinated" instead of a landscape. The safe default is to **leave `WORKSPACE` unset** so it resolves to `default` — only set it when a maintainer points you at a different workspace. If your agent never sees teammates, check `WORKSPACE` (and `TEAM_TOKEN`) first.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## Install
|
|
18
|
-
|
|
19
|
-
The server is published to npm and runs via `npx` — no clone or build required
|
|
20
|
-
(Node 18+):
|
|
21
|
-
|
|
22
|
-
```sh
|
|
23
|
-
npx -y @korso/shepherd
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
You won't normally run that by hand; you put it in your MCP client config (below)
|
|
27
|
-
with the required env vars. `npx` caches the package, so startup is fast after the
|
|
28
|
-
first fetch, and `@korso/shepherd@latest` picks up updates automatically.
|
|
29
|
-
|
|
30
|
-
> Hacking on the server itself? See **[Develop from source](#develop-from-source)**
|
|
31
|
-
> at the bottom.
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
## 2. Environment variables
|
|
36
|
-
|
|
37
|
-
**Only two are required:**
|
|
38
|
-
|
|
39
|
-
| Variable | Description | Example |
|
|
40
|
-
|---|---|---|
|
|
41
|
-
| `HUB_URL` | Base URL of the deployed hub | `https://shepherd.example.com` |
|
|
42
|
-
| `TEAM_TOKEN` | Shared bearer token accepted by the hub | `tok_abc123` |
|
|
43
|
-
|
|
44
|
-
Missing either causes an immediate startup failure with a clear error on stderr
|
|
45
|
-
listing which vars are absent. (No other var triggers this.)
|
|
46
|
-
|
|
47
|
-
**Everything else is optional** — each identity field is resolved at startup as
|
|
48
|
-
**env var → git detection → fallback**, so a plain `npx -y @korso/shepherd` with
|
|
49
|
-
just the two required vars produces a valid, fully-identified session. Set an
|
|
50
|
-
override only to replace what's detected:
|
|
51
|
-
|
|
52
|
-
| Variable | If omitted | Example |
|
|
53
|
-
|---|---|---|
|
|
54
|
-
| `WORKSPACE` | defaults to `default` (**must match hub's `ALLOWED_WORKSPACE` if overridden**) | `shepherd` |
|
|
55
|
-
| `REPO` | `git remote origin` → `owner/repo`, else repo folder name, else `unknown-repo` | `Korsoai/shepherd` |
|
|
56
|
-
| `BRANCH` | `git rev-parse --abbrev-ref HEAD`, else `HEAD` | `main` |
|
|
57
|
-
| `BASE_BRANCH` | `origin/HEAD`, else `origin/main` / `origin/master` (used for the change-awareness heads-up) | `origin/main` |
|
|
58
|
-
| `HUMAN` | git `user.name`, else local-part of `user.email`, else this device's **cached** last-detected name, else a generated name | `daichi` |
|
|
59
|
-
| `PROGRAM` | defaults to `claude-code` | `codex` |
|
|
60
|
-
| `MODEL` | omitted — **never auto-detected**, so set it if you want it shown | `claude-sonnet-4-6` |
|
|
61
|
-
| `HEARTBEAT_INTERVAL_SECONDS` | defaults to `60` | `30` |
|
|
62
|
-
| `SHEPHERD_INBOX_DIR` | defaults to `~/.shepherd/inbox`. Override only to relocate the **announcement-push** inbox (see below); the background heartbeat writes incoming announcements here. If you set it, point your client hook/extension at the **same** dir | `~/.shepherd/inbox` |
|
|
63
|
-
|
|
64
|
-
**Device-identity cache.** Whenever `HUMAN` is unset and git **does** detect a
|
|
65
|
-
name, that name is cached for your OS user at `~/.shepherd/identity.json`. A
|
|
66
|
-
later launch from a directory where git can't be read (e.g. a multi-repo
|
|
67
|
-
workspace root) then reuses the cached name instead of inventing a fresh random
|
|
68
|
-
one each time. The cache refreshes automatically the next time git reports a
|
|
69
|
-
different name, and an explicit `HUMAN` override always wins and never touches
|
|
70
|
-
the cache. It is best-effort: if the file can't be read or written, resolution
|
|
71
|
-
just falls back to a generated name.
|
|
72
|
-
|
|
73
|
-
---
|
|
74
|
-
|
|
75
|
-
## Announcement push (on by default)
|
|
76
|
-
|
|
77
|
-
Announcements reach an agent **without it having to ask**. The background
|
|
78
|
-
heartbeat pulls any pending announcements from the hub every beat and stages them
|
|
79
|
-
in a local **inbox file** (per working directory, under `SHEPHERD_INBOX_DIR`,
|
|
80
|
-
default `~/.shepherd/inbox`). That file is then drained by two paths:
|
|
81
|
-
|
|
82
|
-
1. **Universal drainer (always on, every client).** Whenever the agent calls any
|
|
83
|
-
Shepherd tool (`work`/`sync`/`done`/`announce`), the result also includes
|
|
84
|
-
anything sitting in the inbox. So even with no hook configured, no announcement
|
|
85
|
-
is ever lost — the worst case is the old behaviour (delivered on the next
|
|
86
|
-
Shepherd tool call), never silent drops.
|
|
87
|
-
2. **Passive client hook/extension (optional, per client).** To get announcements
|
|
88
|
-
**without** waiting for a Shepherd tool call — surfaced on the agent's next
|
|
89
|
-
action of any kind — wire up your client's hook below. This is the
|
|
90
|
-
"a subagent finished" style of notification.
|
|
91
|
-
|
|
92
|
-
Both paths read the **same** inbox file and de-duplicate by announcement id, so
|
|
93
|
-
running both is safe (the hub hands each announcement to exactly one drain; the
|
|
94
|
-
merge is just defensive). It's cheap: a **local file read — no network** (the
|
|
95
|
-
heartbeat already did the fetch), and it only adds to the model's context when
|
|
96
|
-
something is actually waiting.
|
|
97
|
-
|
|
98
|
-
It delivers to an agent **while it's active**; an idle agent picks messages up the
|
|
99
|
-
moment it next does anything. (Waking a fully-idle agent is out of scope — for
|
|
100
|
-
Claude Code that needs Channels; Codex/Pi have no equivalent.)
|
|
101
|
-
|
|
102
|
-
### Claude Code — `PreToolUse` hook
|
|
103
|
-
|
|
104
|
-
`PreToolUse` fires before every tool, giving the most frequent passive delivery.
|
|
105
|
-
The hook needs no arguments — it resolves the same default inbox dir the server
|
|
106
|
-
uses (override both with `SHEPHERD_INBOX_DIR` if you relocated it):
|
|
107
|
-
|
|
108
|
-
```json
|
|
109
|
-
{
|
|
110
|
-
"mcpServers": {
|
|
111
|
-
"shepherd": {
|
|
112
|
-
"command": "npx",
|
|
113
|
-
"args": ["-y", "@korso/shepherd"],
|
|
114
|
-
"env": {
|
|
115
|
-
"HUB_URL": "https://shepherd.example.com",
|
|
116
|
-
"TEAM_TOKEN": "tok_abc123"
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
"hooks": {
|
|
121
|
-
"PreToolUse": [
|
|
122
|
-
{
|
|
123
|
-
"matcher": "*",
|
|
124
|
-
"hooks": [
|
|
125
|
-
{ "type": "command", "command": "npx -y -p @korso/shepherd shepherd-inbox-hook" }
|
|
126
|
-
]
|
|
127
|
-
}
|
|
128
|
-
]
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
### Codex — `UserPromptSubmit` hook
|
|
134
|
-
|
|
135
|
-
Codex uses the **same** hook contract as Claude Code (JSON on stdin, a
|
|
136
|
-
`hookSpecificOutput.additionalContext` reply), so the **same bin** serves it. Use
|
|
137
|
-
`UserPromptSubmit` — Codex's `PreToolUse` only fires for Bash, not `apply_patch`
|
|
138
|
-
or MCP calls. Hooks must be enabled with `features.hooks = true`. In
|
|
139
|
-
`~/.codex/config.toml`:
|
|
140
|
-
|
|
141
|
-
```toml
|
|
142
|
-
[features]
|
|
143
|
-
hooks = true
|
|
144
|
-
|
|
145
|
-
[[hooks.UserPromptSubmit]]
|
|
146
|
-
command = ["npx", "-y", "-p", "@korso/shepherd", "shepherd-inbox-hook"]
|
|
147
|
-
# On Windows use command_windows instead:
|
|
148
|
-
# command_windows = ["cmd", "/c", "npx -y -p @korso/shepherd shepherd-inbox-hook"]
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### Pi — extension
|
|
152
|
-
|
|
153
|
-
Pi has no stdin/stdout hook; it loads in-process extensions. Ship the bundled
|
|
154
|
-
extension into Pi's extensions dir:
|
|
155
|
-
|
|
156
|
-
```sh
|
|
157
|
-
# global, applies everywhere:
|
|
158
|
-
mkdir -p ~/.pi/agent/extensions
|
|
159
|
-
cp "$(npm root -g)/@korso/shepherd/dist/inboxExtension.js" ~/.pi/agent/extensions/shepherd-inbox.js
|
|
160
|
-
# …or per-project: copy into .pi/extensions/ in the repo root.
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
It runs on every user turn (`before_agent_start`), drains the same inbox, and
|
|
164
|
-
injects pending announcements. (Or load it ad hoc with
|
|
165
|
-
`pi -e /abs/path/to/dist/inboxExtension.js`.)
|
|
166
|
-
|
|
167
|
-
### Notes
|
|
168
|
-
|
|
169
|
-
Every path is **fail-open**: a missing dir, unreachable hub, or any error means
|
|
170
|
-
nothing is surfaced and the tool call / turn proceeds normally — coordination
|
|
171
|
-
never blocks the agent. The inbox is keyed per working directory; two sessions in
|
|
172
|
-
the exact same directory share it (a benign edge — they're the same repo). If you
|
|
173
|
-
override `SHEPHERD_INBOX_DIR` on the server, set it on the hook/extension to the
|
|
174
|
-
same value (the Claude/Codex bin and the Pi extension both read
|
|
175
|
-
`SHEPHERD_INBOX_DIR`, or you can pass the dir as the first CLI arg to the bin).
|
|
176
|
-
|
|
177
|
-
---
|
|
178
|
-
|
|
179
|
-
## 3. MCP client configuration
|
|
180
|
-
|
|
181
|
-
### Claude Code
|
|
182
|
-
|
|
183
|
-
> **Do not use `~/.claude/mcp.json` — Claude Code does not read it** (a config
|
|
184
|
-
> there loads silently into nothing). Use `claude mcp add` (user scope, applies
|
|
185
|
-
> everywhere) or a project-root `.mcp.json`. Confirm with `claude mcp list`,
|
|
186
|
-
> which should show `shepherd … ✔ Connected`.
|
|
187
|
-
|
|
188
|
-
Recommended — register once at user scope. Written as a **single line** so it
|
|
189
|
-
pastes cleanly into PowerShell, cmd, bash, and zsh (on PowerShell the bash `\`
|
|
190
|
-
line-continuation does not work). Minimal: just the two required vars (identity
|
|
191
|
-
is auto-detected from git):
|
|
192
|
-
|
|
193
|
-
```powershell
|
|
194
|
-
claude mcp add shepherd -s user -e HUB_URL=https://shepherd.example.com -e TEAM_TOKEN=tok_abc123 -- npx -y @korso/shepherd
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
Add any optional overrides from §2 with extra `-e` flags (e.g. `-e MODEL=claude-sonnet-4-6 -e HUMAN=daichi`).
|
|
198
|
-
|
|
199
|
-
Alternative — a `.mcp.json` at the **root of the repo you're working in**
|
|
200
|
-
(optional overrides shown commented-style; drop the ones you don't need):
|
|
201
|
-
|
|
202
|
-
```json
|
|
203
|
-
{
|
|
204
|
-
"mcpServers": {
|
|
205
|
-
"shepherd": {
|
|
206
|
-
"command": "npx",
|
|
207
|
-
"args": ["-y", "@korso/shepherd"],
|
|
208
|
-
"env": {
|
|
209
|
-
"HUB_URL": "https://shepherd.example.com",
|
|
210
|
-
"TEAM_TOKEN": "tok_abc123",
|
|
211
|
-
"MODEL": "claude-sonnet-4-6"
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
> Windows note: the server is a thin stdio client to the Linux-hosted hub, and
|
|
219
|
-
> `npx` works the same on every OS — no file paths to escape. The hub itself runs
|
|
220
|
-
> on Linux (Postgres), so the Windows-native durability concerns from the spike
|
|
221
|
-
> don't apply to clients.
|
|
222
|
-
|
|
223
|
-
### Codex (`~/.codex/config.toml`)
|
|
224
|
-
|
|
225
|
-
Codex uses the same MCP stdio protocol but configures it in **TOML**, not JSON —
|
|
226
|
-
at `~/.codex/config.toml` (global) or `.codex/config.toml` in a trusted project.
|
|
227
|
-
The table is `mcp_servers` with an **underscore** (`mcp-servers`/`mcpServers` are
|
|
228
|
-
silently ignored). Either run `codex mcp add`:
|
|
229
|
-
|
|
230
|
-
```sh
|
|
231
|
-
codex mcp add shepherd --env HUB_URL=https://shepherd.example.com --env TEAM_TOKEN=tok_abc123 --env PROGRAM=codex -- npx -y @korso/shepherd
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
…or add the table directly:
|
|
235
|
-
|
|
236
|
-
```toml
|
|
237
|
-
[mcp_servers.shepherd]
|
|
238
|
-
command = "npx"
|
|
239
|
-
args = ["-y", "@korso/shepherd"]
|
|
240
|
-
env = { HUB_URL = "https://shepherd.example.com", TEAM_TOKEN = "tok_abc123", PROGRAM = "codex", MODEL = "o4-mini" }
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
### Pi (`~/.pi/agent/mcp.json` or `.pi/mcp.json`)
|
|
244
|
-
|
|
245
|
-
Pi uses a JSON `mcpServers` block (project config overrides global):
|
|
246
|
-
|
|
247
|
-
```json
|
|
248
|
-
{
|
|
249
|
-
"mcpServers": {
|
|
250
|
-
"shepherd": {
|
|
251
|
-
"command": "npx",
|
|
252
|
-
"args": ["-y", "@korso/shepherd"],
|
|
253
|
-
"env": {
|
|
254
|
-
"HUB_URL": "https://shepherd.example.com",
|
|
255
|
-
"TEAM_TOKEN": "tok_abc123",
|
|
256
|
-
"PROGRAM": "pi"
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
---
|
|
264
|
-
|
|
265
|
-
## 4. Verify the server starts (quick smoke test)
|
|
266
|
-
|
|
267
|
-
Run with the two required vars set to confirm it connects and idles on stdin.
|
|
268
|
-
PowerShell (set env vars, then run):
|
|
269
|
-
|
|
270
|
-
```powershell
|
|
271
|
-
$env:HUB_URL = "https://shepherd.example.com"
|
|
272
|
-
$env:TEAM_TOKEN = "tok_abc123"
|
|
273
|
-
npx -y @korso/shepherd
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
bash/zsh: `HUB_URL=https://shepherd.example.com TEAM_TOKEN=tok_abc123 npx -y @korso/shepherd`
|
|
277
|
-
|
|
278
|
-
No stderr output and the process blocking on stdin = healthy. Press Ctrl+C to exit.
|
|
279
|
-
|
|
280
|
-
**Missing env vars:** if you omit `HUB_URL` or `TEAM_TOKEN`, you will see:
|
|
281
|
-
|
|
282
|
-
```
|
|
283
|
-
[shepherd] Configuration error — missing or invalid env vars:
|
|
284
|
-
HUB_URL: HUB_URL is required
|
|
285
|
-
TEAM_TOKEN: TEAM_TOKEN is required
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
and the process exits 1 immediately. This is by design. The optional identity
|
|
289
|
-
vars never cause this — they fall back to git detection / defaults.
|
|
290
|
-
|
|
291
|
-
**Wrong WORKSPACE:** if you override `WORKSPACE` to a value the hub doesn't allow, the server starts and connects but the startup auto-join is rejected (400), so every tool call (`work`, `sync`, etc.) reports "session not ready … proceeding uncoordinated". Either leave `WORKSPACE` unset (resolves to `default`) or set it to exactly match the hub's `ALLOWED_WORKSPACE`, then restart.
|
|
292
|
-
|
|
293
|
-
---
|
|
294
|
-
|
|
295
|
-
## Develop from source
|
|
296
|
-
|
|
297
|
-
Only needed if you're changing the MCP server itself. Clone the monorepo and
|
|
298
|
-
point your client at a local build instead of npx:
|
|
299
|
-
|
|
300
|
-
```sh
|
|
301
|
-
git clone https://github.com/Korsoai/shepherd.git
|
|
302
|
-
cd shepherd
|
|
303
|
-
npm install
|
|
304
|
-
npm run build # tsc -b — compiles the workspace for dev + tests
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
For an exact preview of the published artifact (a single self-contained bundle
|
|
308
|
-
with `@shepherd/shared` inlined), build the package directly:
|
|
309
|
-
|
|
310
|
-
```sh
|
|
311
|
-
npm run build --workspace=@korso/shepherd # runs tsup → packages/mcp-server/dist/index.js
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
Then use `node /absolute/path/to/shepherd/packages/mcp-server/dist/index.js` as
|
|
315
|
-
the `command` in your MCP config (Windows: escape backslashes in JSON).
|
|
316
|
-
|
|
317
|
-
### Publishing a new version
|
|
318
|
-
|
|
319
|
-
```sh
|
|
320
|
-
# bump "version" in packages/mcp-server/package.json, then:
|
|
321
|
-
npm publish --workspace=@korso/shepherd # prepublishOnly runs tsup automatically
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
`publishConfig.access` is `public`, so the scoped package publishes publicly.
|
|
325
|
-
|
|
326
|
-
---
|
|
327
|
-
|
|
328
|
-
## Troubleshooting
|
|
329
|
-
|
|
330
|
-
| Symptom | Likely cause | Fix |
|
|
331
|
-
|---|---|---|
|
|
332
|
-
| `Configuration error — missing or invalid env vars` | `HUB_URL` or `TEAM_TOKEN` is absent (only these two are required) | Add the missing var(s) to your client's `env` block |
|
|
333
|
-
| Tools report "session not ready … proceeding uncoordinated" | Startup auto-join rejected — usually a stale `TEAM_TOKEN`, or a `WORKSPACE` override the hub doesn't allow | Re-check `TEAM_TOKEN`; leave `WORKSPACE` unset (→ `default`) or match the hub's `ALLOWED_WORKSPACE`; restart |
|
|
334
|
-
| Agent shows up under a surprising name/repo/branch | Identity auto-detected from git, or reused from the device-identity cache when launched outside a git work tree | Override with `HUMAN`/`REPO`/`BRANCH`/`MODEL` env vars (§2); a correct git `user.name` on the next in-repo launch refreshes the cache, or delete `~/.shepherd/identity.json` to clear it |
|
|
335
|
-
| `npm error 404 … @korso/shepherd` | Package not published yet, or name typo | `npm view @korso/shepherd version` to confirm it's live |
|
|
336
|
-
| Process exits immediately with no error | Rare; check for node version incompatibility | Requires Node 18+ (ESM support) |
|
|
1
|
+
# @korso/shepherd — Shepherd MCP Server
|
|
2
|
+
|
|
3
|
+
Shepherd's stdio MCP server. Gives any MCP-capable agent (Claude Code, Codex, etc.) four advisory coordination tools backed by the shared hub: `work`, `done`, `announce`, and `sync`. The agent **joins the workspace automatically** on startup (no `join` tool), and the server ships standing instructions so the agent self-coordinates without the user prompting it.
|
|
4
|
+
|
|
5
|
+
> **New here?** The [developer quickstart](https://github.com/Korsoai/shepherd/blob/main/docs/shepherd-mcp-quickstart.md) is the fastest path. TL;DR: `npx -y @korso/shepherd` with the env vars below.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## CRITICAL: WORKSPACE must match the hub exactly
|
|
10
|
+
|
|
11
|
+
> **`WORKSPACE` defaults to `default`. If you override it, the value must equal the hub's `ALLOWED_WORKSPACE` env var exactly.**
|
|
12
|
+
|
|
13
|
+
The server fires an automatic `join` call to the hub at startup. If the workspace it sends does not match the hub's `ALLOWED_WORKSPACE`, that call returns HTTP 400 and coordination degrades: every tool reports "session not ready … proceeding uncoordinated" instead of a landscape. The safe default is to **leave `WORKSPACE` unset** so it resolves to `default` — only set it when a maintainer points you at a different workspace. If your agent never sees teammates, check `WORKSPACE` (and `TEAM_TOKEN`) first.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
The server is published to npm and runs via `npx` — no clone or build required
|
|
20
|
+
(Node 18+):
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
npx -y @korso/shepherd
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
You won't normally run that by hand; you put it in your MCP client config (below)
|
|
27
|
+
with the required env vars. `npx` caches the package, so startup is fast after the
|
|
28
|
+
first fetch, and `@korso/shepherd@latest` picks up updates automatically.
|
|
29
|
+
|
|
30
|
+
> Hacking on the server itself? See **[Develop from source](#develop-from-source)**
|
|
31
|
+
> at the bottom.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 2. Environment variables
|
|
36
|
+
|
|
37
|
+
**Only two are required:**
|
|
38
|
+
|
|
39
|
+
| Variable | Description | Example |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `HUB_URL` | Base URL of the deployed hub | `https://shepherd.example.com` |
|
|
42
|
+
| `TEAM_TOKEN` | Shared bearer token accepted by the hub | `tok_abc123` |
|
|
43
|
+
|
|
44
|
+
Missing either causes an immediate startup failure with a clear error on stderr
|
|
45
|
+
listing which vars are absent. (No other var triggers this.)
|
|
46
|
+
|
|
47
|
+
**Everything else is optional** — each identity field is resolved at startup as
|
|
48
|
+
**env var → git detection → fallback**, so a plain `npx -y @korso/shepherd` with
|
|
49
|
+
just the two required vars produces a valid, fully-identified session. Set an
|
|
50
|
+
override only to replace what's detected:
|
|
51
|
+
|
|
52
|
+
| Variable | If omitted | Example |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `WORKSPACE` | defaults to `default` (**must match hub's `ALLOWED_WORKSPACE` if overridden**) | `shepherd` |
|
|
55
|
+
| `REPO` | `git remote origin` → `owner/repo`, else repo folder name, else `unknown-repo` | `Korsoai/shepherd` |
|
|
56
|
+
| `BRANCH` | `git rev-parse --abbrev-ref HEAD`, else `HEAD` | `main` |
|
|
57
|
+
| `BASE_BRANCH` | `origin/HEAD`, else `origin/main` / `origin/master` (used for the change-awareness heads-up) | `origin/main` |
|
|
58
|
+
| `HUMAN` | git `user.name`, else local-part of `user.email`, else this device's **cached** last-detected name, else a generated name | `daichi` |
|
|
59
|
+
| `PROGRAM` | defaults to `claude-code` | `codex` |
|
|
60
|
+
| `MODEL` | omitted — **never auto-detected**, so set it if you want it shown | `claude-sonnet-4-6` |
|
|
61
|
+
| `HEARTBEAT_INTERVAL_SECONDS` | defaults to `60` | `30` |
|
|
62
|
+
| `SHEPHERD_INBOX_DIR` | defaults to `~/.shepherd/inbox`. Override only to relocate the **announcement-push** inbox (see below); the background heartbeat writes incoming announcements here. If you set it, point your client hook/extension at the **same** dir | `~/.shepherd/inbox` |
|
|
63
|
+
|
|
64
|
+
**Device-identity cache.** Whenever `HUMAN` is unset and git **does** detect a
|
|
65
|
+
name, that name is cached for your OS user at `~/.shepherd/identity.json`. A
|
|
66
|
+
later launch from a directory where git can't be read (e.g. a multi-repo
|
|
67
|
+
workspace root) then reuses the cached name instead of inventing a fresh random
|
|
68
|
+
one each time. The cache refreshes automatically the next time git reports a
|
|
69
|
+
different name, and an explicit `HUMAN` override always wins and never touches
|
|
70
|
+
the cache. It is best-effort: if the file can't be read or written, resolution
|
|
71
|
+
just falls back to a generated name.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Announcement push (on by default)
|
|
76
|
+
|
|
77
|
+
Announcements reach an agent **without it having to ask**. The background
|
|
78
|
+
heartbeat pulls any pending announcements from the hub every beat and stages them
|
|
79
|
+
in a local **inbox file** (per working directory, under `SHEPHERD_INBOX_DIR`,
|
|
80
|
+
default `~/.shepherd/inbox`). That file is then drained by two paths:
|
|
81
|
+
|
|
82
|
+
1. **Universal drainer (always on, every client).** Whenever the agent calls any
|
|
83
|
+
Shepherd tool (`work`/`sync`/`done`/`announce`), the result also includes
|
|
84
|
+
anything sitting in the inbox. So even with no hook configured, no announcement
|
|
85
|
+
is ever lost — the worst case is the old behaviour (delivered on the next
|
|
86
|
+
Shepherd tool call), never silent drops.
|
|
87
|
+
2. **Passive client hook/extension (optional, per client).** To get announcements
|
|
88
|
+
**without** waiting for a Shepherd tool call — surfaced on the agent's next
|
|
89
|
+
action of any kind — wire up your client's hook below. This is the
|
|
90
|
+
"a subagent finished" style of notification.
|
|
91
|
+
|
|
92
|
+
Both paths read the **same** inbox file and de-duplicate by announcement id, so
|
|
93
|
+
running both is safe (the hub hands each announcement to exactly one drain; the
|
|
94
|
+
merge is just defensive). It's cheap: a **local file read — no network** (the
|
|
95
|
+
heartbeat already did the fetch), and it only adds to the model's context when
|
|
96
|
+
something is actually waiting.
|
|
97
|
+
|
|
98
|
+
It delivers to an agent **while it's active**; an idle agent picks messages up the
|
|
99
|
+
moment it next does anything. (Waking a fully-idle agent is out of scope — for
|
|
100
|
+
Claude Code that needs Channels; Codex/Pi have no equivalent.)
|
|
101
|
+
|
|
102
|
+
### Claude Code — `PreToolUse` hook
|
|
103
|
+
|
|
104
|
+
`PreToolUse` fires before every tool, giving the most frequent passive delivery.
|
|
105
|
+
The hook needs no arguments — it resolves the same default inbox dir the server
|
|
106
|
+
uses (override both with `SHEPHERD_INBOX_DIR` if you relocated it):
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"mcpServers": {
|
|
111
|
+
"shepherd": {
|
|
112
|
+
"command": "npx",
|
|
113
|
+
"args": ["-y", "@korso/shepherd"],
|
|
114
|
+
"env": {
|
|
115
|
+
"HUB_URL": "https://shepherd.example.com",
|
|
116
|
+
"TEAM_TOKEN": "tok_abc123"
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
"hooks": {
|
|
121
|
+
"PreToolUse": [
|
|
122
|
+
{
|
|
123
|
+
"matcher": "*",
|
|
124
|
+
"hooks": [
|
|
125
|
+
{ "type": "command", "command": "npx -y -p @korso/shepherd shepherd-inbox-hook" }
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Codex — `UserPromptSubmit` hook
|
|
134
|
+
|
|
135
|
+
Codex uses the **same** hook contract as Claude Code (JSON on stdin, a
|
|
136
|
+
`hookSpecificOutput.additionalContext` reply), so the **same bin** serves it. Use
|
|
137
|
+
`UserPromptSubmit` — Codex's `PreToolUse` only fires for Bash, not `apply_patch`
|
|
138
|
+
or MCP calls. Hooks must be enabled with `features.hooks = true`. In
|
|
139
|
+
`~/.codex/config.toml`:
|
|
140
|
+
|
|
141
|
+
```toml
|
|
142
|
+
[features]
|
|
143
|
+
hooks = true
|
|
144
|
+
|
|
145
|
+
[[hooks.UserPromptSubmit]]
|
|
146
|
+
command = ["npx", "-y", "-p", "@korso/shepherd", "shepherd-inbox-hook"]
|
|
147
|
+
# On Windows use command_windows instead:
|
|
148
|
+
# command_windows = ["cmd", "/c", "npx -y -p @korso/shepherd shepherd-inbox-hook"]
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Pi — extension
|
|
152
|
+
|
|
153
|
+
Pi has no stdin/stdout hook; it loads in-process extensions. Ship the bundled
|
|
154
|
+
extension into Pi's extensions dir:
|
|
155
|
+
|
|
156
|
+
```sh
|
|
157
|
+
# global, applies everywhere:
|
|
158
|
+
mkdir -p ~/.pi/agent/extensions
|
|
159
|
+
cp "$(npm root -g)/@korso/shepherd/dist/inboxExtension.js" ~/.pi/agent/extensions/shepherd-inbox.js
|
|
160
|
+
# …or per-project: copy into .pi/extensions/ in the repo root.
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
It runs on every user turn (`before_agent_start`), drains the same inbox, and
|
|
164
|
+
injects pending announcements. (Or load it ad hoc with
|
|
165
|
+
`pi -e /abs/path/to/dist/inboxExtension.js`.)
|
|
166
|
+
|
|
167
|
+
### Notes
|
|
168
|
+
|
|
169
|
+
Every path is **fail-open**: a missing dir, unreachable hub, or any error means
|
|
170
|
+
nothing is surfaced and the tool call / turn proceeds normally — coordination
|
|
171
|
+
never blocks the agent. The inbox is keyed per working directory; two sessions in
|
|
172
|
+
the exact same directory share it (a benign edge — they're the same repo). If you
|
|
173
|
+
override `SHEPHERD_INBOX_DIR` on the server, set it on the hook/extension to the
|
|
174
|
+
same value (the Claude/Codex bin and the Pi extension both read
|
|
175
|
+
`SHEPHERD_INBOX_DIR`, or you can pass the dir as the first CLI arg to the bin).
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 3. MCP client configuration
|
|
180
|
+
|
|
181
|
+
### Claude Code
|
|
182
|
+
|
|
183
|
+
> **Do not use `~/.claude/mcp.json` — Claude Code does not read it** (a config
|
|
184
|
+
> there loads silently into nothing). Use `claude mcp add` (user scope, applies
|
|
185
|
+
> everywhere) or a project-root `.mcp.json`. Confirm with `claude mcp list`,
|
|
186
|
+
> which should show `shepherd … ✔ Connected`.
|
|
187
|
+
|
|
188
|
+
Recommended — register once at user scope. Written as a **single line** so it
|
|
189
|
+
pastes cleanly into PowerShell, cmd, bash, and zsh (on PowerShell the bash `\`
|
|
190
|
+
line-continuation does not work). Minimal: just the two required vars (identity
|
|
191
|
+
is auto-detected from git):
|
|
192
|
+
|
|
193
|
+
```powershell
|
|
194
|
+
claude mcp add shepherd -s user -e HUB_URL=https://shepherd.example.com -e TEAM_TOKEN=tok_abc123 -- npx -y @korso/shepherd
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Add any optional overrides from §2 with extra `-e` flags (e.g. `-e MODEL=claude-sonnet-4-6 -e HUMAN=daichi`).
|
|
198
|
+
|
|
199
|
+
Alternative — a `.mcp.json` at the **root of the repo you're working in**
|
|
200
|
+
(optional overrides shown commented-style; drop the ones you don't need):
|
|
201
|
+
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"mcpServers": {
|
|
205
|
+
"shepherd": {
|
|
206
|
+
"command": "npx",
|
|
207
|
+
"args": ["-y", "@korso/shepherd"],
|
|
208
|
+
"env": {
|
|
209
|
+
"HUB_URL": "https://shepherd.example.com",
|
|
210
|
+
"TEAM_TOKEN": "tok_abc123",
|
|
211
|
+
"MODEL": "claude-sonnet-4-6"
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
> Windows note: the server is a thin stdio client to the Linux-hosted hub, and
|
|
219
|
+
> `npx` works the same on every OS — no file paths to escape. The hub itself runs
|
|
220
|
+
> on Linux (Postgres), so the Windows-native durability concerns from the spike
|
|
221
|
+
> don't apply to clients.
|
|
222
|
+
|
|
223
|
+
### Codex (`~/.codex/config.toml`)
|
|
224
|
+
|
|
225
|
+
Codex uses the same MCP stdio protocol but configures it in **TOML**, not JSON —
|
|
226
|
+
at `~/.codex/config.toml` (global) or `.codex/config.toml` in a trusted project.
|
|
227
|
+
The table is `mcp_servers` with an **underscore** (`mcp-servers`/`mcpServers` are
|
|
228
|
+
silently ignored). Either run `codex mcp add`:
|
|
229
|
+
|
|
230
|
+
```sh
|
|
231
|
+
codex mcp add shepherd --env HUB_URL=https://shepherd.example.com --env TEAM_TOKEN=tok_abc123 --env PROGRAM=codex -- npx -y @korso/shepherd
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
…or add the table directly:
|
|
235
|
+
|
|
236
|
+
```toml
|
|
237
|
+
[mcp_servers.shepherd]
|
|
238
|
+
command = "npx"
|
|
239
|
+
args = ["-y", "@korso/shepherd"]
|
|
240
|
+
env = { HUB_URL = "https://shepherd.example.com", TEAM_TOKEN = "tok_abc123", PROGRAM = "codex", MODEL = "o4-mini" }
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Pi (`~/.pi/agent/mcp.json` or `.pi/mcp.json`)
|
|
244
|
+
|
|
245
|
+
Pi uses a JSON `mcpServers` block (project config overrides global):
|
|
246
|
+
|
|
247
|
+
```json
|
|
248
|
+
{
|
|
249
|
+
"mcpServers": {
|
|
250
|
+
"shepherd": {
|
|
251
|
+
"command": "npx",
|
|
252
|
+
"args": ["-y", "@korso/shepherd"],
|
|
253
|
+
"env": {
|
|
254
|
+
"HUB_URL": "https://shepherd.example.com",
|
|
255
|
+
"TEAM_TOKEN": "tok_abc123",
|
|
256
|
+
"PROGRAM": "pi"
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## 4. Verify the server starts (quick smoke test)
|
|
266
|
+
|
|
267
|
+
Run with the two required vars set to confirm it connects and idles on stdin.
|
|
268
|
+
PowerShell (set env vars, then run):
|
|
269
|
+
|
|
270
|
+
```powershell
|
|
271
|
+
$env:HUB_URL = "https://shepherd.example.com"
|
|
272
|
+
$env:TEAM_TOKEN = "tok_abc123"
|
|
273
|
+
npx -y @korso/shepherd
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
bash/zsh: `HUB_URL=https://shepherd.example.com TEAM_TOKEN=tok_abc123 npx -y @korso/shepherd`
|
|
277
|
+
|
|
278
|
+
No stderr output and the process blocking on stdin = healthy. Press Ctrl+C to exit.
|
|
279
|
+
|
|
280
|
+
**Missing env vars:** if you omit `HUB_URL` or `TEAM_TOKEN`, you will see:
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
[shepherd] Configuration error — missing or invalid env vars:
|
|
284
|
+
HUB_URL: HUB_URL is required
|
|
285
|
+
TEAM_TOKEN: TEAM_TOKEN is required
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
and the process exits 1 immediately. This is by design. The optional identity
|
|
289
|
+
vars never cause this — they fall back to git detection / defaults.
|
|
290
|
+
|
|
291
|
+
**Wrong WORKSPACE:** if you override `WORKSPACE` to a value the hub doesn't allow, the server starts and connects but the startup auto-join is rejected (400), so every tool call (`work`, `sync`, etc.) reports "session not ready … proceeding uncoordinated". Either leave `WORKSPACE` unset (resolves to `default`) or set it to exactly match the hub's `ALLOWED_WORKSPACE`, then restart.
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Develop from source
|
|
296
|
+
|
|
297
|
+
Only needed if you're changing the MCP server itself. Clone the monorepo and
|
|
298
|
+
point your client at a local build instead of npx:
|
|
299
|
+
|
|
300
|
+
```sh
|
|
301
|
+
git clone https://github.com/Korsoai/shepherd.git
|
|
302
|
+
cd shepherd
|
|
303
|
+
npm install
|
|
304
|
+
npm run build # tsc -b — compiles the workspace for dev + tests
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
For an exact preview of the published artifact (a single self-contained bundle
|
|
308
|
+
with `@shepherd/shared` inlined), build the package directly:
|
|
309
|
+
|
|
310
|
+
```sh
|
|
311
|
+
npm run build --workspace=@korso/shepherd # runs tsup → packages/mcp-server/dist/index.js
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Then use `node /absolute/path/to/shepherd/packages/mcp-server/dist/index.js` as
|
|
315
|
+
the `command` in your MCP config (Windows: escape backslashes in JSON).
|
|
316
|
+
|
|
317
|
+
### Publishing a new version
|
|
318
|
+
|
|
319
|
+
```sh
|
|
320
|
+
# bump "version" in packages/mcp-server/package.json, then:
|
|
321
|
+
npm publish --workspace=@korso/shepherd # prepublishOnly runs tsup automatically
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
`publishConfig.access` is `public`, so the scoped package publishes publicly.
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Troubleshooting
|
|
329
|
+
|
|
330
|
+
| Symptom | Likely cause | Fix |
|
|
331
|
+
|---|---|---|
|
|
332
|
+
| `Configuration error — missing or invalid env vars` | `HUB_URL` or `TEAM_TOKEN` is absent (only these two are required) | Add the missing var(s) to your client's `env` block |
|
|
333
|
+
| Tools report "session not ready … proceeding uncoordinated" | Startup auto-join rejected — usually a stale `TEAM_TOKEN`, or a `WORKSPACE` override the hub doesn't allow | Re-check `TEAM_TOKEN`; leave `WORKSPACE` unset (→ `default`) or match the hub's `ALLOWED_WORKSPACE`; restart |
|
|
334
|
+
| Agent shows up under a surprising name/repo/branch | Identity auto-detected from git, or reused from the device-identity cache when launched outside a git work tree | Override with `HUMAN`/`REPO`/`BRANCH`/`MODEL` env vars (§2); a correct git `user.name` on the next in-repo launch refreshes the cache, or delete `~/.shepherd/identity.json` to clear it |
|
|
335
|
+
| `npm error 404 … @korso/shepherd` | Package not published yet, or name typo | `npm view @korso/shepherd version` to confirm it's live |
|
|
336
|
+
| Process exits immediately with no error | Rare; check for node version incompatibility | Requires Node 18+ (ESM support) |
|