@slashfi/agents-sdk 0.8.0 → 0.10.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.
Files changed (59) hide show
  1. package/dist/agent-definitions/auth.d.ts +17 -0
  2. package/dist/agent-definitions/auth.d.ts.map +1 -1
  3. package/dist/agent-definitions/auth.js +135 -1
  4. package/dist/agent-definitions/auth.js.map +1 -1
  5. package/dist/agent-definitions/integrations.d.ts +19 -0
  6. package/dist/agent-definitions/integrations.d.ts.map +1 -1
  7. package/dist/agent-definitions/integrations.js +218 -5
  8. package/dist/agent-definitions/integrations.js.map +1 -1
  9. package/dist/index.d.ts +9 -4
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +6 -2
  12. package/dist/index.js.map +1 -1
  13. package/dist/integration-interface.d.ts +37 -0
  14. package/dist/integration-interface.d.ts.map +1 -0
  15. package/dist/integration-interface.js +94 -0
  16. package/dist/integration-interface.js.map +1 -0
  17. package/dist/integrations-store.d.ts +33 -0
  18. package/dist/integrations-store.d.ts.map +1 -0
  19. package/dist/integrations-store.js +50 -0
  20. package/dist/integrations-store.js.map +1 -0
  21. package/dist/jwt.d.ts +86 -17
  22. package/dist/jwt.d.ts.map +1 -1
  23. package/dist/jwt.js +140 -17
  24. package/dist/jwt.js.map +1 -1
  25. package/dist/registry.d.ts +7 -0
  26. package/dist/registry.d.ts.map +1 -1
  27. package/dist/registry.js +8 -21
  28. package/dist/registry.js.map +1 -1
  29. package/dist/secret-collection.d.ts +37 -0
  30. package/dist/secret-collection.d.ts.map +1 -0
  31. package/dist/secret-collection.js +37 -0
  32. package/dist/secret-collection.js.map +1 -0
  33. package/dist/server.d.ts +41 -44
  34. package/dist/server.d.ts.map +1 -1
  35. package/dist/server.js +236 -592
  36. package/dist/server.js.map +1 -1
  37. package/dist/types.d.ts +7 -1
  38. package/dist/types.d.ts.map +1 -1
  39. package/package.json +5 -2
  40. package/src/agent-definitions/auth.ts +187 -1
  41. package/src/agent-definitions/integrations.ts +260 -5
  42. package/src/index.ts +18 -4
  43. package/src/integration-interface.ts +118 -0
  44. package/src/integrations-store.ts +84 -0
  45. package/src/jwt.ts +233 -65
  46. package/src/registry.ts +17 -2
  47. package/src/secret-collection.ts +66 -0
  48. package/src/server.ts +272 -681
  49. package/src/types.ts +8 -1
  50. package/dist/slack-oauth.d.ts +0 -27
  51. package/dist/slack-oauth.d.ts.map +0 -1
  52. package/dist/slack-oauth.js +0 -48
  53. package/dist/slack-oauth.js.map +0 -1
  54. package/dist/web-pages.d.ts +0 -8
  55. package/dist/web-pages.d.ts.map +0 -1
  56. package/dist/web-pages.js +0 -169
  57. package/dist/web-pages.js.map +0 -1
  58. package/src/slack-oauth.ts +0 -66
  59. package/src/web-pages.ts +0 -178
package/dist/server.js CHANGED
@@ -1,75 +1,34 @@
1
1
  /**
2
2
  * Agent Server (MCP over HTTP)
3
3
  *
4
- * JSON-RPC server implementing the MCP protocol for agent interaction.
5
- * Compatible with atlas-environments and any MCP client.
4
+ * Minimal JSON-RPC server implementing the MCP protocol for agent interaction.
5
+ * Handles only core SDK concerns:
6
+ * - MCP protocol (initialize, tools/list, tools/call)
7
+ * - Agent registry routing (call_agent, list_agents)
8
+ * - Auth resolution (Bearer tokens, root key, JWT)
9
+ * - OAuth2 token exchange (client_credentials)
10
+ * - Health check
11
+ * - CORS
6
12
  *
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
13
+ * Application-specific routes (web UI, OAuth callbacks, tenant management)
14
+ * should be built on top using the exported `fetch` handler.
11
15
  *
12
- * MCP Tools exposed:
13
- * - call_agent → Execute a tool on a registered agent
14
- * - list_agents → List registered agents and their tools
16
+ * @example
17
+ * ```typescript
18
+ * // Standalone usage
19
+ * const server = createAgentServer(registry, { port: 3000 });
20
+ * await server.start();
15
21
  *
16
- * Additional endpoints:
17
- * - POST /oauth/token → OAuth2 client_credentials (when @auth registered)
18
- * - GET /oauth/callback → Unified OAuth callback (provider from state)
19
- * - GET /integrations/callback/* → Legacy OAuth callback (provider from URL path)
20
- * - GET /health → Health check
21
- *
22
- * Auth Integration:
23
- * When an `@auth` agent is registered, the server automatically:
24
- * - Validates Bearer tokens on requests
25
- * - Resolves tokens to identity + scopes
26
- * - Populates caller context from headers (X-Atlas-Actor-Id, etc.)
27
- * - Recognizes the root key for admin access
22
+ * // Composable with any HTTP framework
23
+ * const server = createAgentServer(registry);
24
+ * app.all('/mcp/*', (req) => server.fetch(req));
25
+ * ```
28
26
  */
29
27
  import { processSecretParams, } from "./agent-definitions/secrets.js";
30
28
  import { verifyJwt } from "./jwt.js";
31
- import { renderLoginPage, renderDashboardPage, renderTenantPage } from "./web-pages.js";
32
- import { slackAuthUrl, exchangeSlackCode, getSlackProfile } from "./slack-oauth.js";
33
- function resolveBaseUrl(req, url) {
34
- const proto = req.headers.get("x-forwarded-proto") || url.protocol.replace(":", "");
35
- const host = req.headers.get("x-forwarded-host") || url.host;
36
- return `${proto}://${host}`;
37
- }
29
+ import { generateSigningKey, importSigningKey, exportSigningKey, buildJwks, verifyJwtLocal, verifyJwtFromIssuer } from "./jwt.js";
38
30
  // ============================================
