@slashfi/agents-sdk 0.16.0 → 0.18.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 (96) hide show
  1. package/dist/agent-definitions/auth.d.ts.map +1 -1
  2. package/dist/agent-definitions/auth.js +44 -11
  3. package/dist/agent-definitions/auth.js.map +1 -1
  4. package/dist/agent-definitions/integrations.d.ts.map +1 -1
  5. package/dist/agent-definitions/integrations.js +106 -45
  6. package/dist/agent-definitions/integrations.js.map +1 -1
  7. package/dist/agent-definitions/remote-registry.d.ts.map +1 -1
  8. package/dist/agent-definitions/remote-registry.js +174 -45
  9. package/dist/agent-definitions/remote-registry.js.map +1 -1
  10. package/dist/agent-definitions/secrets.d.ts.map +1 -1
  11. package/dist/agent-definitions/secrets.js +1 -4
  12. package/dist/agent-definitions/secrets.js.map +1 -1
  13. package/dist/agent-definitions/users.d.ts.map +1 -1
  14. package/dist/agent-definitions/users.js +14 -3
  15. package/dist/agent-definitions/users.js.map +1 -1
  16. package/dist/define-config.d.ts +125 -0
  17. package/dist/define-config.d.ts.map +1 -0
  18. package/dist/define-config.js +75 -0
  19. package/dist/define-config.js.map +1 -0
  20. package/dist/define.d.ts +11 -2
  21. package/dist/define.d.ts.map +1 -1
  22. package/dist/define.js +57 -26
  23. package/dist/define.js.map +1 -1
  24. package/dist/events.d.ts +133 -0
  25. package/dist/events.d.ts.map +1 -0
  26. package/dist/events.js +57 -0
  27. package/dist/events.js.map +1 -0
  28. package/dist/index.d.ts +16 -8
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +21 -3
  31. package/dist/index.js.map +1 -1
  32. package/dist/integration-interface.d.ts +3 -3
  33. package/dist/integration-interface.d.ts.map +1 -1
  34. package/dist/integration-interface.js +29 -21
  35. package/dist/integration-interface.js.map +1 -1
  36. package/dist/integrations-store.d.ts +2 -2
  37. package/dist/integrations-store.d.ts.map +1 -1
  38. package/dist/integrations-store.js +3 -3
  39. package/dist/integrations-store.js.map +1 -1
  40. package/dist/jwt.d.ts.map +1 -1
  41. package/dist/jwt.js +7 -5
  42. package/dist/jwt.js.map +1 -1
  43. package/dist/key-manager.d.ts.map +1 -1
  44. package/dist/key-manager.js +5 -3
  45. package/dist/key-manager.js.map +1 -1
  46. package/dist/oidc-signin.d.ts +32 -0
  47. package/dist/oidc-signin.d.ts.map +1 -0
  48. package/dist/oidc-signin.js +138 -0
  49. package/dist/oidc-signin.js.map +1 -0
  50. package/dist/registry-consumer.d.ts +104 -0
  51. package/dist/registry-consumer.d.ts.map +1 -0
  52. package/dist/registry-consumer.js +230 -0
  53. package/dist/registry-consumer.js.map +1 -0
  54. package/dist/registry.d.ts +5 -0
  55. package/dist/registry.d.ts.map +1 -1
  56. package/dist/registry.js +76 -4
  57. package/dist/registry.js.map +1 -1
  58. package/dist/secret-collection.d.ts.map +1 -1
  59. package/dist/secret-collection.js.map +1 -1
  60. package/dist/server.d.ts +3 -0
  61. package/dist/server.d.ts.map +1 -1
  62. package/dist/server.js +222 -27
  63. package/dist/server.js.map +1 -1
  64. package/dist/test-utils/mock-oidc-server.d.ts +36 -0
  65. package/dist/test-utils/mock-oidc-server.d.ts.map +1 -0
  66. package/dist/test-utils/mock-oidc-server.js +96 -0
  67. package/dist/test-utils/mock-oidc-server.js.map +1 -0
  68. package/dist/types.d.ts +106 -0
  69. package/dist/types.d.ts.map +1 -1
  70. package/package.json +6 -1
  71. package/src/agent-definitions/auth.ts +106 -38
  72. package/src/agent-definitions/integrations.ts +201 -73
  73. package/src/agent-definitions/remote-registry.ts +262 -65
  74. package/src/agent-definitions/secrets.ts +22 -8
  75. package/src/agent-definitions/users.ts +16 -4
  76. package/src/cli.ts +293 -0
  77. package/src/codegen.test.ts +527 -0
  78. package/src/codegen.ts +1348 -0
  79. package/src/consumer.test.ts +536 -0
  80. package/src/define-config.ts +205 -0
  81. package/src/define.ts +134 -46
  82. package/src/events.ts +237 -0
  83. package/src/index.ts +107 -8
  84. package/src/integration-interface.ts +52 -28
  85. package/src/integrations-store.ts +9 -5
  86. package/src/jwt.ts +48 -19
  87. package/src/key-manager.test.ts +22 -13
  88. package/src/key-manager.ts +8 -10
  89. package/src/oidc-signin.ts +223 -0
  90. package/src/registry-consumer.ts +413 -0
  91. package/src/registry.ts +115 -9
  92. package/src/secret-collection.ts +2 -1
  93. package/src/server.test.ts +304 -238
  94. package/src/server.ts +371 -69
  95. package/src/test-utils/mock-oidc-server.ts +123 -0
  96. package/src/types.ts +172 -18
