@dougbots/avenor-mcp 0.17.0 → 0.18.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/dist/index.d.ts +15 -1
- package/dist/index.js +77 -46
- package/package.json +3 -2
package/dist/index.d.ts
CHANGED
|
@@ -6,5 +6,19 @@ declare function isAllowedOrigin(originHeader: string | null | undefined): boole
|
|
|
6
6
|
declare function isAllowedHost(hostHeader: string | null | undefined, port: number): boolean;
|
|
7
7
|
declare function getMcpAuthToken(): string;
|
|
8
8
|
declare const server: McpServer;
|
|
9
|
+
interface HttpServerOptions {
|
|
10
|
+
authToken: string;
|
|
11
|
+
/** 0 binds an ephemeral port; the bound port is reported back. */
|
|
12
|
+
port?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Serve the MCP endpoint over HTTP with the transport's web-standard
|
|
16
|
+
* implementation: Hono already hands out a Web `Request`, and the request
|
|
17
|
+
* checks below answer before the transport sees a message.
|
|
18
|
+
*/
|
|
19
|
+
declare function startHttpServer(options: HttpServerOptions): Promise<{
|
|
20
|
+
port: number;
|
|
21
|
+
stop: () => Promise<void>;
|
|
22
|
+
}>;
|
|
9
23
|
//#endregion
|
|
10
|
-
export { getMcpAuthToken, isAllowedHost, isAllowedOrigin, parseBearerToken, server };
|
|
24
|
+
export { HttpServerOptions, getMcpAuthToken, isAllowedHost, isAllowedOrigin, parseBearerToken, server, startHttpServer };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import {
|
|
4
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import { z } from "zod";
|
|
7
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";
|
|
@@ -64,7 +64,7 @@ var message_input_flat_default = {
|
|
|
64
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
65
|
properties: {
|
|
66
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 —
|
|
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 — Retained in the contract for the retired claude-channel backend's reply path; no surface can deliver one.",
|
|
68
68
|
"enum": [
|
|
69
69
|
"tell",
|
|
70
70
|
"ask",
|
|
@@ -100,7 +100,7 @@ var message_input_flat_default = {
|
|
|
100
100
|
"type": "string"
|
|
101
101
|
},
|
|
102
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
|
|
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 control message id, passed through unchanged.",
|
|
104
104
|
"type": "string"
|
|
105
105
|
},
|
|
106
106
|
"timeout_ms": {
|
|
@@ -204,7 +204,7 @@ server.registerTool("spawn", {
|
|
|
204
204
|
roster_file,
|
|
205
205
|
roster_entry
|
|
206
206
|
});
|
|
207
|
-
return spawnTool({
|
|
207
|
+
return passthroughResult(spawnTool({
|
|
208
208
|
agent,
|
|
209
209
|
dir: repo_dir,
|
|
210
210
|
prompt,
|
|
@@ -218,7 +218,7 @@ server.registerTool("spawn", {
|
|
|
218
218
|
rosterEntry: roster_entry,
|
|
219
219
|
serverUrl: server_url,
|
|
220
220
|
supervisorId: supervisor_id
|
|
221
|
-
});
|
|
221
|
+
}));
|
|
222
222
|
});
|
|
223
223
|
server.registerTool("status", {
|
|
224
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.",
|
|
@@ -228,11 +228,11 @@ server.registerTool("status", {
|
|
|
228
228
|
supervisor_id: z.string().optional().describe("Supervisor ID for multi-supervisor mode")
|
|
229
229
|
}
|
|
230
230
|
}, async ({ run_id, view, supervisor_id }) => {
|
|
231
|
-
return statusTool({
|
|
231
|
+
return passthroughResult(statusTool({
|
|
232
232
|
runId: run_id,
|
|
233
233
|
supervisorId: supervisor_id,
|
|
234
234
|
view
|
|
235
|
-
});
|
|
235
|
+
}));
|
|
236
236
|
});
|
|
237
237
|
server.registerTool("result", {
|
|
238
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",
|
|
@@ -243,12 +243,12 @@ server.registerTool("result", {
|
|
|
243
243
|
supervisor_id: z.string().optional().describe("Supervisor ID for multi-supervisor mode")
|
|
244
244
|
}
|
|
245
245
|
}, async ({ run_id, wait, timeout, supervisor_id }) => {
|
|
246
|
-
return resultTool({
|
|
246
|
+
return passthroughResult(resultTool({
|
|
247
247
|
runId: run_id,
|
|
248
248
|
supervisorId: supervisor_id,
|
|
249
249
|
wait,
|
|
250
250
|
timeout
|
|
251
|
-
});
|
|
251
|
+
}));
|
|
252
252
|
});
|
|
253
253
|
server.registerTool("answer_permission", {
|
|
254
254
|
description: "Answer a pending permission request",
|
|
@@ -260,13 +260,13 @@ server.registerTool("answer_permission", {
|
|
|
260
260
|
message: z.string().optional().describe("Optional write-in response text. Required only when the selected option declares requiresMessage: true.")
|
|
261
261
|
}
|
|
262
262
|
}, async ({ run_id, option_id, request_id, supervisor_id, message }) => {
|
|
263
|
-
return answerPermissionTool({
|
|
263
|
+
return passthroughResult(answerPermissionTool({
|
|
264
264
|
runId: run_id,
|
|
265
265
|
optionId: option_id,
|
|
266
266
|
requestId: request_id,
|
|
267
267
|
supervisorId: supervisor_id,
|
|
268
268
|
message
|
|
269
|
-
});
|
|
269
|
+
}));
|
|
270
270
|
});
|
|
271
271
|
/**
|
|
272
272
|
* The message and peers tools publish the flat schema compiled from the
|
|
@@ -315,6 +315,19 @@ async function withProvider(run) {
|
|
|
315
315
|
return run(await messageProvider());
|
|
316
316
|
}
|
|
317
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* The pass-through tools answer with the core tool's result object, which the
|
|
320
|
+
* MCP result schema carries as free-form fields beside `content`. The schema
|
|
321
|
+
* defaults `content` to an empty list, so sending it explicitly is what a
|
|
322
|
+
* client already ends up with, and it keeps the handler's declared result
|
|
323
|
+
* type honest without hiding the result fields.
|
|
324
|
+
*/
|
|
325
|
+
async function passthroughResult(result) {
|
|
326
|
+
return {
|
|
327
|
+
...await result,
|
|
328
|
+
content: []
|
|
329
|
+
};
|
|
330
|
+
}
|
|
318
331
|
/** A router tool error is a failure result on every surface; anything else is the transport's to report. */
|
|
319
332
|
function toolFailure(error) {
|
|
320
333
|
if (!(error instanceof ToolError)) throw error;
|
|
@@ -380,12 +393,12 @@ server.registerTool("workflow_cancel", {
|
|
|
380
393
|
supervisor_id: z.string().optional().describe("Supervisor ID for multi-supervisor mode")
|
|
381
394
|
}
|
|
382
395
|
}, async ({ workflow_id, actor, reason, supervisor_id }) => {
|
|
383
|
-
return workflowCancelTool({
|
|
396
|
+
return passthroughResult(workflowCancelTool({
|
|
384
397
|
workflow_id,
|
|
385
398
|
actor,
|
|
386
399
|
reason,
|
|
387
400
|
supervisorId: supervisor_id
|
|
388
|
-
});
|
|
401
|
+
}));
|
|
389
402
|
});
|
|
390
403
|
server.registerTool("events", {
|
|
391
404
|
description: "Get event log for a run",
|
|
@@ -396,12 +409,12 @@ server.registerTool("events", {
|
|
|
396
409
|
supervisor_id: z.string().optional().describe("Supervisor ID for multi-supervisor mode")
|
|
397
410
|
}
|
|
398
411
|
}, async ({ run_id, types, limit, supervisor_id }) => {
|
|
399
|
-
return eventsTool({
|
|
412
|
+
return passthroughResult(eventsTool({
|
|
400
413
|
runId: run_id,
|
|
401
414
|
types,
|
|
402
415
|
limit,
|
|
403
416
|
supervisorId: supervisor_id
|
|
404
|
-
});
|
|
417
|
+
}));
|
|
405
418
|
});
|
|
406
419
|
server.registerTool("kill", {
|
|
407
420
|
description: "Terminate a run without shutting down the supervisor. Other runs and the supervisor itself keep running.",
|
|
@@ -410,10 +423,10 @@ server.registerTool("kill", {
|
|
|
410
423
|
supervisor_id: z.string().optional().describe("Supervisor ID for multi-supervisor mode")
|
|
411
424
|
}
|
|
412
425
|
}, async ({ run_id, supervisor_id }) => {
|
|
413
|
-
return killTool({
|
|
426
|
+
return passthroughResult(killTool({
|
|
414
427
|
runId: run_id,
|
|
415
428
|
supervisorId: supervisor_id
|
|
416
|
-
});
|
|
429
|
+
}));
|
|
417
430
|
});
|
|
418
431
|
server.registerTool("shutdown", {
|
|
419
432
|
description: "Shut down the supervisor",
|
|
@@ -422,10 +435,10 @@ server.registerTool("shutdown", {
|
|
|
422
435
|
force: z.boolean().optional().describe("Force shutdown instead of graceful")
|
|
423
436
|
}
|
|
424
437
|
}, async ({ supervisor_id, force }) => {
|
|
425
|
-
return shutdownTool({
|
|
438
|
+
return passthroughResult(shutdownTool({
|
|
426
439
|
supervisorId: supervisor_id,
|
|
427
440
|
force
|
|
428
|
-
});
|
|
441
|
+
}));
|
|
429
442
|
});
|
|
430
443
|
server.registerTool("workflow_status", {
|
|
431
444
|
description: "Get lightweight status for a workflow instance",
|
|
@@ -434,10 +447,10 @@ server.registerTool("workflow_status", {
|
|
|
434
447
|
supervisor_id: z.string().optional().describe("Supervisor ID for multi-supervisor mode")
|
|
435
448
|
}
|
|
436
449
|
}, async ({ workflow_id, supervisor_id }) => {
|
|
437
|
-
return workflowStatusTool({
|
|
450
|
+
return passthroughResult(workflowStatusTool({
|
|
438
451
|
workflowId: workflow_id,
|
|
439
452
|
supervisorId: supervisor_id
|
|
440
|
-
});
|
|
453
|
+
}));
|
|
441
454
|
});
|
|
442
455
|
server.registerTool("workflow_wait", {
|
|
443
456
|
description: "Wait for a workflow to reach a terminal state or until timeout",
|
|
@@ -447,11 +460,11 @@ server.registerTool("workflow_wait", {
|
|
|
447
460
|
supervisor_id: z.string().optional().describe("Supervisor ID for multi-supervisor mode")
|
|
448
461
|
}
|
|
449
462
|
}, async ({ workflow_id, timeout, supervisor_id }) => {
|
|
450
|
-
return workflowWaitTool({
|
|
463
|
+
return passthroughResult(workflowWaitTool({
|
|
451
464
|
workflowId: workflow_id,
|
|
452
465
|
timeout,
|
|
453
466
|
supervisorId: supervisor_id
|
|
454
|
-
});
|
|
467
|
+
}));
|
|
455
468
|
});
|
|
456
469
|
server.registerTool("workflow_inspect", {
|
|
457
470
|
description: "Return the full instance detail for a workflow",
|
|
@@ -460,10 +473,10 @@ server.registerTool("workflow_inspect", {
|
|
|
460
473
|
supervisor_id: z.string().optional().describe("Supervisor ID for multi-supervisor mode")
|
|
461
474
|
}
|
|
462
475
|
}, async ({ workflow_id, supervisor_id }) => {
|
|
463
|
-
return workflowInspectTool({
|
|
476
|
+
return passthroughResult(workflowInspectTool({
|
|
464
477
|
workflowId: workflow_id,
|
|
465
478
|
supervisorId: supervisor_id
|
|
466
|
-
});
|
|
479
|
+
}));
|
|
467
480
|
});
|
|
468
481
|
server.registerTool("workflow_events", {
|
|
469
482
|
description: "Read log events from a workflow instance's event log",
|
|
@@ -474,12 +487,12 @@ server.registerTool("workflow_events", {
|
|
|
474
487
|
supervisor_id: z.string().optional().describe("Supervisor ID for multi-supervisor mode")
|
|
475
488
|
}
|
|
476
489
|
}, async ({ workflow_id, after_seq, limit, supervisor_id }) => {
|
|
477
|
-
return workflowEventsTool({
|
|
490
|
+
return passthroughResult(workflowEventsTool({
|
|
478
491
|
workflowId: workflow_id,
|
|
479
492
|
afterSeq: after_seq,
|
|
480
493
|
limit,
|
|
481
494
|
supervisorId: supervisor_id
|
|
482
|
-
});
|
|
495
|
+
}));
|
|
483
496
|
});
|
|
484
497
|
server.registerTool("workflow_complete", {
|
|
485
498
|
description: "Atomically complete a machine/external handoff activation",
|
|
@@ -497,10 +510,10 @@ server.registerTool("workflow_complete", {
|
|
|
497
510
|
}
|
|
498
511
|
}, async (args) => {
|
|
499
512
|
const { supervisor_id, ...rest } = args;
|
|
500
|
-
return workflowCompleteTool({
|
|
513
|
+
return passthroughResult(workflowCompleteTool({
|
|
501
514
|
...rest,
|
|
502
515
|
supervisorId: supervisor_id
|
|
503
|
-
});
|
|
516
|
+
}));
|
|
504
517
|
});
|
|
505
518
|
server.registerTool("workflow_gate", {
|
|
506
519
|
description: "Record a gate decision on a parked awaiting_gate activation",
|
|
@@ -529,10 +542,10 @@ server.registerTool("workflow_gate", {
|
|
|
529
542
|
}
|
|
530
543
|
}, async (args) => {
|
|
531
544
|
const { supervisor_id, ...rest } = args;
|
|
532
|
-
return workflowGateTool({
|
|
545
|
+
return passthroughResult(workflowGateTool({
|
|
533
546
|
...rest,
|
|
534
547
|
supervisorId: supervisor_id
|
|
535
|
-
});
|
|
548
|
+
}));
|
|
536
549
|
});
|
|
537
550
|
server.registerTool("workflow_controller_status", {
|
|
538
551
|
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 ...).",
|
|
@@ -541,36 +554,54 @@ server.registerTool("workflow_controller_status", {
|
|
|
541
554
|
supervisor_id: z.string().optional().describe("Supervisor ID for multi-supervisor mode")
|
|
542
555
|
}
|
|
543
556
|
}, async ({ controller_id, supervisor_id }) => {
|
|
544
|
-
return workflowControllerStatusTool({
|
|
557
|
+
return passthroughResult(workflowControllerStatusTool({
|
|
545
558
|
controllerId: controller_id,
|
|
546
559
|
supervisorId: supervisor_id
|
|
547
|
-
});
|
|
560
|
+
}));
|
|
548
561
|
});
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
562
|
+
/**
|
|
563
|
+
* Serve the MCP endpoint over HTTP with the transport's web-standard
|
|
564
|
+
* implementation: Hono already hands out a Web `Request`, and the request
|
|
565
|
+
* checks below answer before the transport sees a message.
|
|
566
|
+
*/
|
|
567
|
+
async function startHttpServer(options) {
|
|
568
|
+
const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: () => crypto.randomUUID() });
|
|
569
|
+
let boundPort = options.port ?? 0;
|
|
555
570
|
const app = new Hono();
|
|
556
|
-
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => crypto.randomUUID() });
|
|
557
571
|
app.all("/mcp", (c) => {
|
|
558
572
|
const request = c.req.raw;
|
|
559
573
|
const originHeader = request.headers.get("origin");
|
|
560
574
|
const hostHeader = request.headers.get("host");
|
|
561
575
|
const token = parseBearerToken(request.headers.get("authorization"));
|
|
562
576
|
if (!isAllowedOrigin(originHeader)) return c.text("Forbidden origin", 403);
|
|
563
|
-
if (!isAllowedHost(hostHeader,
|
|
564
|
-
if (token !== authToken) return c.text("Unauthorized", 401, { "WWW-Authenticate": "Bearer" });
|
|
565
|
-
return transport.handleRequest(
|
|
577
|
+
if (!isAllowedHost(hostHeader, boundPort)) return c.text("Forbidden host", 403);
|
|
578
|
+
if (token !== options.authToken) return c.text("Unauthorized", 401, { "WWW-Authenticate": "Bearer" });
|
|
579
|
+
return transport.handleRequest(request);
|
|
566
580
|
});
|
|
567
581
|
await server.connect(transport);
|
|
568
|
-
Bun.serve({
|
|
582
|
+
const http = Bun.serve({
|
|
569
583
|
fetch: app.fetch,
|
|
570
|
-
port,
|
|
584
|
+
port: boundPort,
|
|
571
585
|
hostname: "127.0.0.1"
|
|
572
586
|
});
|
|
587
|
+
boundPort = http.port ?? boundPort;
|
|
588
|
+
return {
|
|
589
|
+
port: boundPort,
|
|
590
|
+
stop: async () => {
|
|
591
|
+
await server.close();
|
|
592
|
+
await http.stop(true);
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
if (import.meta.main) if (process.env.MCP_TRANSPORT !== "sse") {
|
|
597
|
+
const transport = new StdioServerTransport();
|
|
598
|
+
await server.connect(transport);
|
|
599
|
+
} else {
|
|
600
|
+
const { port } = await startHttpServer({
|
|
601
|
+
authToken: getMcpAuthToken(),
|
|
602
|
+
port: Number(process.env.MCP_PORT ?? 3748)
|
|
603
|
+
});
|
|
573
604
|
console.error(`avenor MCP server listening on :${port}`);
|
|
574
605
|
}
|
|
575
606
|
//#endregion
|
|
576
|
-
export { getMcpAuthToken, isAllowedHost, isAllowedOrigin, parseBearerToken, server };
|
|
607
|
+
export { getMcpAuthToken, isAllowedHost, isAllowedOrigin, parseBearerToken, server, startHttpServer };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dougbots/avenor-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,11 +26,12 @@
|
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "tsdown",
|
|
29
|
+
"typecheck": "tsc --noEmit",
|
|
29
30
|
"test": "bun test",
|
|
30
31
|
"dev": "tsdown --watch"
|
|
31
32
|
},
|
|
32
33
|
"dependencies": {
|
|
33
|
-
"@dougbots/avenor-core": "^0.
|
|
34
|
+
"@dougbots/avenor-core": "^0.18.0",
|
|
34
35
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
35
36
|
"hono": "^4.12.19",
|
|
36
37
|
"zod": "^4.4.3"
|