@slashfi/agents-sdk 0.11.2 → 0.13.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 (48) hide show
  1. package/dist/agent-definitions/auth.d.ts +17 -1
  2. package/dist/agent-definitions/auth.d.ts.map +1 -1
  3. package/dist/agent-definitions/auth.js +123 -4
  4. package/dist/agent-definitions/auth.js.map +1 -1
  5. package/dist/agent-definitions/integrations.d.ts +2 -14
  6. package/dist/agent-definitions/integrations.d.ts.map +1 -1
  7. package/dist/agent-definitions/integrations.js +64 -19
  8. package/dist/agent-definitions/integrations.js.map +1 -1
  9. package/dist/agent-definitions/remote-registry.d.ts +19 -14
  10. package/dist/agent-definitions/remote-registry.d.ts.map +1 -1
  11. package/dist/agent-definitions/remote-registry.js +219 -381
  12. package/dist/agent-definitions/remote-registry.js.map +1 -1
  13. package/dist/agent-definitions/users.d.ts.map +1 -1
  14. package/dist/agent-definitions/users.js +29 -1
  15. package/dist/agent-definitions/users.js.map +1 -1
  16. package/dist/define.d.ts +6 -4
  17. package/dist/define.d.ts.map +1 -1
  18. package/dist/define.js +82 -3
  19. package/dist/define.js.map +1 -1
  20. package/dist/index.d.ts +3 -2
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/jwt.js +1 -1
  25. package/dist/jwt.js.map +1 -1
  26. package/dist/key-manager.d.ts +76 -0
  27. package/dist/key-manager.d.ts.map +1 -0
  28. package/dist/key-manager.js +156 -0
  29. package/dist/key-manager.js.map +1 -0
  30. package/dist/server.d.ts +39 -0
  31. package/dist/server.d.ts.map +1 -1
  32. package/dist/server.js +228 -52
  33. package/dist/server.js.map +1 -1
  34. package/dist/types.d.ts +53 -1
  35. package/dist/types.d.ts.map +1 -1
  36. package/package.json +1 -1
  37. package/src/agent-definitions/auth.ts +165 -6
  38. package/src/agent-definitions/integrations.ts +59 -28
  39. package/src/agent-definitions/remote-registry.ts +219 -513
  40. package/src/agent-definitions/users.ts +35 -1
  41. package/src/define.ts +98 -6
  42. package/src/index.ts +3 -1
  43. package/src/jwt.ts +1 -1
  44. package/src/key-manager.test.ts +273 -0
  45. package/src/key-manager.ts +257 -0
  46. package/src/server.test.ts +284 -0
  47. package/src/server.ts +334 -60
  48. package/src/types.ts +44 -1
@@ -1,133 +1,51 @@
1
1
  /**
2
- * Remote Registry Agent
2
+ * Remote Registry Agent (JWKS Auth)
3
3
  *
4
4
  * Integration agent for connecting to remote agent registries.
5
- * Uses the IntegrationMethods pattern so @integrations can discover
6
- * and interact with it uniformly via setup/connect/list/get/update.
5
+ * Uses JWKS trust exchange + jwt_exchange for authentication.
6
+ * No client credentials stored uses signJwt for outbound calls.
7
7
  *
8
- * Each remote registry connection stores:
9
- * - url: the registry's base URL
10
- * - tenantId: the tenant created on the remote registry
11
- * - clientId + clientSecret: credentials for authentication
8
+ * Flow:
9
+ * setup: discover JWKS add trusted issuer → call @auth/create_tenant → store connection
10
+ * connect: POST /oauth/token jwt_exchange identity_required /oauth/authorize linked
11
+ * proxy: sign JWT POST to remote MCP endpoint
12
12
  *
13
13
  * @example
14
14
  * ```typescript
15
- * import { createRemoteRegistryAgent, createAgentRegistry } from '@slashfi/agents-sdk';
15
+ * import { createRemoteRegistryAgent, createAgentServer } from '@slashfi/agents-sdk';
16
16
  *
17
- * const registry = createAgentRegistry();
18
- * registry.register(createRemoteRegistryAgent({ secretStore }));
17
+ * const server = createAgentServer(registry, { ... });
18
+ * await server.start();
19
19
  *
20
- * // Then via @integrations:
21
- * // setup_integration({ provider: 'remote-registry', params: { url: 'https://registry.slash.com', name: 'slash' } })
22
- * // connect_integration({ provider: 'remote-registry', params: { registryId: 'slash', userId: 'user_123' } })
20
+ * registry.register(createRemoteRegistryAgent({
21
+ * secretStore,
22
+ * signJwt: (claims) => server.signJwt(claims),
23
+ * }));
23
24
  * ```
24
25
  */
