@rk0429/agentic-relay 0.6.2 → 0.6.4

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 (2) hide show
  1. package/dist/relay.mjs +143 -17
  2. package/package.json +1 -1
package/dist/relay.mjs CHANGED
@@ -245,13 +245,61 @@ function readSkillContext(skillContext) {
245
245
  return null;
246
246
  }
247
247
  }
248
+ function readProjectMcpJson(cwd) {
249
+ try {
250
+ const dir = cwd ?? process.cwd();
251
+ const mcpJsonPath = join6(dir, ".mcp.json");
252
+ if (!existsSync(mcpJsonPath)) {
253
+ return {};
254
+ }
255
+ const raw = readFileSync(mcpJsonPath, "utf-8");
256
+ const parsed = JSON.parse(raw);
257
+ const servers = parsed.mcpServers;
258
+ if (!servers || typeof servers !== "object") {
259
+ return {};
260
+ }
261
+ return servers;
262
+ } catch {
263
+ return {};
264
+ }
265
+ }
266
+ function isRelayEntry(name, config) {
267
+ if (name === "agentic-relay") return true;
268
+ const argsStr = (config.args ?? []).join(" ");
269
+ if (config.command === "npx" && argsStr.includes("@rk0429/agentic-relay")) return true;
270
+ if (argsStr.includes("relay.mjs") && argsStr.includes("mcp")) return true;
271
+ if (config.command?.includes("relay.mjs") || config.command?.includes("agentic-relay")) return true;
272
+ return false;
273
+ }
274
+ function buildChildMcpServers(parentMcpServers, childHttpUrl) {
275
+ const result = {};
276
+ for (const [name, config] of Object.entries(parentMcpServers)) {
277
+ if (isRelayEntry(name, config)) {
278
+ result[name] = { type: "http", url: childHttpUrl };
279
+ } else if (config.type === "http" || config.type === "sse") {
280
+ result[name] = {
281
+ type: config.type,
282
+ url: config.url ?? "",
283
+ ...config.headers ? { headers: config.headers } : {}
284
+ };
285
+ } else {
286
+ result[name] = {
287
+ type: "stdio",
288
+ command: config.command ?? "",
289
+ ...config.args ? { args: config.args } : {},
290
+ ...config.env ? { env: config.env } : {}
291
+ };
292
+ }
293
+ }
294
+ return result;
295
+ }
248
296
  function buildContextFromEnv() {
249
297
  const traceId = process.env["RELAY_TRACE_ID"] ?? `trace-${nanoid2()}`;
250
298
  const parentSessionId = process.env["RELAY_PARENT_SESSION_ID"] ?? null;
251
299
  const depth = Number(process.env["RELAY_DEPTH"] ?? "0");
252
300
  return { traceId, parentSessionId, depth };
253
301
  }
254
- async function executeSpawnAgent(input, registry2, sessionManager2, guard, hooksEngine2, contextMonitor2, backendSelector) {
302
+ async function executeSpawnAgent(input, registry2, sessionManager2, guard, hooksEngine2, contextMonitor2, backendSelector, childHttpUrl) {
255
303
  let effectiveBackend = input.backend;
256
304
  if (backendSelector) {
257
305
  const availableBackends = registry2.listIds();
@@ -383,6 +431,13 @@ ${wrapped}` : wrapped;
383
431
  }
384
432
  result = await adapter.continueSession(input.resumeSessionId, input.prompt);
385
433
  } else {
434
+ let mcpServers;
435
+ if (childHttpUrl) {
436
+ const parentMcpServers = readProjectMcpJson();
437
+ if (Object.keys(parentMcpServers).length > 0) {
438
+ mcpServers = buildChildMcpServers(parentMcpServers, childHttpUrl);
439
+ }
440
+ }
386
441
  result = await adapter.execute({
387
442
  prompt: input.prompt,
388
443
  agent: input.agent,
@@ -394,7 +449,8 @@ ${wrapped}` : wrapped;
394
449
  depth: envContext.depth + 1,
395
450
  maxDepth: guard.getConfig().maxDepth,
396
451
  traceId: envContext.traceId
397
- }
452
+ },
453
+ ...mcpServers ? { mcpServers } : {}
398
454
  });
399
455
  }
