@dougbots/avenor-mcp 0.15.0 → 0.16.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/README.md +75 -24
- package/dist/index.js +259 -25
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ MCP server that makes [avenor](https://github.com/dougbots/avenor) agent runs av
|
|
|
6
6
|
|
|
7
7
|
## When you need this
|
|
8
8
|
|
|
9
|
-
You have `avenor` installed and you want Claude (or another MCP host) to spawn and manage agent runs — kicking off a Codex or Gemini session, checking its status, answering permission prompts, tailing event logs, and resuming completed runs
|
|
9
|
+
You have `avenor` installed and you want Claude (or another MCP host) to spawn and manage agent runs — kicking off a Codex or Gemini session, checking its status, answering permission prompts, tailing event logs, and resuming completed runs.
|
|
10
10
|
|
|
11
11
|
This package is the MCP layer. It depends on `@dougbots/avenor-core` for all the actual wiring (supervisor lifecycle, binary discovery, socket protocol). You don't need to know the internals to use it.
|
|
12
12
|
|
|
@@ -86,9 +86,9 @@ SSE mode restricts incoming requests to `localhost` / `127.0.0.1` / `[::1]` orig
|
|
|
86
86
|
|
|
87
87
|
## Tools
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
This surface registers tool names **without** the `avenor_` prefix — `spawn`, `status`, `result`, `answer_permission`, `message`, `peers`, `events`, `kill`, `shutdown`, and the `workflow_*` set. MCP clients namespace their own servers (Claude Code shows them as `mcp__avenor__spawn`, for example), so the prefix would only repeat itself. The `avenor` binary's own MCP server uses the same names.
|
|
90
90
|
|
|
91
|
-
### `
|
|
91
|
+
### `spawn`
|
|
92
92
|
|
|
93
93
|
Start a new agent run.
|
|
94
94
|
|
|
@@ -125,7 +125,7 @@ Use both `roster_file` and `roster_entry` for roster mode. A roster entry requir
|
|
|
125
125
|
|
|
126
126
|
The roster map is strict and currently supports only `backend`, `agent`, and `model` in each entry. `system` and `thinking` are not roster fields. The `thinking` argument remains run-level and is validated against the effective backend chosen by the direct request or roster entry. A resumed session cannot switch to a different backend.
|
|
127
127
|
|
|
128
|
-
### `
|
|
128
|
+
### `status`
|
|
129
129
|
|
|
130
130
|
Get the status of one run, or list all runs. Use the compact `lifecycle` view for progress and pending permission checks; `full` remains the compatibility default.
|
|
131
131
|
|
|
@@ -137,7 +137,7 @@ Get the status of one run, or list all runs. Use the compact `lifecycle` view fo
|
|
|
137
137
|
|
|
138
138
|
Returns a status object (or array) with `status` being one of `running`, `done`, `failed`, `timeout`, or `killed`. When a run is blocked on a permission request, the response includes a `pending_permission` field with the available options.
|
|
139
139
|
|
|
140
|
-
### `
|
|
140
|
+
### `result`
|
|
141
141
|
|
|
142
142
|
Wait for a run to finish and return its complete final output without transcript or event details.
|
|
143
143
|
|
|
@@ -150,33 +150,50 @@ Wait for a run to finish and return its complete final output without transcript
|
|
|
150
150
|
|
|
151
151
|
Terminal responses include `ready: true` and `output` when the backend exposed one. A pending permission returns immediately. If the result wait itself expires, the response has `ready: false` and `timed_out: true` without changing the run.
|
|
152
152
|
|
|
153
|
-
### `
|
|
153
|
+
### `answer_permission`
|
|
154
154
|
|
|
155
155
|
Respond to a permission prompt that paused a run.
|
|
156
156
|
|
|
157
157
|
| Parameter | Required | Description |
|
|
158
158
|
|---|---|---|
|
|
159
159
|
| `run_id` | yes | Run ID or label |
|
|
160
|
-
| `option_id` | yes | The option to select (from the `pending_permission` shown by `
|
|
160
|
+
| `option_id` | yes | The option to select (from the `pending_permission` shown by `status`) |
|
|
161
161
|
| `request_id` | no | Specific permission request ID. Auto-detected from the pending request if omitted. |
|
|
162
|
+
| `message` | no | Write-in response text, required only when the selected option declares `requiresMessage: true` |
|
|
162
163
|
| `supervisor_id` | no | Supervisor ID for multi-supervisor setups |
|
|
163
164
|
|
|
164
165
|
Returns `{ ok: true }` on success. Throws if no permission request is pending for the given run.
|
|
165
166
|
|
|
166
|
-
### `
|
|
167
|
+
### `message`
|
|
167
168
|
|
|
168
|
-
|
|
169
|
+
Send a message to another run, withdraw a pending ask, resume a completed run, or deliver a control reply. Cross-variant argument rules are enforced by the shared router, so an argument outside the chosen action's field set is an error rather than a silent no-op.
|
|
169
170
|
|
|
170
|
-
|
|
|
171
|
+
| `action` | Required fields | What it does |
|
|
171
172
|
|---|---|---|
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
174
|
-
| `
|
|
175
|
-
| `
|
|
173
|
+
| `tell` | `target`, `message` | Deliver a message without creating an ask edge |
|
|
174
|
+
| `ask` | `target`, `message`, optional `timeout_ms` | Deliver a message and wait for a reply |
|
|
175
|
+
| `reply` | `reply_to`, `message` | Answer an inbound ask. This server receives no inbound delivery, so the result is always `edge-absent` and nothing is sent |
|
|
176
|
+
| `resume` | `target`, `message`, optional `label`, `idempotency_key` | Resume a completed run from its persisted session |
|
|
177
|
+
| `withdraw` | `message_id` | Withdraw a pending ask |
|
|
178
|
+
| `control_reply` | `target`, optional `payload` | Deliver a control reply where the provider supports it |
|
|
179
|
+
|
|
180
|
+
`target` is a run id, a peer label, or `"self"`. `timeout_ms` must be an integer no greater than the shared ask cap. This tool does not take `supervisor_id`; it is bound to the default supervisor. A router tool error is returned as an MCP error result carrying the contract sentence and a `code`.
|
|
181
|
+
|
|
182
|
+
Every successful result is the shared contract object in snake_case (`action`, `outcome`, `text`, and `message_id`, `run_id`, `label`, `reply`, `sentinel_file`, `on_event` when the outcome carries them), returned as the structured content, with the contract sentence as the text content, exactly like the Go MCP host.
|
|
183
|
+
|
|
184
|
+
### `peers`
|
|
185
|
+
|
|
186
|
+
List the visible agent sessions, rename a run, or read a run's last agent message.
|
|
176
187
|
|
|
177
|
-
|
|
188
|
+
| `action` | Fields | What it does |
|
|
189
|
+
|---|---|---|
|
|
190
|
+
| `list` (default) | — | List the visible peers |
|
|
191
|
+
| `rename` | `name`, optional `session_id` | Change a display name, self by default |
|
|
192
|
+
| `last` | `target` | Read the target's last agent message with its stop reason |
|
|
193
|
+
|
|
194
|
+
`last` is read-only. This surface never publishes a turn of its own, so `last` of the server's own session returns the shared empty result. This tool does not take `supervisor_id`; it is bound to the default supervisor. The structured result is `{peers}`, `{renamed}` or `{last}` in snake_case, the same object the Go MCP host returns.
|
|
178
195
|
|
|
179
|
-
### `
|
|
196
|
+
### `events`
|
|
180
197
|
|
|
181
198
|
Fetch the event log for a run. Useful for inspecting what an agent did.
|
|
182
199
|
|
|
@@ -189,7 +206,16 @@ Fetch the event log for a run. Useful for inspecting what an agent did.
|
|
|
189
206
|
|
|
190
207
|
Returns an array of event objects (`{ type, ts, ... }`) in chronological order.
|
|
191
208
|
|
|
192
|
-
### `
|
|
209
|
+
### `kill`
|
|
210
|
+
|
|
211
|
+
Terminate a run without shutting down the supervisor. Other runs and the supervisor itself keep running.
|
|
212
|
+
|
|
213
|
+
| Parameter | Required | Description |
|
|
214
|
+
|---|---|---|
|
|
215
|
+
| `run_id` | yes | Run ID or label of the run to terminate |
|
|
216
|
+
| `supervisor_id` | no | Supervisor ID for multi-supervisor setups |
|
|
217
|
+
|
|
218
|
+
### `shutdown`
|
|
193
219
|
|
|
194
220
|
Stop the avenor supervisor and clean up sentinel files.
|
|
195
221
|
|
|
@@ -200,15 +226,40 @@ Stop the avenor supervisor and clean up sentinel files.
|
|
|
200
226
|
|
|
201
227
|
Returns `{ ok: true, cleaned_up: [...] }`. Use this when you're done with avenor in your session or when the supervisor crashes and needs to be restarted.
|
|
202
228
|
|
|
229
|
+
### `workflow_cancel`
|
|
230
|
+
|
|
231
|
+
Cancel a workflow instance. Both `actor` and `reason` are required; the manager rejects a missing actor before it looks at the reason.
|
|
232
|
+
|
|
233
|
+
| Parameter | Required | Description |
|
|
234
|
+
|---|---|---|
|
|
235
|
+
| `workflow_id` | yes | Workflow ID |
|
|
236
|
+
| `actor` | yes | Who is cancelling |
|
|
237
|
+
| `reason` | yes | Why the workflow is being cancelled |
|
|
238
|
+
| `supervisor_id` | no | Supervisor ID for multi-supervisor setups |
|
|
239
|
+
|
|
240
|
+
### Workflow tools
|
|
241
|
+
|
|
242
|
+
The remaining workflow tools read or drive workflow instances. Their argument lists are in [`docs/control-protocol.md`](../../docs/control-protocol.md).
|
|
243
|
+
|
|
244
|
+
| Tool | Purpose |
|
|
245
|
+
|---|---|
|
|
246
|
+
| `workflow_status` | Lightweight instance status |
|
|
247
|
+
| `workflow_wait` | Wait for a terminal state or a timeout |
|
|
248
|
+
| `workflow_inspect` | Full instance detail |
|
|
249
|
+
| `workflow_events` | The instance's log events |
|
|
250
|
+
| `workflow_complete` | Complete a machine/external handoff activation |
|
|
251
|
+
| `workflow_gate` | Record a gate decision on a parked awaiting_gate activation |
|
|
252
|
+
| `workflow_controller_status` | Controller status, with summary state |
|
|
253
|
+
|
|
203
254
|
## Typical workflow
|
|
204
255
|
|
|
205
|
-
1. **Spawn a run:** `
|
|
206
|
-
2. **Check lifecycle when needed:** `
|
|
207
|
-
3. **If permission requested:** run `
|
|
208
|
-
4. **Retrieve the result:** `
|
|
209
|
-
5. **Inspect history when needed:** `
|
|
210
|
-
6. **
|
|
211
|
-
7. **Clean up:** `
|
|
256
|
+
1. **Spawn a run:** `spawn(agent: "codex", repo_dir: "/path/to/project", prompt: "audit the API routes for auth gaps")`
|
|
257
|
+
2. **Check lifecycle when needed:** `status(run_id: "<id>", view: "lifecycle")` for progress or pending permissions.
|
|
258
|
+
3. **If permission requested:** run `answer_permission` with the offered option.
|
|
259
|
+
4. **Retrieve the result:** `result(run_id: "<id>")` waits and returns the complete final output.
|
|
260
|
+
5. **Inspect history when needed:** `events(run_id: "<id>")` returns raw recent events.
|
|
261
|
+
6. **Iterate:** `message(action: "resume", target: "<id>", message: "now write tests for that")`
|
|
262
|
+
7. **Clean up:** `shutdown()` when you're finished.
|
|
212
263
|
|
|
213
264
|
## Package structure
|
|
214
265
|
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4
4
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
-
import { THINKING_LEVELS, answerPermissionTool, eventsTool,
|
|
7
|
+
import { THINKING_LEVELS, ToolError, answerPermissionTool, createHostMessageProvider, eventsTool, getSupervisorClient, isGenerationMismatchError, killTool, messageTool, peersDetails, peersTool, renderPeerList, resultOutput, resultTool, shutdownTool, spawnTool, statusTool, validateSpawnSelection, workflowCancelTool, workflowCompleteTool, workflowControllerStatusTool, workflowEventsTool, workflowGateTool, workflowInspectTool, workflowStatusTool, workflowWaitTool } from "@dougbots/avenor-core";
|
|
8
8
|
//#region src/spawn-schema.ts
|
|
9
9
|
const spawnInputShape = {
|
|
10
10
|
agent: z.string().optional().describe("Optional agent name; omission uses the supplied model or runtime defaults"),
|
|
@@ -22,6 +22,137 @@ const spawnInputShape = {
|
|
|
22
22
|
supervisor_id: z.string().optional().describe("Supervisor ID for multi-supervisor mode")
|
|
23
23
|
};
|
|
24
24
|
//#endregion
|
|
25
|
+
//#region src/router-schema.ts
|
|
26
|
+
/**
|
|
27
|
+
* The tool input schema for a tool whose admission belongs to the shared
|
|
28
|
+
* router.
|
|
29
|
+
*
|
|
30
|
+
* The MCP SDK parses every `tools/call` argument object against the schema the
|
|
31
|
+
* tool publishes before the handler runs, and answers a failure in its own
|
|
32
|
+
* error shape. The published shape is not what the router admits either: the
|
|
33
|
+
* router refuses a wrong type, a missing field and an unknown action with the
|
|
34
|
+
* contract's own sentence. This schema therefore publishes the supplied JSON
|
|
35
|
+
* Schema (through zod's `toJSONSchema` override) while admitting every argument
|
|
36
|
+
* object unchanged, including the `undefined` a call that omits `arguments`
|
|
37
|
+
* produces and an explicit `null`, so the router answers first wherever the
|
|
38
|
+
* argument object reaches this schema. A `null` delivered over the protocol does
|
|
39
|
+
* not: the SDK's own `CallToolRequestSchema` types `arguments` as a record and
|
|
40
|
+
* refuses it there, one layer above this coercion.
|
|
41
|
+
*
|
|
42
|
+
* Both behaviours ride on zod internals, so construction checks they are there:
|
|
43
|
+
* a zod release that drops them would otherwise leave this surface publishing
|
|
44
|
+
* the loose object and refusing the router's own sentences per call.
|
|
45
|
+
*/
|
|
46
|
+
function routerAdmittedSchema(published) {
|
|
47
|
+
const schema = z.looseObject({});
|
|
48
|
+
const internals = schema._zod;
|
|
49
|
+
if (!internals || typeof internals.run !== "function") throw new Error("routerAdmittedSchema needs the zod internals `_zod.run` and `_zod.toJSONSchema`; this zod version does not expose them, so the router admission pass-through and the published input schema would stop working");
|
|
50
|
+
const run = internals.run.bind(internals);
|
|
51
|
+
internals.run = (payload, ctx) => run(payload.value == null ? {
|
|
52
|
+
...payload,
|
|
53
|
+
value: {}
|
|
54
|
+
} : payload, ctx);
|
|
55
|
+
internals.toJSONSchema = () => structuredClone(published);
|
|
56
|
+
return schema;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
//#region ../../schemas/message.input.flat.json
|
|
60
|
+
var message_input_flat_default = {
|
|
61
|
+
$id: "https://avenor.dev/schemas/message.input.flat.json",
|
|
62
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
63
|
+
additionalProperties: false,
|
|
64
|
+
description: "Flat input schema for the avenor_message tool, compiled from schemas/message.input.json's per-action union so an MCP client publishes one object whose property types survive: an integer stays an integer and an object stays an object. Every action's properties appear here, so a field belonging to another action is not a schema error. Per-action admission (required fields, unknown properties, types and limits) belongs to the router, which answers a mismatch at once with the contract's typed invalid_request and its repair text. Generated by internal/messaging/cmd/genschema; do not edit.",
|
|
65
|
+
properties: {
|
|
66
|
+
"action": {
|
|
67
|
+
"description": "Action to perform: tell — Deliver a message without creating a reply edge; ask — Deliver a message and wait for one reply; reply — Answer an ask this surface received; resume — Continue a finished run in a new run; withdraw — Retract an ask this caller sent; control_reply — Answer a claude-channel control request; registered only on the claude-channel backend.",
|
|
68
|
+
"enum": [
|
|
69
|
+
"tell",
|
|
70
|
+
"ask",
|
|
71
|
+
"reply",
|
|
72
|
+
"resume",
|
|
73
|
+
"withdraw",
|
|
74
|
+
"control_reply"
|
|
75
|
+
],
|
|
76
|
+
"type": "string"
|
|
77
|
+
},
|
|
78
|
+
"idempotency_key": {
|
|
79
|
+
"description": "Caller-chosen key that makes a repeated resume replay the first result.",
|
|
80
|
+
"type": "string"
|
|
81
|
+
},
|
|
82
|
+
"label": {
|
|
83
|
+
"description": "Display name for the new run; defaults to the prior run's label.",
|
|
84
|
+
"type": "string"
|
|
85
|
+
},
|
|
86
|
+
"message": {
|
|
87
|
+
"description": "Text the recipient reads. Question the recipient answers. Reply text the asker reads. Instruction the resumed run receives.",
|
|
88
|
+
"type": "string"
|
|
89
|
+
},
|
|
90
|
+
"message_id": {
|
|
91
|
+
"description": "Message id of the ask to withdraw.",
|
|
92
|
+
"type": "string"
|
|
93
|
+
},
|
|
94
|
+
"payload": {
|
|
95
|
+
"description": "JSON payload the control channel receives; absent sends an empty object.",
|
|
96
|
+
"type": "object"
|
|
97
|
+
},
|
|
98
|
+
"reply_to": {
|
|
99
|
+
"description": "Message id of the inbound ask, as an opaque handle.",
|
|
100
|
+
"type": "string"
|
|
101
|
+
},
|
|
102
|
+
"target": {
|
|
103
|
+
"description": "Recipient: a reserved self alias, a broker/runtime id, a host-surface public run id, or a peer label. Prior run: a reserved self alias, a broker/runtime id, a host-surface public run id, or a peer label. Opaque claude-channel control message id, passed through unchanged.",
|
|
104
|
+
"type": "string"
|
|
105
|
+
},
|
|
106
|
+
"timeout_ms": {
|
|
107
|
+
"description": "Bounded wait in whole milliseconds, at least zero and at most the shared wait cap (600000); absent or zero uses the shared wait cap.",
|
|
108
|
+
"maximum": 6e5,
|
|
109
|
+
"minimum": 0,
|
|
110
|
+
"type": "integer"
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
required: ["action"],
|
|
114
|
+
title: "avenor_message flat input",
|
|
115
|
+
type: "object"
|
|
116
|
+
};
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region ../../schemas/peers.input.flat.json
|
|
119
|
+
var peers_input_flat_default = {
|
|
120
|
+
$id: "https://avenor.dev/schemas/peers.input.flat.json",
|
|
121
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
122
|
+
additionalProperties: false,
|
|
123
|
+
description: "Flat input schema for the avenor_peers tool, compiled from schemas/peers.input.json's per-action union so an MCP client publishes one object whose property types survive. Every action's properties appear here, so a field belonging to another action is not a schema error. Per-action admission belongs to the router, which answers a mismatch at once with the contract's typed error and its repair text. Generated by internal/messaging/cmd/genschema; do not edit.",
|
|
124
|
+
properties: {
|
|
125
|
+
"action": {
|
|
126
|
+
"default": "list",
|
|
127
|
+
"description": "Action to perform: list — List the sessions visible to the caller (the default); rename — Rename a run's display name; last — Read a peer's last agent message, the final text of its latest turn.",
|
|
128
|
+
"enum": [
|
|
129
|
+
"list",
|
|
130
|
+
"rename",
|
|
131
|
+
"last"
|
|
132
|
+
],
|
|
133
|
+
"type": "string"
|
|
134
|
+
},
|
|
135
|
+
"name": {
|
|
136
|
+
"description": "New display name, 1-64 characters with no control or formatting characters (including bidi overrides and zero-width characters, so a label cannot be rendered to look like another peer's). Length is counted in Unicode code points, as JSON Schema maxLength counts it; the rename authority applies the same rune rule as a second check.",
|
|
137
|
+
"maxLength": 64,
|
|
138
|
+
"minLength": 1,
|
|
139
|
+
"pattern": "^[^\\p{Cc}\\p{Cf}\\p{Zl}\\p{Zp}]+$",
|
|
140
|
+
"type": "string"
|
|
141
|
+
},
|
|
142
|
+
"session_id": {
|
|
143
|
+
"description": "Run to rename. Omitted renames the caller's own run for a broker caller and the supervisor session for the owner.",
|
|
144
|
+
"type": "string"
|
|
145
|
+
},
|
|
146
|
+
"target": {
|
|
147
|
+
"description": "Peer: a reserved self alias, a broker/runtime id, a host-surface public run id, or a peer label.",
|
|
148
|
+
"type": "string"
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
required: [],
|
|
152
|
+
title: "avenor_peers flat input",
|
|
153
|
+
type: "object"
|
|
154
|
+
};
|
|
155
|
+
//#endregion
|
|
25
156
|
//#region src/mcp.ts
|
|
26
157
|
const LOCALHOST_ORIGINS = new Set([
|
|
27
158
|
"localhost",
|
|
@@ -62,7 +193,7 @@ const server = new McpServer({
|
|
|
62
193
|
name: "avenor",
|
|
63
194
|
version: "0.1.0"
|
|
64
195
|
});
|
|
65
|
-
server.registerTool("
|
|
196
|
+
server.registerTool("spawn", {
|
|
66
197
|
description: "Spawn a new agent run with an optional canonical thinking level; unsupported backends reject explicit values",
|
|
67
198
|
inputSchema: spawnInputShape
|
|
68
199
|
}, async ({ repo_dir, agent, prompt, prompt_file, label, timeout, model, thinking, backend, roster_file, roster_entry, server_url, supervisor_id }) => {
|
|
@@ -89,7 +220,7 @@ server.registerTool("avenor_spawn", {
|
|
|
89
220
|
supervisorId: supervisor_id
|
|
90
221
|
});
|
|
91
222
|
});
|
|
92
|
-
server.registerTool("
|
|
223
|
+
server.registerTool("status", {
|
|
93
224
|
description: "Get lifecycle status of one or all runs. Use lifecycle view for compact polling. Without run_id, returns an object with runs (array of status objects) and count.",
|
|
94
225
|
inputSchema: {
|
|
95
226
|
run_id: z.string().optional().describe("Run ID or label to query"),
|
|
@@ -103,7 +234,7 @@ server.registerTool("avenor_status", {
|
|
|
103
234
|
view
|
|
104
235
|
});
|
|
105
236
|
});
|
|
106
|
-
server.registerTool("
|
|
237
|
+
server.registerTool("result", {
|
|
107
238
|
description: "Retrieve the complete final output of a run without transcript or event details. Waits by default; set wait=false to retrieve an already-completed run without blocking",
|
|
108
239
|
inputSchema: {
|
|
109
240
|
run_id: z.string().describe("Run ID or label"),
|
|
@@ -119,7 +250,7 @@ server.registerTool("avenor_result", {
|
|
|
119
250
|
timeout
|
|
120
251
|
});
|
|
121
252
|
});
|
|
122
|
-
server.registerTool("
|
|
253
|
+
server.registerTool("answer_permission", {
|
|
123
254
|
description: "Answer a pending permission request",
|
|
124
255
|
inputSchema: {
|
|
125
256
|
run_id: z.string().describe("Run ID or label"),
|
|
@@ -137,23 +268,126 @@ server.registerTool("avenor_answer_permission", {
|
|
|
137
268
|
message
|
|
138
269
|
});
|
|
139
270
|
});
|
|
140
|
-
|
|
141
|
-
|
|
271
|
+
/**
|
|
272
|
+
* The message and peers tools publish the flat schema compiled from the
|
|
273
|
+
* shared contract's per-action union, but admit through the shared router:
|
|
274
|
+
* admission belongs to the router on every surface, so the SDK's own validation
|
|
275
|
+
* must not answer first. The union itself is not published here because a
|
|
276
|
+
* `oneOf` input schema loses an integer's and an object's type in at least one
|
|
277
|
+
* real MCP client.
|
|
278
|
+
*/
|
|
279
|
+
const messageInputSchema = routerAdmittedSchema(message_input_flat_default);
|
|
280
|
+
const peersInputSchema = routerAdmittedSchema(peers_input_flat_default);
|
|
281
|
+
/**
|
|
282
|
+
* The bound host provider: one supervisor and one generation at a time. It is
|
|
283
|
+
* rebuilt when the supervisor's client changes (a shutdown followed by a fresh
|
|
284
|
+
* singleton), so a revoked generation is never reused. This surface never
|
|
285
|
+
* publishes a turn of its own, so the provider has no `recordLastTurn` caller.
|
|
286
|
+
*/
|
|
287
|
+
let hostProvider;
|
|
288
|
+
async function messageProvider() {
|
|
289
|
+
const { client, supervisorId } = await getSupervisorClient(void 0, { owner: true });
|
|
290
|
+
if (hostProvider && hostProvider.client === client) return hostProvider.provider;
|
|
291
|
+
const provider = createHostMessageProvider({
|
|
292
|
+
surface: "mcp-ts",
|
|
293
|
+
supervisorId,
|
|
294
|
+
generation: (await client.messagePreflight()).generation,
|
|
295
|
+
client
|
|
296
|
+
});
|
|
297
|
+
hostProvider = {
|
|
298
|
+
client,
|
|
299
|
+
provider
|
|
300
|
+
};
|
|
301
|
+
return provider;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Run one call through the bound provider, retrying once after a re-preflight
|
|
305
|
+
* when the supervisor says the cached generation is stale. This is the policy
|
|
306
|
+
* the Go host already applies inside a single call, so a restarted supervisor
|
|
307
|
+
* does not fail the call that discovers it.
|
|
308
|
+
*/
|
|
309
|
+
async function withProvider(run) {
|
|
310
|
+
try {
|
|
311
|
+
return await run(await messageProvider());
|
|
312
|
+
} catch (error) {
|
|
313
|
+
if (!isGenerationMismatchError(error)) throw error;
|
|
314
|
+
hostProvider = void 0;
|
|
315
|
+
return run(await messageProvider());
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/** A router tool error is a failure result on every surface; anything else is the transport's to report. */
|
|
319
|
+
function toolFailure(error) {
|
|
320
|
+
if (!(error instanceof ToolError)) throw error;
|
|
321
|
+
const text = error.message;
|
|
322
|
+
return {
|
|
323
|
+
content: [{
|
|
324
|
+
type: "text",
|
|
325
|
+
text
|
|
326
|
+
}],
|
|
327
|
+
structuredContent: {
|
|
328
|
+
code: error.code,
|
|
329
|
+
text
|
|
330
|
+
},
|
|
331
|
+
isError: true
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
async function messageCall(args) {
|
|
335
|
+
try {
|
|
336
|
+
const result = await withProvider((provider) => messageTool(args, provider));
|
|
337
|
+
const output = resultOutput(result);
|
|
338
|
+
return {
|
|
339
|
+
content: [{
|
|
340
|
+
type: "text",
|
|
341
|
+
text: result.text ?? ""
|
|
342
|
+
}],
|
|
343
|
+
structuredContent: output
|
|
344
|
+
};
|
|
345
|
+
} catch (error) {
|
|
346
|
+
return toolFailure(error);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function peersText(result) {
|
|
350
|
+
return result.action === "list" ? [result.text, renderPeerList(result.peers)].filter((part) => part !== "").join("\n\n") : result.text;
|
|
351
|
+
}
|
|
352
|
+
async function peersCall(args) {
|
|
353
|
+
try {
|
|
354
|
+
const result = await withProvider((provider) => peersTool(args, provider));
|
|
355
|
+
return {
|
|
356
|
+
content: [{
|
|
357
|
+
type: "text",
|
|
358
|
+
text: peersText(result)
|
|
359
|
+
}],
|
|
360
|
+
structuredContent: peersDetails(result)
|
|
361
|
+
};
|
|
362
|
+
} catch (error) {
|
|
363
|
+
return toolFailure(error);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
server.registerTool("message", {
|
|
367
|
+
description: "Send a message to another agent run, withdraw a pending ask, resume a completed run, or deliver a control reply. Every action is explicit and the result states whether a failed ask can still be withdrawn. `reply` always reports edge-absent here: this server receives no inbound asks, so nothing is sent. This tool is bound to the default supervisor and takes no `supervisor_id`.",
|
|
368
|
+
inputSchema: messageInputSchema
|
|
369
|
+
}, async (args) => messageCall(args));
|
|
370
|
+
server.registerTool("peers", {
|
|
371
|
+
description: "List the visible agent sessions, rename a run, or read a run's last agent message. `last` returns the target's final text with its stop reason, or an empty result when the target has no completed turn yet. This tool is bound to the default supervisor and takes no `supervisor_id`.",
|
|
372
|
+
inputSchema: peersInputSchema
|
|
373
|
+
}, async (args) => peersCall(args));
|
|
374
|
+
server.registerTool("workflow_cancel", {
|
|
375
|
+
description: "Cancel a workflow instance. The actor and reason are required.",
|
|
142
376
|
inputSchema: {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
377
|
+
workflow_id: z.string().describe("Workflow ID"),
|
|
378
|
+
actor: z.string().describe("Who is cancelling"),
|
|
379
|
+
reason: z.string().describe("Why the workflow is being cancelled"),
|
|
146
380
|
supervisor_id: z.string().optional().describe("Supervisor ID for multi-supervisor mode")
|
|
147
381
|
}
|
|
148
|
-
}, async ({
|
|
149
|
-
return
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
382
|
+
}, async ({ workflow_id, actor, reason, supervisor_id }) => {
|
|
383
|
+
return workflowCancelTool({
|
|
384
|
+
workflow_id,
|
|
385
|
+
actor,
|
|
386
|
+
reason,
|
|
153
387
|
supervisorId: supervisor_id
|
|
154
388
|
});
|
|
155
389
|
});
|
|
156
|
-
server.registerTool("
|
|
390
|
+
server.registerTool("events", {
|
|
157
391
|
description: "Get event log for a run",
|
|
158
392
|
inputSchema: {
|
|
159
393
|
run_id: z.string().describe("Run ID or label"),
|
|
@@ -169,7 +403,7 @@ server.registerTool("avenor_events", {
|
|
|
169
403
|
supervisorId: supervisor_id
|
|
170
404
|
});
|
|
171
405
|
});
|
|
172
|
-
server.registerTool("
|
|
406
|
+
server.registerTool("kill", {
|
|
173
407
|
description: "Terminate a run without shutting down the supervisor. Other runs and the supervisor itself keep running.",
|
|
174
408
|
inputSchema: {
|
|
175
409
|
run_id: z.string().min(1).describe("Run ID or label"),
|
|
@@ -181,7 +415,7 @@ server.registerTool("avenor_kill", {
|
|
|
181
415
|
supervisorId: supervisor_id
|
|
182
416
|
});
|
|
183
417
|
});
|
|
184
|
-
server.registerTool("
|
|
418
|
+
server.registerTool("shutdown", {
|
|
185
419
|
description: "Shut down the supervisor",
|
|
186
420
|
inputSchema: {
|
|
187
421
|
supervisor_id: z.string().optional().describe("Supervisor ID for multi-supervisor mode"),
|
|
@@ -193,7 +427,7 @@ server.registerTool("avenor_shutdown", {
|
|
|
193
427
|
force
|
|
194
428
|
});
|
|
195
429
|
});
|
|
196
|
-
server.registerTool("
|
|
430
|
+
server.registerTool("workflow_status", {
|
|
197
431
|
description: "Get lightweight status for a workflow instance",
|
|
198
432
|
inputSchema: {
|
|
199
433
|
workflow_id: z.string().describe("Workflow ID"),
|
|
@@ -205,7 +439,7 @@ server.registerTool("avenor_workflow_status", {
|
|
|
205
439
|
supervisorId: supervisor_id
|
|
206
440
|
});
|
|
207
441
|
});
|
|
208
|
-
server.registerTool("
|
|
442
|
+
server.registerTool("workflow_wait", {
|
|
209
443
|
description: "Wait for a workflow to reach a terminal state or until timeout",
|
|
210
444
|
inputSchema: {
|
|
211
445
|
workflow_id: z.string().describe("Workflow ID"),
|
|
@@ -219,7 +453,7 @@ server.registerTool("avenor_workflow_wait", {
|
|
|
219
453
|
supervisorId: supervisor_id
|
|
220
454
|
});
|
|
221
455
|
});
|
|
222
|
-
server.registerTool("
|
|
456
|
+
server.registerTool("workflow_inspect", {
|
|
223
457
|
description: "Return the full instance detail for a workflow",
|
|
224
458
|
inputSchema: {
|
|
225
459
|
workflow_id: z.string().describe("Workflow ID"),
|
|
@@ -231,7 +465,7 @@ server.registerTool("avenor_workflow_inspect", {
|
|
|
231
465
|
supervisorId: supervisor_id
|
|
232
466
|
});
|
|
233
467
|
});
|
|
234
|
-
server.registerTool("
|
|
468
|
+
server.registerTool("workflow_events", {
|
|
235
469
|
description: "Read log events from a workflow instance's event log",
|
|
236
470
|
inputSchema: {
|
|
237
471
|
workflow_id: z.string().describe("Workflow ID"),
|
|
@@ -247,7 +481,7 @@ server.registerTool("avenor_workflow_events", {
|
|
|
247
481
|
supervisorId: supervisor_id
|
|
248
482
|
});
|
|
249
483
|
});
|
|
250
|
-
server.registerTool("
|
|
484
|
+
server.registerTool("workflow_complete", {
|
|
251
485
|
description: "Atomically complete a machine/external handoff activation",
|
|
252
486
|
inputSchema: {
|
|
253
487
|
workflow_id: z.string().describe("Workflow ID"),
|
|
@@ -268,7 +502,7 @@ server.registerTool("avenor_workflow_complete", {
|
|
|
268
502
|
supervisorId: supervisor_id
|
|
269
503
|
});
|
|
270
504
|
});
|
|
271
|
-
server.registerTool("
|
|
505
|
+
server.registerTool("workflow_gate", {
|
|
272
506
|
description: "Record a gate decision on a parked awaiting_gate activation",
|
|
273
507
|
inputSchema: {
|
|
274
508
|
workflow_id: z.string().describe("Workflow ID"),
|
|
@@ -300,7 +534,7 @@ server.registerTool("avenor_workflow_gate", {
|
|
|
300
534
|
supervisorId: supervisor_id
|
|
301
535
|
});
|
|
302
536
|
});
|
|
303
|
-
server.registerTool("
|
|
537
|
+
server.registerTool("workflow_controller_status", {
|
|
304
538
|
description: "Show workflow controller status. With controller_id, returns that controller's full status; without it, lists all controllers with summary state. Create, enable, and disable are CLI-only (avenor workflow controller ...).",
|
|
305
539
|
inputSchema: {
|
|
306
540
|
controller_id: z.string().optional().describe("Controller ID; omit to list all controllers"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dougbots/avenor-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"dev": "tsdown --watch"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@dougbots/avenor-core": "^0.
|
|
33
|
+
"@dougbots/avenor-core": "^0.16.1",
|
|
34
34
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
35
35
|
"hono": "^4.12.19",
|
|
36
36
|
"zod": "^4.4.3"
|