@botbotgo/agent-harness 0.0.307 → 0.0.309

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/cli.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
2
3
  import { EventEmitter } from "node:events";
3
- import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
4
+ import { existsSync, openSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
5
+ import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
4
6
  import { createInterface as createReadlineInterface } from "node:readline";
5
7
  import { Writable } from "node:stream";
6
8
  import path from "node:path";
@@ -14,7 +16,7 @@ import { serveA2aOverHttp } from "./protocol/a2a/http.js";
14
16
  import { serveAgUiOverHttp } from "./protocol/ag-ui/http.js";
15
17
  import { serveAcpOverHttp } from "./protocol/acp/http.js";
16
18
  import { serveAcpOverStdio } from "./protocol/acp/stdio.js";
17
- import { serveRuntimeMcpOverStdio } from "./mcp.js";
19
+ import { serveRuntimeMcpOverStdio, serveRuntimeMcpOverStreamableHttp } from "./mcp.js";
18
20
  import { interpolateEnvPlaceholders } from "./workspace/yaml-object-reader.js";
19
21
  function renderUsage() {
20
22
  return `Usage:
@@ -23,7 +25,11 @@ function renderUsage() {
23
25
  agent-harness [-w <path>] [prompt]
24
26
  botbotgo [-w <path>] [prompt]
25
27
  agent-harness acp serve [--workspace <path>] [--transport stdio|http] [--host <hostname>] [--port <port>]
28
+ agent-harness acp start [--workspace <path>] [--host <hostname>] [--port <port>]
29
+ agent-harness acp stop [--workspace <path>]
26
30
  agent-harness a2a serve [--workspace <path>] [--host <hostname>] [--port <port>]
31
+ agent-harness a2a start [--workspace <path>] [--host <hostname>] [--port <port>]
32
+ agent-harness a2a stop [--workspace <path>]
27
33
  agent-harness ag-ui serve [--workspace <path>] [--host <hostname>] [--port <port>]
28
34
  agent-harness runtime overview [--workspace <path>] [--limit <n>] [--json]
29
35
  agent-harness runtime health [--workspace <path>] [--json]
@@ -34,7 +40,12 @@ function renderUsage() {
34
40
  agent-harness runtime scheduled-run --workspace <path> --schedule <scheduleId>
35
41
  agent-harness runtime export request --workspace <path> --session <sessionId> --request <requestId> [--artifacts] [--artifact-contents] [--health] [--json]
36
42
  agent-harness runtime export session --workspace <path> --session <sessionId> [--artifacts] [--artifact-contents] [--health] [--json]
37
- agent-harness runtime-mcp serve [--workspace <path>]
43
+ agent-harness runtime-mcp serve [--workspace <path>] [--transport stdio|streamable-http] [--host <hostname>] [--port <port>]
44
+ agent-harness runtime-mcp start [--workspace <path>] [--host <hostname>] [--port <port>]
45
+ agent-harness runtime-mcp stop [--workspace <path>]
46
+ agent-harness mcp serve [--workspace <path>] [--transport stdio|streamable-http] [--host <hostname>] [--port <port>]
47
+ agent-harness mcp start [--workspace <path>] [--host <hostname>] [--port <port>]
48
+ agent-harness mcp stop [--workspace <path>]
38
49
 
39
50
  Run botbotgo or agent-harness from any folder.
40
51
  If ./config/ is absent, the runtime falls back to the bundled system defaults and bundled resources.
@@ -80,9 +91,9 @@ function parseInitOptions(args) {
80
91
  }
81
92
  return { options };
82
93
  }
83
- function parseAcpServeOptions(args) {
94
+ function parseAcpServeOptions(args, defaultTransport = "stdio") {
84
95
  let workspaceRoot;
85
- let transport = "stdio";
96
+ let transport = defaultTransport;
86
97
  let hostname;
87
98
  let port;
88
99
  for (let index = 0; index < args.length; index += 1) {
@@ -134,6 +145,23 @@ function parseAcpServeOptions(args) {
134
145
  }
135
146
  return { workspaceRoot, transport, hostname, port };
136
147
  }
148
+ function parseWorkspaceOnlyOptions(args) {
149
+ let workspaceRoot;
150
+ for (let index = 0; index < args.length; index += 1) {
151
+ const arg = args[index];
152
+ if (arg === "--workspace") {
153
+ const value = args[index + 1];
154
+ if (!value) {
155
+ return { error: "Missing value for --workspace" };
156
+ }
157
+ workspaceRoot = value;
158
+ index += 1;
159
+ continue;
160
+ }
161
+ return { error: `Unknown option: ${arg}` };
162
+ }
163
+ return { workspaceRoot };
164
+ }
137
165
  function parseHttpServeOptions(args, serviceLabel = "HTTP") {
138
166
  let workspaceRoot;
139
167
  let hostname;
@@ -175,6 +203,60 @@ function parseHttpServeOptions(args, serviceLabel = "HTTP") {
175
203
  }
176
204
  return { workspaceRoot, hostname, port };
177
205
  }
206
+ function parseRuntimeMcpServeOptions(args, defaultTransport = "stdio") {
207
+ let workspaceRoot;
208
+ let transport = defaultTransport;
209
+ let hostname;
210
+ let port;
211
+ for (let index = 0; index < args.length; index += 1) {
212
+ const arg = args[index];
213
+ if (arg === "--workspace") {
214
+ const value = args[index + 1];
215
+ if (!value) {
216
+ return { transport, hostname, port, error: "Missing value for --workspace" };
217
+ }
218
+ workspaceRoot = value;
219
+ index += 1;
220
+ continue;
221
+ }
222
+ if (arg === "--transport") {
223
+ const value = args[index + 1];
224
+ if (!value) {
225
+ return { transport, hostname, port, error: "Missing value for --transport" };
226
+ }
227
+ if (value !== "stdio" && value !== "streamable-http") {
228
+ return { transport, hostname, port, error: `Unsupported runtime MCP transport: ${value}` };
229
+ }
230
+ transport = value;
231
+ index += 1;
232
+ continue;
233
+ }
234
+ if (arg === "--host") {
235
+ const value = args[index + 1];
236
+ if (!value) {
237
+ return { transport, hostname, port, error: "Missing value for --host" };
238
+ }
239
+ hostname = value;
240
+ index += 1;
241
+ continue;
242
+ }
243
+ if (arg === "--port") {
244
+ const value = args[index + 1];
245
+ if (!value) {
246
+ return { transport, hostname, port, error: "Missing value for --port" };
247
+ }
248
+ const parsedPort = Number.parseInt(value, 10);
249
+ if (!Number.isFinite(parsedPort) || parsedPort < 0) {
250
+ return { transport, hostname, port, error: `Invalid runtime MCP port: ${value}` };
251
+ }
252
+ port = parsedPort;
253
+ index += 1;
254
+ continue;
255
+ }
256
+ return { transport, hostname, port, error: `Unknown option: ${arg}` };
257
+ }
258
+ return { workspaceRoot, transport, hostname, port };
259
+ }
178
260
  function parseChatOptions(args) {
179
261
  let workspaceRoot;
180
262
  let agentId;
@@ -250,9 +332,66 @@ function isTopLevelCliCommand(value) {
250
332
  || value === "acp"
251
333
  || value === "a2a"
252
334
  || value === "ag-ui"
335
+ || value === "mcp"
253
336
  || value === "runtime"
254
337
  || value === "runtime-mcp";
255
338
  }
339
+ function resolveManagedServiceRoot(workspaceRoot) {
340
+ return path.join(workspaceRoot, ".botbotgo", "services");
341
+ }
342
+ function managedServiceStatePath(workspaceRoot, service) {
343
+ return path.join(resolveManagedServiceRoot(workspaceRoot), `${service}.json`);
344
+ }
345
+ function managedServiceLogPath(workspaceRoot, service, stream) {
346
+ return path.join(resolveManagedServiceRoot(workspaceRoot), `${service}.${stream}.log`);
347
+ }
348
+ async function readManagedServiceState(workspaceRoot, service) {
349
+ const statePath = managedServiceStatePath(workspaceRoot, service);
350
+ if (!existsSync(statePath)) {
351
+ return null;
352
+ }
353
+ try {
354
+ return JSON.parse(await readFile(statePath, "utf8"));
355
+ }
356
+ catch {
357
+ return null;
358
+ }
359
+ }
360
+ async function writeManagedServiceState(state) {
361
+ const statePath = managedServiceStatePath(state.workspaceRoot, state.service);
362
+ await mkdir(path.dirname(statePath), { recursive: true });
363
+ await writeFile(statePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
364
+ }
365
+ async function clearManagedServiceState(workspaceRoot, service) {
366
+ await rm(managedServiceStatePath(workspaceRoot, service), { force: true });
367
+ }
368
+ function defaultIsManagedProcessRunning(pid) {
369
+ try {
370
+ process.kill(pid, 0);
371
+ return true;
372
+ }
373
+ catch {
374
+ return false;
375
+ }
376
+ }
377
+ function defaultSignalManagedProcess(pid, signal) {
378
+ process.kill(pid, signal);
379
+ }
380
+ async function defaultSpawnManagedCliProcess(input) {
381
+ await mkdir(path.dirname(input.stdoutPath), { recursive: true });
382
+ const stdoutFd = openSync(input.stdoutPath, "a");
383
+ const stderrFd = openSync(input.stderrPath, "a");
384
+ const child = spawn(process.execPath, [fileURLToPath(import.meta.url), ...input.args], {
385
+ cwd: input.cwd,
386
+ detached: true,
387
+ stdio: ["ignore", stdoutFd, stderrFd],
388
+ });
389
+ child.unref();
390
+ return { pid: child.pid };
391
+ }
392
+ async function sleep(ms) {
393
+ await new Promise((resolve) => setTimeout(resolve, ms));
394
+ }
256
395
  function parseRuntimeInspectOptions(args) {
257
396
  let workspaceRoot;
258
397
  let json = false;
@@ -821,11 +960,6 @@ function renderHealthSnapshot(snapshot, workspacePath) {
821
960
  }
822
961
  return `${lines.join("\n")}\n`;
823
962
  }
824
- async function sleep(ms) {
825
- await new Promise((resolve) => {
826
- setTimeout(resolve, ms);
827
- });
828
- }
829
963
  function renderApprovalList(approvals) {
830
964
  if (approvals.length === 0) {
831
965
  return "No approvals matched.\n";
@@ -2041,6 +2175,10 @@ export async function runCli(argv, io = {}, deps = {}) {
2041
2175
  const serveAcpHttp = deps.serveAcpOverHttp ?? serveAcpOverHttp;
2042
2176
  const serveAcp = deps.serveAcpOverStdio ?? serveAcpOverStdio;
2043
2177
  const serveRuntimeMcp = deps.serveRuntimeMcpOverStdio ?? serveRuntimeMcpOverStdio;
2178
+ const serveRuntimeMcpStreamableHttp = deps.serveRuntimeMcpOverStreamableHttp ?? serveRuntimeMcpOverStreamableHttp;
2179
+ const spawnManagedCliProcess = deps.spawnManagedCliProcess ?? defaultSpawnManagedCliProcess;
2180
+ const isManagedProcessRunning = deps.isManagedProcessRunning ?? defaultIsManagedProcessRunning;
2181
+ const signalManagedProcess = deps.signalManagedProcess ?? defaultSignalManagedProcess;
2044
2182
  const createChatClient = deps.createChatClient ?? (async (input) => {
2045
2183
  if (input.transport === "http") {
2046
2184
  return createAcpHttpHarnessClient({
@@ -2342,6 +2480,68 @@ export async function runCli(argv, io = {}, deps = {}) {
2342
2480
  return 1;
2343
2481
  }
2344
2482
  };
2483
+ const startManagedHttpService = async (service, workspacePath, args) => {
2484
+ const existing = await readManagedServiceState(workspacePath, service);
2485
+ if (existing && isManagedProcessRunning(existing.pid)) {
2486
+ stderr(`${service.toUpperCase()} service is already running for ${workspacePath} (pid ${existing.pid}).\n`);
2487
+ return 0;
2488
+ }
2489
+ if (existing) {
2490
+ await clearManagedServiceState(workspacePath, service);
2491
+ }
2492
+ const stdoutPath = managedServiceLogPath(workspacePath, service, "stdout");
2493
+ const stderrPath = managedServiceLogPath(workspacePath, service, "stderr");
2494
+ const spawned = await spawnManagedCliProcess({
2495
+ args,
2496
+ cwd: workspacePath,
2497
+ stdoutPath,
2498
+ stderrPath,
2499
+ });
2500
+ if (!spawned.pid || spawned.pid <= 0) {
2501
+ stderr(`Failed to start ${service.toUpperCase()} service for ${workspacePath}.\n`);
2502
+ return 1;
2503
+ }
2504
+ await writeManagedServiceState({
2505
+ pid: spawned.pid,
2506
+ service,
2507
+ workspaceRoot: workspacePath,
2508
+ transport: service === "mcp" ? "streamable-http" : "http",
2509
+ startedAt: new Date().toISOString(),
2510
+ stdoutPath,
2511
+ stderrPath,
2512
+ });
2513
+ await sleep(250);
2514
+ if (!isManagedProcessRunning(spawned.pid)) {
2515
+ await clearManagedServiceState(workspacePath, service);
2516
+ stderr(`Failed to keep ${service.toUpperCase()} service running for ${workspacePath}. Check ${stderrPath}.\n`);
2517
+ return 1;
2518
+ }
2519
+ stderr(`Started ${service.toUpperCase()} service for ${workspacePath} (pid ${spawned.pid}). Logs: ${stdoutPath}, ${stderrPath}\n`);
2520
+ return 0;
2521
+ };
2522
+ const stopManagedHttpService = async (service, workspacePath) => {
2523
+ const existing = await readManagedServiceState(workspacePath, service);
2524
+ if (!existing) {
2525
+ stderr(`No managed ${service.toUpperCase()} service is recorded for ${workspacePath}.\n`);
2526
+ return 0;
2527
+ }
2528
+ if (!isManagedProcessRunning(existing.pid)) {
2529
+ await clearManagedServiceState(workspacePath, service);
2530
+ stderr(`Managed ${service.toUpperCase()} service for ${workspacePath} is already stopped.\n`);
2531
+ return 0;
2532
+ }
2533
+ signalManagedProcess(existing.pid, "SIGTERM");
2534
+ for (let attempt = 0; attempt < 20; attempt += 1) {
2535
+ await sleep(100);
2536
+ if (!isManagedProcessRunning(existing.pid)) {
2537
+ await clearManagedServiceState(workspacePath, service);
2538
+ stderr(`Stopped ${service.toUpperCase()} service for ${workspacePath}.\n`);
2539
+ return 0;
2540
+ }
2541
+ }
2542
+ stderr(`Timed out while stopping ${service.toUpperCase()} service for ${workspacePath} (pid ${existing.pid}).\n`);
2543
+ return 1;
2544
+ };
2345
2545
  if (command === "init") {
2346
2546
  if (!projectName?.trim()) {
2347
2547
  stderr(renderUsage());
@@ -2392,28 +2592,58 @@ export async function runCli(argv, io = {}, deps = {}) {
2392
2592
  }
2393
2593
  if (command === "acp") {
2394
2594
  const [subcommand, ...subcommandArgs] = [projectName, ...rest];
2395
- if (subcommand !== "serve") {
2595
+ if (subcommand !== "serve" && subcommand !== "start" && subcommand !== "stop") {
2396
2596
  stderr(renderUsage());
2397
2597
  return 1;
2398
2598
  }
2399
- const parsed = parseAcpServeOptions(subcommandArgs);
2400
- if (parsed.error) {
2401
- stderr(`${parsed.error}\n`);
2599
+ const parsedStop = subcommand === "stop" ? parseWorkspaceOnlyOptions(subcommandArgs) : undefined;
2600
+ const parsedServe = subcommand !== "stop"
2601
+ ? parseAcpServeOptions(subcommandArgs, subcommand === "start" ? "http" : "stdio")
2602
+ : undefined;
2603
+ const parseError = parsedStop?.error ?? parsedServe?.error;
2604
+ if (parseError) {
2605
+ stderr(`${parseError}\n`);
2402
2606
  stderr(renderUsage());
2403
2607
  return 1;
2404
2608
  }
2405
2609
  try {
2406
- const workspacePath = resolveCliWorkspaceRoot(cwd, parsed.workspaceRoot);
2407
- const workspaceError = validateCliWorkspaceRoot(workspacePath, parsed.workspaceRoot);
2610
+ const workspaceRoot = parsedStop?.workspaceRoot ?? parsedServe?.workspaceRoot;
2611
+ const workspacePath = resolveCliWorkspaceRoot(cwd, workspaceRoot);
2612
+ const workspaceError = validateCliWorkspaceRoot(workspacePath, workspaceRoot);
2408
2613
  if (workspaceError) {
2409
2614
  stderr(`${workspaceError}\n`);
2410
2615
  return 1;
2411
2616
  }
2617
+ if (subcommand === "start") {
2618
+ if (!parsedServe) {
2619
+ stderr(renderUsage());
2620
+ return 1;
2621
+ }
2622
+ if (parsedServe.transport !== "http") {
2623
+ stderr("ACP start only supports --transport http. Use `acp serve --transport stdio` for stdio clients.\n");
2624
+ return 1;
2625
+ }
2626
+ const args = ["acp", "serve", "--workspace", workspacePath, "--transport", "http"];
2627
+ if (parsedServe.hostname) {
2628
+ args.push("--host", parsedServe.hostname);
2629
+ }
2630
+ if (typeof parsedServe.port === "number") {
2631
+ args.push("--port", String(parsedServe.port));
2632
+ }
2633
+ return startManagedHttpService("acp", workspacePath, args);
2634
+ }
2635
+ if (subcommand === "stop") {
2636
+ return stopManagedHttpService("acp", workspacePath);
2637
+ }
2638
+ if (!parsedServe) {
2639
+ stderr(renderUsage());
2640
+ return 1;
2641
+ }
2412
2642
  const runtime = await createHarness(workspacePath);
2413
- if (parsed.transport === "http") {
2643
+ if (parsedServe.transport === "http") {
2414
2644
  const server = await serveAcpHttp(runtime, {
2415
- hostname: parsed.hostname,
2416
- port: parsed.port,
2645
+ hostname: parsedServe.hostname,
2646
+ port: parsedServe.port,
2417
2647
  });
2418
2648
  stderr(`Serving ACP over http from ${workspacePath} at ${server.rpcUrl} (events ${server.eventsUrl})\n`);
2419
2649
  await server.completed;
@@ -2469,27 +2699,47 @@ export async function runCli(argv, io = {}, deps = {}) {
2469
2699
  }
2470
2700
  if (command === "a2a") {
2471
2701
  const [subcommand, ...subcommandArgs] = [projectName, ...rest];
2472
- if (subcommand !== "serve") {
2702
+ if (subcommand !== "serve" && subcommand !== "start" && subcommand !== "stop") {
2473
2703
  stderr(renderUsage());
2474
2704
  return 1;
2475
2705
  }
2476
- const parsed = parseHttpServeOptions(subcommandArgs, "A2A");
2477
- if (parsed.error) {
2478
- stderr(`${parsed.error}\n`);
2706
+ const parsedStop = subcommand === "stop" ? parseWorkspaceOnlyOptions(subcommandArgs) : undefined;
2707
+ const parsedServe = subcommand !== "stop" ? parseHttpServeOptions(subcommandArgs, "A2A") : undefined;
2708
+ const parseError = parsedStop?.error ?? parsedServe?.error;
2709
+ if (parseError) {
2710
+ stderr(`${parseError}\n`);
2479
2711
  stderr(renderUsage());
2480
2712
  return 1;
2481
2713
  }
2482
2714
  try {
2483
- const workspacePath = resolveCliWorkspaceRoot(cwd, parsed.workspaceRoot);
2484
- const workspaceError = validateCliWorkspaceRoot(workspacePath, parsed.workspaceRoot);
2715
+ const workspaceRoot = parsedStop?.workspaceRoot ?? parsedServe?.workspaceRoot;
2716
+ const workspacePath = resolveCliWorkspaceRoot(cwd, workspaceRoot);
2717
+ const workspaceError = validateCliWorkspaceRoot(workspacePath, workspaceRoot);
2485
2718
  if (workspaceError) {
2486
2719
  stderr(`${workspaceError}\n`);
2487
2720
  return 1;
2488
2721
  }
2722
+ if (subcommand === "start") {
2723
+ const args = ["a2a", "serve", "--workspace", workspacePath];
2724
+ if (parsedServe?.hostname) {
2725
+ args.push("--host", parsedServe.hostname);
2726
+ }
2727
+ if (typeof parsedServe?.port === "number") {
2728
+ args.push("--port", String(parsedServe.port));
2729
+ }
2730
+ return startManagedHttpService("a2a", workspacePath, args);
2731
+ }
2732
+ if (subcommand === "stop") {
2733
+ return stopManagedHttpService("a2a", workspacePath);
2734
+ }
2735
+ if (!parsedServe) {
2736
+ stderr(renderUsage());
2737
+ return 1;
2738
+ }
2489
2739
  const runtime = await createHarness(workspacePath);
2490
2740
  const server = await serveA2a(runtime, {
2491
- hostname: parsed.hostname,
2492
- port: parsed.port,
2741
+ hostname: parsedServe.hostname,
2742
+ port: parsedServe.port,
2493
2743
  });
2494
2744
  stderr(`Serving A2A over http from ${workspacePath} at ${server.rpcUrl} (card ${server.agentCardUrl})\n`);
2495
2745
  await server.completed;
@@ -2502,33 +2752,73 @@ export async function runCli(argv, io = {}, deps = {}) {
2502
2752
  return 1;
2503
2753
  }
2504
2754
  }
2505
- if (command === "runtime-mcp") {
2755
+ if (command === "mcp" || command === "runtime-mcp") {
2506
2756
  const [subcommand, ...subcommandArgs] = [projectName, ...rest];
2507
- if (subcommand !== "serve") {
2757
+ if (subcommand !== "serve" && subcommand !== "start" && subcommand !== "stop") {
2508
2758
  stderr(renderUsage());
2509
2759
  return 1;
2510
2760
  }
2511
- const parsed = parseHttpServeOptions(subcommandArgs, "runtime MCP");
2512
- if (parsed.error) {
2513
- stderr(`${parsed.error}\n`);
2761
+ const parsedStop = subcommand === "stop" ? parseWorkspaceOnlyOptions(subcommandArgs) : undefined;
2762
+ const parsedServe = subcommand !== "stop"
2763
+ ? parseRuntimeMcpServeOptions(subcommandArgs, subcommand === "start" ? "streamable-http" : "stdio")
2764
+ : undefined;
2765
+ const parseError = parsedStop?.error ?? parsedServe?.error;
2766
+ if (parseError) {
2767
+ stderr(`${parseError}\n`);
2514
2768
  stderr(renderUsage());
2515
2769
  return 1;
2516
2770
  }
2517
2771
  try {
2518
- const workspacePath = resolveCliWorkspaceRoot(cwd, parsed.workspaceRoot);
2519
- const workspaceError = validateCliWorkspaceRoot(workspacePath, parsed.workspaceRoot);
2772
+ const workspaceRoot = parsedStop?.workspaceRoot ?? parsedServe?.workspaceRoot;
2773
+ const workspacePath = resolveCliWorkspaceRoot(cwd, workspaceRoot);
2774
+ const workspaceError = validateCliWorkspaceRoot(workspacePath, workspaceRoot);
2520
2775
  if (workspaceError) {
2521
2776
  stderr(`${workspaceError}\n`);
2522
2777
  return 1;
2523
2778
  }
2779
+ if (subcommand === "start") {
2780
+ if (!parsedServe) {
2781
+ stderr(renderUsage());
2782
+ return 1;
2783
+ }
2784
+ if (parsedServe.transport !== "streamable-http") {
2785
+ stderr("MCP start only supports --transport streamable-http. Use `mcp serve --transport stdio` for stdio clients.\n");
2786
+ return 1;
2787
+ }
2788
+ const args = ["mcp", "serve", "--workspace", workspacePath, "--transport", "streamable-http"];
2789
+ if (parsedServe.hostname) {
2790
+ args.push("--host", parsedServe.hostname);
2791
+ }
2792
+ if (typeof parsedServe.port === "number") {
2793
+ args.push("--port", String(parsedServe.port));
2794
+ }
2795
+ return startManagedHttpService("mcp", workspacePath, args);
2796
+ }
2797
+ if (subcommand === "stop") {
2798
+ return stopManagedHttpService("mcp", workspacePath);
2799
+ }
2800
+ if (!parsedServe) {
2801
+ stderr(renderUsage());
2802
+ return 1;
2803
+ }
2524
2804
  const runtime = await createHarness(workspacePath);
2525
- stderr(`Serving runtime MCP over stdio from ${workspacePath}\n`);
2526
- const server = await serveRuntimeMcp(runtime);
2527
- await new Promise((resolve, reject) => {
2528
- process.stdin.on("end", resolve);
2529
- process.stdin.on("error", reject);
2530
- });
2531
- await server.close();
2805
+ if (parsedServe.transport === "streamable-http") {
2806
+ const server = await serveRuntimeMcpStreamableHttp(runtime, {
2807
+ hostname: parsedServe.hostname,
2808
+ port: parsedServe.port,
2809
+ });
2810
+ stderr(`Serving runtime MCP over streamable HTTP from ${workspacePath} at ${server.url}\n`);
2811
+ await server.completed;
2812
+ }
2813
+ else {
2814
+ stderr(`Serving runtime MCP over stdio from ${workspacePath}\n`);
2815
+ const server = await serveRuntimeMcp(runtime);
2816
+ await new Promise((resolve, reject) => {
2817
+ process.stdin.on("end", resolve);
2818
+ process.stdin.on("error", reject);
2819
+ });
2820
+ await server.close();
2821
+ }
2532
2822
  await runtime.stop();
2533
2823
  return 0;
2534
2824
  }
@@ -29,6 +29,22 @@ spec:
29
29
  # agent-harness feature: stable runtime profile identifier for this data folder.
30
30
  profile: default
31
31
 
32
+ # agent-harness feature: explicit tool and skill discovery sources.
33
+ # The default local workspace contract stays:
34
+ # - tools from ./resources/tools
35
+ # - skills from ./resources/skills
36
+ #
37
+ # Supported source forms today:
38
+ # - tools: file://<folder>, npm://<package-or-spec>
39
+ # - skills: file://<folder-or-SKILL.md>, http(s)://.../SKILL.md
40
+ #
41
+ # Discovery never traverses node_modules directories.
42
+ sources:
43
+ tools:
44
+ - file://./resources/tools
45
+ skills:
46
+ - file://./resources/skills
47
+
32
48
  # agent-harness feature: runtime-level task queue and maximum number of concurrent requests.
33
49
  # Additional requests wait in the harness queue until a slot becomes available.
34
50
  concurrency:
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { AgentHarnessAcpServer, AgentHarnessRuntime, cancelRequest, createAgentHarness, createAcpServer, createAcpStdioClient, createRuntimeMcpServer, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, exportEvaluationBundle, exportFlow, exportSequence, exportRequestPackage, exportSessionPackage, replayEvaluationBundle, getArtifact, getAgent, getApproval, getOperatorOverview, getRequestPlanState, getRequest, getHealth, listMemories, listRequestTraceItems, getSession, listAgentSkills, listRequestArtifacts, listApprovals, listRequests, listRequestEvents, listSessionSummaries, listSessions, memorize, normalizeUserChatInput, recordArtifact, request, recall, removeMemory, resolveApproval, serveA2aHttp, serveAcpHttp, serveAcpStdio, serveAgUiHttp, serveRuntimeMcpOverStdio, serveToolsOverStdio, subscribe, stop, updateMemory, } from "./api.js";
1
+ export { AgentHarnessAcpServer, AgentHarnessRuntime, cancelRequest, createAgentHarness, createAcpServer, createAcpStdioClient, createRuntimeMcpServer, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, exportEvaluationBundle, exportFlow, exportSequence, exportRequestPackage, exportSessionPackage, replayEvaluationBundle, getArtifact, getAgent, getApproval, getOperatorOverview, getRequestPlanState, getRequest, getHealth, listMemories, listRequestTraceItems, getSession, listAgentSkills, listRequestArtifacts, listApprovals, listRequests, listRequestEvents, listSessionSummaries, listSessions, memorize, normalizeUserChatInput, recordArtifact, request, recall, removeMemory, resolveApproval, serveA2aHttp, serveAcpHttp, serveAcpStdio, serveAgUiHttp, serveRuntimeMcpOverStdio, serveRuntimeMcpOverStreamableHttp, serveToolsOverStdio, subscribe, stop, updateMemory, } from "./api.js";
2
2
  export { AcpHarnessClient, InProcessHarnessClient, createAcpHarnessClient, createAcpHttpHarnessClient, createAcpStdioHarnessClient, createAgentHarnessClient, createInProcessHarnessClient, } from "./client.js";
3
3
  export { createKnowledgeModule, readKnowledgeRuntimeConfig } from "./knowledge/index.js";
4
4
  export { readProceduralMemoryRuntimeConfig } from "./procedural/index.js";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { AgentHarnessAcpServer, AgentHarnessRuntime, cancelRequest, createAgentHarness, createAcpServer, createAcpStdioClient, createRuntimeMcpServer, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, exportEvaluationBundle, exportFlow, exportSequence, exportRequestPackage, exportSessionPackage, replayEvaluationBundle, getArtifact, getAgent, getApproval, getOperatorOverview, getRequestPlanState, getRequest, getHealth, listMemories, listRequestTraceItems, getSession, listAgentSkills, listRequestArtifacts, listApprovals, listRequests, listRequestEvents, listSessionSummaries, listSessions, memorize, normalizeUserChatInput, recordArtifact, request, recall, removeMemory, resolveApproval, serveA2aHttp, serveAcpHttp, serveAcpStdio, serveAgUiHttp, serveRuntimeMcpOverStdio, serveToolsOverStdio, subscribe, stop, updateMemory, } from "./api.js";
1
+ export { AgentHarnessAcpServer, AgentHarnessRuntime, cancelRequest, createAgentHarness, createAcpServer, createAcpStdioClient, createRuntimeMcpServer, createUpstreamTimelineReducer, createToolMcpServer, deleteSession, describeInventory, exportEvaluationBundle, exportFlow, exportSequence, exportRequestPackage, exportSessionPackage, replayEvaluationBundle, getArtifact, getAgent, getApproval, getOperatorOverview, getRequestPlanState, getRequest, getHealth, listMemories, listRequestTraceItems, getSession, listAgentSkills, listRequestArtifacts, listApprovals, listRequests, listRequestEvents, listSessionSummaries, listSessions, memorize, normalizeUserChatInput, recordArtifact, request, recall, removeMemory, resolveApproval, serveA2aHttp, serveAcpHttp, serveAcpStdio, serveAgUiHttp, serveRuntimeMcpOverStdio, serveRuntimeMcpOverStreamableHttp, serveToolsOverStdio, subscribe, stop, updateMemory, } from "./api.js";
2
2
  export { AcpHarnessClient, InProcessHarnessClient, createAcpHarnessClient, createAcpHttpHarnessClient, createAcpStdioHarnessClient, createAgentHarnessClient, createInProcessHarnessClient, } from "./client.js";
3
3
  export { createKnowledgeModule, readKnowledgeRuntimeConfig } from "./knowledge/index.js";
4
4
  export { readProceduralMemoryRuntimeConfig } from "./procedural/index.js";
package/dist/mcp.d.ts CHANGED
@@ -19,6 +19,19 @@ export type RuntimeMcpServerOptions = {
19
19
  version?: string;
20
20
  };
21
21
  };
22
+ export type RuntimeMcpStreamableHttpServerOptions = RuntimeMcpServerOptions & {
23
+ hostname?: string;
24
+ port?: number;
25
+ path?: string;
26
+ };
27
+ export type RuntimeMcpStreamableHttpServer = {
28
+ hostname: string;
29
+ port: number;
30
+ path: string;
31
+ url: string;
32
+ completed: Promise<void>;
33
+ close: () => Promise<void>;
34
+ };
22
35
  type RuntimeMcpRuntime = {
23
36
  listSessions: (filter?: {
24
37
  agentId?: string;
@@ -59,4 +72,5 @@ export declare function createToolMcpServerFromTools(tools: ToolMcpServerTool[],
59
72
  export declare function serveToolsOverStdioFromHarness(tools: ToolMcpServerTool[], options: ToolMcpServerOptions): Promise<McpServer>;
60
73
  export declare function createRuntimeMcpServer(runtime: RuntimeMcpRuntime, options?: RuntimeMcpServerOptions): Promise<McpServer>;
61
74
  export declare function serveRuntimeMcpOverStdio(runtime: RuntimeMcpRuntime, options?: RuntimeMcpServerOptions): Promise<McpServer>;
75
+ export declare function serveRuntimeMcpOverStreamableHttp(runtime: RuntimeMcpRuntime, options?: RuntimeMcpStreamableHttpServerOptions): Promise<RuntimeMcpStreamableHttpServer>;
62
76
  export {};