@jaggerxtrm/specialists 2.0.1 → 2.1.0
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/README.md +60 -12
- package/dist/index.js +82 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Specialists
|
|
2
2
|
|
|
3
3
|
**One MCP Server. Many Specialists. Real AI Agents.**
|
|
4
4
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
[](https://www.typescriptlang.org/)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
**Specialists** is a **Model Context Protocol (MCP) server** that lets Claude (and other AI agents) discover and run specialist agents — each a full autonomous coding agent powered by [pi](https://github.com/mariozechner/pi), scoped to a specific task.
|
|
10
10
|
|
|
11
11
|
**Designed for agents, not just users.** Claude can autonomously route heavy tasks (code review, bug hunting, deep reasoning, session init) to the right specialist without user intervention. Specialists run in the background while Claude continues working.
|
|
12
12
|
|
|
@@ -14,26 +14,27 @@ OmniSpecialist is a **Model Context Protocol (MCP) server** that lets Claude (an
|
|
|
14
14
|
|
|
15
15
|
## How it works
|
|
16
16
|
|
|
17
|
-
Specialists are `.specialist.yaml` files that define an autonomous agent: its model, system prompt, task template, and permission tier.
|
|
17
|
+
Specialists are `.specialist.yaml` files that define an autonomous agent: its model, system prompt, task template, and permission tier. The server discovers them across three scopes:
|
|
18
18
|
|
|
19
19
|
| Scope | Location | Purpose |
|
|
20
20
|
|-------|----------|---------|
|
|
21
21
|
| **project** | `./specialists/` | Per-project specialists |
|
|
22
22
|
| **user** | `~/.agents/specialists/` | Personal specialists |
|
|
23
|
-
| **system** | bundled with
|
|
23
|
+
| **system** | bundled with the package | Built-in defaults |
|
|
24
24
|
|
|
25
|
-
When a specialist runs,
|
|
25
|
+
When a specialist runs, the server spawns a `pi` subprocess with the right model, tools, and system prompt injected. Output streams back in real time via cursor-based polling.
|
|
26
26
|
|
|
27
27
|
---
|
|
28
28
|
|
|
29
|
-
## MCP Tools (
|
|
29
|
+
## MCP Tools (8)
|
|
30
30
|
|
|
31
31
|
| Tool | Description |
|
|
32
32
|
|------|-------------|
|
|
33
|
+
| `specialist_init` | Session bootstrap: init beads if needed, return available specialists |
|
|
33
34
|
| `list_specialists` | Discover all available specialists across scopes |
|
|
34
35
|
| `use_specialist` | Run a specialist synchronously and return the result |
|
|
35
36
|
| `start_specialist` | Fire-and-forget: start a specialist job, returns `job_id` |
|
|
36
|
-
| `poll_specialist` | Poll a running job; returns delta since last cursor |
|
|
37
|
+
| `poll_specialist` | Poll a running job; returns delta since last cursor + `beadId` |
|
|
37
38
|
| `stop_specialist` | Cancel a running job |
|
|
38
39
|
| `run_parallel` | Run multiple specialists concurrently or as a pipeline |
|
|
39
40
|
| `specialist_status` | Circuit breaker health + job status |
|
|
@@ -58,7 +59,7 @@ When a specialist runs, OmniSpecialist spawns a `pi` subprocess with the right m
|
|
|
58
59
|
|
|
59
60
|
## Permission Tiers
|
|
60
61
|
|
|
61
|
-
Specialists declare their required permission level
|
|
62
|
+
Specialists declare their required permission level, enforced at spawn time via `pi --tools`:
|
|
62
63
|
|
|
63
64
|
| Tier | Allowed tools | Use case |
|
|
64
65
|
|------|--------------|----------|
|
|
@@ -69,13 +70,60 @@ Specialists declare their required permission level. OmniSpecialist enforces thi
|
|
|
69
70
|
|
|
70
71
|
---
|
|
71
72
|
|
|
73
|
+
## Beads Integration
|
|
74
|
+
|
|
75
|
+
Specialists with write permissions (`LOW`/`MEDIUM`/`HIGH`) automatically create a [beads](https://github.com/beads/bd) issue when they run and close it on completion. Control this per-specialist with `beads_integration`:
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
beads_integration: auto # default — create for LOW/MEDIUM/HIGH, skip for READ_ONLY
|
|
79
|
+
beads_integration: always # always create, regardless of permission tier
|
|
80
|
+
beads_integration: never # never create
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The orchestrating agent can retrieve the `beadId` from `poll_specialist` output to link the issue for follow-up (`bd remember`, `bd update --notes`, etc.).
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
72
87
|
## Installation
|
|
73
88
|
|
|
89
|
+
### One-line installer (recommended)
|
|
90
|
+
|
|
74
91
|
```bash
|
|
75
|
-
|
|
92
|
+
npx --package=@jaggerxtrm/specialists install
|
|
76
93
|
```
|
|
77
94
|
|
|
78
|
-
|
|
95
|
+
Installs: **pi** (`@mariozechner/pi-coding-agent`), **beads** (`@beads/bd`), **dolt** (interactive sudo on Linux / brew on macOS), registers the `specialists` MCP at user scope, scaffolds `~/.agents/specialists/`.
|
|
96
|
+
|
|
97
|
+
After running, **restart Claude Code** to load the MCP.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
### Manual installation
|
|
102
|
+
|
|
103
|
+
**1. pi** — coding agent runtime:
|
|
104
|
+
```bash
|
|
105
|
+
npm install -g @mariozechner/pi-coding-agent
|
|
106
|
+
```
|
|
107
|
+
Run `pi` once, then `pi config` to enable your model providers (Anthropic, Google, etc.).
|
|
108
|
+
|
|
109
|
+
**2. beads** — issue tracker:
|
|
110
|
+
```bash
|
|
111
|
+
npm install -g @beads/bd
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**3. dolt** — beads sync backend:
|
|
115
|
+
```bash
|
|
116
|
+
# Linux
|
|
117
|
+
sudo bash -c 'curl -L https://github.com/dolthub/dolt/releases/latest/download/install.sh | bash'
|
|
118
|
+
# macOS
|
|
119
|
+
brew install dolt
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**4. specialists + MCP:**
|
|
123
|
+
```bash
|
|
124
|
+
npm install -g @jaggerxtrm/specialists
|
|
125
|
+
claude mcp add --scope user specialists -- specialists
|
|
126
|
+
```
|
|
79
127
|
|
|
80
128
|
Then **restart Claude Code**.
|
|
81
129
|
|
|
@@ -93,7 +141,7 @@ specialist:
|
|
|
93
141
|
description: "What this specialist does."
|
|
94
142
|
category: analysis
|
|
95
143
|
tags: [analysis, example]
|
|
96
|
-
updated: "2026-03-
|
|
144
|
+
updated: "2026-03-09"
|
|
97
145
|
|
|
98
146
|
execution:
|
|
99
147
|
mode: tool
|
|
@@ -132,7 +180,7 @@ bun test
|
|
|
132
180
|
```
|
|
133
181
|
|
|
134
182
|
- **Build**: `bun build src/index.ts --target=node --outfile=dist/index.js`
|
|
135
|
-
- **Test**: `bun --bun vitest run` (
|
|
183
|
+
- **Test**: `bun --bun vitest run` (67 unit tests)
|
|
136
184
|
- **Lint**: `tsc --noEmit`
|
|
137
185
|
|
|
138
186
|
See [CLAUDE.md](CLAUDE.md) for the full architecture guide and [ROADMAP.md](ROADMAP.md) for planned features.
|
package/dist/index.js
CHANGED
|
@@ -24568,12 +24568,12 @@ class StdioServerTransport {
|
|
|
24568
24568
|
}
|
|
24569
24569
|
|
|
24570
24570
|
// src/server.ts
|
|
24571
|
-
import { join as
|
|
24571
|
+
import { join as join3 } from "node:path";
|
|
24572
24572
|
|
|
24573
24573
|
// src/constants.ts
|
|
24574
|
-
var LOG_PREFIX = "[
|
|
24574
|
+
var LOG_PREFIX = "[specialists]";
|
|
24575
24575
|
var MCP_CONFIG = {
|
|
24576
|
-
SERVER_NAME: "
|
|
24576
|
+
SERVER_NAME: "specialists",
|
|
24577
24577
|
VERSION: "1.0.0",
|
|
24578
24578
|
CAPABILITIES: {
|
|
24579
24579
|
tools: {},
|
|
@@ -24748,6 +24748,7 @@ var SpecialistSchema = objectType({
|
|
|
24748
24748
|
capabilities: CapabilitiesSchema,
|
|
24749
24749
|
communication: CommunicationSchema,
|
|
24750
24750
|
validation: ValidationSchema,
|
|
24751
|
+
beads_integration: enumType(["auto", "always", "never"]).default("auto"),
|
|
24751
24752
|
heartbeat: unknownType().optional()
|
|
24752
24753
|
})
|
|
24753
24754
|
});
|
|
@@ -25078,6 +25079,15 @@ class PiAgentSession {
|
|
|
25078
25079
|
}
|
|
25079
25080
|
}
|
|
25080
25081
|
|
|
25082
|
+
// src/specialist/beads.ts
|
|
25083
|
+
function shouldCreateBead(beadsIntegration, permissionRequired) {
|
|
25084
|
+
if (beadsIntegration === "never")
|
|
25085
|
+
return false;
|
|
25086
|
+
if (beadsIntegration === "always")
|
|
25087
|
+
return true;
|
|
25088
|
+
return permissionRequired !== "READ_ONLY";
|
|
25089
|
+
}
|
|
25090
|
+
|
|
25081
25091
|
// src/specialist/runner.ts
|
|
25082
25092
|
class SpecialistRunner {
|
|
25083
25093
|
deps;
|
|
@@ -25087,7 +25097,7 @@ class SpecialistRunner {
|
|
|
25087
25097
|
this.sessionFactory = deps.sessionFactory ?? PiAgentSession.create.bind(PiAgentSession);
|
|
25088
25098
|
}
|
|
25089
25099
|
async run(options, onProgress, onEvent, onMeta, onKillRegistered) {
|
|
25090
|
-
const { loader, hooks, circuitBreaker } = this.deps;
|
|
25100
|
+
const { loader, hooks, circuitBreaker, beadsClient } = this.deps;
|
|
25091
25101
|
const invocationId = crypto.randomUUID();
|
|
25092
25102
|
const start = Date.now();
|
|
25093
25103
|
const spec = await loader.get(options.name);
|
|
@@ -25142,6 +25152,11 @@ You have access via Bash:
|
|
|
25142
25152
|
timeout_ms: execution.timeout_ms,
|
|
25143
25153
|
permission_level: permissionLevel
|
|
25144
25154
|
});
|
|
25155
|
+
const beadsIntegration = spec.specialist.beads_integration ?? "auto";
|
|
25156
|
+
let beadId;
|
|
25157
|
+
if (beadsClient && shouldCreateBead(beadsIntegration, execution.permission_required)) {
|
|
25158
|
+
beadId = beadsClient.createBead(metadata.name) ?? undefined;
|
|
25159
|
+
}
|
|
25145
25160
|
let output;
|
|
25146
25161
|
let session;
|
|
25147
25162
|
try {
|
|
@@ -25179,6 +25194,8 @@ You have access via Bash:
|
|
|
25179
25194
|
circuitBreaker.recordSuccess(model);
|
|
25180
25195
|
} catch (err) {
|
|
25181
25196
|
circuitBreaker.recordFailure(model);
|
|
25197
|
+
if (beadId)
|
|
25198
|
+
beadsClient?.closeBead(beadId, "ERROR", Date.now() - start, model);
|
|
25182
25199
|
await hooks.emit("post_execute", invocationId, metadata.name, metadata.version, {
|
|
25183
25200
|
status: "ERROR",
|
|
25184
25201
|
duration_ms: Date.now() - start,
|
|
@@ -25198,12 +25215,17 @@ You have access via Bash:
|
|
|
25198
25215
|
duration_ms: durationMs,
|
|
25199
25216
|
output_valid: true
|
|
25200
25217
|
});
|
|
25218
|
+
if (beadId) {
|
|
25219
|
+
beadsClient?.closeBead(beadId, "COMPLETE", durationMs, model);
|
|
25220
|
+
beadsClient?.auditBead(beadId, metadata.name, model, 0);
|
|
25221
|
+
}
|
|
25201
25222
|
return {
|
|
25202
25223
|
output,
|
|
25203
25224
|
backend: session.meta.backend,
|
|
25204
25225
|
model,
|
|
25205
25226
|
durationMs,
|
|
25206
|
-
specialistVersion: metadata.version
|
|
25227
|
+
specialistVersion: metadata.version,
|
|
25228
|
+
beadId
|
|
25207
25229
|
};
|
|
25208
25230
|
}
|
|
25209
25231
|
async startAsync(options, registry2) {
|
|
@@ -25478,6 +25500,12 @@ class JobRegistry {
|
|
|
25478
25500
|
if (meta.model)
|
|
25479
25501
|
job.model = meta.model;
|
|
25480
25502
|
}
|
|
25503
|
+
setBeadId(id, beadId) {
|
|
25504
|
+
const job = this.jobs.get(id);
|
|
25505
|
+
if (!job)
|
|
25506
|
+
return;
|
|
25507
|
+
job.beadId = beadId;
|
|
25508
|
+
}
|
|
25481
25509
|
setKillFn(id, killFn) {
|
|
25482
25510
|
const job = this.jobs.get(id);
|
|
25483
25511
|
if (!job)
|
|
@@ -25499,6 +25527,8 @@ class JobRegistry {
|
|
|
25499
25527
|
job.model = result.model;
|
|
25500
25528
|
job.specialistVersion = result.specialistVersion;
|
|
25501
25529
|
job.endedAtMs = Date.now();
|
|
25530
|
+
if (result.beadId)
|
|
25531
|
+
job.beadId = result.beadId;
|
|
25502
25532
|
}
|
|
25503
25533
|
fail(id, err) {
|
|
25504
25534
|
const job = this.jobs.get(id);
|
|
@@ -25535,7 +25565,8 @@ class JobRegistry {
|
|
|
25535
25565
|
model: job.model,
|
|
25536
25566
|
specialist_version: job.specialistVersion,
|
|
25537
25567
|
duration_ms: (job.endedAtMs ?? Date.now()) - job.startedAtMs,
|
|
25538
|
-
error: job.error
|
|
25568
|
+
error: job.error,
|
|
25569
|
+
beadId: job.beadId
|
|
25539
25570
|
};
|
|
25540
25571
|
}
|
|
25541
25572
|
delete(id) {
|
|
@@ -25606,15 +25637,50 @@ function createStopSpecialistTool(registry2) {
|
|
|
25606
25637
|
};
|
|
25607
25638
|
}
|
|
25608
25639
|
|
|
25640
|
+
// src/tools/specialist/specialist_init.tool.ts
|
|
25641
|
+
import { spawnSync } from "node:child_process";
|
|
25642
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
25643
|
+
import { join as join2 } from "node:path";
|
|
25644
|
+
var specialistInitSchema = exports_external.object({});
|
|
25645
|
+
function createSpecialistInitTool(loader, deps) {
|
|
25646
|
+
const resolved = deps ?? {
|
|
25647
|
+
bdAvailable: () => spawnSync("bd", ["--version"], { stdio: "ignore" }).status === 0,
|
|
25648
|
+
beadsExists: () => existsSync2(join2(process.cwd(), ".beads")),
|
|
25649
|
+
bdInit: () => spawnSync("bd", ["init"], { stdio: "ignore" })
|
|
25650
|
+
};
|
|
25651
|
+
return {
|
|
25652
|
+
name: "specialist_init",
|
|
25653
|
+
description: "Session bootstrap: initializes beads in the project if not already set up, " + "then returns available specialists. Call at session start for orientation.",
|
|
25654
|
+
inputSchema: specialistInitSchema,
|
|
25655
|
+
async execute(_input) {
|
|
25656
|
+
const available = resolved.bdAvailable();
|
|
25657
|
+
let initialized = false;
|
|
25658
|
+
if (available) {
|
|
25659
|
+
if (resolved.beadsExists()) {
|
|
25660
|
+
initialized = true;
|
|
25661
|
+
} else {
|
|
25662
|
+
const result = resolved.bdInit();
|
|
25663
|
+
initialized = result.status === 0;
|
|
25664
|
+
}
|
|
25665
|
+
}
|
|
25666
|
+
const specialists = await loader.list();
|
|
25667
|
+
return {
|
|
25668
|
+
specialists,
|
|
25669
|
+
beads: { available, initialized }
|
|
25670
|
+
};
|
|
25671
|
+
}
|
|
25672
|
+
};
|
|
25673
|
+
}
|
|
25674
|
+
|
|
25609
25675
|
// src/server.ts
|
|
25610
|
-
class
|
|
25676
|
+
class SpecialistsServer {
|
|
25611
25677
|
server;
|
|
25612
25678
|
tools;
|
|
25613
25679
|
constructor() {
|
|
25614
25680
|
const circuitBreaker = new CircuitBreaker;
|
|
25615
25681
|
const loader = new SpecialistLoader;
|
|
25616
25682
|
const hooks = new HookEmitter({
|
|
25617
|
-
tracePath:
|
|
25683
|
+
tracePath: join3(process.cwd(), ".specialists", "trace.jsonl")
|
|
25618
25684
|
});
|
|
25619
25685
|
const runner = new SpecialistRunner({ loader, hooks, circuitBreaker });
|
|
25620
25686
|
const registry2 = new JobRegistry;
|
|
@@ -25625,7 +25691,8 @@ class UnitAIServer {
|
|
|
25625
25691
|
createSpecialistStatusTool(loader, circuitBreaker),
|
|
25626
25692
|
createStartSpecialistTool(runner, registry2),
|
|
25627
25693
|
createPollSpecialistTool(registry2),
|
|
25628
|
-
createStopSpecialistTool(registry2)
|
|
25694
|
+
createStopSpecialistTool(registry2),
|
|
25695
|
+
createSpecialistInitTool(loader)
|
|
25629
25696
|
];
|
|
25630
25697
|
this.server = new Server({ name: MCP_CONFIG.SERVER_NAME, version: MCP_CONFIG.VERSION }, { capabilities: MCP_CONFIG.CAPABILITIES });
|
|
25631
25698
|
this.setupHandlers();
|
|
@@ -25639,7 +25706,8 @@ class UnitAIServer {
|
|
|
25639
25706
|
specialist_status: exports_external.object({}),
|
|
25640
25707
|
start_specialist: startSpecialistSchema,
|
|
25641
25708
|
poll_specialist: pollSpecialistSchema,
|
|
25642
|
-
stop_specialist: stopSpecialistSchema
|
|
25709
|
+
stop_specialist: stopSpecialistSchema,
|
|
25710
|
+
specialist_init: specialistInitSchema
|
|
25643
25711
|
};
|
|
25644
25712
|
this.toolSchemas = schemaMap;
|
|
25645
25713
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
@@ -25665,7 +25733,7 @@ class UnitAIServer {
|
|
|
25665
25733
|
const onProgress = (msg) => {
|
|
25666
25734
|
this.server.notification({
|
|
25667
25735
|
method: "notifications/message",
|
|
25668
|
-
params: { level: "info", logger: "
|
|
25736
|
+
params: { level: "info", logger: "specialists", data: msg }
|
|
25669
25737
|
}).catch(() => {});
|
|
25670
25738
|
};
|
|
25671
25739
|
try {
|
|
@@ -25689,7 +25757,7 @@ class UnitAIServer {
|
|
|
25689
25757
|
try {
|
|
25690
25758
|
const transport = new StdioServerTransport;
|
|
25691
25759
|
await this.server.connect(transport);
|
|
25692
|
-
logger.info(`
|
|
25760
|
+
logger.info(`Specialists MCP Server v2 started — ${this.tools.length} tools registered`);
|
|
25693
25761
|
} catch (error2) {
|
|
25694
25762
|
logger.error("Failed to start server", error2);
|
|
25695
25763
|
process.exit(1);
|
|
@@ -25702,8 +25770,8 @@ class UnitAIServer {
|
|
|
25702
25770
|
|
|
25703
25771
|
// src/index.ts
|
|
25704
25772
|
async function main() {
|
|
25705
|
-
logger.info("Starting
|
|
25706
|
-
const server = new
|
|
25773
|
+
logger.info("Starting Specialists MCP Server...");
|
|
25774
|
+
const server = new SpecialistsServer;
|
|
25707
25775
|
await server.start();
|
|
25708
25776
|
}
|
|
25709
25777
|
main().catch((error2) => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jaggerxtrm/specialists",
|
|
3
|
-
"version": "2.0
|
|
4
|
-
"description": "OmniSpecialist
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "OmniSpecialist — 7-tool MCP orchestration layer powered by the Specialist System. Discover and execute .specialist.yaml files across project/user/system scopes via pi.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|