39
- // Secrets Collection (one-time tokens)
40
- // ============================================
41
- function escHtml(s) {
42
- return s.replace(/&/g, "\&amp;").replace(/</g, "\&lt;").replace(/>/g, "\&gt;").replace(/"/g, "\&quot;");
43
- }
44
- function renderSecretForm(token, pending, baseUrl) {
45
- const fields = pending.fields.map(f => `
46
- <div class="field">
47
- <label>${escHtml(f.name)}${f.secret ? ` <span class="badge">SECRET</span>` : ""}${f.required ? ` <span class="req">*</span>` : ""}</label>
48
- ${f.description ? `<p class="desc">${escHtml(f.description)}</p>` : ""}
49
- <input type="${f.secret ? "password" : "text"}" name="${escHtml(f.name)}" ${f.required ? "required" : ""} autocomplete="off" />
50
- </div>`).join("");
51
- return `<!DOCTYPE html>
52
- <html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Secure Setup</title>
53
- <style>*{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#0d1117;color:#c9d1d9;min-height:100vh;display:flex;align-items:center;justify-content:center}.card{background:#161b22;border:1px solid #30363d;border-radius:12px;padding:32px;max-width:480px;width:100%}.header{display:flex;align-items:center;gap:12px;margin-bottom:8px}.lock{font-size:24px}h1{font-size:20px;font-weight:600}.subtitle{color:#8b949e;font-size:14px;margin-bottom:24px}.shield{display:inline-flex;align-items:center;gap:4px;background:#1a2332;border:1px solid #1f6feb33;color:#58a6ff;font-size:12px;padding:2px 8px;border-radius:12px;margin-bottom:20px}label{display:block;font-size:14px;font-weight:500;margin-bottom:6px}.desc{font-size:12px;color:#8b949e;margin-bottom:4px}.badge{background:#3d1f00;color:#f0883e;font-size:10px;padding:1px 6px;border-radius:4px}.req{color:#f85149}input{width:100%;padding:10px 12px;background:#0d1117;border:1px solid #30363d;border-radius:6px;color:#c9d1d9;font-size:14px;margin-bottom:16px;outline:none}input:focus{border-color:#58a6ff;box-shadow:0 0 0 3px #1f6feb33}button{width:100%;padding:12px;background:#238636;border:none;border-radius:6px;color:#fff;font-size:14px;font-weight:600;cursor:pointer}button:hover{background:#2ea043}button:disabled{opacity:.5;cursor:not-allowed}.footer{text-align:center;margin-top:16px;font-size:12px;color:#484f58}.error{background:#3d1418;border:1px solid #f8514966;color:#f85149;padding:10px 12px;border-radius:6px;font-size:13px;margin-bottom:16px;display:none}.ok{text-align:center;padding:40px 0}.ok .icon{font-size:48px;margin-bottom:12px}.ok h2{font-size:18px;margin-bottom:8px;color:#3fb950}.ok p{color:#8b949e;font-size:14px}.field{position:relative}</style></head><body>
54
- <div class="card" id="fc"><div class="header"><span class="lock">🔐</span><h1>${escHtml(pending.tool)} on ${escHtml(pending.agent)}</h1></div>
55
- <p class="subtitle">Enter credentials below. They are encrypted and stored securely — they never pass through the AI.</p>
56
- <div class="shield">🛡️ End-to-end encrypted</div><div id="err" class="error"></div>
57
- <form id="f">${fields}<button type="submit">Submit Securely</button></form>
58
- <p class="footer">Expires in 10 minutes</p></div>
59
- <div class="card ok" id="ok" style="display:none"><div class="icon">✅</div><h2>Done</h2><p>Credentials stored securely. You can close this window.</p></div>
60
- <script>document.getElementById("f").addEventListener("submit",async e=>{e.preventDefault();const b=e.target.querySelector("button");b.disabled=true;b.textContent="Submitting...";try{const fd=new FormData(e.target),vals=Object.fromEntries(fd.entries());const r=await fetch("${baseUrl}/secrets/collect",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:"${token}",values:vals})});const d=await r.json();if(d.success){document.getElementById("fc").style.display="none";document.getElementById("ok").style.display="block";}else throw new Error(d.error?.message||JSON.stringify(d));}catch(err){const el=document.getElementById("err");el.textContent=err.message;el.style.display="block";b.disabled=false;b.textContent="Submit Securely";}});</script></body></html>`;
61
- }
62
- export const pendingCollections = new Map();
63
- export function generateCollectionToken() {
64
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
65
- let token = "sc_";
66
- for (let i = 0; i < 32; i++) {
67
- token += chars[Math.floor(Math.random() * chars.length)];
68
- }
69
- return token;
70
- }
71
- // ============================================
72
- // Helpers
31
+ // HTTP Helpers
73
32
  // ============================================
74
33
  function jsonResponse(data, status = 200) {
75
34
  return new Response(JSON.stringify(data), {
@@ -81,9 +40,19 @@ function corsHeaders() {
81
40
  return {
82
41
  "Access-Control-Allow-Origin": "*",
83
42
  "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
84
- "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Atlas-Actor-Id, X-Atlas-Agent-Id, X-Atlas-Session-Id",
43
+ "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Atlas-Actor-Id, X-Atlas-Actor-Type",
85
44
  };
86
45
  }
46
+ function addCors(res) {
47
+ const headers = new Headers(res.headers);
48
+ for (const [k, v] of Object.entries(corsHeaders())) {
49
+ headers.set(k, v);
50
+ }
51
+ return new Response(res.body, { status: res.status, headers });
52
+ }
53
+ // ============================================
54
+ // JSON-RPC Helpers
55
+ // ============================================
87
56
  function jsonRpcSuccess(id, result) {
88
57
  return { jsonrpc: "2.0", id, result };
89
58
  }
@@ -107,9 +76,9 @@ function mcpResult(value, isError = false) {
107
76
  };
108
77
  }
109
78
  // ============================================
110
- // Auth Detection
79
+ // Auth Detection & Resolution
111
80
  // ============================================
112
- function detectAuth(registry) {
81
+ export function detectAuth(registry) {
113
82
  const authAgent = registry.get("@auth");
114
83
  if (!authAgent?.__authStore || !authAgent.__rootKey)
115
84
  return null;
@@ -119,13 +88,14 @@ function detectAuth(registry) {
119
88
  tokenTtl: authAgent.__tokenTtl ?? 3600,
120
89
  };
121
90
  }
122
- async function resolveAuth(req, authConfig) {
91
+ export async function resolveAuth(req, authConfig, jwksOptions) {
123
92
  const authHeader = req.headers.get("Authorization");
124
93
  if (!authHeader)
125
94
  return null;
126
95
  const [scheme, credential] = authHeader.split(" ", 2);
127
96
  if (scheme?.toLowerCase() !== "bearer" || !credential)
128
97
  return null;
98
+ // Root key check
129
99
  if (credential === authConfig.rootKey) {
130
100
  return {
131
101
  callerId: "root",
@@ -134,18 +104,52 @@ async function resolveAuth(req, authConfig) {
134
104
  isRoot: true,
135
105
  };
136
106
  }
137
- // Try JWT verification first (stateless)
138
- // JWT is signed with the client's secret hash
139
- // Decode payload to get client_id, look up client, verify signature
107
+ // Try ES256 verification against own signing keys
140
108
  const parts = credential.split(".");
109
+ if (parts.length === 3 && jwksOptions?.signingKeys?.length) {
110
+ for (const key of jwksOptions.signingKeys) {
111
+ try {
112
+ const verified = await verifyJwtLocal(credential, key.publicKey);
113
+ if (verified) {
114
+ return {
115
+ callerId: verified.sub ?? verified.name ?? "unknown",
116
+ callerType: "agent",
117
+ scopes: verified.scopes ?? ["*"],
118
+ isRoot: false,
119
+ };
120
+ }
121
+ }
122
+ catch {
123
+ continue;
124
+ }
125
+ }
126
+ }
127
+ // Try trusted issuers (remote JWKS verification)
128
+ if (parts.length === 3 && jwksOptions?.trustedIssuers?.length) {
129
+ for (const issuer of jwksOptions.trustedIssuers) {
130
+ try {
131
+ const verified = await verifyJwtFromIssuer(credential, issuer);
132
+ if (verified) {
133
+ return {
134
+ callerId: verified.sub ?? verified.name ?? "unknown",
135
+ callerType: "agent",
136
+ scopes: verified.scopes ?? ["*"],
137
+ isRoot: false,
138
+ };
139
+ }
140
+ }
141
+ catch {
142
+ continue;
143
+ }
144
+ }
145
+ }
146
+ // Try HMAC JWT verification (legacy, stateless)
141
147
  if (parts.length === 3) {
142
- // Looks like a JWT - decode payload to get client_id
143
148
  try {
144
149
  const payloadB64 = parts[1];
145
150
  const padded = payloadB64.replace(/-/g, "+").replace(/_/g, "/");
146
151
  const payload = JSON.parse(atob(padded));
147
152
  if (payload.sub) {
148
- // Look up client to get the signing secret (secret hash)
149
153
  const client = await authConfig.store.getClient(payload.sub);
150
154
  if (client) {
151
155
  const verified = await verifyJwt(credential, client.clientSecretHash);
@@ -176,7 +180,7 @@ async function resolveAuth(req, authConfig) {
176
180
  isRoot: false,
177
181
  };
178
182
  }
179
- function canSeeAgent(agent, auth) {
183
+ export function canSeeAgent(agent, auth) {
180
184
  const visibility = (agent.visibility ??
181
185
  agent.config?.visibility ??
182
186
  "internal");
@@ -210,11 +214,11 @@ function getToolDefinitions() {
210
214
  },
211
215
  path: {
212
216
  type: "string",
213
- description: "Agent path (e.g. '@registry')",
217
+ description: "Agent path (e.g., '@my-agent')",
214
218
  },
215
219
  tool: {
216
220
  type: "string",
217
- description: "Tool name to call (for execute_tool)",
221
+ description: "Tool name to call",
218
222
  },
219
223
  params: {
220
224
  type: "object",
@@ -243,29 +247,28 @@ function getToolDefinitions() {
243
247
  // ============================================
244
248
  export function createAgentServer(registry, options = {}) {
245
249
  const { port = 3000, hostname = "localhost", basePath = "", cors = true, serverName = "agents-sdk", serverVersion = "1.0.0", secretStore, } = options;
246
- let serverInstance = null;
247
- let serverUrl = null;
250
+ // Signing keys for JWKS-based auth
251
+ const serverSigningKeys = [];
252
+ const configTrustedIssuers = options.trustedIssuers ?? [];
248
253
  const authConfig = detectAuth(registry);
254
+ let serverInstance = null;
249
255
  // ──────────────────────────────────────────
250
- // MCP JSON-RPC handler
256
+ // JSON-RPC handler
251
257
  // ──────────────────────────────────────────
252
258
  async function handleJsonRpc(request, auth) {
253
259
  switch (request.method) {
254
- // MCP protocol handshake
255
260
  case "initialize":
256
261
  return jsonRpcSuccess(request.id, {
257
262
  protocolVersion: "2024-11-05",
258
- capabilities: { tools: {} },
263
+ capabilities: { tools: { listChanged: false } },
259
264
  serverInfo: { name: serverName, version: serverVersion },
260
265
  });
261
266
  case "notifications/initialized":
262
267
  return jsonRpcSuccess(request.id, {});
263
- // List MCP tools
264
268
  case "tools/list":
265
269
  return jsonRpcSuccess(request.id, {
266
270
  tools: getToolDefinitions(),
267
271
  });
268
- // Call an MCP tool
269
272
  case "tools/call": {
270
273
  const { name, arguments: args } = (request.params ?? {});
271
274
  try {
@@ -273,7 +276,7 @@ export function createAgentServer(registry, options = {}) {
273
276
  return jsonRpcSuccess(request.id, result);
274
277
  }
275
278
  catch (err) {
276
- console.error("[server] Request error:", err);
279
+ console.error("[server] Tool call error:", err);
277
280
  return jsonRpcSuccess(request.id, mcpResult(`Error: ${err instanceof Error ? err.message : String(err)}`, true));
278
281
  }
279
282
  }
@@ -289,6 +292,10 @@ export function createAgentServer(registry, options = {}) {
289
292
  case "call_agent": {
290
293
  const req = (args.request ?? args);
291
294
  // Inject auth context
295
+ // No auth = internal/trusted call (e.g., atlas-api → atlas-os RPC)
296
+ if (!auth) {
297
+ req.callerType = "system";
298
+ }
292
299
  if (auth) {
293
300
  req.callerId = auth.callerId;
294
301
  req.callerType = auth.callerType;
@@ -301,10 +308,8 @@ export function createAgentServer(registry, options = {}) {
301
308
  req.callerType = "system";
302
309
  }
303
310
  // Process secret params: resolve refs, store raw secrets
304
- // Auto-resolve secret:xxx refs in tool params before execution
305
311
  if (req.params && secretStore) {
306
312
  const ownerId = auth?.callerId ?? "anonymous";
307
- // Find the tool schema to check for secret: true fields
308
313
  const agent = registry.get(req.path);
309
314
  const tool = agent?.tools.find((t) => t.name === req.tool);
310
315
  const schema = tool?.inputSchema;
@@ -332,6 +337,8 @@ export function createAgentServer(registry, options = {}) {
332
337
  return true;
333
338
  if (tv === "public")
334
339
  return true;
340
+ if (tv === "authenticated" && auth?.callerId && auth.callerId !== "anonymous")
341
+ return true;
335
342
  if (tv === "internal" && auth)
336
343
  return true;
337
344
  return false;
@@ -345,7 +352,7 @@ export function createAgentServer(registry, options = {}) {
345
352
  }
346
353
  }
347
354
  // ──────────────────────────────────────────
348
- // OAuth2 token handler (unchanged)
355
+ // OAuth2 token handler
349
356
  // ──────────────────────────────────────────
350
357
  async function handleOAuthToken(req) {
351
358
  if (!authConfig) {
@@ -380,556 +387,193 @@ export function createAgentServer(registry, options = {}) {
380
387
  error_description: "Missing client_id or client_secret",
381
388
  }, 400);
382
389
  }
383
- const client = await authConfig.store.validateClient(clientId, clientSecret);
384
- if (!client) {
390
+ try {
391
+ const result = await registry.call({
392
+ action: "execute_tool",
393
+ path: "@auth",
394
+ tool: "token",
395
+ params: { clientId, clientSecret },
396
+ callerType: "system",
397
+ });
398
+ const tokenResult = result?.result;
399
+ if (!tokenResult?.accessToken) {
400
+ return jsonResponse({ error: "invalid_client", error_description: "Authentication failed" }, 401);
401
+ }
385
402
  return jsonResponse({
386
- error: "invalid_client",
387
- error_description: "Invalid client credentials",
388
- }, 401);
403
+ access_token: tokenResult.accessToken,
404
+ token_type: "Bearer",
405
+ expires_in: tokenResult.expiresIn ?? authConfig.tokenTtl,
406
+ refresh_token: tokenResult.refreshToken,
407
+ });
389
408
  }
390
- // Delegate to @auth agent's token tool which generates proper JWTs
391
- const tokenResult = await registry.call({
392
- action: "execute_tool",
393
- path: "@auth",
394
- tool: "token",
395
- params: {
396
- grantType: "client_credentials",
397
- clientId,
398
- clientSecret,
399
- },
400
- context: {
401
- tenantId: "default",
402
- agentPath: "@auth",
403
- callerId: "oauth_endpoint",
404
- callerType: "system",
405
- },
406
- });
407
- // Extract the result - registry.call returns { success, result: { accessToken, tokenType, expiresIn, scopes } }
408
- const callResponse = tokenResult;
409
- if (!callResponse.success) {
410
- return jsonResponse({ error: "token_generation_failed", error_description: callResponse.error ?? "Unknown error" }, 500);
409
+ catch (err) {
410
+ console.error("[oauth] Token error:", err);
411
+ return jsonResponse({ error: "server_error", error_description: "Token exchange failed" }, 500);
411
412
  }
412
- const tokenData = callResponse.result;
413
- // accessToken may be wrapped as { $agent_type: "secret", value: "<jwt>" }
414
- const accessToken = tokenData.accessToken?.$agent_type === "secret"
415
- ? tokenData.accessToken.value
416
- : tokenData.accessToken;
417
- return jsonResponse({
418
- access_token: accessToken,
419
- token_type: tokenData.tokenType ?? "Bearer",
420
- expires_in: tokenData.expiresIn ?? authConfig.tokenTtl,
421
- scope: Array.isArray(tokenData.scopes) ? tokenData.scopes.join(" ") : client.scopes.join(" "),
422
- });
423
413
  }
424
414
  // ──────────────────────────────────────────
425
- // HTTP request handler
426
- // ─��────────────────────────────────────────
415
+ // Main fetch handler
416
+ // ──────────────────────────────────────────
427
417
  async function fetch(req) {
428
- const url = new URL(req.url);
429
- const path = url.pathname.replace(basePath, "") || "/";
430
- // CORS preflight
431
- if (cors && req.method === "OPTIONS") {
432
- return new Response(null, { status: 204, headers: corsHeaders() });
433
- }
434
- const addCors = (response) => {
435
- if (!cors)
436
- return response;
437
- const headers = new Headers(response.headers);
438
- for (const [key, value] of Object.entries(corsHeaders())) {
439
- headers.set(key, value);
440
- }
441
- return new Response(response.body, {
442
- status: response.status,
443
- statusText: response.statusText,
444
- headers,
445
- });
446
- };
447
- const auth = authConfig ? await resolveAuth(req, authConfig) : null;
448
418
  try {
449
- // MCP endpoint: POST / or POST /mcp
450
- if ((path === "/" || path === "/mcp") && req.method === "POST") {
451
- const body = (await req.json());
452
- const response = await handleJsonRpc(body, auth);
453
- return addCors(jsonResponse(response));
454
- }
455
- // OAuth2 token endpoint
456
- if (path === "/oauth/token" && req.method === "POST") {
457
- return addCors(await handleOAuthToken(req));
458
- }
459
- // Health check
460
- if (path === "/health" && req.method === "GET") {
461
- return addCors(jsonResponse({ status: "ok" }));
462
- }
463
- // Backwards compat: GET /list (returns agents directly)
464
- if (path === "/list" && req.method === "GET") {
465
- const agents = registry.list();
466
- const visible = agents.filter((agent) => canSeeAgent(agent, auth));
467
- return addCors(jsonResponse({
468
- success: true,
469
- agents: visible.map((agent) => ({
470
- path: agent.path,
471
- name: agent.config?.name,
472
- description: agent.config?.description,
473
- supportedActions: agent.config?.supportedActions,
474
- integration: agent.config?.integration || null,
475
- tools: agent.tools
476
- .filter((t) => {
477
- const tv = t.visibility ?? "internal";
478
- if (auth?.isRoot)
479
- return true;
480
- if (tv === "public")
481
- return true;
482
- if (tv === "internal" && auth)
483
- return true;
484
- return false;
485
- })
486
- .map((t) => t.name),
487
- })),
488
- }));
419
+ const url = new URL(req.url);
420
+ const path = url.pathname.replace(basePath, "") || "/";
421
+ // CORS preflight
422
+ if (cors && req.method === "OPTIONS") {
423
+ return new Response(null, { status: 204, headers: corsHeaders() });
489
424
  }
490
- // ---- Shared OAuth callback handler ----
491
- async function handleIntegrationOAuthCallback(provider, req) {
492
- const url = new URL(req.url);
493
- const code = url.searchParams.get("code");
494
- const state = url.searchParams.get("state");
495
- const oauthError = url.searchParams.get("error");
496
- const errorDescription = url.searchParams.get("error_description");
497
- if (oauthError) {
498
- return new Response(`<html><body><h1>Authorization Failed</h1><p>${errorDescription ?? oauthError}</p></body></html>`, { status: 400, headers: { "Content-Type": "text/html", ...corsHeaders() } });
499
- }
500
- if (!code) {
501
- return addCors(jsonResponse({ error: "Missing authorization code" }, 400));
502
- }
503
- try {
504
- await registry.call({
505
- action: "execute_tool",
506
- path: "@integrations",
507
- tool: "handle_oauth_callback",
508
- params: { provider, code, state: state ?? undefined },
509
- context: {
510
- tenantId: "default",
511
- agentPath: "@integrations",
512
- callerId: "oauth_callback",
513
- callerType: "system",
514
- },
515
- });
516
- // Parse redirect URL from state (base64-encoded JSON)
517
- let redirectUrl = "/";
518
- if (state) {
519
- try {
520
- const parsed = JSON.parse(atob(state));
521
- if (parsed.redirectUrl)
522
- redirectUrl = parsed.redirectUrl;
523
- }
524
- catch {
525
- // Fallback: try raw JSON for backward compat
526
- try {
527
- const parsed = JSON.parse(state);
528
- if (parsed.redirectUrl)
529
- redirectUrl = parsed.redirectUrl;
530
- }
531
- catch { }
532
- }
533
- }
534
- const sep = redirectUrl.includes("?") ? "&" : "?";
535
- return Response.redirect(`${redirectUrl}${sep}connected=${provider}`, 302);
536
- }
537
- catch (err) {
538
- return new Response(`<html><body><h1>Connection Failed</h1><p>${err instanceof Error ? err.message : String(err)}</p></body></html>`, { status: 500, headers: { "Content-Type": "text/html", ...corsHeaders() } });
539
- }
540
- }
541
- // GET /oauth/callback - Unified OAuth callback (provider from state param)
542
- if (path === "/oauth/callback" && req.method === "GET") {
543
- const url = new URL(req.url);
544
- const state = url.searchParams.get("state");
545
- let provider;
546
- if (state) {
547
- try {
548
- const parsed = JSON.parse(atob(state));
549
- provider = parsed.providerId;
550
- }
551
- catch {
552
- // Fallback: try raw JSON for backward compat
553
- try {
554
- const parsed = JSON.parse(state);
555
- provider = parsed.providerId;
556
- }
557
- catch { }
558
- }
559
- }
560
- if (!provider) {
561
- return addCors(jsonResponse({ error: "Missing provider in state param" }, 400));
562
- }
563
- return handleIntegrationOAuthCallback(provider, req);
564
- }
565
- // GET /integrations/callback/:provider - Legacy OAuth callback (provider from URL path)
566
- if (path.startsWith("/integrations/callback/") && req.method === "GET") {
567
- const provider = path.split("/integrations/callback/")[1]?.split("?")[0];
568
- if (!provider) {
569
- return addCors(jsonResponse({ error: "Missing provider" }, 400));
570
- }
571
- return handleIntegrationOAuthCallback(provider, req);
572
- }
573
- // GET /secrets/form/:token - Serve hosted secrets form
574
- if (path.startsWith("/secrets/form/") && req.method === "GET") {
575
- const token = path.split("/").pop() ?? "";
576
- const pending = pendingCollections.get(token);
577
- if (!pending) {
578
- return addCors(new Response("Invalid or expired form link", { status: 404 }));
579
- }
580
- if (Date.now() - pending.createdAt > 10 * 60 * 1000) {
581
- pendingCollections.delete(token);
582
- return addCors(new Response("Form link expired", { status: 410 }));
583
- }
584
- const reqUrl = new URL(req.url);
585
- const baseUrl = resolveBaseUrl(req, reqUrl);
586
- const html = renderSecretForm(token, pending, baseUrl);
587
- return addCors(new Response(html, { headers: { "Content-Type": "text/html" } }));
588
- }
589
- // POST /secrets/collect - Submit collected secrets and auto-forward to tool
590
- if (path === "/secrets/collect" && req.method === "POST") {
591
- const body = (await req.json());
592
- const pending = pendingCollections.get(body.token);
593
- if (!pending) {
594
- return addCors(jsonResponse({ error: "Invalid or expired collection token" }, 400));
595
- }
596
- // One-time use
597
- pendingCollections.delete(body.token);
598
- // Check expiry (10 min)
599
- if (Date.now() - pending.createdAt > 10 * 60 * 1000) {
600
- return addCors(jsonResponse({ error: "Collection token expired" }, 400));
601
- }
602
- // Encrypt secret values and store as refs
603
- const mergedParams = { ...pending.params };
604
- for (const [fieldName, value] of Object.entries(body.values)) {
605
- const fieldDef = pending.fields.find((f) => f.name === fieldName);
606
- if (fieldDef?.secret && secretStore) {
607
- // Store encrypted, get ref
608
- const ownerId = pending.auth?.callerId ?? "anonymous";
609
- const secretId = await secretStore.store(value, ownerId);
610
- mergedParams[fieldName] = `secret:${secretId}`;
611
- }
612
- else {
613
- mergedParams[fieldName] = value;
425
+ // Resolve auth for all requests
426
+ const auth = authConfig ? await resolveAuth(req, authConfig, {
427
+ signingKeys: serverSigningKeys,
428
+ trustedIssuers: configTrustedIssuers,
429
+ }) : null;
430
+ // Also check header-based identity (for proxied requests)
431
+ const headerAuth = !auth
432
+ ? (() => {
433
+ const actorId = req.headers.get("X-Atlas-Actor-Id");
434
+ const actorType = req.headers.get("X-Atlas-Actor-Type");
435
+ if (actorId) {
436
+ return {
437
+ callerId: actorId,
438
+ callerType: actorType ?? "agent",
439
+ scopes: ["*"],
440
+ isRoot: false,
441
+ };
614
442
  }
615
- }
616
- // Auto-forward to the target tool
617
- const callRequest = {
618
- action: "execute_tool",
619
- path: pending.agent,
620
- tool: pending.tool,
621
- params: mergedParams,
622
- };
623
- const toolCtx = {
624
- tenantId: "default",
625
- agentPath: pending.agent,
626
- callerId: pending.auth?.callerId ?? "anonymous",
627
- callerType: pending.auth?.callerType ?? "system",
628
- };
629
- const result = await registry.call({
630
- ...callRequest,
631
- context: toolCtx,
632
- });
633
- return addCors(jsonResponse({ success: true, result }));
634
- }
635
- // --- Web pages (plain HTML, served from same server) ---
636
- const htmlRes = (body) => addCors(new Response(body, { headers: { "Content-Type": "text/html; charset=utf-8" } }));
637
- const reqUrl = new URL(req.url);
638
- const baseUrl = resolveBaseUrl(req, reqUrl);
639
- const slackConfig = process.env.SLACK_CLIENT_ID && process.env.SLACK_CLIENT_SECRET
640
- ? {
641
- clientId: process.env.SLACK_CLIENT_ID,
642
- clientSecret: process.env.SLACK_CLIENT_SECRET,
643
- redirectUri: `${baseUrl}/auth/slack/callback`,
644
- }
645
- : null;
646
- // Helper: read session from cookie
647
- function getSession(r) {
648
- const c = r.headers.get("Cookie") || "";
649
- const m = c.match(/s_session=([^;]+)/);
650
- if (!m)
651
- return null;
652
- try {
653
- return JSON.parse(Buffer.from(m[1], "base64url").toString());
654
- }
655
- catch {
656
443
  return null;
657
- }
658
- }
659
- // Helper: generate JWT from client credentials
660
- async function generateMcpToken() {
661
- const clientRes = await registry.call({ action: "execute_tool", path: "@auth", tool: "create_client", callerType: "system", params: {
662
- name: "mcp-" + Date.now(),
663
- scopes: ["*"],
664
- } });
665
- const cid = clientRes?.result?.clientId;
666
- const csec = clientRes?.result?.clientSecret;
667
- if (!cid || !csec)
668
- throw new Error("Failed to create client: " + JSON.stringify(clientRes));
669
- const tokenRes = await globalThis.fetch(`http://localhost:${port}/oauth/token`, {
670
- method: "POST",
671
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
672
- body: new URLSearchParams({ grant_type: "client_credentials", client_id: cid, client_secret: csec }),
673
- });
674
- const tokenData = await tokenRes.json();
675
- if (!tokenData.access_token)
676
- throw new Error("Failed to get JWT: " + JSON.stringify(tokenData));
677
- return tokenData.access_token;
678
- }
679
- // Helper: set session cookie and redirect
680
- function sessionRedirect(location, session) {
681
- const data = Buffer.from(JSON.stringify(session)).toString("base64url");
682
- return new Response(null, {
683
- status: 302,
684
- headers: {
685
- Location: location,
686
- "Set-Cookie": `s_session=${data}; Path=/; HttpOnly; SameSite=Lax; Max-Age=604800`,
687
- },
688
- });
689
- }
690
- // GET / — login page (or redirect to dashboard if session exists)
691
- if (path === "/" && req.method === "GET") {
692
- const session = getSession(req);
693
- if (session?.token)
694
- return Response.redirect(`${baseUrl}/dashboard`, 302);
695
- return htmlRes(renderLoginPage(baseUrl, !!slackConfig));
444
+ })()
445
+ : null;
446
+ const effectiveAuth = auth ?? headerAuth;
447
+ // ── POST / → MCP JSON-RPC ──
448
+ if (path === "/" && req.method === "POST") {
449
+ const body = (await req.json());
450
+ const result = await handleJsonRpc(body, effectiveAuth);
451
+ return cors ? addCors(jsonResponse(result)) : jsonResponse(result);
696
452
  }
697
- // GET /auth/slack start Slack OAuth
698
- if (path === "/auth/slack" && req.method === "GET") {
699
- if (!slackConfig)
700
- return htmlRes("<h1>Slack OAuth not configured</h1>");
701
- return Response.redirect(slackAuthUrl(slackConfig), 302);
453
+ // ── POST /oauth/token OAuth2 client_credentials ──
454
+ if (path === "/oauth/token" && req.method === "POST") {
455
+ const res = await handleOAuthToken(req);
456
+ return cors ? addCors(res) : res;
702
457
  }
703
- // GET /auth/slack/callback handle Slack OAuth callback
704
- if (path === "/auth/slack/callback" && req.method === "GET") {
705
- if (!slackConfig)
706
- return htmlRes("<h1>Slack OAuth not configured</h1>");
707
- const authCode = reqUrl.searchParams.get("code");
708
- const authError = reqUrl.searchParams.get("error");
709
- if (authError || !authCode)
710
- return Response.redirect(`${baseUrl}/?error=${authError || "no_code"}`, 302);
711
- try {
712
- const tokens = await exchangeSlackCode(authCode, slackConfig);
713
- const profile = await getSlackProfile(tokens.access_token);
714
- const teamId = profile["https://slack.com/team_id"] || "";
715
- const teamName = profile["https://slack.com/team_name"] || "";
716
- // Check if user already exists
717
- console.log("[auth] Looking up slack user:", profile.sub, profile.email);
718
- const existing = await registry.call({
719
- action: "execute_tool", path: "@users", callerType: "system", tool: "resolve_identity",
720
- params: { provider: "slack", providerUserId: profile.sub },
721
- });
722
- console.log("[auth] resolve_identity:", JSON.stringify(existing));
723
- if (existing?.result?.found && existing?.result?.user?.tenantId) {
724
- // Returning user — generate token and go to dashboard
725
- console.log("[auth] Returning user, generating token...");
726
- const mcpToken = await generateMcpToken();
727
- return sessionRedirect(`${baseUrl}/dashboard`, {
728
- userId: existing.result.user.id,
729
- tenantId: existing.result.user.tenantId,
730
- email: existing.result.user.email,
731
- name: existing.result.user.name,
732
- token: mcpToken,
733
- });
734
- }
735
- // Check if Slack team already has a tenant
736
- if (teamId) {
737
- console.log("[auth] Checking tenant_identities for team:", teamId);
738
- try {
739
- // Direct DB query via the auth store's underlying connection
740
- // We'll use a simple fetch to our own MCP endpoint to call @db-connections
741
- // Actually, simpler: just query the DB directly via the server's context
742
- // For now, use registry.call to a custom tool or direct SQL
743
- // Simplest: call @auth list_tenants and check metadata
744
- // Even simpler: direct SQL via globalThis.fetch to ourselves
745
- const dbUrl = process.env.DATABASE_URL;
746
- if (dbUrl) {
747
- const { default: postgres } = await import("postgres");
748
- const sql = postgres(dbUrl);
749
- const rows = await sql `SELECT tenant_id FROM tenant_identities WHERE provider = 'slack' AND provider_org_id = ${teamId} LIMIT 1`;
750
- await sql.end();
751
- if (rows.length > 0) {
752
- const existingTenantId = rows[0].tenant_id;
753
- console.log("[auth] Found existing tenant for team:", existingTenantId);
754
- // Create user on existing tenant
755
- const userRes = await registry.call({ action: "execute_tool", path: "@users", callerType: "system", tool: "create_user", params: {
756
- email: profile.email, name: profile.name, tenantId: existingTenantId,
757
- } });
758
- const newUserId = userRes?.result?.id || userRes?.result?.user?.id;
759
- console.log("[auth] Created user on existing tenant:", newUserId);
760
- // Link identity
761
- if (newUserId) {
762
- await registry.call({ action: "execute_tool", path: "@users", callerType: "system", tool: "link_identity", params: {
763
- userId: newUserId, provider: "slack", providerUserId: profile.sub,
764
- email: profile.email, name: profile.name,
765
- metadata: { slackTeamId: teamId, slackTeamName: teamName },
766
- } });
767
- }
768
- // Generate token and go to dashboard
769
- const mcpToken = await generateMcpToken();
770
- return sessionRedirect(`${baseUrl}/dashboard`, {
771
- userId: newUserId, tenantId: existingTenantId,
772
- email: profile.email, name: profile.name, token: mcpToken,
773
- });
774
- }
775
- }
776
- }
777
- catch (e) {
778
- console.error("[auth] tenant_identity lookup error:", e.message);
779
- }
780
- }
781
- // New user — redirect to setup
782
- return sessionRedirect(`${baseUrl}/setup`, {
783
- email: profile.email,
784
- name: profile.name,
785
- picture: profile.picture,
786
- slackUserId: profile.sub,
787
- slackTeamId: teamId,
788
- slackTeamName: teamName,
789
- });
790
- }
791
- catch (err) {
792
- console.error("[auth] callback error:", err);
793
- return Response.redirect(`${baseUrl}/?error=oauth_failed`, 302);
794
- }
458
+ // ── GET /health Health check ──
459
+ if (path === "/health" && req.method === "GET") {
460
+ const res = jsonResponse({ status: "ok", agents: registry.listPaths() });
461
+ return cors ? addCors(res) : res;
795
462
  }
796
- // GET /setup tenant creation page
797
- if (path === "/setup" && req.method === "GET") {
798
- const session = getSession(req);
799
- if (!session?.email)
800
- return Response.redirect(`${baseUrl}/`, 302);
801
- return htmlRes(renderTenantPage(baseUrl, session.email, session.name || ""));
463
+ // ── GET /.well-known/jwks.json JWKS public keys ──
464
+ if (path === "/.well-known/jwks.json" && req.method === "GET") {
465
+ const jwks = serverSigningKeys.length > 0
466
+ ? await buildJwks(serverSigningKeys)
467
+ : { keys: [] };
468
+ const res = jsonResponse(jwks);
469
+ return cors ? addCors(res) : res;
802
470
  }
803
- // POST /setup create tenant + user + link identity + generate token
804
- if (path === "/setup" && req.method === "POST") {
805
- try {
806
- const body = await req.json();
807
- const session = getSession(req);
808
- console.log("[setup] body:", JSON.stringify(body), "session:", JSON.stringify(session));
809
- // 1. Create tenant
810
- const tenantRes = await registry.call({ action: "execute_tool", path: "@auth", callerType: "system", tool: "create_tenant", params: { name: body.tenant } });
811
- const tenantId = tenantRes?.result?.tenantId;
812
- if (!tenantId)
813
- return addCors(jsonResponse({ error: "Failed to create tenant" }, 400));
814
- console.log("[setup] tenant created:", tenantId);
815
- // 2. Create user
816
- const userRes = await registry.call({ action: "execute_tool", path: "@users", callerType: "system", tool: "create_user", params: { email: body.email, name: session?.name, tenantId } });
817
- const userId = userRes?.result?.id || userRes?.result?.user?.id;
818
- console.log("[setup] user created:", userId);
819
- // 2b. Link tenant to Slack team
820
- if (session?.slackTeamId) {
821
- try {
822
- const dbUrl = process.env.DATABASE_URL;
823
- if (dbUrl) {
824
- const { default: postgres } = await import("postgres");
825
- const sql = postgres(dbUrl);
826
- const id = "ti_" + Math.random().toString(36).slice(2, 14);
827
- await sql `INSERT INTO tenant_identities (id, tenant_id, provider, provider_org_id, name) VALUES (${id}, ${tenantId}, 'slack', ${session.slackTeamId}, ${session.slackTeamName || ''})`;
828
- await sql.end();
829
- console.log("[setup] Created tenant_identity for slack team:", session.slackTeamId);
830
- }
831
- }
832
- catch (e) {
833
- console.error("[setup] tenant_identity insert error:", e.message);
834
- }
835
- }
836
- // 3. Link Slack identity
837
- if (session?.slackUserId && userId) {
838
- console.log("[setup] linking slack identity:", session.slackUserId);
839
- const linkRes = await registry.call({ action: "execute_tool", path: "@users", callerType: "system", tool: "link_identity", params: {
840
- userId,
841
- provider: "slack",
842
- providerUserId: session.slackUserId,
843
- email: body.email,
844
- name: session.name,
845
- metadata: { slackTeamId: session.slackTeamId, slackTeamName: session.slackTeamName }, callerType: "system"
846
- } });
847
- console.log("[setup] link_identity result:", JSON.stringify(linkRes));
848
- }
849
- // 4. Generate MCP token
850
- const mcpToken = await generateMcpToken();
851
- console.log("[setup] token generated, length:", mcpToken.length);
852
- return addCors(jsonResponse({ success: true, result: { tenantId, userId, token: mcpToken } }));
853
- }
854
- catch (err) {
855
- console.error("[setup] error:", err);
856
- return addCors(jsonResponse({ error: err.message }, 400));
857
- }
858
- }
859
- // POST /logout — clear session
860
- if (path === "/logout" && req.method === "POST") {
861
- return new Response(null, {
862
- status: 302,
863
- headers: {
864
- Location: `${baseUrl}/`,
865
- "Set-Cookie": "s_session=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0",
866
- },
471
+ // ── GET /.well-known/configuration Server discovery ──
472
+ if (path === "/.well-known/configuration" && req.method === "GET") {
473
+ const baseUrl = new URL(req.url).origin;
474
+ const res = jsonResponse({
475
+ issuer: baseUrl,
476
+ jwks_uri: `${baseUrl}/.well-known/jwks.json`,
477
+ token_endpoint: `${baseUrl}/oauth/token`,
478
+ agents_endpoint: `${baseUrl}/list`,
479
+ call_endpoint: baseUrl,
480
+ supported_grant_types: ["client_credentials"],
481
+ agents: registry.listPaths(),
867
482
  });
483
+ return cors ? addCors(res) : res;
868
484
  }
869
- // GET /dashboard show MCP URL and setup instructions
870
- if (path === "/dashboard" && req.method === "GET") {
871
- const session = getSession(req);
872
- let token = session?.token || reqUrl.searchParams.get("token") || "";
873
- if (!token)
874
- return Response.redirect(`${baseUrl}/`, 302);
875
- // Persist token in cookie
876
- const sessData = Buffer.from(JSON.stringify({ ...session, token })).toString("base64url");
877
- return new Response(renderDashboardPage(baseUrl, token, session || undefined), {
878
- headers: {
879
- "Content-Type": "text/html; charset=utf-8",
880
- "Set-Cookie": `s_session=${sessData}; Path=/; HttpOnly; SameSite=Lax; Max-Age=604800`,
881
- },
882
- });
485
+ // ── GET /list List agents (legacy endpoint) ──
486
+ if (path === "/list" && req.method === "GET") {
487
+ const agents = registry.list();
488
+ const visible = agents.filter((agent) => canSeeAgent(agent, effectiveAuth));
489
+ const res = jsonResponse(visible.map((agent) => ({
490
+ path: agent.path,
491
+ name: agent.config?.name,
492
+ description: agent.config?.description,
493
+ supportedActions: agent.config?.supportedActions,
494
+ integration: agent.config?.integration || null,
495
+ tools: agent.tools
496
+ .filter((t) => {
497
+ const tv = t.visibility ?? "internal";
498
+ if (effectiveAuth?.isRoot)
499
+ return true;
500
+ if (tv === "public")
501
+ return true;
502
+ if (tv === "internal" && effectiveAuth)
503
+ return true;
504
+ return false;
505
+ })
506
+ .map((t) => ({
507
+ name: t.name,
508
+ description: t.description,
509
+ })),
510
+ })));
511
+ return cors ? addCors(res) : res;
883
512
  }
884
- return addCors(jsonResponse({
513
+ // ── Not found ──
514
+ const res = jsonResponse({
885
515
  jsonrpc: "2.0",
886
516
  id: null,
887
517
  error: {
888
518
  code: -32601,
889
519
  message: `Not found: ${req.method} ${path}`,
890
520
  },
891
- }, 404));
521
+ }, 404);
522
+ return cors ? addCors(res) : res;
892
523
  }
893
524
  catch (err) {
894
525
  console.error("[server] Request error:", err);
895
- return addCors(jsonResponse({
526
+ const res = jsonResponse({
896
527
  jsonrpc: "2.0",
897
528
  id: null,
898
- error: { code: -32603, message: "Internal error" },
899
- }, 500));
529
+ error: {
530
+ code: -32603,
531
+ message: err instanceof Error ? err.message : "Internal error",
532
+ },
533
+ }, 500);
534
+ return cors ? addCors(res) : res;
900
535
  }
901
536
  }
902
537
  // ──────────────────────────────────────────
903
538
  // Server lifecycle
904
539
  // ──────────────────────────────────────────
905
- const server = {
540
+ return {
541
+ url: null,
542
+ registry,
906
543
  async start() {
907
- if (serverInstance)
908
- throw new Error("Server is already running");
909
- serverInstance = Bun.serve({ port, hostname, fetch });
910
- serverUrl = `http://${hostname}:${port}${basePath}`;
911
- console.log(`Agent server running at ${serverUrl}`);
912
- console.log(" POST / - MCP JSON-RPC endpoint");
913
- console.log(" POST /mcp - MCP JSON-RPC endpoint (alias)");
914
- console.log(" GET /health - Health check");
915
- if (authConfig) {
916
- console.log(" POST /oauth/token - OAuth2 token endpoint");
917
- console.log(" Auth: enabled");
544
+ // Load or generate signing key for JWKS
545
+ if (options.signingKey) {
546
+ serverSigningKeys.push(options.signingKey);
547
+ }
548
+ else if (authConfig?.store?.getSigningKeys) {
549
+ const stored = await authConfig.store.getSigningKeys() ?? [];
550
+ for (const exported of stored) {
551
+ serverSigningKeys.push(await importSigningKey(exported));
552
+ }
918
553
  }
919
- console.log(" MCP tools: call_agent, list_agents");
554
+ if (serverSigningKeys.length === 0) {
555
+ const key = await generateSigningKey();
556
+ serverSigningKeys.push(key);
557
+ if (authConfig?.store?.storeSigningKey) {
558
+ await authConfig.store.storeSigningKey(await exportSigningKey(key));
559
+ }
560
+ }
561
+ serverInstance = Bun.serve({
562
+ port,
563
+ hostname,
564
+ fetch,
565
+ });
566
+ this.url = `http://${hostname}:${port}`;
567
+ console.log(`[agents-sdk] Server listening on http://${hostname}:${port}`);
920
568
  },
921
569
  async stop() {
922
570
  if (serverInstance) {
923
571
  serverInstance.stop();
924
572
  serverInstance = null;
925
- serverUrl = null;
573
+ this.url = null;
926
574
  }
927
575
  },
928
576
  fetch,
929
- get url() {
930
- return serverUrl;
931
- },
932
577
  };
933
- return server;
934
578
  }
935
579
  //# sourceMappingURL=server.js.map