package/src/server.ts CHANGED
@@ -32,7 +32,16 @@ import {
32
32
  } from "./agent-definitions/secrets.js";
33
33
  import { verifyJwt } from "./jwt.js";
34
34
  import type { SigningKey } from "./jwt.js";
35
- import { generateSigningKey, importSigningKey, exportSigningKey, buildJwks, verifyJwtLocal, verifyJwtFromIssuer, signJwtES256 } from "./jwt.js";
35
+ import {
36
+ buildJwks,
37
+ exportSigningKey,
38
+ generateSigningKey,
39
+ importSigningKey,
40
+ signJwtES256,
41
+ verifyJwtFromIssuer,
42
+ verifyJwtLocal,
43
+ } from "./jwt.js";
44
+ import { type OIDCProviderConfig, createOIDCSignIn } from "./oidc-signin.js";
36
45
  import type { AgentRegistry } from "./registry.js";
37
46
  import type { AgentDefinition, CallAgentRequest, Visibility } from "./types.js";
38
47
 
@@ -48,36 +57,40 @@ export interface TrustedIssuer {
48
57
  scopes: string[];
49
58
  }
50
59
 
51
-
52
-
53
60
  /** OAuth identity provider for /oauth/authorize + /oauth/callback flows */
54
61
  export interface OAuthIdentityProvider {
55
62
  /**
56
63
  * Handle /oauth/authorize — redirect the user to an external IdP.
57
64
  * Return a Response (typically a 302 redirect).
58
65
  */
59
- authorize(req: Request, params: {
60
- /** The verified JWT from the foreign registry */
61
- token: string;
62
- /** Claims from the verified JWT */
63
- claims: Record<string, unknown>;
64
- /** Where to redirect after completion */
65
- redirectUri: string;
66
- /** Base URL of this server */
67
- baseUrl: string;
68
- /** OAuth scope (e.g. "setup" for tenant creation flow) */
69
- scope?: string;
70
- }): Promise<Response>;
66
+ authorize(
67
+ req: Request,
68
+ params: {
69
+ /** The verified JWT from the foreign registry */
70
+ token: string;
71
+ /** Claims from the verified JWT */
72
+ claims: Record<string, unknown>;
73
+ /** Where to redirect after completion */
74
+ redirectUri: string;
75
+ /** Base URL of this server */
76
+ baseUrl: string;
77
+ /** OAuth scope (e.g. "setup" for tenant creation flow) */
78
+ scope?: string;
79
+ },
80
+ ): Promise<Response>;
71
81
 
72
82
  /**
73
83
  * Handle /oauth/callback — process the IdP response.
74
84
  * Should link the foreign identity to a local user and redirect back.
75
85
  * Return a Response (typically a 302 redirect to redirectUri).
76
86
  */
77
- callback(req: Request, params: {
78
- /** Base URL of this server */
79
- baseUrl: string;
80
- }): Promise<Response>;
87
+ callback(
88
+ req: Request,
89
+ params: {
90
+ /** Base URL of this server */
91
+ baseUrl: string;
92
+ },
93
+ ): Promise<Response>;
81
94
  }
