@slashfi/agents-sdk 0.1.0 → 0.2.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/src/server.ts CHANGED
@@ -1,21 +1,28 @@
1
1
  /**
2
- * Agent Server
2
+ * Agent Server (MCP over HTTP)
3
3
  *
4
- * HTTP server that exposes the agent registry via JSON-RPC endpoints.
5
- * Compatible with MCP (Model Context Protocol) over HTTP.
4
+ * JSON-RPC server implementing the MCP protocol for agent interaction.
5
+ * Compatible with atlas-environments and any MCP client.
6
6
  *
7
- * Endpoints:
8
- * - POST /call - Execute agent actions (execute_tool, describe_tools, load)
9
- * - GET /list - List registered agents
10
- * - POST /oauth/token - OAuth2 token endpoint (when @auth is registered)
7
+ * MCP Methods:
8
+ * - initialize → Protocol handshake
9
+ * - tools/list List available MCP tools (call_agent, list_agents)
10
+ * - tools/call → Execute an MCP tool
11
+ *
12
+ * MCP Tools exposed:
13
+ * - call_agent → Execute a tool on a registered agent
14
+ * - list_agents → List registered agents and their tools
15
+ *
16
+ * Additional endpoints:
17
+ * - POST /oauth/token → OAuth2 client_credentials (when @auth registered)
18
+ * - GET /health → Health check
11
19
  *
12
20
  * Auth Integration:
13
21
  * When an `@auth` agent is registered, the server automatically:
14
22
  * - Validates Bearer tokens on requests
15
23
  * - Resolves tokens to identity + scopes
16
- * - Populates callerId, callerType in the request context
24
+ * - Populates caller context from headers (X-Atlas-Actor-Id, etc.)
17
25
  * - Recognizes the root key for admin access
18
- * - Mounts the /oauth/token endpoint
19
26
  */
20
27
 
21
28
  import type { AuthStore } from "./auth.js";
@@ -26,45 +33,52 @@ import type { AgentDefinition, CallAgentRequest, Visibility } from "./types.js";
26
33
  // Server Types
27
34
  // ============================================
28
35
 
29
- /**
30
- * Server configuration options.
31
- */
32
36
  export interface AgentServerOptions {
33
37
  /** Port to listen on (default: 3000) */
34
38
  port?: number;
35
-
36
39
  /** Hostname to bind to (default: 'localhost') */
37
40
  hostname?: string;
38
-
39
41
  /** Base path for endpoints (default: '') */
40
42
  basePath?: string;
41
-
42
43
  /** Enable CORS (default: true) */
43
44
  cors?: boolean;
44
-
45
- /** Custom request handler for unmatched routes */
46
- onNotFound?: (req: Request) => Response | Promise<Response>;
45
+ /** Server name reported in MCP initialize (default: 'agents-sdk') */
46
+ serverName?: string;
47
+ /** Server version reported in MCP initialize (default: '1.0.0') */
48
+ serverVersion?: string;
47
49
  }
48
50
 
49
- /**
50
- * Agent server instance.
51
- */
52
51
  export interface AgentServer {
53
52
  /** Start the server */
54
53
  start(): Promise<void>;
55
-
56
54
  /** Stop the server */
57
55
  stop(): Promise<void>;
58
-
59
56
  /** Handle a request (for custom integrations) */
60
57
  fetch(req: Request): Promise<Response>;
61
-
62
58
  /** Get the server URL (only available after start) */
63
59
  url: string | null;
64
60
  }
65
61
 
66
62
  // ============================================
67
- // Auth Integration Types
63
+ // JSON-RPC Types
64
+ // ============================================
65
+
66
+ interface JsonRpcRequest {
67
+ jsonrpc?: string;
68
+ id?: unknown;
69
+ method: string;
70
+ params?: Record<string, unknown>;
71
+ }
72
+
73
+ interface JsonRpcResponse {
74
+ jsonrpc: string;
75
+ id: unknown;
76
+ result?: unknown;
77
+ error?: { code: number; message: string; data?: unknown };
78
+ }
79
+
80
+ // ============================================
81
+ // Auth Types
68
82
  // ============================================
69
83
 