400
456
  if (contextMonitor2) {
@@ -661,15 +717,25 @@ var init_server = __esm({
661
717
  this.backendSelector = new BackendSelector();
662
718
  this.server = new McpServer({
663
719
  name: "agentic-relay",
664
- version: "0.4.0"
720
+ version: "0.6.4"
665
721
  });
666
- this.registerTools();
722
+ this.registerTools(this.server);
667
723
  }
668
724
  server;
669
725
  guard;
670
726
  backendSelector;
671
- registerTools() {
672
- this.server.tool(
727
+ _childHttpServer;
728
+ _childHttpUrl;
729
+ /** URL for child agents to connect via HTTP. Available after start() in stdio mode. */
730
+ get childHttpUrl() {
731
+ return this._childHttpUrl;
732
+ }
733
+ /** Exposed for testing: the HTTP server serving child agents */
734
+ get childHttpServer() {
735
+ return this._childHttpServer;
736
+ }
737
+ registerTools(server) {
738
+ server.tool(
673
739
  "spawn_agent",
674
740
  "Spawn a sub-agent on the specified backend CLI (Claude Code, Codex CLI, or Gemini CLI). The agent executes the given prompt in non-interactive mode and returns the result. Use 'agent' for named agent configurations (Claude only), 'systemPrompt' for custom role instructions (all backends), 'skillContext' to inject a skill definition (SKILL.md/SUBSKILL.md), 'agentDefinition' to inject an agent definition file into the sub-agent's system prompt, 'preferredBackend' to override automatic backend selection, or 'taskType' to hint at the task nature for backend selection.",
675
741
  {
@@ -701,7 +767,8 @@ var init_server = __esm({
701
767
  this.guard,
702
768
  this.hooksEngine,
703
769
  this.contextMonitor,
704
- this.backendSelector
770
+ this.backendSelector,
771
+ this._childHttpUrl
705
772
  );
706
773
  const isError = result.exitCode !== 0;
707
774
  const text = isError ? `Error (exit ${result.exitCode}): ${result.stderr || result.stdout}` : `Session: ${result.sessionId}
@@ -720,7 +787,7 @@ ${result.stdout}`;
720
787
  }
721
788
  }
722
789
  );
723
- this.server.tool(
790
+ server.tool(
724
791
  "list_sessions",
725
792
  "List relay sessions, optionally filtered by backend.",
726
793
  {
@@ -750,7 +817,7 @@ ${result.stdout}`;
750
817
  }
751
818
  }
752
819
  );
753
- this.server.tool(
820
+ server.tool(
754
821
  "get_context_status",
755
822
  "Get the context usage status of a relay session. Returns usage data from ContextMonitor when available, otherwise estimated values.",
756
823
  {
@@ -780,7 +847,7 @@ ${result.stdout}`;
780
847
  }
781
848
  }
782
849
  );
783
- this.server.tool(
850
+ server.tool(
784
851
  "list_available_backends",
785
852
  "List all registered backends with their health status. Use this before spawn_agent to check which backends are available.",
786
853
  {},
@@ -811,6 +878,7 @@ ${result.stdout}`;
811
878
  logger.info("Starting agentic-relay MCP server (stdio transport)...");
812
879
  const transport = new StdioServerTransport();
813
880
  await this.server.connect(transport);
881
+ await this.startChildHttpServer();
814
882
  return;
815
883
  }
816
884
  const port = options?.port ?? 3100;
@@ -841,6 +909,62 @@ ${result.stdout}`;
841
909
  httpServer.on("close", resolve);
842
910
  });
843
911
  }
912
+ /**
913
+ * Start an HTTP server for child agents.
914
+ * Each client connection gets its own McpServer + StreamableHTTPServerTransport pair
915
+ * because StreamableHTTPServerTransport only supports a single session per instance.
916
+ * Routes requests to the correct session based on the Mcp-Session-Id header.
917
+ */
918
+ async startChildHttpServer() {
919
+ const sessions = /* @__PURE__ */ new Map();
920
+ const httpServer = createServer(async (req, res) => {
921
+ const url = req.url ?? "";
922
+ if (url !== "/mcp" && !url.startsWith("/mcp?")) {
923
+ res.writeHead(404, { "Content-Type": "text/plain" });
924
+ res.end("Not found");
925
+ return;
926
+ }
927
+ const sessionId = req.headers["mcp-session-id"];
928
+ if (sessionId) {
929
+ const session = sessions.get(sessionId);
930
+ if (session) {
931
+ await session.transport.handleRequest(req, res);
932
+ return;
933
+ }
934
+ }
935
+ const transport = new StreamableHTTPServerTransport({
936
+ sessionIdGenerator: () => randomUUID()
937
+ });
938
+ const server = new McpServer({
939
+ name: "agentic-relay",
940
+ version: "0.6.4"
941
+ });
942
+ this.registerTools(server);
943
+ transport.onclose = () => {
944
+ const sid2 = transport.sessionId;
945
+ if (sid2) {
946
+ sessions.delete(sid2);
947
+ logger.debug(`Child MCP session closed: ${sid2}`);
948
+ }
949
+ };
950
+ await server.connect(transport);
951
+ await transport.handleRequest(req, res);
952
+ const sid = transport.sessionId;
953
+ if (sid) {
954
+ sessions.set(sid, { transport, server });
955
+ logger.debug(`Child MCP session created: ${sid}`);
956
+ }
957
+ });
958
+ this._childHttpServer = httpServer;
959
+ await new Promise((resolve) => {
960
+ httpServer.listen(0, "127.0.0.1", () => {
961
+ const addr = httpServer.address();
962
+ this._childHttpUrl = `http://127.0.0.1:${addr.port}/mcp`;
963
+ logger.info(`Child MCP server listening on ${this._childHttpUrl}`);
964
+ resolve();
965
+ });
966
+ });
967
+ }
844
968
  /** Exposed for testing and graceful shutdown */
845
969
  get httpServer() {
846
970
  return this._httpServer;
@@ -1231,10 +1355,11 @@ var ClaudeAdapter = class extends BaseAdapter {
1231
1355
  abortController,
1232
1356
  env,
1233
1357
  cwd: process.cwd(),
1234
- settingSources: [],
1358
+ strictMcpConfig: true,
1235
1359
  ...flags.model ? { model: flags.model } : {},
1236
1360
  ...flags.maxTurns ? { maxTurns: flags.maxTurns } : {},
1237
- ...flags.systemPrompt ? { systemPrompt: flags.systemPrompt } : {}
1361
+ ...flags.systemPrompt ? { systemPrompt: flags.systemPrompt } : {},
1362
+ ...flags.mcpServers ? { mcpServers: flags.mcpServers } : {}
1238
1363
  };
1239
1364
  if (permissionMode === "bypassPermissions") {
1240
1365
  options.permissionMode = "bypassPermissions";
@@ -1298,10 +1423,11 @@ var ClaudeAdapter = class extends BaseAdapter {
1298
1423
  abortController,
1299
1424
  env,
1300
1425
  cwd: process.cwd(),
1301
- settingSources: [],
1426
+ strictMcpConfig: true,
1302
1427
  ...flags.model ? { model: flags.model } : {},
1303
1428
  ...flags.maxTurns ? { maxTurns: flags.maxTurns } : {},
1304
- ...flags.systemPrompt ? { systemPrompt: flags.systemPrompt } : {}
1429
+ ...flags.systemPrompt ? { systemPrompt: flags.systemPrompt } : {},
1430
+ ...flags.mcpServers ? { mcpServers: flags.mcpServers } : {}
1305
1431
  };
1306
1432
  if (permissionMode === "bypassPermissions") {
1307
1433
  options.permissionMode = "bypassPermissions";
@@ -1380,7 +1506,7 @@ var ClaudeAdapter = class extends BaseAdapter {
1380
1506
  resume: nativeSessionId,
1381
1507
  maxTurns: 1,
1382
1508
  cwd: process.cwd(),
1383
- settingSources: []
1509
+ strictMcpConfig: true
1384
1510
  };
1385
1511
  if (permissionMode === "bypassPermissions") {
1386
1512
  options.permissionMode = "bypassPermissions";
@@ -3793,7 +3919,7 @@ function createVersionCommand(registry2) {
3793
3919
  description: "Show relay and backend versions"
3794
3920
  },
3795
3921
  async run() {
3796
- const relayVersion = "0.6.2";
3922
+ const relayVersion = "0.6.4";
3797
3923
  console.log(`agentic-relay v${relayVersion}`);
3798
3924
  console.log("");
3799
3925
  console.log("Backends:");
@@ -4143,7 +4269,7 @@ void configManager.getConfig().then((config) => {
4143
4269
  var main = defineCommand10({
4144
4270
  meta: {
4145
4271
  name: "relay",
4146
- version: "0.6.2",
4272
+ version: "0.6.4",
4147
4273
  description: "Unified CLI proxy for Claude Code, Codex CLI, and Gemini CLI"
4148
4274
  },
4149
4275
  subCommands: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rk0429/agentic-relay",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "Unified CLI proxy for Claude Code, Codex CLI, and Gemini CLI with MCP-based multi-layer sub-agent orchestration",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",