82
95
  export interface AgentServerOptions {
83
96
  /** Port to listen on (default: 3000) */
@@ -102,6 +115,8 @@ export interface AgentServerOptions {
102
115
  oauthIdentityProvider?: OAuthIdentityProvider;
103
116
  /** Key store for managed key rotation (if provided, uses createKeyManager instead of simple key gen) */
104
117
  keyStore?: import("./key-manager.js").KeyStore;
118
+ /** OIDC provider for user sign-in (authorization code flow) */
119
+ oidcProvider?: OIDCProviderConfig;
105
120
  }
106
121
 
107
122
  export interface AgentServer {
@@ -247,7 +262,10 @@ export function detectAuth(registry: AgentRegistry): AuthConfig {
247
262
  export async function resolveAuth(
248
263
  req: Request,
249
264
  authConfig: AuthConfig,
250
- jwksOptions?: { signingKeys?: SigningKey[]; trustedIssuers?: TrustedIssuer[] },
265
+ jwksOptions?: {
266
+ signingKeys?: SigningKey[];
267
+ trustedIssuers?: TrustedIssuer[];
268
+ },
251
269
  ): Promise<ResolvedAuth | null> {
252
270
  const authHeader = req.headers.get("Authorization");
253
271
  if (!authHeader) return null;
@@ -279,9 +297,7 @@ export async function resolveAuth(
279
297
  isRoot: false,
280
298
  };
281
299
  }
282
- } catch {
283
- continue;
284
- }
300
+ } catch {}
285
301
  }
286
302
  }
287
303
 