70
84
  interface AuthConfig {
@@ -81,15 +95,13 @@ interface ResolvedAuth {
81
95
  }
82
96
 
83
97
  // ============================================
84
- // Response Helpers
98
+ // Helpers
85
99
  // ============================================
86
100
 
87
101
  function jsonResponse(data: unknown, status = 200): Response {
88
102
  return new Response(JSON.stringify(data), {
89
103
  status,
90
- headers: {
91
- "Content-Type": "application/json",
92
- },
104
+ headers: { "Content-Type": "application/json" },
93
105
  });
94
106
  }
95
107
 
@@ -97,7 +109,34 @@ function corsHeaders(): Record<string, string> {
97
109
  return {
98
110
  "Access-Control-Allow-Origin": "*",
99
111
  "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
100
- "Access-Control-Allow-Headers": "Content-Type, Authorization",
112
+ "Access-Control-Allow-Headers":
113
+ "Content-Type, Authorization, X-Atlas-Actor-Id, X-Atlas-Agent-Id, X-Atlas-Session-Id",
114
+ };
115
+ }
116
+
117
+ function jsonRpcSuccess(id: unknown, result: unknown): JsonRpcResponse {
118
+ return { jsonrpc: "2.0", id, result };
119
+ }
120
+
121
+ function jsonRpcError(
122
+ id: unknown,
123
+ code: number,
124
+ message: string,
125
+ data?: unknown,
126
+ ): JsonRpcResponse {
127
+ return { jsonrpc: "2.0", id, error: { code, message, ...(data !== undefined && { data }) } };
128
+ }
129
+
130
+ /** Wrap a value as MCP tool result content */
131
+ function mcpResult(value: unknown, isError = false) {
132
+ return {
133
+ content: [
134
+ {
135
+ type: "text",
136
+ text: typeof value === "string" ? value : JSON.stringify(value, null, 2),
137
+ },
138
+ ],
139
+ ...(isError && { isError: true }),
101
140
  };
102
141
  }
103
142
 
@@ -133,23 +172,14 @@ async function resolveAuth(
133
172
  const [scheme, credential] = authHeader.split(" ", 2);
134
173
  if (scheme?.toLowerCase() !== "bearer" || !credential) return null;
135
174
 
136
- // Check root key
137
175
  if (credential === authConfig.rootKey) {
138
- return {
139
- callerId: "root",
140
- callerType: "system",
141
- scopes: ["*"],
142
- isRoot: true,
143
- };
176
+ return { callerId: "root", callerType: "system", scopes: ["*"], isRoot: true };
144
177
  }
145
178
 
146
- // Validate token
147
179
  const token = await authConfig.store.validateToken(credential);
148
180
  if (!token) return null;
149
181
 
150
- // Look up client name
151
182
  const client = await authConfig.store.getClient(token.clientId);
152
-
153
183
  return {
154
184
  callerId: client?.name ?? token.clientId,
155
185
  callerType: "agent",
@@ -158,42 +188,71 @@ async function resolveAuth(
158
188
  };
159
189
  }
160
190
 
161
- // ============================================
162
- // Visibility Filtering for /list
163
- // ============================================
164
-
165
- function canSeeAgent(
166
- agent: AgentDefinition,
167
- auth: ResolvedAuth | null,
168
- ): boolean {
169
- const visibility: Visibility = agent.visibility ?? "internal";
170
-
191
+ function canSeeAgent(agent: AgentDefinition, auth: ResolvedAuth | null): boolean {
192
+ const visibility = (agent.config?.visibility ?? "internal") as Visibility;
171
193
  if (auth?.isRoot) return true;
172
194
  if (visibility === "public") return true;
173
195
  if (visibility === "internal" && auth) return true;
174
196
  return false;
175
197
  }
176
198
 
199
+ // ============================================
200
+ // MCP Tool Definitions
201
+ // ============================================
202
+
203
+ function getToolDefinitions() {
204
+ return [
205
+ {
206
+ name: "call_agent",
207
+ description:
208
+ "Execute a tool on a registered agent. Provide the agent path and tool name.",
209
+ inputSchema: {
210
+ type: "object",
211
+ properties: {
212
+ request: {
213
+ type: "object",
214
+ description: "The call request",
215
+ properties: {
216
+ action: {
217
+ type: "string",
218
+ enum: ["execute_tool", "describe_tools", "load"],
219
+ description: "Action to perform",
220
+ },
221
+ path: {
222
+ type: "string",
223
+ description: "Agent path (e.g. '@registry')",
224
+ },
225
+ tool: {
226
+ type: "string",
227
+ description: "Tool name to call (for execute_tool)",
228
+ },
229
+ params: {
230
+ type: "object",
231
+ description: "Parameters for the tool",
232
+ additionalProperties: true,
233
+ },
234
+ },
235
+ required: ["action", "path"],
236
+ },
237
+ },
238
+ required: ["request"],
239
+ },
240
+ },
241
+ {
242
+ name: "list_agents",
243
+ description: "List all registered agents and their available tools.",
244
+ inputSchema: {
245
+ type: "object",
246
+ properties: {},
247
+ },
248
+ },
249
+ ];
250
+ }
251
+
177
252
  // ============================================
178
253
  // Create Server
179
254
  // ============================================
180
255
 
181
- /**
182
- * Create an HTTP server for the agent registry.
183
- *
184
- * @example
185
- * ```typescript
186
- * const registry = createAgentRegistry();
187
- * registry.register(createAuthAgent({ rootKey: 'rk_xxx' }));
188
- * registry.register(myAgent);
189
- *
190
- * const server = createAgentServer(registry, { port: 3000 });
191
- * await server.start();
192
- * // POST /call - Execute agent actions
193
- * // GET /list - List agents (filtered by auth)
194
- * // POST /oauth/token - OAuth2 token endpoint
195
- * ```
196
- */
197
256
  export function createAgentServer(
198
257
  registry: AgentRegistry,
199
258
  options: AgentServerOptions = {},
@@ -203,31 +262,210 @@ export function createAgentServer(
203
262
  hostname = "localhost",
204
263
  basePath = "",
205
264
  cors = true,
206
- onNotFound,
265
+ serverName = "agents-sdk",
266
+ serverVersion = "1.0.0",
207
267
  } = options;
208
268
 
209
269
  let serverInstance: ReturnType<typeof Bun.serve> | null = null;
210
270
  let serverUrl: string | null = null;
211
271
 
212
- // Detect auth configuration
213
272
  const authConfig = detectAuth(registry);
214
273
 
215
- /**
216
- * Handle incoming requests.
217
- */
274
+ // ──────────────────────────────────────────
275
+ // MCP JSON-RPC handler
276
+ // ──────────────────────────────────────────
277
+
278
+ async function handleJsonRpc(
279
+ request: JsonRpcRequest,
280
+ auth: ResolvedAuth | null,
281
+ ): Promise<JsonRpcResponse> {
282
+ switch (request.method) {
283
+ // MCP protocol handshake
284
+ case "initialize":
285
+ return jsonRpcSuccess(request.id, {
286
+ protocolVersion: "2024-11-05",
287
+ capabilities: { tools: {} },
288
+ serverInfo: { name: serverName, version: serverVersion },
289
+ });
290
+
291
+ case "notifications/initialized":
292
+ return jsonRpcSuccess(request.id, {});
293
+
294
+ // List MCP tools
295
+ case "tools/list":
296
+ return jsonRpcSuccess(request.id, {
297
+ tools: getToolDefinitions(),
298
+ });
299
+
300
+ // Call an MCP tool
301
+ case "tools/call": {
302
+ const { name, arguments: args } = (request.params ?? {}) as {
303
+ name: string;
304
+ arguments?: Record<string, unknown>;
305
+ };
306
+
307
+ try {
308
+ const result = await handleToolCall(name, args ?? {}, auth);
309
+ return jsonRpcSuccess(request.id, result);
310
+ } catch (err) {
311
+ return jsonRpcSuccess(
312
+ request.id,
313
+ mcpResult(
314
+ `Error: ${err instanceof Error ? err.message : String(err)}`,
315
+ true,
316
+ ),
317
+ );
318
+ }
319
+ }
320
+
321
+ default:
322
+ return jsonRpcError(
323
+ request.id,
324
+ -32601,
325
+ `Method not found: ${request.method}`,
326
+ );
327
+ }
328
+ }
329
+
330
+ // ──────────────────────────────────────────
331
+ // MCP tool implementations
332
+ // ──────────────────────────────────────────
333
+
334
+ async function handleToolCall(
335
+ toolName: string,
336
+ args: Record<string, unknown>,
337
+ auth: ResolvedAuth | null,
338
+ ) {
339
+ switch (toolName) {
340
+ case "call_agent": {
341
+ const req = (args.request ?? args) as CallAgentRequest;
342
+
343
+ // Inject auth context
344
+ if (auth) {
345
+ req.callerId = auth.callerId;
346
+ req.callerType = auth.callerType;
347
+ if (!req.metadata) req.metadata = {};
348
+ req.metadata.scopes = auth.scopes;
349
+ req.metadata.isRoot = auth.isRoot;
350
+ }
351
+ if (auth?.isRoot) {
352
+ req.callerType = "system";
353
+ }
354
+
355
+ const result = await registry.call(req);
356
+ return mcpResult(result);
357
+ }
358
+
359
+ case "list_agents": {
360
+ const agents = registry.list();
361
+ const visible = agents.filter((agent) => canSeeAgent(agent, auth));
362
+
363
+ return mcpResult({
364
+ success: true,
365
+ agents: visible.map((agent) => ({
366
+ path: agent.path,
367
+ name: agent.config?.name,
368
+ description: agent.config?.description,
369
+ supportedActions: agent.config?.supportedActions,
370
+ tools: agent.tools
371
+ .filter((t) => {
372
+ const tv = t.visibility ?? "internal";
373
+ if (auth?.isRoot) return true;
374
+ if (tv === "public") return true;
375
+ if (tv === "internal" && auth) return true;
376
+ return false;
377
+ })
378
+ .map((t) => t.name),
379
+ })),
380
+ });
381
+ }
382
+
383
+ default:
384
+ throw new Error(`Unknown tool: ${toolName}`);
385
+ }
386
+ }
387
+
388
+ // ──────────────────────────────────────────
389
+ // OAuth2 token handler (unchanged)
390
+ // ──────────────────────────────────────────
391
+
392
+ async function handleOAuthToken(req: Request): Promise<Response> {
393
+ if (!authConfig) {
394
+ return jsonResponse({ error: "auth_not_configured" }, 404);
395
+ }
396
+
397
+ const contentType = req.headers.get("Content-Type") ?? "";
398
+ let grantType: string;
399
+ let clientId: string;
400
+ let clientSecret: string;
401
+
402
+ if (contentType.includes("application/x-www-form-urlencoded")) {
403
+ const body = await req.text();
404
+ const params = new URLSearchParams(body);
405
+ grantType = params.get("grant_type") ?? "";
406
+ clientId = params.get("client_id") ?? "";
407
+ clientSecret = params.get("client_secret") ?? "";
408
+ } else {
409
+ const body = (await req.json()) as Record<string, string>;
410
+ grantType = body.grant_type ?? "";
411
+ clientId = body.client_id ?? "";
412
+ clientSecret = body.client_secret ?? "";
413
+ }
414
+
415
+ if (grantType !== "client_credentials") {
416
+ return jsonResponse(
417
+ { error: "unsupported_grant_type", error_description: "Only client_credentials is supported" },
418
+ 400,
419
+ );
420
+ }
421
+
422
+ if (!clientId || !clientSecret) {
423
+ return jsonResponse(
424
+ { error: "invalid_request", error_description: "Missing client_id or client_secret" },
425
+ 400,
426
+ );
427
+ }
428
+
429
+ const client = await authConfig.store.validateClient(clientId, clientSecret);
430
+ if (!client) {
431
+ return jsonResponse(
432
+ { error: "invalid_client", error_description: "Invalid client credentials" },
433
+ 401,
434
+ );
435
+ }
436
+
437
+ const tokenString = `at_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
438
+ const now = Date.now();
439
+
440
+ await authConfig.store.storeToken({
441
+ token: tokenString,
442
+ clientId: client.clientId,
443
+ scopes: client.scopes,
444
+ issuedAt: now,
445
+ expiresAt: now + authConfig.tokenTtl * 1000,
446
+ });
447
+
448
+ return jsonResponse({
449
+ access_token: tokenString,
450
+ token_type: "Bearer",
451
+ expires_in: authConfig.tokenTtl,
452
+ scope: client.scopes.join(" "),
453
+ });
454
+ }
455
+
456
+ // ──────────────────────────────────────────
457
+ // HTTP request handler
458
+ // ─��────────────────────────────────────────
459
+
218
460
  async function fetch(req: Request): Promise<Response> {
219
461
  const url = new URL(req.url);
220
462
  const path = url.pathname.replace(basePath, "") || "/";
221
463
 
222
- // Handle CORS preflight
464
+ // CORS preflight
223
465
  if (cors && req.method === "OPTIONS") {
224
- return new Response(null, {
225
- status: 204,
226
- headers: corsHeaders(),
227
- });
466
+ return new Response(null, { status: 204, headers: corsHeaders() });
228
467
  }
229
468
 
230
- // Add CORS headers to response
231
469
  const addCors = (response: Response): Response => {
232
470
  if (!cors) return response;
233
471
  const headers = new Headers(response.headers);
@@ -241,133 +479,30 @@ export function createAgentServer(
241
479
  });
242
480
  };
243
481
 
244
- // Resolve auth on every request
245
482
  const auth = authConfig ? await resolveAuth(req, authConfig) : null;
246
483
 
247
484
  try {
248
- // POST /oauth/token - Standard OAuth2 endpoint
249
- if (path === "/oauth/token" && req.method === "POST" && authConfig) {
250
- const contentType = req.headers.get("Content-Type") ?? "";
251
- let grantType: string;
252
- let clientId: string;
253
- let clientSecret: string;
254
-
255
- if (contentType.includes("application/x-www-form-urlencoded")) {
256
- const body = await req.text();
257
- const params = new URLSearchParams(body);
258
- grantType = params.get("grant_type") ?? "";
259
- clientId = params.get("client_id") ?? "";
260
- clientSecret = params.get("client_secret") ?? "";
261
- } else {
262
- const body = (await req.json()) as Record<string, string>;
263
- grantType = body.grant_type ?? "";
264
- clientId = body.client_id ?? "";
265
- clientSecret = body.client_secret ?? "";
266
- }
267
-
268
- if (grantType !== "client_credentials") {
269
- return addCors(
270
- jsonResponse(
271
- {
272
- error: "unsupported_grant_type",
273
- error_description: "Only client_credentials is supported",
274
- },
275
- 400,
276
- ),
277
- );
278
- }
279
-
280
- if (!clientId || !clientSecret) {
281
- return addCors(
282
- jsonResponse(
283
- {
284
- error: "invalid_request",
285
- error_description: "Missing client_id or client_secret",
286
- },
287
- 400,
288
- ),
289
- );
290
- }
291
-
292
- const client = await authConfig.store.validateClient(
293
- clientId,
294
- clientSecret,
295
- );
296
- if (!client) {
297
- return addCors(
298
- jsonResponse(
299
- {
300
- error: "invalid_client",
301
- error_description: "Invalid client credentials",
302
- },
303
- 401,
304
- ),
305
- );
306
- }
307
-
308
- // Generate token
309
- const tokenString = `at_${Array.from({ length: 48 }, () => "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"[Math.floor(Math.random() * 62)]).join("")}`;
310
- const token = {
311
- token: tokenString,
312
- clientId: client.clientId,
313
- scopes: client.scopes,
314
- issuedAt: Date.now(),
315
- expiresAt: Date.now() + authConfig.tokenTtl * 1000,
316
- };
317
- await authConfig.store.storeToken(token);
318
-
319
- // Standard OAuth2 response
320
- return addCors(
321
- jsonResponse({
322
- access_token: token.token,
323
- token_type: "bearer",
324
- expires_in: authConfig.tokenTtl,
325
- scope: client.scopes.join(" "),
326
- }),
327
- );
485
+ // MCP endpoint: POST / or POST /mcp
486
+ if ((path === "/" || path === "/mcp") && req.method === "POST") {
487
+ const body = (await req.json()) as JsonRpcRequest;
488
+ const response = await handleJsonRpc(body, auth);
489
+ return addCors(jsonResponse(response));
328
490
  }
329
491
 
330
- // POST /call - Execute agent action
331
- if (path === "/call" && req.method === "POST") {
332
- const body = (await req.json()) as CallAgentRequest;
333
-
334
- if (!body.path || !body.action) {
335
- return addCors(
336
- jsonResponse(
337
- {
338
- success: false,
339
- error: "Missing required fields: path, action",
340
- code: "INVALID_REQUEST",
341
- },
342
- 400,
343
- ),
344
- );
345
- }
346
-
347
- // Inject auth context into request
348
- if (auth) {
349
- body.callerId = auth.callerId;
350
- body.callerType = auth.callerType;
351
- if (!body.metadata) body.metadata = {};
352
- body.metadata.scopes = auth.scopes;
353
- body.metadata.isRoot = auth.isRoot;
354
- }
355
-
356
- // Root key bypasses all access checks
357
- if (auth?.isRoot) {
358
- body.callerType = "system";
359
- }
492
+ // OAuth2 token endpoint
493
+ if (path === "/oauth/token" && req.method === "POST") {
494
+ return addCors(await handleOAuthToken(req));
495
+ }
360
496
 
361
- const result = await registry.call(body);
362
- const status = "success" in result && result.success ? 200 : 400;
363
- return addCors(jsonResponse(result, status));
497
+ // Health check
498
+ if (path === "/health" && req.method === "GET") {
499
+ return addCors(jsonResponse({ status: "ok" }));
364
500
  }
365
501
 
366
- // GET /list - List agents (filtered by visibility)
502
+ // Backwards compat: GET /list (returns agents directly)
367
503
  if (path === "/list" && req.method === "GET") {
368
504
  const agents = registry.list();
369
505
  const visible = agents.filter((agent) => canSeeAgent(agent, auth));
370
-
371
506
  return addCors(
372
507
  jsonResponse({
373
508
  success: true,
@@ -390,55 +525,37 @@ export function createAgentServer(
390
525
  );
391
526
  }
392
527
 
393
- // Not found
394
- if (onNotFound) {
395
- return addCors(await onNotFound(req));
396
- }
397
-
398
- return addCors(
399
- jsonResponse(
400
- {
401
- success: false,
402
- error: `Not found: ${req.method} ${path}`,
403
- code: "NOT_FOUND",
404
- },
405
- 404,
406
- ),
407
- );
528
+ return addCors(jsonResponse({ jsonrpc: "2.0", id: null, error: { code: -32601, message: `Not found: ${req.method} ${path}` } }, 404));
408
529
  } catch (err) {
409
530
  return addCors(
410
531
  jsonResponse(
411
- {
412
- success: false,
413
- error: err instanceof Error ? err.message : String(err),
414
- code: "INTERNAL_ERROR",
415
- },
532
+ { jsonrpc: "2.0", id: null, error: { code: -32603, message: "Internal error" } },
416
533
  500,
417
534
  ),
418
535
  );
419
536
  }
420
537
  }
421
538
 
539
+ // ──────────────────────────────────────────
540
+ // Server lifecycle
541
+ // ──────────────────────────────────────────
542
+
422
543
  const server: AgentServer = {
423
544
  async start(): Promise<void> {
424
- if (serverInstance) {
425
- throw new Error("Server is already running");
426
- }
427
-
428
- serverInstance = Bun.serve({
429
- port,
430
- hostname,
431
- fetch,
432
- });
545
+ if (serverInstance) throw new Error("Server is already running");
433
546
 
547
+ serverInstance = Bun.serve({ port, hostname, fetch });
434
548
  serverUrl = `http://${hostname}:${port}${basePath}`;
549
+
435
550
  console.log(`Agent server running at ${serverUrl}`);
436
- console.log(` POST ${basePath}/call - Execute agent actions`);
437
- console.log(` GET ${basePath}/list - List agents`);
551
+ console.log(` POST / - MCP JSON-RPC endpoint`);
552
+ console.log(` POST /mcp - MCP JSON-RPC endpoint (alias)`);
553
+ console.log(` GET /health - Health check`);
438
554
  if (authConfig) {
439
- console.log(` POST ${basePath}/oauth/token - OAuth2 token endpoint`);
440
- console.log(" Auth: enabled (root key configured)");
555
+ console.log(` POST /oauth/token - OAuth2 token endpoint`);
556
+ console.log(" Auth: enabled");
441
557
  }
558
+ console.log(` MCP tools: call_agent, list_agents`);
442
559
  },
443
560
 
444
561
  async stop(): Promise<void> {