@radaros/transport 0.3.15 → 0.3.16

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 CHANGED
@@ -1,4 +1,4 @@
1
- import { Agent, A2AAgentCard, Registry, Servable, Team, Workflow, ToolDef, Toolkit, EventBus, VoiceAgent } from '@radaros/core';
1
+ import { Agent, A2AAgentCard, MCPToolProviderConfig, ToolDef, MCPToolProvider, Registry, Servable, Team, Workflow, Toolkit, EventBus, VoiceAgent } from '@radaros/core';
2
2
 
3
3
  interface A2AServerOptions {
4
4
  agents: Record<string, Agent>;
@@ -34,6 +34,85 @@ declare function generateMultiAgentCard(agents: Record<string, Agent>, serverUrl
34
34
  url?: string;
35
35
  }, version?: string): A2AAgentCard;
36
36
 
37
+ interface MCPServerEntry {
38
+ id: string;
39
+ config: MCPToolProviderConfig;
40
+ provider: MCPToolProvider;
41
+ status: "disconnected" | "connecting" | "connected" | "error";
42
+ error?: string;
43
+ toolCount: number;
44
+ connectedAt?: Date;
45
+ }
46
+ interface MCPServerSummary {
47
+ id: string;
48
+ name: string;
49
+ transport: string;
50
+ url?: string;
51
+ command?: string;
52
+ status: string;
53
+ toolCount: number;
54
+ error?: string;
55
+ connectedAt?: string;
56
+ }
57
+ /**
58
+ * Manages multiple MCP server connections at runtime.
59
+ * Servers can be added/removed/connected/disconnected dynamically,
60
+ * and their tools can be collected for injection into agents.
61
+ */
62
+ declare class MCPManager {
63
+ private servers;
64
+ /** Add a server config. Auto-generates an id from the name if not provided. */
65
+ add(config: MCPToolProviderConfig, id?: string): MCPServerSummary;
66
+ /** Connect a server by id. Discovers tools on success. */
67
+ connect(id: string): Promise<MCPServerSummary>;
68
+ /** Disconnect a server by id. */
69
+ disconnect(id: string): Promise<MCPServerSummary>;
70
+ /** Remove a server entirely. Disconnects first if connected. */
71
+ remove(id: string): Promise<void>;
72
+ /** Get all tools from all connected servers, merged into one array. */
73
+ getAllTools(): Promise<ToolDef[]>;
74
+ /** Get tools from a specific server. */
75
+ getTools(id: string): Promise<ToolDef[]>;
76
+ /** List all registered servers. */
77
+ list(): MCPServerSummary[];
78
+ /** Get a single server summary. */
79
+ get(id: string): MCPServerSummary;
80
+ has(id: string): boolean;
81
+ /** Disconnect all servers. */
82
+ closeAll(): Promise<void>;
83
+ private getEntry;
84
+ private summarize;
85
+ }
86
+
87
+ interface AdminRouterOptions {
88
+ /** Shared MCPManager instance. If omitted, a new one is created. */
89
+ mcpManager?: MCPManager;
90
+ }
91
+ /**
92
+ * Creates an Express sub-router with admin endpoints for managing
93
+ * MCP servers and the toolkit catalog at runtime.
94
+ *
95
+ * Mount under a prefix: `app.use("/admin", createAdminRouter())`
96
+ *
97
+ * Routes:
98
+ * GET /mcp — list MCP servers
99
+ * POST /mcp — add + connect an MCP server
100
+ * GET /mcp/:id — single server details
101
+ * POST /mcp/:id/connect — connect a server
102
+ * POST /mcp/:id/disconnect — disconnect
103
+ * DELETE /mcp/:id — remove a server
104
+ * GET /mcp/:id/tools — tools from a specific server
105
+ * GET /mcp/tools — all tools across connected servers
106
+ *
107
+ * GET /toolkits — list toolkit catalog
108
+ * GET /toolkits/:id — single toolkit meta
109
+ * POST /toolkits/:id — instantiate a toolkit with config
110
+ */
111
+ declare function createAdminRouter(opts?: AdminRouterOptions): {
112
+ router: any;
113
+ mcpManager: MCPManager;
114
+ };
115
+
37
116
  interface FileUploadOptions {
38
117
  maxFileSize?: number;
39
118
  maxFiles?: number;
@@ -108,6 +187,13 @@ interface RouterOptions {
108
187
  toolLibrary?: Record<string, ToolDef>;
109
188
  /** Toolkit instances whose tools are exposed via GET /tools. Merged with toolLibrary. */
110
189
  toolkits?: Toolkit[];
190
+ /**
191
+ * Enable admin routes under `/admin` for managing MCP servers and the toolkit catalog.
192
+ * Pass `true` to use defaults, or provide an MCPManager instance to share state.
193
+ */
194
+ admin?: boolean | {
195
+ mcpManager?: MCPManager;
196
+ };
111
197
  }
112
198
 
113
199
  declare function createAgentRouter(opts: RouterOptions): any;
@@ -238,4 +324,4 @@ interface VoiceGatewayOptions {
238
324
  }
239
325
  declare function createVoiceGateway(opts: VoiceGatewayOptions): void;
240
326
 
241
- export { type A2AServerOptions, type BrowserGatewayOptions, type FileUploadOptions, type GatewayOptions, type RouterOptions, type SwaggerOptions, type VoiceGatewayOptions, buildMultiModalInput, createA2AServer, createAgentGateway, createAgentRouter, createBrowserGateway, createFileUploadMiddleware, createVoiceGateway, errorHandler, generateAgentCard, generateMultiAgentCard, generateOpenAPISpec, requestLogger };
327
+ export { type A2AServerOptions, type AdminRouterOptions, type BrowserGatewayOptions, type FileUploadOptions, type GatewayOptions, MCPManager, type MCPServerEntry, type MCPServerSummary, type RouterOptions, type SwaggerOptions, type VoiceGatewayOptions, buildMultiModalInput, createA2AServer, createAdminRouter, createAgentGateway, createAgentRouter, createBrowserGateway, createFileUploadMiddleware, createVoiceGateway, errorHandler, generateAgentCard, generateMultiAgentCard, generateOpenAPISpec, requestLogger };
package/dist/index.js CHANGED
@@ -341,9 +341,263 @@ function handleTasksCancel(res, body, store) {
341
341
  });