25
26
  import { defineAgent, defineTool } from "../define.js";
26
- // ============================================
27
- // Helpers
28
- // ============================================
29
- /**
30
- * Make an MCP JSON-RPC call to a remote registry.
31
- */
32
- async function mcpCall(url, token, request) {
33
- const res = await globalThis.fetch(url, {
34
- method: "POST",
35
- headers: {
36
- "Content-Type": "application/json",
37
- Authorization: `Bearer ${token}`,
38
- },
39
- body: JSON.stringify({
40
- jsonrpc: "2.0",
41
- id: Date.now(),
42
- method: "tools/call",
43
- params: {
44
- name: "call_agent",
45
- arguments: {
46
- request: {
47
- action: request.action,
48
- path: request.path,
49
- tool: request.tool,
50
- params: request.params ?? {},
51
- },
52
- },
53
- },
54
- }),
55
- });
56
- if (!res.ok) {
57
- throw new Error(`Registry call failed: ${res.status} ${res.statusText}`);
58
- }
59
- const json = (await res.json());
60
- if (json.error) {
61
- throw new Error(`Registry RPC error: ${json.error.message ?? JSON.stringify(json.error)}`);
62
- }
63
- // Parse the tool result from MCP response
64
- const text = json?.result?.content?.[0]?.text;
65
- if (!text)
66
- return json?.result;
67
- try {
68
- return JSON.parse(text);
69
- }
70
- catch {
71
- return { raw: text };
72
- }
73
- }
74
- /**
75
- * Get an access token from a remote registry via /oauth/token.
76
- */
77
- async function getRegistryToken(url, clientId, clientSecret) {
78
- const tokenUrl = url.replace(/\/$/, "") + "/oauth/token";
79
- const res = await globalThis.fetch(tokenUrl, {
80
- method: "POST",
81
- headers: { "Content-Type": "application/json" },
82
- body: JSON.stringify({
83
- grant_type: "client_credentials",
84
- client_id: clientId,
85
- client_secret: clientSecret,
86
- }),
87
- });
88
- if (!res.ok) {
89
- const body = await res.text();
90
- throw new Error(`Token exchange failed: ${res.status} ${body}`);
91
- }
92
- const json = (await res.json());
93
- return json.access_token;
94
- }
95
- // ============================================
96
- // Create Remote Registry Agent
97
- // ============================================
27
+ const ENTITY_TYPE = "remote-registry-connections";
98
28
  export function createRemoteRegistryAgent(options) {
99
- const { secretStore } = options;
100
- // We store all registry connections as a single JSON blob per owner.
101
- // The secret ID is stored via associate/resolveByEntity for lookup.
102
- const ENTITY_TYPE = "remote-registry-connections";
103
- /**
104
- * Store a registry connection (metadata + credentials).
105
- */
106
- async function storeConnection(ownerId, conn, clientSecret) {
107
- // Load existing connections, update, and store back
29
+ const { secretStore, signJwt, addTrustedIssuer } = options;
30
+ // --- Connection storage (KV via SecretStore) ---
31
+ async function storeConnection(ownerId, conn) {
32
+ console.error("[remote-registry] storeConnection for owner:", ownerId, "conn:", conn.id);
108
33
  const all = await loadAllConnections(ownerId);
109
- all[conn.id] = { ...conn, clientSecret };
34
+ all[conn.id] = conn;
110
35
  const value = JSON.stringify(all);
111
- // Store the blob
112
36
  const scope = { tenantId: ownerId };
113
37
  const secretId = await secretStore.store(value, ownerId);
114
- // Link it so we can find it later
115
38
  if (secretStore.associate) {
116
39
  await secretStore.associate(secretId, ENTITY_TYPE, ownerId, scope);
117
40
  }
118
41
  }
119
- /**
120
- * Load all connections from the stored blob.
121
- */
122
42
  async function loadAllConnections(ownerId) {
123
- // Try resolveByEntity first (v0.7.0+)
43
+ console.error("[remote-registry] loadAllConnections for owner:", ownerId);
124
44
  if (secretStore.resolveByEntity) {
125
45
  const scope = { tenantId: ownerId };
126
46
  const secretIds = await secretStore.resolveByEntity(ENTITY_TYPE, ownerId, scope);
127
- if (secretIds && secretIds.length > 0) {
128
- // Resolve the latest stored blob
129
- const latestId = secretIds[secretIds.length - 1];
130
- const raw = await secretStore.resolve(latestId, ownerId);
47
+ if (secretIds?.length) {
48
+ const raw = await secretStore.resolve(secretIds[secretIds.length - 1], ownerId);
131
49
  if (raw) {
132
50
  try {
133
51
  return JSON.parse(raw);
@@ -140,321 +58,241 @@ export function createRemoteRegistryAgent(options) {
140
58
  }
141
59
  return {};
142
60
  }
143
- /**
144
- * Load a registry connection.
145
- */
146
61
  async function loadConnection(ownerId, registryId) {
147
62
  const all = await loadAllConnections(ownerId);
148
- const entry = all[registryId];
149
- if (!entry)
150
- return null;
151
- const { clientSecret, ...conn } = entry;
152
- return { conn, clientSecret };
63
+ return all[registryId] ?? null;
153
64
  }
154
- /**
155
- * List all registry connections for an owner.
156
- */
157
- async function listConnectionsList(ownerId) {
158
- const all = await loadAllConnections(ownerId);
159
- return Object.values(all).map(({ clientSecret: _, ...conn }) => conn);
65
+ // --- MCP call helper ---
66
+ async function mcpCall(url, jwt, request) {
67
+ const res = await globalThis.fetch(url, {
68
+ method: "POST",
69
+ headers: {
70
+ "Content-Type": "application/json",
71
+ Authorization: `Bearer ${jwt}`,
72
+ },
73
+ body: JSON.stringify({
74
+ jsonrpc: "2.0",
75
+ id: Date.now(),
76
+ method: "tools/call",
77
+ params: { name: "call_agent", arguments: { request } },
78
+ }),
79
+ });
80
+ const rpc = await res.json();
81
+ const text = rpc.result?.content?.[0]?.text;
82
+ return text ? JSON.parse(text) : rpc.result;
160
83
  }
161
- /**
162
- * Get an authenticated token for a registry connection.
163
- */
164
- async function getAuthenticatedToken(ownerId, registryId) {
165
- const data = await loadConnection(ownerId, registryId);
166
- if (!data) {
167
- throw new Error(`No registry connection '${registryId}'. Use setup_integration first.`);
84
+ // --- Proxy: sign JWT and call remote ---
85
+ async function proxyCall(ownerId, registryId, request) {
86
+ const conn = await loadConnection(ownerId, registryId);
87
+ if (!conn) {
88
+ return { success: false, error: `No connection '${registryId}'. Use setup_integration first.` };
168
89
  }
169
- const token = await getRegistryToken(data.conn.url, data.conn.clientId, data.clientSecret);
170
- return { token, conn: data.conn };
90
+ const jwt = await signJwt({ tenantId: conn.remoteTenantId, action: "proxy", type: "agent-registry" });
91
+ const result = await mcpCall(conn.url, jwt, request);
92
+ return { success: true, result };
171
93
  }
172
- // ---- Tools ----
173
- const callRemoteTool = defineTool({
174
- name: "call_remote",
175
- description: "Make an authenticated MCP call to a remote agent registry. " +
176
- "Proxies the request with the stored tenant credentials.",
94
+ // --- Tools ---
95
+ const proxyTool = defineTool({
96
+ name: "proxy_call",
97
+ description: "Proxy an MCP call to a connected remote registry.",
177
98
  visibility: "public",
178
99
  inputSchema: {
179
100
  type: "object",
180
101
  properties: {
181
- registryId: {
182
- type: "string",
183
- description: "Registry connection ID",
184
- },
185
- agentPath: {
186
- type: "string",
187
- description: "Agent path on the remote registry (e.g. '@integrations')",
188
- },
189
- action: {
190
- type: "string",
191
- description: "Action to perform (e.g. 'execute_tool')",
192
- },
193
- tool: {
194
- type: "string",
195
- description: "Tool name to call",
196
- },
197
- params: {
198
- type: "object",
199
- description: "Tool parameters",
200
- },
102
+ registryId: { type: "string", description: "Registry connection ID" },
103
+ action: { type: "string" },
104
+ path: { type: "string" },
105
+ tool: { type: "string" },
106
+ params: { type: "object" },
201
107
  },
202
- required: ["registryId", "agentPath", "action", "tool"],
108
+ required: ["registryId", "action", "path", "tool"],
203
109
  },
204
- execute: async (input, ctx) => {
205
- const { token, conn } = await getAuthenticatedToken(ctx.callerId, input.registryId);
206
- return mcpCall(conn.url, token, {
110
+ execute: async (input, _ctx) => {
111
+ return proxyCall("system", input.registryId, {
207
112
  action: input.action,
208
- path: input.agentPath,
113
+ path: input.path,
209
114
  tool: input.tool,
210
115
  params: input.params,
211
116
  });
212
117
  },
213
118
  });
214
- const listRemoteAgentsTool = defineTool({
215
- name: "list_remote_agents",
216
- description: "List agents available on a remote registry.",
119
+ const listTool = defineTool({
120
+ name: "list_connections",
121
+ description: "List all connected remote registries.",
217
122
  visibility: "public",
218
- inputSchema: {
219
- type: "object",
220
- properties: {
221
- registryId: {
222
- type: "string",
223
- description: "Registry connection ID",
224
- },
225
- },
226
- required: ["registryId"],
123
+ inputSchema: { type: "object", properties: {} },
124
+ execute: async (_input, _ctx) => {
125
+ const all = await loadAllConnections("system");
126
+ return {
127
+ connections: Object.values(all).map(c => ({
128
+ id: c.id,
129
+ name: c.name,
130
+ url: c.url,
131
+ remoteTenantId: c.remoteTenantId,
132
+ })),
133
+ };
227
134
  },
228
- execute: async (input, ctx) => {
229
- const { token, conn } = await getAuthenticatedToken(ctx.callerId, input.registryId);
230
- const listUrl = conn.url.replace(/\/$/, "") + "/list";
231
- const res = await globalThis.fetch(listUrl, {
232
- headers: { Authorization: `Bearer ${token}` },
135
+ });
136
+ // Extract setup/connect as standalone functions to avoid circular reference
137
+ const setupFn = async (params, _ctx) => {
138
+ console.log("[remote-registry] setupFn called with:", JSON.stringify(params));
139
+ const url = params.url;
140
+ const name = params.name ?? "registry";
141
+ const oidcUserId = params.oidcUserId;
142
+ if (!url)
143
+ return { success: false, error: "url is required" };
144
+ try {
145
+ const baseUrl = url.replace(/\/$/, "");
146
+ // Phase 2: Complete setup after OIDC — store connection with resolved tenant
147
+ if (oidcUserId) {
148
+ const jwt = await signJwt({ sub: oidcUserId, action: "setup", type: "agent-registry" });
149
+ const tokenRes = await globalThis.fetch(baseUrl + "/oauth/token", {
150
+ method: "POST", headers: { "Content-Type": "application/json" },
151
+ body: JSON.stringify({ grant_type: "jwt_exchange", assertion: jwt, scope: "setup", redirect_uri: params.redirect_uri ?? "" }),
152
+ });
153
+ const tokenData = await tokenRes.json();
154
+ if (!tokenData.access_token && !tokenData.tenant_id) {
155
+ return { success: false, error: tokenData.error_description ?? tokenData.error ?? "Setup completion failed" };
156
+ }
157
+ const remoteTenantId = tokenData.tenant_id ?? name;
158
+ const ownerId = "system";
159
+ await storeConnection(ownerId, { id: name, name, url: baseUrl, remoteTenantId, createdAt: Date.now() });
160
+ return { success: true, data: { registryId: name, url, remoteTenantId } };
161
+ }
162
+ // Phase 1: Discover JWKS, establish trust, then request OIDC
163
+ const configUrl = baseUrl + "/.well-known/configuration";
164
+ console.log("[setupFn] fetching config:", configUrl);
165
+ const configRes = await globalThis.fetch(configUrl);
166
+ console.log("[setupFn] config status:", configRes.status);
167
+ if (!configRes.ok)
168
+ return { success: false, error: "Failed to discover registry at " + configUrl };
169
+ const remoteConfig = await configRes.json();
170
+ if (remoteConfig.jwks_uri) {
171
+ console.log("[setupFn] fetching JWKS:", remoteConfig.jwks_uri);
172
+ const jwksRes = await globalThis.fetch(remoteConfig.jwks_uri);
173
+ console.log("[setupFn] JWKS status:", jwksRes.status);
174
+ if (!jwksRes.ok)
175
+ return { success: false, error: "JWKS not reachable" };
176
+ }
177
+ if (addTrustedIssuer) {
178
+ console.log("[setupFn] adding trusted issuer:", baseUrl);
179
+ await addTrustedIssuer(baseUrl);
180
+ console.log("[setupFn] added trusted issuer");
181
+ }
182
+ // Request identity — atlas will return authorize URL for Slack OIDC
183
+ console.log("[setupFn] Phase 1: requesting identity via jwt_exchange");
184
+ const jwt = await signJwt({ action: "setup", type: "agent-registry", targetUrl: url });
185
+ console.log("[setupFn] POSTing to:", baseUrl + "/oauth/token");
186
+ const tokenRes = await globalThis.fetch(baseUrl + "/oauth/token", {
187
+ method: "POST", headers: { "Content-Type": "application/json" },
188
+ body: JSON.stringify({ grant_type: "jwt_exchange", assertion: jwt, scope: "setup", redirect_uri: params.redirect_uri ?? "" }),
233
189
  });
234
- if (!res.ok) {
235
- throw new Error(`Failed to list agents: ${res.status}`);
190
+ console.log("[setupFn] token status:", tokenRes.status);
191
+ const tokenData = await tokenRes.json();
192
+ console.log("[setupFn] tokenData:", JSON.stringify(tokenData).substring(0, 300));
193
+ // If already set up (user linked), store connection directly
194
+ if (tokenData.access_token) {
195
+ const remoteTenantId = tokenData.tenant_id ?? name;
196
+ const ownerId = "system";
197
+ await storeConnection(ownerId, { id: name, name, url: baseUrl, remoteTenantId, createdAt: Date.now() });
198
+ return { success: true, data: { registryId: name, url, remoteTenantId } };
236
199
  }
237
- return res.json();
238
- },
239
- });
240
- // ---- Agent Definition ----
200
+ // Need OIDC — return authorize URL to caller
201
+ if (tokenData.error === "identity_required") {
202
+ return { success: false, error: "identity_required", data: { authorizeUrl: tokenData.authorize_url, registryId: name, url } };
203
+ }
204
+ return { success: false, error: tokenData.error_description ?? tokenData.error ?? "Unexpected response from token endpoint" };
205
+ }
206
+ catch (err) {
207
+ return { success: false, error: err instanceof Error ? err.message : String(err) };
208
+ }
209
+ };
210
+ const connectFn = async (params, ctx) => {
211
+ const registryId = params.registryId;
212
+ const redirectUri = params.redirectUri ?? "";
213
+ const oidcUserId = params.oidcUserId;
214
+ if (!registryId)
215
+ return { success: false, error: "registryId is required" };
216
+ try {
217
+ const ownerId = "system"; // tenant-scoped
218
+ const conn = await loadConnection(ownerId, registryId);
219
+ if (!conn)
220
+ return { success: false, error: "No connection '" + registryId + "'" };
221
+ // Use OIDC-issued identity if available, never send "anonymous" as sub
222
+ const sub = oidcUserId ?? (ctx.callerId !== "anonymous" ? ctx.callerId : undefined);
223
+ const jwt = await signJwt({ ...(sub ? { sub } : {}), tenantId: conn.remoteTenantId, action: "connect", type: "agent-registry" });
224
+ const tokenRes = await globalThis.fetch(conn.url + "/oauth/token", {
225
+ method: "POST", headers: { "Content-Type": "application/json" },
226
+ body: JSON.stringify({ grant_type: "jwt_exchange", assertion: jwt, redirect_uri: redirectUri }),
227
+ });
228
+ const tokenData = await tokenRes.json();
229
+ if (tokenData.access_token)
230
+ return { success: true, data: { registryId, accessToken: tokenData.access_token, userId: tokenData.user_id, tenantId: tokenData.tenant_id } };
231
+ if (tokenData.error === "identity_required")
232
+ return { success: false, error: "identity_required", data: { authorizeUrl: tokenData.authorize_url, tenantId: tokenData.tenant_id } };
233
+ return { success: false, error: tokenData.error_description ?? tokenData.error ?? "Token exchange failed" };
234
+ }
235
+ catch (err) {
236
+ return { success: false, error: err instanceof Error ? err.message : String(err) };
237
+ }
238
+ };
241
239
  return defineAgent({
242
240
  path: "@remote-registry",
243
241
  entrypoint: "You manage connections to remote agent registries. " +
244
- "Use setup to connect a new registry, connect to register users, " +
245
- "and call_remote to proxy authenticated MCP calls.",
242
+ "Use setup to connect a new registry, connect to link user identities, " +
243
+ "and proxy_call to make authenticated calls.",
246
244
  config: {
247
245
  name: "Remote Registry",
248
- description: "Connect to remote agent registries (MCP over HTTP) for federated integrations",
246
+ description: "Connect to remote agent registries via JWKS trust + jwt_exchange",
249
247
  supportedActions: ["execute_tool", "describe_tools", "load"],
250
- integration: {
251
- provider: "remote-registry",
252
- displayName: "Agent Registry",
253
- icon: "server",
254
- category: "infrastructure",
255
- description: "Connect to a remote agent registry to access its integrations, databases, and agents.",
256
- },
257
248
  },
258
249
  visibility: "public",
259
- integrationMethods: {
260
- async setup(params, ctx) {
261
- const url = params.url;
262
- const name = params.name ?? "registry";
263
- if (!url) {
264
- return { success: false, error: "url is required" };
265
- }
250
+ integration: {
251
+ provider: "remote-registry",
252
+ displayName: "Agent Registry",
253
+ icon: "server",
254
+ category: "infrastructure",
255
+ description: "Connect to a remote agent registry via JWKS trust exchange.",
256
+ setup: (params, ctx) => setupFn(params, ctx),
257
+ connect: (params, ctx) => connectFn(params, ctx),
258
+ async discover(params) {
259
+ const url = params.url ?? "";
266
260
  try {
267
- // 1. Create tenant on remote registry
268
- const setupUrl = url.replace(/\/$/, "") + "/setup";
269
- const setupRes = await globalThis.fetch(setupUrl, {
270
- method: "POST",
271
- headers: { "Content-Type": "application/json" },
272
- body: JSON.stringify({ tenant: name }),
273
- });
274
- if (!setupRes.ok) {
275
- const body = await setupRes.text();
276
- return {
277
- success: false,
278
- error: `Failed to create tenant on registry: ${setupRes.status} ${body}`,
279
- };
280
- }
281
- const setupResult = (await setupRes.json());
282
- if (!setupResult.success || !setupResult.result?.tenantId) {
283
- return {
284
- success: false,
285
- error: "Registry /setup did not return a tenantId",
286
- };
287
- }
288
- const remoteTenantId = setupResult.result.tenantId;
289
- // 2. Register a client for this tenant
290
- // Use the setup token (or root key) to create client credentials
291
- const token = setupResult.result.token;
292
- if (!token) {
293
- return {
294
- success: false,
295
- error: "Registry /setup did not return a token for client creation",
296
- };
297
- }
298
- const registerResult = await mcpCall(url, token, {
299
- action: "execute_tool",
300
- path: "@auth",
301
- tool: "register",
302
- params: {
303
- name: `${name}-client`,
304
- scopes: ["integrations", "secrets", "users"],
305
- },
306
- });
307
- const clientId = registerResult?.clientId ?? registerResult?.result?.clientId;
308
- const clientSecret = registerResult?.clientSecret?.value ??
309
- registerResult?.result?.clientSecret?.value ??
310
- registerResult?.clientSecret;
311
- if (!clientId || !clientSecret) {
312
- return {
313
- success: false,
314
- error: `Failed to register client: ${JSON.stringify(registerResult)}`,
315
- };
316
- }
317
- // 3. Store connection
318
- const conn = {
319
- id: name,
320
- name,
321
- url: url.replace(/\/$/, ""),
322
- remoteTenantId,
323
- clientId,
324
- createdAt: Date.now(),
325
- };
326
- await storeConnection(ctx.callerId, conn, clientSecret);
327
- return {
328
- success: true,
329
- data: {
330
- registryId: name,
331
- url: conn.url,
332
- remoteTenantId,
333
- clientId,
334
- },
335
- };
261
+ const res = await globalThis.fetch(url.replace(/\/$/, "") + "/.well-known/configuration");
262
+ if (!res.ok)
263
+ return { success: false, error: "No configuration endpoint at " + url };
264
+ const config = await res.json();
265
+ return { success: true, data: { url, issuer: config.issuer, grantTypes: config.supported_grant_types, jwksUri: config.jwks_uri } };
336
266
  }
337
267
  catch (err) {
338
- return {
339
- success: false,
340
- error: err instanceof Error ? err.message : String(err),
341
- };
268
+ return { success: false, error: err instanceof Error ? err.message : String(err) };
342
269
  }
343
270
  },
344
- async connect(params, ctx) {
345
- const registryId = params.registryId;
346
- const userId = params.userId ?? ctx.callerId;
347
- if (!registryId) {
348
- return { success: false, error: "registryId is required" };
349
- }
350
- try {
351
- const { token, conn } = await getAuthenticatedToken(ctx.callerId, registryId);
352
- // Register user on the remote registry
353
- const result = await mcpCall(conn.url, token, {
354
- action: "execute_tool",
355
- path: "@users",
356
- tool: "create_user",
357
- params: { name: userId, tenantId: conn.remoteTenantId },
358
- });
359
- return {
360
- success: true,
361
- data: {
362
- registryId,
363
- userId,
364
- remoteUser: result,
365
- },
366
- };
367
- }
368
- catch (err) {
369
- return {
370
- success: false,
371
- error: err instanceof Error ? err.message : String(err),
372
- };
373
- }
271
+ async list() {
272
+ const all = await loadAllConnections("system");
273
+ return { success: true, data: { connections: Object.values(all).map(c => ({ id: c.id, name: c.name, url: c.url, remoteTenantId: c.remoteTenantId })) } };
374
274
  },
375
- async list(_params, ctx) {
376
- try {
377
- const conns = await listConnectionsList(ctx.callerId);
378
- return {
379
- success: true,
380
- data: conns.map((c) => ({
381
- id: c.id,
382
- name: c.name,
383
- url: c.url,
384
- remoteTenantId: c.remoteTenantId,
385
- createdAt: c.createdAt,
386
- })),
387
- };
388
- }
389
- catch (err) {
390
- return {
391
- success: false,
392
- error: err instanceof Error ? err.message : String(err),
393
- };
394
- }
275
+ async get(params) {
276
+ const id = params.registryId ?? "";
277
+ const conn = await loadConnection("system", id);
278
+ if (!conn)
279
+ return { success: false, error: "No connection '" + id + "'" };
280
+ return { success: true, data: conn };
395
281
  },
396
- async get(params, ctx) {
397
- const registryId = params.registryId;
398
- if (!registryId) {
399
- return { success: false, error: "registryId is required" };
400
- }
401
- try {
402
- const data = await loadConnection(ctx.callerId, registryId);
403
- if (!data) {
404
- return {
405
- success: false,
406
- error: `No registry connection '${registryId}'`,
407
- };
408
- }
409
- return {
410
- success: true,
411
- data: {
412
- id: data.conn.id,
413
- name: data.conn.name,
414
- url: data.conn.url,
415
- remoteTenantId: data.conn.remoteTenantId,
416
- clientId: data.conn.clientId,
417
- createdAt: data.conn.createdAt,
418
- },
419
- };
420
- }
421
- catch (err) {
422
- return {
423
- success: false,
424
- error: err instanceof Error ? err.message : String(err),
425
- };
426
- }
427
- },
428
- async update(params, ctx) {
429
- const registryId = params.registryId;
430
- if (!registryId) {
431
- return { success: false, error: "registryId is required" };
432
- }
433
- try {
434
- const data = await loadConnection(ctx.callerId, registryId);
435
- if (!data) {
436
- return {
437
- success: false,
438
- error: `No registry connection '${registryId}'`,
439
- };
440
- }
441
- // Update mutable fields
442
- if (params.name)
443
- data.conn.name = params.name;
444
- if (params.url)
445
- data.conn.url = params.url.replace(/\/$/, "");
446
- await storeConnection(ctx.callerId, data.conn, data.clientSecret);
447
- return { success: true, data: { id: data.conn.id, updated: true } };
448
- }
449
- catch (err) {
450
- return {
451
- success: false,
452
- error: err instanceof Error ? err.message : String(err),
453
- };
454
- }
282
+ async update(params) {
283
+ const id = params.registryId ?? "";
284
+ const conn = await loadConnection("system", id);
285
+ if (!conn)
286
+ return { success: false, error: "No connection '" + id + "'" };
287
+ if (params.name)
288
+ conn.name = params.name;
289
+ if (params.url)
290
+ conn.url = params.url;
291
+ await storeConnection("system", conn);
292
+ return { success: true, data: conn };
455
293
  },
456
294
  },
457
- tools: [callRemoteTool, listRemoteAgentsTool],
295
+ tools: [proxyTool, listTool],
458
296
  });
459
297
  }
460
298
  //# sourceMappingURL=remote-registry.js.map