@@ -294,13 +310,17 @@ export async function resolveAuth(
294
310
  const unverified = JSON.parse(atob(payloadB64)) as { iss?: string };
295
311
  if (unverified.iss) {
296
312
  const issuerConfig = jwksOptions.trustedIssuers.find(
297
- (i) => i.issuer === unverified.iss
313
+ (i) => i.issuer === unverified.iss,
298
314
  );
299
315
  if (issuerConfig) {
300
- const verified = await verifyJwtFromIssuer(credential, issuerConfig.issuer);
316
+ const verified = await verifyJwtFromIssuer(
317
+ credential,
318
+ issuerConfig.issuer,
319
+ );
301
320
  if (verified) {
302
321
  const scopes = issuerConfig.scopes;
303
- const isSystem = scopes.includes("*") || scopes.includes("agents:admin");
322
+ const isSystem =
323
+ scopes.includes("*") || scopes.includes("agents:admin");
304
324
  return {
305
325
  callerId: verified.sub ?? verified.name ?? "unknown",
306
326
  callerType: isSystem ? "system" : "agent",
@@ -373,6 +393,33 @@ export function canSeeAgent(
373
393
  return false;
374
394
  }
375
395
 
396
+ /**
397
+ * Filter tools visible on a public agent endpoint.
398
+ * For /agents/ routes, tools inherit the agent's visibility:
399
+ * - If agent is public, tools without explicit visibility are shown
400
+ * - Tool-level visibility still overrides (e.g. visibility: "private" hides it)
401
+ */
402
+ function getVisibleTools(
403
+ agent: AgentDefinition,
404
+ auth: ResolvedAuth | null,
405
+ ): typeof agent.tools {
406
+ const agentVisibility = ((agent as any).visibility ??
407
+ agent.config?.visibility ??
408
+ "internal") as Visibility;
409
+ return agent.tools.filter((t) => {
410
+ const tv = t.visibility;
411
+ if (auth?.isRoot) return true;
412
+ // Tool has explicit visibility — respect it
413
+ if (tv === "public") return true;
414
+ if (tv === "private") return auth?.isRoot ?? false;
415
+ if (tv === "internal" && auth) return true;
416
+ // No explicit tool visibility — inherit from agent
417
+ if (!tv && agentVisibility === "public") return true;
418
+ if (!tv && agentVisibility === "internal" && auth) return true;
419
+ return false;
420
+ });
421
+ }
422
+
376
423
  // ============================================
377
424
  // MCP Tool Definitions
378
425
  // ============================================
@@ -417,8 +464,7 @@ function getToolDefinitions() {
417
464
  },
418
465
  {
419
466
  name: "list_agents",
420
- description:
421
- "List all registered agents and their available tools.",
467
+ description: "List all registered agents and their available tools.",
422
468
  inputSchema: {
423
469
  type: "object",
424
470
  properties: {},
@@ -446,12 +492,17 @@ export function createAgentServer(
446
492
  oauthIdentityProvider,
447
493
  } = options;
448
494
 
495
+ // OIDC sign-in handler (if configured)
496
+ const oidcSignIn = options.oidcProvider
497
+ ? createOIDCSignIn(options.oidcProvider)
498
+ : null;
499
+
449
500
  // Signing keys for JWKS-based auth
450
501
  const serverSigningKeys: SigningKey[] = [];
451
502
  // Normalize trustedIssuers to TrustedIssuer objects
452
- const configTrustedIssuers: TrustedIssuer[] = (options.trustedIssuers ?? []).map(
453
- (i) => typeof i === 'string' ? { issuer: i, scopes: ['*'] } : i
454
- );
503
+ const configTrustedIssuers: TrustedIssuer[] = (
504
+ options.trustedIssuers ?? []
505
+ ).map((i) => (typeof i === "string" ? { issuer: i, scopes: ["*"] } : i));
455
506
 
456
507
  const authConfig = detectAuth(registry);
457
508
  let serverInstance: ReturnType<typeof Bun.serve> | null = null;
@@ -572,7 +623,12 @@ export function createAgentServer(
572
623
  const tv = t.visibility ?? "internal";
573
624
  if (auth?.isRoot) return true;
574
625
  if (tv === "public") return true;
575
- if (tv === "authenticated" && auth?.callerId && auth.callerId !== "anonymous") return true;
626
+ if (
627
+ tv === "authenticated" &&
628
+ auth?.callerId &&
629
+ auth.callerId !== "anonymous"
630
+ )
631
+ return true;
576
632
  if (tv === "internal" && auth) return true;
577
633
  return false;
578
634
  })
@@ -621,7 +677,10 @@ export function createAgentServer(
621
677
  const assertion = params.assertion ?? "";
622
678
  if (!assertion) {
623
679
  return jsonResponse(
624
- { error: "invalid_request", error_description: "Missing assertion parameter" },
680
+ {
681
+ error: "invalid_request",
682
+ error_description: "Missing assertion parameter",
683
+ },
625
684
  400,
626
685
  );
627
686
  }
@@ -639,7 +698,12 @@ export function createAgentServer(
639
698
  // If the tool call failed, forward the error
640
699
  if ((result as any)?.success === false) {
641
700
  return jsonResponse(
642
- { error: "server_error", error_description: (result as any)?.error ?? "Exchange tool failed", raw: JSON.stringify(result)?.slice(0, 300) },
701
+ {
702
+ error: "server_error",
703
+ error_description:
704
+ (result as any)?.error ?? "Exchange tool failed",
705
+ raw: JSON.stringify(result)?.slice(0, 300),
706
+ },
643
707
  500,
644
708
  );
645
709
  }
@@ -648,8 +712,13 @@ export function createAgentServer(
648
712
  try {
649
713
  const assertionParts = assertion.split(".");
650
714
  if (assertionParts.length === 3) {
651
- const assertionPayload = JSON.parse(Buffer.from(assertionParts[1], "base64url").toString()) as any;
652
- if (assertionPayload.type === "agent-registry" && assertionPayload.iss) {
715
+ const assertionPayload = JSON.parse(
716
+ Buffer.from(assertionParts[1], "base64url").toString(),
717
+ ) as any;
718
+ if (
719
+ assertionPayload.type === "agent-registry" &&
720
+ assertionPayload.iss
721
+ ) {
653
722
  // Use add_connection (direct store) instead of setup_integration (which would cause infinite loop)
654
723
  const addResult = await registry.call({
655
724
  action: "execute_tool",
@@ -665,19 +734,30 @@ export function createAgentServer(
665
734
  callerType: "system",
666
735
  });
667
736
  if (addResult.success) {
668
- console.error(`[jwt_exchange] Reverse connection stored for ${assertionPayload.iss}`);
737
+ console.error(
738
+ `[jwt_exchange] Reverse connection stored for ${assertionPayload.iss}`,
739
+ );
669
740
  } else {
670
- console.error(`[jwt_exchange] Reverse connection failed:`, (addResult as any).error);
741
+ console.error(
742
+ "[jwt_exchange] Reverse connection failed:",
743
+ (addResult as any).error,
744
+ );
671
745
  }
672
746
  }
673
747
  }
674
748
  } catch (reverseErr) {
675
- console.error("[jwt_exchange] Reverse registration check failed:", reverseErr);
749
+ console.error(
750
+ "[jwt_exchange] Reverse registration check failed:",
751
+ reverseErr,
752
+ );
676
753
  }
677
754
 
678
755
  if (!exchangeResult) {
679
756
  return jsonResponse(
680
- { error: "server_error", error_description: `Exchange returned null: ${JSON.stringify(result)?.slice(0, 300)}` },
757
+ {
758
+ error: "server_error",
759
+ error_description: `Exchange returned null: ${JSON.stringify(result)?.slice(0, 300)}`,
760
+ },
681
761
  500,
682
762
  );
683
763
  }
@@ -696,7 +776,8 @@ export function createAgentServer(
696
776
  return jsonResponse(
697
777
  {
698
778
  error: "identity_required",
699
- error_description: "User identity not linked. Redirect to authorize_url to complete linking.",
779
+ error_description:
780
+ "User identity not linked. Redirect to authorize_url to complete linking.",
700
781
  authorize_url: authorizeUrl.toString(),
701
782
  tenant_id: exchangeResult.tenantId,
702
783
  },
@@ -733,7 +814,10 @@ export function createAgentServer(
733
814
  } catch (err) {
734
815
  console.error("[oauth] JWT exchange error:", err);
735
816
  return jsonResponse(
736
- { error: "server_error", error_description: `JWT exchange failed: ${err instanceof Error ? err.message : String(err)}` },
817
+ {
818
+ error: "server_error",
819
+ error_description: `JWT exchange failed: ${err instanceof Error ? err.message : String(err)}`,
820
+ },
737
821
  500,
738
822
  );
739
823
  }
@@ -746,7 +830,10 @@ export function createAgentServer(
746
830
 
747
831
  if (!clientId || !clientSecret) {
748
832
  return jsonResponse(
749
- { error: "invalid_request", error_description: "Missing client_id or client_secret" },
833
+ {
834
+ error: "invalid_request",
835
+ error_description: "Missing client_id or client_secret",
836
+ },
750
837
  400,
751
838
  );
752
839
  }
@@ -763,7 +850,10 @@ export function createAgentServer(
763
850
  const tokenResult = (result as any)?.result;
764
851
  if (!tokenResult?.accessToken) {
765
852
  return jsonResponse(
766
- { error: "invalid_client", error_description: "Authentication failed" },
853
+ {
854
+ error: "invalid_client",
855
+ error_description: "Authentication failed",
856
+ },
767
857
  401,
768
858
  );
769
859
  }
@@ -786,7 +876,8 @@ export function createAgentServer(
786
876
  return jsonResponse(
787
877
  {
788
878
  error: "unsupported_grant_type",
789
- error_description: "Supported grant types: client_credentials, jwt_exchange",
879
+ error_description:
880
+ "Supported grant types: client_credentials, jwt_exchange",
790
881
  },
791
882
  400,
792
883
  );
@@ -798,7 +889,6 @@ export function createAgentServer(
798
889
 
799
890
  async function fetch(req: Request): Promise<Response> {
800
891
  try {
801
-
802
892
  const url = new URL(req.url);
803
893
  const path = url.pathname.replace(basePath, "") || "/";
804
894
 
@@ -845,11 +935,28 @@ export function createAgentServer(
845
935
  return cors ? addCors(res) : res;
846
936
  }
847
937
 
938
+ // ── OIDC Sign-In (authorize + callback) ──
939
+ if (
940
+ oidcSignIn &&
941
+ (path === "/signin/authorize" || path === "/signin/callback")
942
+ ) {
943
+ const baseUrl = resolveBaseUrl(req);
944
+ const res = await oidcSignIn.handleRequest(req, {
945
+ baseUrl: baseUrl + basePath,
946
+ signingKey: serverSigningKeys[0],
947
+ issuerUrl: baseUrl,
948
+ });
949
+ if (res) return cors ? addCors(res) : res;
950
+ }
951
+
848
952
  // ── GET /oauth/authorize → Identity linking redirect (browser flow) ──
849
953
  if (path === "/oauth/authorize" && req.method === "GET") {
850
954
  if (!oauthIdentityProvider) {
851
955
  const res = jsonResponse(
852
- { error: "not_configured", error_description: "No OAuth identity provider configured" },
956
+ {
957
+ error: "not_configured",
958
+ error_description: "No OAuth identity provider configured",
959
+ },
853
960
  404,
854
961
  );
855
962
  return cors ? addCors(res) : res;
@@ -860,7 +967,10 @@ export function createAgentServer(
860
967
 
861
968
  if (!token) {
862
969
  const res = jsonResponse(
863
- { error: "invalid_request", error_description: "Missing token parameter" },
970
+ {
971
+ error: "invalid_request",
972
+ error_description: "Missing token parameter",
973
+ },
864
974
  400,
865
975
  );
866
976
  return cors ? addCors(res) : res;
@@ -871,22 +981,51 @@ export function createAgentServer(
871
981
  const storeIssuers = authConfig?.store
872
982
  ? await authConfig.store.listTrustedIssuers()
873
983
  : [];
874
- const configIssuerUrls = configTrustedIssuers.map(i => typeof i === "string" ? i : i.issuer);
875
- const allIssuerUrls = [...new Set([...storeIssuers, ...configIssuerUrls])];
876
- console.log("[oauth/authorize] storeIssuers:", storeIssuers.length, "configIssuers:", configIssuerUrls.length, "total:", allIssuerUrls.length, "urls:", allIssuerUrls);
984
+ const configIssuerUrls = configTrustedIssuers.map((i) =>
985
+ typeof i === "string" ? i : i.issuer,
986
+ );
987
+ const allIssuerUrls = [
988
+ ...new Set([...storeIssuers, ...configIssuerUrls]),
989
+ ];
990
+ console.log(
991
+ "[oauth/authorize] storeIssuers:",
992
+ storeIssuers.length,
993
+ "configIssuers:",
994
+ configIssuerUrls.length,
995
+ "total:",
996
+ allIssuerUrls.length,
997
+ "urls:",
998
+ allIssuerUrls,
999
+ );
877
1000
  for (const issuerUrl of allIssuerUrls) {
878
1001
  try {
879
1002
  const result = await verifyJwtFromIssuer(token, issuerUrl);
880
- console.log("[oauth/authorize] verify", issuerUrl, "->", result ? "OK" : "null");
1003
+ console.log(
1004
+ "[oauth/authorize] verify",
1005
+ issuerUrl,
1006
+ "->",
1007
+ result ? "OK" : "null",
1008
+ );
881
1009
  if (result) {
882
1010
  claims = result as unknown as Record<string, unknown>;
883
1011
  break;
884
1012
  }
885
- } catch (e: any) { console.log("[oauth/authorize] verify", issuerUrl, "-> ERROR:", e.message); }
1013
+ } catch (e: any) {
1014
+ console.log(
1015
+ "[oauth/authorize] verify",
1016
+ issuerUrl,
1017
+ "-> ERROR:",
1018
+ e.message,
1019
+ );
1020
+ }
886
1021
  }
887
1022
  if (!claims) {
888
1023
  const res = jsonResponse(
889
- { error: "invalid_token", error_description: "JWT verification failed against all trusted issuers" },
1024
+ {
1025
+ error: "invalid_token",
1026
+ error_description:
1027
+ "JWT verification failed against all trusted issuers",
1028
+ },
890
1029
  401,
891
1030
  );
892
1031
  return cors ? addCors(res) : res;
@@ -908,7 +1047,10 @@ export function createAgentServer(
908
1047
  if (path === "/oauth/callback" && req.method === "GET") {
909
1048
  if (!oauthIdentityProvider) {
910
1049
  const res = jsonResponse(
911
- { error: "not_configured", error_description: "No OAuth identity provider configured" },
1050
+ {
1051
+ error: "not_configured",
1052
+ error_description: "No OAuth identity provider configured",
1053
+ },
912
1054
  404,
913
1055
  );
914
1056
  return cors ? addCors(res) : res;
@@ -922,15 +1064,19 @@ export function createAgentServer(
922
1064
 
923
1065
  // ── GET /health → Health check ──
924
1066
  if (path === "/health" && req.method === "GET") {
925
- const res = jsonResponse({ status: "ok", agents: registry.listPaths() });
1067
+ const res = jsonResponse({
1068
+ status: "ok",
1069
+ agents: registry.listPaths(),
1070
+ });
926
1071
  return cors ? addCors(res) : res;
927
1072
  }
928
1073
 
929
1074
  // ── GET /.well-known/jwks.json → JWKS public keys ──
930
1075
  if (path === "/.well-known/jwks.json" && req.method === "GET") {
931
- const jwks = serverSigningKeys.length > 0
932
- ? await buildJwks(serverSigningKeys)
933
- : { keys: [] };
1076
+ const jwks =
1077
+ serverSigningKeys.length > 0
1078
+ ? await buildJwks(serverSigningKeys)
1079
+ : { keys: [] };
934
1080
  const res = jsonResponse(jwks);
935
1081
  return cors ? addCors(res) : res;
936
1082
  }
@@ -946,7 +1092,9 @@ export function createAgentServer(
946
1092
  call_endpoint: baseUrl,
947
1093
  supported_grant_types: ["client_credentials", "jwt_exchange"],
948
1094
  authorization_endpoint: `${baseUrl}/oauth/authorize`,
949
- agents: registry.listPaths(),
1095
+ ...(oidcSignIn
1096
+ ? { signin_endpoint: `${baseUrl}/signin/authorize` }
1097
+ : {}),
950
1098
  });
951
1099
  return cors ? addCors(res) : res;
952
1100
  }
@@ -954,7 +1102,9 @@ export function createAgentServer(
954
1102
  // ── GET /list → List agents (legacy endpoint) ──
955
1103
  if (path === "/list" && req.method === "GET") {
956
1104
  const agents = registry.list();
957
- const visible = agents.filter((agent) => canSeeAgent(agent, effectiveAuth));
1105
+ const visible = agents.filter((agent) =>
1106
+ canSeeAgent(agent, effectiveAuth),
1107
+ );
958
1108
  const res = jsonResponse(
959
1109
  visible.map((agent) => ({
960
1110
  path: agent.path,
@@ -979,6 +1129,151 @@ export function createAgentServer(
979
1129
  return cors ? addCors(res) : res;
980
1130
  }
981
1131
 
1132
+ // ── GET /agents → List public agents (discovery endpoint) ──
1133
+ if (path === "/agents" && req.method === "GET") {
1134
+ const agents = registry.list();
1135
+ const visible = agents.filter((agent) => {
1136
+ // Only show agents with explicit visibility
1137
+ if (!agent.visibility) return false;
1138
+ return canSeeAgent(agent, effectiveAuth);
1139
+ });
1140
+ const res = jsonResponse(
1141
+ visible.map((agent) => ({
1142
+ path: agent.path,
1143
+ name: agent.config?.name,
1144
+ description:
1145
+ agent.config?.description ?? agent.entrypoint?.slice(0, 200),
1146
+ tools: getVisibleTools(agent, effectiveAuth).map((t) => ({
1147
+ name: t.name,
1148
+ description: t.description,
1149
+ })),
1150
+ })),
1151
+ );
1152
+ return cors ? addCors(res) : res;
1153
+ }
1154
+
1155
+ // ── GET /agents/{name} → Agent info (single agent discovery) ──
1156
+ if (path.startsWith("/agents/") && req.method === "GET") {
1157
+ const agentPath = path.slice("/agents/".length); // e.g. "notion"
1158
+ const agent = registry.get(agentPath) ?? registry.get(`@${agentPath}`);
1159
+ if (!agent || !canSeeAgent(agent, effectiveAuth)) {
1160
+ const res = jsonResponse(
1161
+ { error: "not_found", message: `Agent not found: ${agentPath}` },
1162
+ 404,
1163
+ );
1164
+ return cors ? addCors(res) : res;
1165
+ }
1166
+ const res = jsonResponse({
1167
+ path: agent.path,
1168
+ name: agent.config?.name,
1169
+ description:
1170
+ agent.config?.description ?? agent.entrypoint?.slice(0, 200),
1171
+ tools: getVisibleTools(agent, effectiveAuth).map((t) => ({
1172
+ name: t.name,
1173
+ description: t.description,
1174
+ inputSchema: t.inputSchema,
1175
+ })),
1176
+ });
1177
+ return cors ? addCors(res) : res;
1178
+ }
1179
+
1180
+ // ── POST /agents/{name} → Scoped MCP call to single agent ──
1181
+ if (path.startsWith("/agents/") && req.method === "POST") {
1182
+ const agentPath = path.slice("/agents/".length);
1183
+ const agent = registry.get(agentPath) ?? registry.get(`@${agentPath}`);
1184
+ if (!agent || !canSeeAgent(agent, effectiveAuth)) {
1185
+ const res = jsonResponse(
1186
+ { error: "not_found", message: `Agent not found: ${agentPath}` },
1187
+ 404,
1188
+ );
1189
+ return cors ? addCors(res) : res;
1190
+ }
1191
+
1192
+ const body = (await req.json()) as JsonRpcRequest;
1193
+
1194
+ // Scoped JSON-RPC: tools/list only shows this agent's tools
1195
+ if (body.method === "initialize") {
1196
+ const res = jsonResponse(
1197
+ jsonRpcSuccess(body.id, {
1198
+ protocolVersion: "2024-11-05",
1199
+ capabilities: { tools: { listChanged: false } },
1200
+ serverInfo: {
1201
+ name: agent.config?.name ?? agent.path,
1202
+ version: "1.0.0",
1203
+ },
1204
+ }),
1205
+ );
1206
+ return cors ? addCors(res) : res;
1207
+ }
1208
+
1209
+ if (body.method === "tools/list") {
1210
+ const tools = getVisibleTools(agent, effectiveAuth).map((t) => ({
1211
+ name: t.name,
1212
+ description: t.description,
1213
+ inputSchema: t.inputSchema,
1214
+ }));
1215
+ const res = jsonResponse(jsonRpcSuccess(body.id, { tools }));
1216
+ return cors ? addCors(res) : res;
1217
+ }
1218
+
1219
+ if (body.method === "tools/call") {
1220
+ // Auth required for tool execution
1221
+ if (!effectiveAuth) {
1222
+ const res = jsonResponse(
1223
+ jsonRpcError(
1224
+ body.id,
1225
+ -32600,
1226
+ "Authentication required to call tools",
1227
+ ),
1228
+ 401,
1229
+ );
1230
+ return cors ? addCors(res) : res;
1231
+ }
1232
+ const { name, arguments: args } = (body.params ?? {}) as {
1233
+ name: string;
1234
+ arguments?: Record<string, unknown>;
1235
+ };
1236
+ try {
1237
+ const result = await registry.call({
1238
+ action: "execute_tool",
1239
+ path: agent.path,
1240
+ tool: name,
1241
+ params: args ?? {},
1242
+ callerId: effectiveAuth?.callerId,
1243
+ callerType: effectiveAuth?.callerType ?? "external",
1244
+ metadata: effectiveAuth
1245
+ ? {
1246
+ scopes: effectiveAuth.scopes,
1247
+ isRoot: effectiveAuth.isRoot,
1248
+ ...(effectiveAuth.issuer
1249
+ ? { issuer: effectiveAuth.issuer }
1250
+ : {}),
1251
+ }
1252
+ : undefined,
1253
+ });
1254
+ const res = jsonResponse(jsonRpcSuccess(body.id, result));
1255
+ return cors ? addCors(res) : res;
1256
+ } catch (err) {
1257
+ console.error("[server] Scoped tool call error:", err);
1258
+ const res = jsonResponse(
1259
+ jsonRpcSuccess(
1260
+ body.id,
1261
+ mcpResult(
1262
+ `Error: ${err instanceof Error ? err.message : String(err)}`,
1263
+ true,
1264
+ ),
1265
+ ),
1266
+ );
1267
+ return cors ? addCors(res) : res;
1268
+ }
1269
+ }
1270
+
1271
+ const res = jsonResponse(
1272
+ jsonRpcError(body.id, -32601, `Method not found: ${body.method}`),
1273
+ );
1274
+ return cors ? addCors(res) : res;
1275
+ }
1276
+
982
1277
  // ── Not found ──
983
1278
  const res = jsonResponse(
984
1279
  {
@@ -1022,7 +1317,7 @@ export function createAgentServer(
1022
1317
  if (options.signingKey && serverSigningKeys.length === 0) {
1023
1318
  serverSigningKeys.push(options.signingKey);
1024
1319
  } else if (authConfig?.store && serverSigningKeys.length === 0) {
1025
- const stored = await authConfig.store.getSigningKeys() ?? [];
1320
+ const stored = (await authConfig.store.getSigningKeys()) ?? [];
1026
1321
  for (const exported of stored) {
1027
1322
  serverSigningKeys.push(await importSigningKey(exported));
1028
1323
  }
@@ -1045,7 +1340,9 @@ export function createAgentServer(
1045
1340
  fetch,
1046
1341
  });
1047
1342
  (this as any).url = `http://${hostname}:${port}`;
1048
- console.log(`[agents-sdk] Server listening on http://${hostname}:${port}`);
1343
+ console.log(
1344
+ `[agents-sdk] Server listening on http://${hostname}:${port}`,
1345
+ );
1049
1346
  },
1050
1347
 
1051
1348
  async stop() {
@@ -1060,23 +1357,28 @@ export function createAgentServer(
1060
1357
 
1061
1358
  async signJwt(claims: Record<string, unknown>): Promise<string> {
1062
1359
  if (serverSigningKeys.length === 0) {
1063
- throw new Error('No signing keys available. Call start() or initKeys() first.');
1360
+ throw new Error(
1361
+ "No signing keys available. Call start() or initKeys() first.",
1362
+ );
1064
1363
  }
1065
1364
  const key = serverSigningKeys[0];
1066
1365
  return signJwtES256(
1067
- { sub: 'system', name: 'atlas-os', scopes: ['*'], ...claims } as any,
1366
+ { sub: "system", name: "atlas-os", scopes: ["*"], ...claims } as any,
1068
1367
  key.privateKey,
1069
1368
  key.kid,
1070
- options.serverName ?? 'agents-sdk',
1071
- '1h',
1369
+ options.serverName ?? "agents-sdk",
1370
+ "1h",
1072
1371
  );
1073
1372
  },
1074
1373
 
1075
1374
  addTrustedIssuer(issuerUrl: string, scopes?: string[]): void {
1076
1375
  // Avoid duplicates
1077
- const existing = configTrustedIssuers.find(i => i.issuer === issuerUrl);
1376
+ const existing = configTrustedIssuers.find((i) => i.issuer === issuerUrl);
1078
1377
  if (!existing) {
1079
- configTrustedIssuers.push({ issuer: issuerUrl, scopes: scopes ?? ['*'] });
1378
+ configTrustedIssuers.push({
1379
+ issuer: issuerUrl,
1380
+ scopes: scopes ?? ["*"],
1381
+ });
1080
1382
  console.error(`[agent-server] Added trusted issuer: ${issuerUrl}`);
1081
1383
  }
1082
1384
  },