342
342
  }
343
343
 
344
- // src/express/file-upload.ts
344
+ // src/express/admin-router.ts
345
345
  import { createRequire as createRequire2 } from "module";
346
+ import { toolkitCatalog } from "@radaros/core";
347
+
348
+ // src/express/mcp-manager.ts
349
+ import { MCPToolProvider } from "@radaros/core";
350
+ var MCPManager = class {
351
+ servers = /* @__PURE__ */ new Map();
352
+ /** Add a server config. Auto-generates an id from the name if not provided. */
353
+ add(config, id) {
354
+ const serverId = id ?? config.name;
355
+ if (this.servers.has(serverId)) {
356
+ throw new Error(`MCP server "${serverId}" already exists`);
357
+ }
358
+ const provider = new MCPToolProvider(config);
359
+ const entry = {
360
+ id: serverId,
361
+ config,
362
+ provider,
363
+ status: "disconnected",
364
+ toolCount: 0
365
+ };
366
+ this.servers.set(serverId, entry);
367
+ return this.summarize(entry);
368
+ }
369
+ /** Connect a server by id. Discovers tools on success. */
370
+ async connect(id) {
371
+ const entry = this.getEntry(id);
372
+ entry.status = "connecting";
373
+ entry.error = void 0;
374
+ try {
375
+ await entry.provider.connect();
376
+ const tools = await entry.provider.getTools();
377
+ entry.status = "connected";
378
+ entry.toolCount = tools.length;
379
+ entry.connectedAt = /* @__PURE__ */ new Date();
380
+ } catch (err) {
381
+ entry.status = "error";
382
+ entry.error = err.message;
383
+ }
384
+ return this.summarize(entry);
385
+ }
386
+ /** Disconnect a server by id. */
387
+ async disconnect(id) {
388
+ const entry = this.getEntry(id);
389
+ try {
390
+ await entry.provider.close();
391
+ } catch {
392
+ }
393
+ entry.status = "disconnected";
394
+ entry.toolCount = 0;
395
+ entry.connectedAt = void 0;
396
+ return this.summarize(entry);
397
+ }
398
+ /** Remove a server entirely. Disconnects first if connected. */
399
+ async remove(id) {
400
+ const entry = this.getEntry(id);
401
+ if (entry.status === "connected" || entry.status === "connecting") {
402
+ try {
403
+ await entry.provider.close();
404
+ } catch {
405
+ }
406
+ }
407
+ this.servers.delete(id);
408
+ }
409
+ /** Get all tools from all connected servers, merged into one array. */
410
+ async getAllTools() {
411
+ const all = [];
412
+ for (const entry of this.servers.values()) {
413
+ if (entry.status === "connected") {
414
+ try {
415
+ const tools = await entry.provider.getTools();
416
+ all.push(...tools);
417
+ } catch {
418
+ }
419
+ }
420
+ }
421
+ return all;
422
+ }
423
+ /** Get tools from a specific server. */
424
+ async getTools(id) {
425
+ const entry = this.getEntry(id);
426
+ if (entry.status !== "connected") {
427
+ throw new Error(`MCP server "${id}" is not connected`);
428
+ }
429
+ return entry.provider.getTools();
430
+ }
431
+ /** List all registered servers. */
432
+ list() {
433
+ return Array.from(this.servers.values()).map((e) => this.summarize(e));
434
+ }
435
+ /** Get a single server summary. */
436
+ get(id) {
437
+ return this.summarize(this.getEntry(id));
438
+ }
439
+ has(id) {
440
+ return this.servers.has(id);
441
+ }
442
+ /** Disconnect all servers. */
443
+ async closeAll() {
444
+ const promises = Array.from(this.servers.values()).map(async (entry) => {
445
+ if (entry.status === "connected") {
446
+ try {
447
+ await entry.provider.close();
448
+ } catch {
449
+ }
450
+ entry.status = "disconnected";
451
+ entry.toolCount = 0;
452
+ }
453
+ });
454
+ await Promise.all(promises);
455
+ }
456
+ getEntry(id) {
457
+ const entry = this.servers.get(id);
458
+ if (!entry) throw new Error(`MCP server "${id}" not found`);
459
+ return entry;
460
+ }
461
+ summarize(entry) {
462
+ return {
463
+ id: entry.id,
464
+ name: entry.config.name,
465
+ transport: entry.config.transport,
466
+ url: entry.config.url,
467
+ command: entry.config.command,
468
+ status: entry.status,
469
+ toolCount: entry.toolCount,
470
+ error: entry.error,
471
+ connectedAt: entry.connectedAt?.toISOString()
472
+ };
473
+ }
474
+ };
475
+
476
+ // src/express/admin-router.ts
346
477
  var _require2 = createRequire2(import.meta.url);
