@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.
- package/dist/agent-definitions/auth.d.ts.map +1 -1
- package/dist/agent-definitions/auth.js +44 -11
- package/dist/agent-definitions/auth.js.map +1 -1
- package/dist/agent-definitions/integrations.d.ts.map +1 -1
- package/dist/agent-definitions/integrations.js +106 -45
- package/dist/agent-definitions/integrations.js.map +1 -1
- package/dist/agent-definitions/remote-registry.d.ts.map +1 -1
- package/dist/agent-definitions/remote-registry.js +174 -45
- package/dist/agent-definitions/remote-registry.js.map +1 -1
- package/dist/agent-definitions/secrets.d.ts.map +1 -1
- package/dist/agent-definitions/secrets.js +1 -4
- package/dist/agent-definitions/secrets.js.map +1 -1
- package/dist/agent-definitions/users.d.ts.map +1 -1
- package/dist/agent-definitions/users.js +14 -3
- package/dist/agent-definitions/users.js.map +1 -1
- package/dist/define-config.d.ts +125 -0
- package/dist/define-config.d.ts.map +1 -0
- package/dist/define-config.js +75 -0
- package/dist/define-config.js.map +1 -0
- package/dist/define.d.ts +11 -2
- package/dist/define.d.ts.map +1 -1
- package/dist/define.js +57 -26
- package/dist/define.js.map +1 -1
- package/dist/events.d.ts +133 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +57 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +16 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -3
- package/dist/index.js.map +1 -1
- package/dist/integration-interface.d.ts +3 -3
- package/dist/integration-interface.d.ts.map +1 -1
- package/dist/integration-interface.js +29 -21
- package/dist/integration-interface.js.map +1 -1
- package/dist/integrations-store.d.ts +2 -2
- package/dist/integrations-store.d.ts.map +1 -1
- package/dist/integrations-store.js +3 -3
- package/dist/integrations-store.js.map +1 -1
- package/dist/jwt.d.ts.map +1 -1
- package/dist/jwt.js +7 -5
- package/dist/jwt.js.map +1 -1
- package/dist/key-manager.d.ts.map +1 -1
- package/dist/key-manager.js +5 -3
- package/dist/key-manager.js.map +1 -1
- package/dist/oidc-signin.d.ts +32 -0
- package/dist/oidc-signin.d.ts.map +1 -0
- package/dist/oidc-signin.js +138 -0
- package/dist/oidc-signin.js.map +1 -0
- package/dist/registry-consumer.d.ts +104 -0
- package/dist/registry-consumer.d.ts.map +1 -0
- package/dist/registry-consumer.js +230 -0
- package/dist/registry-consumer.js.map +1 -0
- package/dist/registry.d.ts +5 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +76 -4
- package/dist/registry.js.map +1 -1
- package/dist/secret-collection.d.ts.map +1 -1
- package/dist/secret-collection.js.map +1 -1
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +222 -27
- package/dist/server.js.map +1 -1
- package/dist/test-utils/mock-oidc-server.d.ts +36 -0
- package/dist/test-utils/mock-oidc-server.d.ts.map +1 -0
- package/dist/test-utils/mock-oidc-server.js +96 -0
- package/dist/test-utils/mock-oidc-server.js.map +1 -0
- package/dist/types.d.ts +106 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -1
- package/src/agent-definitions/auth.ts +106 -38
- package/src/agent-definitions/integrations.ts +201 -73
- package/src/agent-definitions/remote-registry.ts +262 -65
- package/src/agent-definitions/secrets.ts +22 -8
- package/src/agent-definitions/users.ts +16 -4
- package/src/cli.ts +293 -0
- package/src/codegen.test.ts +527 -0
- package/src/codegen.ts +1348 -0
- package/src/consumer.test.ts +536 -0
- package/src/define-config.ts +205 -0
- package/src/define.ts +134 -46
- package/src/events.ts +237 -0
- package/src/index.ts +107 -8
- package/src/integration-interface.ts +52 -28
- package/src/integrations-store.ts +9 -5
- package/src/jwt.ts +48 -19
- package/src/key-manager.test.ts +22 -13
- package/src/key-manager.ts +8 -10
- package/src/oidc-signin.ts +223 -0
- package/src/registry-consumer.ts +413 -0
- package/src/registry.ts +115 -9
- package/src/secret-collection.ts +2 -1
- package/src/server.test.ts +304 -238
- package/src/server.ts +371 -69
- package/src/test-utils/mock-oidc-server.ts +123 -0
- 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 {
|
|
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(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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?: {
|
|
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(
|
|
316
|
+
const verified = await verifyJwtFromIssuer(
|
|
317
|
+
credential,
|
|
318
|
+
issuerConfig.issuer,
|
|
319
|
+
);
|
|
301
320
|
if (verified) {
|
|
302
321
|
const scopes = issuerConfig.scopes;
|
|
303
|
-
const isSystem =
|
|
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[] = (
|
|
453
|
-
|
|
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 (
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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(
|
|
652
|
-
|
|
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(
|
|
737
|
+
console.error(
|
|
738
|
+
`[jwt_exchange] Reverse connection stored for ${assertionPayload.iss}`,
|
|
739
|
+
);
|
|
669
740
|
} else {
|
|
670
|
-
console.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(
|
|
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
|
-
{
|
|
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:
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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:
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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 =>
|
|
875
|
-
|
|
876
|
-
|
|
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(
|
|
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) {
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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({
|
|
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 =
|
|
932
|
-
|
|
933
|
-
|
|
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
|
-
|
|
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) =>
|
|
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(
|
|
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(
|
|
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:
|
|
1366
|
+
{ sub: "system", name: "atlas-os", scopes: ["*"], ...claims } as any,
|
|
1068
1367
|
key.privateKey,
|
|
1069
1368
|
key.kid,
|
|
1070
|
-
options.serverName ??
|
|
1071
|
-
|
|
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({
|
|
1378
|
+
configTrustedIssuers.push({
|
|
1379
|
+
issuer: issuerUrl,
|
|
1380
|
+
scopes: scopes ?? ["*"],
|
|
1381
|
+
});
|
|
1080
1382
|
console.error(`[agent-server] Added trusted issuer: ${issuerUrl}`);
|
|
1081
1383
|
}
|
|
1082
1384
|
},
|