@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.
Files changed (3) hide show
  1. package/README.md +75 -24
  2. package/dist/index.js +259 -25
  3. 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 with a follow-up.
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
- All seven tools register under the `avenor_` prefix to avoid collisions with other MCP servers.
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
- ### `avenor_spawn`
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
- ### `avenor_status`
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
- ### `avenor_result`
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
- ### `avenor_answer_permission`
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 `avenor_status`) |
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
- ### `avenor_follow_up`
167
+ ### `message`
167
168
 
168
- Resume a completed run with a new message. The agent picks up from the same session, so it retains context from the original run.
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
- | Parameter | Required | Description |
171
+ | `action` | Required fields | What it does |
171
172
  |---|---|---|
172
- | `run_id` | yes | Run ID or label of the *completed* run to resume |
173
- | `message` | yes | Follow-up prompt |
174
- | `label` | no | Label for the follow-up run (defaults to `<original-label>-followup`) |
175
- | `supervisor_id` | no | Supervisor ID for multi-supervisor setups |
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
- Returns `{ run_id, label }` for the new follow-up run. Throws if the original run has no persisted session.
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
- ### `avenor_events`
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
- ### `avenor_shutdown`
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:** `avenor_spawn(agent: "codex", repo_dir: "/path/to/project", prompt: "audit the API routes for auth gaps")`
206
- 2. **Check lifecycle when needed:** `avenor_status(run_id: "<id>", view: "lifecycle")` for progress or pending permissions.
207
- 3. **If permission requested:** run `avenor_answer_permission` with the offered option.
208
- 4. **Retrieve the result:** `avenor_result(run_id: "<id>")` waits and returns the complete final output.
209
- 5. **Inspect history when needed:** `avenor_events(run_id: "<id>")` returns raw recent events.
210
- 6. **Optional follow-up:** `avenor_follow_up(run_id: "<id>", message: "now write tests for that")`
211
- 7. **Clean up:** `avenor_shutdown()` when you're finished.
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, followUpTool, killTool, resultTool, shutdownTool, spawnTool, statusTool, validateSpawnSelection, workflowCompleteTool, workflowControllerStatusTool, workflowEventsTool, workflowGateTool, workflowInspectTool, workflowStatusTool, workflowWaitTool } from "@dougbots/avenor-core";
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("avenor_spawn", {
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("avenor_status", {
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("avenor_result", {
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("avenor_answer_permission", {
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
- server.registerTool("avenor_follow_up", {
141
- description: "Resume a completed run with a follow-up message",
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
- run_id: z.string().describe("Run ID or label to follow up on"),
144
- message: z.string().describe("Follow-up message"),
145
- label: z.string().optional().describe("Label for the follow-up run"),
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 ({ run_id, message, label, supervisor_id }) => {
149
- return followUpTool({
150
- runId: run_id,
151
- message,
152
- label,
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("avenor_events", {
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("avenor_kill", {
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("avenor_shutdown", {
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("avenor_workflow_status", {
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("avenor_workflow_wait", {
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("avenor_workflow_inspect", {
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("avenor_workflow_events", {
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("avenor_workflow_complete", {
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("avenor_workflow_gate", {
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("avenor_workflow_controller_status", {
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.15.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.15.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"