478
+ function createAdminRouter(opts) {
479
+ let express;
480
+ try {
481
+ express = _require2("express");
482
+ } catch {
483
+ throw new Error("express is required for createAdminRouter. Install it: npm install express");
484
+ }
485
+ const router = express.Router();
486
+ const mcpManager = opts?.mcpManager ?? new MCPManager();
487
+ router.get("/mcp", (_req, res) => {
488
+ res.json(mcpManager.list());
489
+ });
490
+ router.get("/mcp/tools", async (_req, res) => {
491
+ try {
492
+ const tools = await mcpManager.getAllTools();
493
+ res.json(
494
+ tools.map((t) => ({
495
+ name: t.name,
496
+ description: t.description,
497
+ parameters: Object.keys(t.parameters?.shape ?? {})
498
+ }))
499
+ );
500
+ } catch (err) {
501
+ res.status(500).json({ error: err.message });
502
+ }
503
+ });
504
+ router.post("/mcp", async (req, res) => {
505
+ try {
506
+ const { name, transport, url, command, args, env, headers, id, autoConnect } = req.body;
507
+ if (!name || !transport) {
508
+ return res.status(400).json({ error: "name and transport are required" });
509
+ }
510
+ const config = { name, transport };
511
+ if (url) config.url = url;
512
+ if (command) config.command = command;
513
+ if (args) config.args = args;
514
+ if (env) config.env = env;
515
+ if (headers) config.headers = headers;
516
+ const summary = mcpManager.add(config, id);
517
+ if (autoConnect !== false) {
518
+ const connected = await mcpManager.connect(summary.id);
519
+ return res.status(201).json(connected);
520
+ }
521
+ res.status(201).json(summary);
522
+ } catch (err) {
523
+ res.status(400).json({ error: err.message });
524
+ }
525
+ });
526
+ router.get("/mcp/:id", (req, res) => {
527
+ try {
528
+ res.json(mcpManager.get(req.params.id));
529
+ } catch (err) {
530
+ res.status(404).json({ error: err.message });
531
+ }
532
+ });
533
+ router.post("/mcp/:id/connect", async (req, res) => {
534
+ try {
535
+ const summary = await mcpManager.connect(req.params.id);
536
+ res.json(summary);
537
+ } catch (err) {
538
+ res.status(400).json({ error: err.message });
539
+ }
540
+ });
541
+ router.post("/mcp/:id/disconnect", async (req, res) => {
542
+ try {
543
+ const summary = await mcpManager.disconnect(req.params.id);
544
+ res.json(summary);
545
+ } catch (err) {
546
+ res.status(400).json({ error: err.message });
547
+ }
548
+ });
549
+ router.delete("/mcp/:id", async (req, res) => {
550
+ try {
551
+ await mcpManager.remove(req.params.id);
552
+ res.json({ ok: true });
553
+ } catch (err) {
554
+ res.status(404).json({ error: err.message });
555
+ }
556
+ });
557
+ router.get("/mcp/:id/tools", async (req, res) => {
558
+ try {
559
+ const tools = await mcpManager.getTools(req.params.id);
560
+ res.json(
561
+ tools.map((t) => ({
562
+ name: t.name,
563
+ description: t.description,
564
+ parameters: Object.keys(t.parameters?.shape ?? {})
565
+ }))
566
+ );
567
+ } catch (err) {
568
+ res.status(400).json({ error: err.message });
569
+ }
570
+ });
571
+ router.get("/toolkits", (_req, res) => {
572
+ res.json(toolkitCatalog.list());
573
+ });
574
+ router.get("/toolkits/:id", (req, res) => {
575
+ const meta = toolkitCatalog.get(req.params.id);
576
+ if (!meta) return res.status(404).json({ error: `Toolkit "${req.params.id}" not found` });
577
+ res.json(meta);
578
+ });
579
+ router.post("/toolkits/:id", (req, res) => {
580
+ try {
581
+ const toolkit = toolkitCatalog.create(req.params.id, req.body ?? {});
582
+ const tools = toolkit.getTools().map((t) => ({
583
+ name: t.name,
584
+ description: t.description
585
+ }));
586
+ res.status(201).json({
587
+ id: req.params.id,
588
+ name: toolkit.name,
589
+ tools
590
+ });
591
+ } catch (err) {
592
+ res.status(400).json({ error: err.message });
593
+ }
594
+ });
595
+ return { router, mcpManager };
596
+ }
597
+
598
+ // src/express/file-upload.ts
599
+ import { createRequire as createRequire3 } from "module";
600
+ var _require3 = createRequire3(import.meta.url);
347
601
  var MIME_TO_PART_TYPE = {
348
602
  "image/png": "image",
349
603
  "image/jpeg": "image",
@@ -365,7 +619,7 @@ function getPartType(mimeType) {
365
619
  function createFileUploadMiddleware(opts = {}) {
366
620
  let multer;
367
621
  try {
368
- multer = _require2("multer");
622
+ multer = _require3("multer");
369
623
  } catch {
370
624
  throw new Error("multer is required for file uploads. Install it: npm install multer");
371
625
  }
@@ -430,15 +684,15 @@ function requestLogger(options) {
430
684
  }
431
685
 
432
686
  // src/express/router-factory.ts
433
- import { createRequire as createRequire4 } from "module";
687
+ import { createRequire as createRequire5 } from "module";
434
688
  import { classifyServables, collectToolkitTools, describeToolLibrary, registry as globalRegistry } from "@radaros/core";
435
689
 
436
690
  // src/express/swagger.ts
437
- import { createRequire as createRequire3 } from "module";
438
- var _require3 = createRequire3(import.meta.url);
691
+ import { createRequire as createRequire4 } from "module";
692
+ var _require4 = createRequire4(import.meta.url);
439
693
  function zodSchemaToJsonSchema(schema) {
440
694
  try {
441
- const zodToJsonSchema = _require3("zod-to-json-schema").default ?? _require3("zod-to-json-schema");
695
+ const zodToJsonSchema = _require4("zod-to-json-schema").default ?? _require4("zod-to-json-schema");
442
696
  const result = zodToJsonSchema(schema, { target: "openApi3" });
443
697
  const { $schema, ...rest } = result;
444
698
  return rest;
@@ -861,7 +1115,7 @@ ${agentDesc}`,
861
1115
  function serveSwaggerUI(spec) {
862
1116
  let swaggerUiExpress;
863
1117
  try {
864
- swaggerUiExpress = _require3("swagger-ui-express");
1118
+ swaggerUiExpress = _require4("swagger-ui-express");
865
1119
  } catch {
866
1120
  throw new Error("swagger-ui-express is required for Swagger UI. Install it: npm install swagger-ui-express");
867
1121
  }
@@ -875,7 +1129,7 @@ function serveSwaggerUI(spec) {
875
1129
  }
876
1130
 
877
1131
  // src/express/router-factory.ts
878
- var _require4 = createRequire4(import.meta.url);
1132
+ var _require5 = createRequire5(import.meta.url);
879
1133
  function corsMiddleware(origins) {
880
1134
  return (req, res, next) => {
881
1135
  const origin = req.headers.origin;
@@ -968,7 +1222,7 @@ function createAgentRouter(opts) {
968
1222
  const reg = opts.registry === false ? null : opts.registry ?? globalRegistry;
969
1223
  let express;
970
1224
  try {
971
- express = _require4("express");
1225
+ express = _require5("express");
972
1226
  } catch {
973
1227
  throw new Error("express is required for createAgentRouter. Install it: npm install express");
974
1228
  }
@@ -1287,6 +1541,13 @@ function createAgentRouter(opts) {
1287
1541
  res.json(reg.list());
1288
1542
  });
1289
1543
  }
1544
+ if (opts.admin) {
1545
+ const adminOpts = typeof opts.admin === "object" ? opts.admin : {};
1546
+ const { router: adminRouter } = createAdminRouter({
1547
+ mcpManager: adminOpts.mcpManager
1548
+ });
1549
+ router.use("/admin", adminRouter);
1550
+ }
1290
1551
  const fromToolkits = opts.toolkits ? collectToolkitTools(opts.toolkits) : {};
1291
1552
  const mergedTools = { ...fromToolkits, ...opts.toolLibrary ?? {} };
1292
1553
  if (Object.keys(mergedTools).length > 0) {
@@ -1698,8 +1959,10 @@ function createVoiceGateway(opts) {
1698
1959
  });
1699
1960
  }
1700
1961
  export {
1962
+ MCPManager,
1701
1963
  buildMultiModalInput,
1702
1964
  createA2AServer,
1965
+ createAdminRouter,
1703
1966
  createAgentGateway,
1704
1967
  createAgentRouter,
1705
1968
  createBrowserGateway,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radaros/transport",
3
- "version": "0.3.15",
3
+ "version": "0.3.16",
4
4
  "description": "HTTP and WebSocket transport layer for RadarOS agents",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -39,7 +39,7 @@
39
39
  "typescript": "^5.6.0"
40
40
  },
41
41
  "peerDependencies": {
42
- "@radaros/core": "^0.3.15",
42
+ "@radaros/core": "^0.3.16",
43
43
  "@types/express": "^4.0.0 || ^5.0.0",
44
44
  "express": "^4.0.0 || ^5.0.0",
45
45
  "multer": ">=2.0.0",