@slashfi/agents-sdk 0.75.0 → 0.77.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/adk.js +186 -150
- package/dist/adk.js.map +1 -1
- package/dist/cjs/config-store.js +642 -38
- package/dist/cjs/config-store.js.map +1 -1
- package/dist/cjs/define-config.js.map +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/mcp-client.js +98 -0
- package/dist/cjs/mcp-client.js.map +1 -1
- package/dist/cjs/registry-consumer.js +76 -10
- package/dist/cjs/registry-consumer.js.map +1 -1
- package/dist/cjs/server.js +8 -0
- package/dist/cjs/server.js.map +1 -1
- package/dist/config-store.d.ts +43 -8
- package/dist/config-store.d.ts.map +1 -1
- package/dist/config-store.js +643 -39
- package/dist/config-store.js.map +1 -1
- package/dist/define-config.d.ts +83 -17
- package/dist/define-config.d.ts.map +1 -1
- package/dist/define-config.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-client.d.ts +44 -0
- package/dist/mcp-client.d.ts.map +1 -1
- package/dist/mcp-client.js +95 -0
- package/dist/mcp-client.js.map +1 -1
- package/dist/registry-consumer.d.ts +10 -0
- package/dist/registry-consumer.d.ts.map +1 -1
- package/dist/registry-consumer.js +76 -10
- package/dist/registry-consumer.js.map +1 -1
- package/dist/server.d.ts +11 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +8 -0
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/src/adk.ts +107 -65
- package/src/config-store.test.ts +381 -1
- package/src/config-store.ts +750 -55
- package/src/define-config.ts +89 -23
- package/src/index.ts +0 -2
- package/src/mcp-client.ts +121 -0
- package/src/registry-consumer.ts +101 -12
- package/src/server.ts +19 -0
package/src/define-config.ts
CHANGED
|
@@ -34,6 +34,73 @@ export type RegistryAuth =
|
|
|
34
34
|
| { type: "api-key"; key?: string; header?: string }
|
|
35
35
|
| { type: "jwt"; issuer?: string };
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Proxy configuration for a registry.
|
|
39
|
+
*
|
|
40
|
+
* When set, ref operations (`auth`, `auth-status`, `call`, `inspect`,
|
|
41
|
+
* `resources`, `read`, `refresh-token`) for refs sourced from this
|
|
42
|
+
* registry are forwarded to a server-side agent that implements the
|
|
43
|
+
* adk-tools surface. Use this for cloud-hosted registries that own
|
|
44
|
+
* OAuth client credentials and/or user tokens on behalf of consumers
|
|
45
|
+
* (e.g. `api.twin.slash.com/mcp`).
|
|
46
|
+
*
|
|
47
|
+
* - `mode: 'required'` — all ref ops MUST route through the proxy agent.
|
|
48
|
+
* Local handshake (`ref.authLocal`) is refused for refs from this
|
|
49
|
+
* registry because the local environment has no way to build an
|
|
50
|
+
* authorize URL without the server's client credentials.
|
|
51
|
+
* - `mode: 'optional'` — proxy is the default; callers may opt out via
|
|
52
|
+
* `{ preferLocal: true }` on a per-op basis when they already hold
|
|
53
|
+
* local credentials.
|
|
54
|
+
*/
|
|
55
|
+
export interface RegistryProxy {
|
|
56
|
+
mode: 'required' | 'optional';
|
|
57
|
+
/** Agent path to forward to. Defaults to `@config`. */
|
|
58
|
+
agent?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* OAuth state captured after `adk registry auth` completes a dynamic-client
|
|
63
|
+
* registration + authorization-code flow against a registry. Stored alongside
|
|
64
|
+
* `auth.token` so the access token can be refreshed without re-prompting the
|
|
65
|
+
* user. The `auth.token` slot holds the current access token; everything
|
|
66
|
+
* needed to refresh it lives here.
|
|
67
|
+
*/
|
|
68
|
+
export interface RegistryOAuthState {
|
|
69
|
+
/** Token endpoint used for code exchange / refresh. */
|
|
70
|
+
tokenEndpoint: string;
|
|
71
|
+
/** Client ID issued by dynamic client registration (RFC 7591). */
|
|
72
|
+
clientId: string;
|
|
73
|
+
/** Client secret from dynamic registration, when the server issued one. */
|
|
74
|
+
clientSecret?: string;
|
|
75
|
+
/** Refresh token returned by the token endpoint, if any. */
|
|
76
|
+
refreshToken?: string;
|
|
77
|
+
/** Absolute expiry (ISO 8601) derived from `expires_in` at exchange time. */
|
|
78
|
+
expiresAt?: string;
|
|
79
|
+
/** Scopes the access token was granted for. */
|
|
80
|
+
scopes?: string[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Captured auth challenge from a registry that rejected an unauthenticated
|
|
85
|
+
* probe (RFC 6750 `WWW-Authenticate` + RFC 9728 protected-resource metadata).
|
|
86
|
+
* When present on a `RegistryEntry`, the registry has been seen to require
|
|
87
|
+
* credentials and ref ops will fail until `adk registry auth` is run.
|
|
88
|
+
*/
|
|
89
|
+
export interface RegistryAuthRequirement {
|
|
90
|
+
/** Auth scheme advertised in `WWW-Authenticate` (e.g. `Bearer`). */
|
|
91
|
+
scheme?: string;
|
|
92
|
+
/** Realm advertised in `WWW-Authenticate`. */
|
|
93
|
+
realm?: string;
|
|
94
|
+
/** RFC 9728 `resource_metadata` URL parsed from the challenge. */
|
|
95
|
+
resourceMetadataUrl?: string;
|
|
96
|
+
/** Authorization servers from the protected-resource metadata. */
|
|
97
|
+
authorizationServers?: string[];
|
|
98
|
+
/** Scopes supported by the resource. */
|
|
99
|
+
scopes?: string[];
|
|
100
|
+
/** Bearer-methods advertised by the resource (`header`, `body`, `query`). */
|
|
101
|
+
bearerMethodsSupported?: string[];
|
|
102
|
+
}
|
|
103
|
+
|
|
37
104
|
/** A registry endpoint the consumer connects to */
|
|
38
105
|
export interface RegistryEntry {
|
|
39
106
|
/** Registry URL (e.g., 'https://registry.slash.com') */
|
|
@@ -53,6 +120,28 @@ export interface RegistryEntry {
|
|
|
53
120
|
|
|
54
121
|
/** Connection status — set by validation/test, used to filter active entries */
|
|
55
122
|
status?: 'active' | 'inactive' | 'error';
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* If set, ref ops for refs sourced from this registry are forwarded
|
|
126
|
+
* to a server-side adk-tools agent (default `@config`) instead of
|
|
127
|
+
* running locally. See {@link RegistryProxy}.
|
|
128
|
+
*/
|
|
129
|
+
proxy?: RegistryProxy;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Populated by `adk registry add` when the probe returned 401. Cleared
|
|
133
|
+
* by `adk registry auth`. Registry ops refuse to run while this is set
|
|
134
|
+
* and no usable auth credentials are configured.
|
|
135
|
+
*/
|
|
136
|
+
authRequirement?: RegistryAuthRequirement;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* OAuth lifecycle state — refresh token, client credentials from dynamic
|
|
140
|
+
* registration, token endpoint, expiry. Populated by `adk registry auth`
|
|
141
|
+
* when the flow went through OAuth; absent for manually-supplied bearer
|
|
142
|
+
* tokens.
|
|
143
|
+
*/
|
|
144
|
+
oauth?: RegistryOAuthState;
|
|
56
145
|
}
|
|
57
146
|
|
|
58
147
|
// ============================================
|
|
@@ -100,35 +189,12 @@ export type RefEntry = {
|
|
|
100
189
|
status?: 'active' | 'inactive' | 'error';
|
|
101
190
|
};
|
|
102
191
|
|
|
103
|
-
// ============================================
|
|
104
|
-
// Proxy Config
|
|
105
|
-
// ============================================
|
|
106
|
-
|
|
107
|
-
/** A proxy target — remote adk server that handles ref/registry operations */
|
|
108
|
-
export interface ProxyEntry {
|
|
109
|
-
/** Human-readable name */
|
|
110
|
-
name: string;
|
|
111
|
-
/** URL of the remote server */
|
|
112
|
-
url: string;
|
|
113
|
-
/** Connection type: 'mcp' (direct MCP server) or 'registry' (agent on a registry) */
|
|
114
|
-
type: 'mcp' | 'registry';
|
|
115
|
-
/** For type 'registry': the agent path that implements adk tools (e.g. '@config') */
|
|
116
|
-
agent?: string;
|
|
117
|
-
/** Auth for connecting to the proxy */
|
|
118
|
-
auth?: RegistryAuth;
|
|
119
|
-
/** Whether this is the default proxy when no local refs/registries exist */
|
|
120
|
-
default?: boolean;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
192
|
// ============================================
|
|
124
193
|
// Consumer Config
|
|
125
194
|
// ============================================
|
|
126
195
|
|
|
127
196
|
/** The full consumer configuration */
|
|
128
197
|
export interface ConsumerConfig {
|
|
129
|
-
/** Remote adk proxies — forward operations to a remote server */
|
|
130
|
-
proxies?: ProxyEntry[];
|
|
131
|
-
|
|
132
198
|
/** Registries to connect to, in resolution order */
|
|
133
199
|
registries?: (string | RegistryEntry)[];
|
|
134
200
|
|
package/src/index.ts
CHANGED
|
@@ -306,7 +306,6 @@ export {
|
|
|
306
306
|
export type {
|
|
307
307
|
RegistryAuth,
|
|
308
308
|
RegistryEntry,
|
|
309
|
-
ProxyEntry,
|
|
310
309
|
RefConfig,
|
|
311
310
|
RefEntry,
|
|
312
311
|
ConsumerConfig,
|
|
@@ -427,7 +426,6 @@ export { createAdk } from "./config-store.js";
|
|
|
427
426
|
export type {
|
|
428
427
|
Adk,
|
|
429
428
|
AdkOptions,
|
|
430
|
-
AdkProxyApi,
|
|
431
429
|
AdkRegistryApi,
|
|
432
430
|
AdkRefApi,
|
|
433
431
|
AdkAgentRegistry,
|
package/src/mcp-client.ts
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { generatePkcePair } from "./pkce.js";
|
|
17
|
+
import type { RegistryAuthRequirement } from "./define-config.js";
|
|
18
|
+
import type { FetchFn } from "./fetch-types.js";
|
|
17
19
|
|
|
18
20
|
// ============================================
|
|
19
21
|
// Types
|
|
@@ -239,3 +241,122 @@ export async function refreshAccessToken(
|
|
|
239
241
|
expiresIn: data.expires_in as number | undefined,
|
|
240
242
|
};
|
|
241
243
|
}
|
|
244
|
+
|
|
245
|
+
// ============================================
|
|
246
|
+
// Registry Auth Probe (RFC 6750 + RFC 9728)
|
|
247
|
+
// ============================================
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Parse a `WWW-Authenticate` header of the form
|
|
251
|
+
* `Bearer realm="x", resource_metadata="https://..."`
|
|
252
|
+
* Returns the scheme and any `key="value"` params. Tolerant of
|
|
253
|
+
* single-value headers and missing params.
|
|
254
|
+
*/
|
|
255
|
+
export function parseWwwAuthenticate(
|
|
256
|
+
header: string,
|
|
257
|
+
): { scheme: string; params: Record<string, string> } {
|
|
258
|
+
const spaceIdx = header.indexOf(" ");
|
|
259
|
+
const scheme = (spaceIdx === -1 ? header : header.slice(0, spaceIdx)).trim();
|
|
260
|
+
const rest = spaceIdx === -1 ? "" : header.slice(spaceIdx + 1);
|
|
261
|
+
const params: Record<string, string> = {};
|
|
262
|
+
for (const match of rest.matchAll(/([a-zA-Z_][a-zA-Z0-9_-]*)\s*=\s*"([^"]*)"/g)) {
|
|
263
|
+
params[match[1]!.toLowerCase()] = match[2]!;
|
|
264
|
+
}
|
|
265
|
+
return { scheme, params };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** RFC 9728 protected-resource metadata. */
|
|
269
|
+
export interface ProtectedResourceMetadata {
|
|
270
|
+
resource: string;
|
|
271
|
+
authorization_servers?: string[];
|
|
272
|
+
bearer_methods_supported?: string[];
|
|
273
|
+
scopes_supported?: string[];
|
|
274
|
+
resource_documentation?: string;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Fetch RFC 9728 metadata. Returns null on any failure. */
|
|
278
|
+
export async function discoverProtectedResourceMetadata(
|
|
279
|
+
metadataUrl: string,
|
|
280
|
+
fetchFn: FetchFn = globalThis.fetch,
|
|
281
|
+
): Promise<ProtectedResourceMetadata | null> {
|
|
282
|
+
try {
|
|
283
|
+
const res = await fetchFn(metadataUrl);
|
|
284
|
+
if (!res.ok) return null;
|
|
285
|
+
return (await res.json()) as ProtectedResourceMetadata;
|
|
286
|
+
} catch {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Probe an MCP URL to see whether it requires authentication.
|
|
293
|
+
*
|
|
294
|
+
* Sends a minimal `initialize` request. On 200 the server accepts anonymous
|
|
295
|
+
* connections; on 401 we parse the `WWW-Authenticate` header and, if it
|
|
296
|
+
* points at RFC 9728 resource metadata, fetch it so the caller can record
|
|
297
|
+
* the authorization servers and scopes.
|
|
298
|
+
*
|
|
299
|
+
* Returns `{ ok: true }` when no auth is required, or
|
|
300
|
+
* `{ ok: false, requirement }` when the server challenged the request.
|
|
301
|
+
* Other failures (DNS, TLS, unexpected status) surface as `{ ok: null }`
|
|
302
|
+
* — the caller should treat those as probe-inconclusive rather than
|
|
303
|
+
* asserting auth is required.
|
|
304
|
+
*/
|
|
305
|
+
export async function probeRegistryAuth(
|
|
306
|
+
registryUrl: string,
|
|
307
|
+
fetchFn: FetchFn = globalThis.fetch,
|
|
308
|
+
): Promise<
|
|
309
|
+
| { ok: true }
|
|
310
|
+
| { ok: false; requirement: RegistryAuthRequirement }
|
|
311
|
+
| { ok: null }
|
|
312
|
+
> {
|
|
313
|
+
const url = registryUrl.replace(/\/$/, "");
|
|
314
|
+
let res: Response;
|
|
315
|
+
try {
|
|
316
|
+
res = await fetchFn(url, {
|
|
317
|
+
method: "POST",
|
|
318
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
319
|
+
body: JSON.stringify({
|
|
320
|
+
jsonrpc: "2.0",
|
|
321
|
+
id: 1,
|
|
322
|
+
method: "initialize",
|
|
323
|
+
params: {
|
|
324
|
+
protocolVersion: "2024-11-05",
|
|
325
|
+
capabilities: {},
|
|
326
|
+
clientInfo: { name: "agents-sdk-probe", version: "1.0.0" },
|
|
327
|
+
},
|
|
328
|
+
}),
|
|
329
|
+
});
|
|
330
|
+
} catch {
|
|
331
|
+
return { ok: null };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (res.status !== 401) {
|
|
335
|
+
return res.ok ? { ok: true } : { ok: null };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const wwwAuth = res.headers.get("www-authenticate") ?? "";
|
|
339
|
+
const { scheme, params } = parseWwwAuthenticate(wwwAuth);
|
|
340
|
+
const requirement: RegistryAuthRequirement = {};
|
|
341
|
+
if (scheme) requirement.scheme = scheme;
|
|
342
|
+
if (params.realm) requirement.realm = params.realm;
|
|
343
|
+
|
|
344
|
+
const metadataUrl = params.resource_metadata;
|
|
345
|
+
if (metadataUrl) {
|
|
346
|
+
requirement.resourceMetadataUrl = metadataUrl;
|
|
347
|
+
const metadata = await discoverProtectedResourceMetadata(metadataUrl, fetchFn);
|
|
348
|
+
if (metadata) {
|
|
349
|
+
if (metadata.authorization_servers?.length) {
|
|
350
|
+
requirement.authorizationServers = metadata.authorization_servers;
|
|
351
|
+
}
|
|
352
|
+
if (metadata.scopes_supported?.length) {
|
|
353
|
+
requirement.scopes = metadata.scopes_supported;
|
|
354
|
+
}
|
|
355
|
+
if (metadata.bearer_methods_supported?.length) {
|
|
356
|
+
requirement.bearerMethodsSupported = metadata.bearer_methods_supported;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return { ok: false, requirement };
|
|
362
|
+
}
|
package/src/registry-consumer.ts
CHANGED
|
@@ -41,6 +41,8 @@ import type {
|
|
|
41
41
|
import type { CallAgentRequest } from "./call-agent-schema.js";
|
|
42
42
|
import type { FetchFn } from "./fetch-types.js";
|
|
43
43
|
import type { SecuritySchemeSummary, CallAgentResponse } from "./types.js";
|
|
44
|
+
import { AdkError } from "./adk-error.js";
|
|
45
|
+
import { parseWwwAuthenticate } from "./mcp-client.js";
|
|
44
46
|
import {
|
|
45
47
|
isSecretUri,
|
|
46
48
|
normalizeRef,
|
|
@@ -207,6 +209,16 @@ export interface RegistryConfiguration {
|
|
|
207
209
|
jwks_uri?: string;
|
|
208
210
|
token_endpoint?: string;
|
|
209
211
|
supported_grant_types?: string[];
|
|
212
|
+
/**
|
|
213
|
+
* When the registry advertises proxy support in its `initialize` response,
|
|
214
|
+
* consumers can auto-populate `RegistryEntry.proxy` at add time so ref ops
|
|
215
|
+
* forward to the server-side adk-tools agent automatically.
|
|
216
|
+
*/
|
|
217
|
+
proxy?: {
|
|
218
|
+
mode: "required" | "optional";
|
|
219
|
+
/** Agent path to forward to. Defaults to '@config'. */
|
|
220
|
+
agent?: string;
|
|
221
|
+
};
|
|
210
222
|
}
|
|
211
223
|
|
|
212
224
|
/** Fields common to every agent reference a registry can return. */
|
|
@@ -381,12 +393,19 @@ async function listFromMcpServer(
|
|
|
381
393
|
...(params && { params }),
|
|
382
394
|
}),
|
|
383
395
|
});
|
|
396
|
+
if (res.status === 401) throwRegistryAuthError(serverUrl, res);
|
|
384
397
|
if (!res.ok) {
|
|
385
|
-
|
|
398
|
+
const body = await res.text().catch(() => "");
|
|
399
|
+
throwRegistryCallError(serverUrl, res.status, body);
|
|
386
400
|
}
|
|
387
401
|
const json = (await res.json()) as { result?: unknown; error?: { message: string } };
|
|
388
402
|
if (json.error) {
|
|
389
|
-
throw new
|
|
403
|
+
throw new AdkError({
|
|
404
|
+
code: "registry_rpc_error",
|
|
405
|
+
message: `Registry at ${serverUrl} returned an RPC error: ${json.error.message}`,
|
|
406
|
+
hint: "Check the tool name and parameters, or inspect the registry.",
|
|
407
|
+
details: { url: serverUrl, rpcError: json.error.message },
|
|
408
|
+
});
|
|
390
409
|
}
|
|
391
410
|
return json.result;
|
|
392
411
|
}
|
|
@@ -456,12 +475,19 @@ async function discoverRegistryViaMcp(
|
|
|
456
475
|
...(params && { params }),
|
|
457
476
|
}),
|
|
458
477
|
});
|
|
478
|
+
if (res.status === 401) throwRegistryAuthError(serverUrl, res);
|
|
459
479
|
if (!res.ok) {
|
|
460
|
-
|
|
480
|
+
const body = await res.text().catch(() => "");
|
|
481
|
+
throwRegistryCallError(serverUrl, res.status, body);
|
|
461
482
|
}
|
|
462
483
|
const json = (await res.json()) as { result?: unknown; error?: { message: string } };
|
|
463
484
|
if (json.error) {
|
|
464
|
-
throw new
|
|
485
|
+
throw new AdkError({
|
|
486
|
+
code: "registry_rpc_error",
|
|
487
|
+
message: `Registry at ${serverUrl} returned an RPC error: ${json.error.message}`,
|
|
488
|
+
hint: "Check the registry URL and that the server speaks MCP.",
|
|
489
|
+
details: { url: serverUrl, rpcError: json.error.message },
|
|
490
|
+
});
|
|
465
491
|
}
|
|
466
492
|
return json.result;
|
|
467
493
|
}
|
|
@@ -472,20 +498,76 @@ async function discoverRegistryViaMcp(
|
|
|
472
498
|
clientInfo: { name: "agents-sdk-consumer", version: "1.0.0" },
|
|
473
499
|
})) as {
|
|
474
500
|
serverInfo?: { name?: string; version?: string };
|
|
501
|
+
capabilities?: {
|
|
502
|
+
registry?: {
|
|
503
|
+
proxy?: {
|
|
504
|
+
mode?: "required" | "optional";
|
|
505
|
+
agent?: string;
|
|
506
|
+
};
|
|
507
|
+
};
|
|
508
|
+
};
|
|
475
509
|
};
|
|
476
510
|
|
|
477
511
|
await rpc("notifications/initialized").catch(() => {});
|
|
478
512
|
|
|
479
513
|
const issuer = issuerFromMcpUrlAndServerInfo(serverUrl, initResult?.serverInfo);
|
|
480
514
|
|
|
515
|
+
const advertisedProxy = initResult?.capabilities?.registry?.proxy;
|
|
516
|
+
const proxy = advertisedProxy?.mode
|
|
517
|
+
? {
|
|
518
|
+
mode: advertisedProxy.mode,
|
|
519
|
+
...(advertisedProxy.agent && { agent: advertisedProxy.agent }),
|
|
520
|
+
}
|
|
521
|
+
: undefined;
|
|
522
|
+
|
|
481
523
|
return {
|
|
482
524
|
issuer,
|
|
483
525
|
jwks_uri: `${issuer}/.well-known/jwks.json`,
|
|
484
526
|
token_endpoint: `${issuer}/oauth/token`,
|
|
485
527
|
supported_grant_types: ["client_credentials", "jwt_exchange"],
|
|
528
|
+
...(proxy && { proxy }),
|
|
486
529
|
};
|
|
487
530
|
}
|
|
488
531
|
|
|
532
|
+
/**
|
|
533
|
+
* Throw a typed auth error with context parsed from the challenge. Used when
|
|
534
|
+
* any MCP call returns 401, so callers see a friendly message pointing at
|
|
535
|
+
* `adk registry auth` rather than a bare "MCP call failed: 401".
|
|
536
|
+
*/
|
|
537
|
+
function throwRegistryAuthError(
|
|
538
|
+
serverUrl: string,
|
|
539
|
+
res: Response,
|
|
540
|
+
): never {
|
|
541
|
+
const wwwAuth = res.headers.get("www-authenticate") ?? "";
|
|
542
|
+
const { scheme, params } = parseWwwAuthenticate(wwwAuth);
|
|
543
|
+
throw new AdkError({
|
|
544
|
+
code: "registry_auth_required",
|
|
545
|
+
message: `Registry at ${serverUrl} returned 401 Unauthorized.`,
|
|
546
|
+
hint: `Run: adk registry auth <name> --token <token>`,
|
|
547
|
+
details: {
|
|
548
|
+
url: serverUrl,
|
|
549
|
+
scheme,
|
|
550
|
+
realm: params.realm,
|
|
551
|
+
resourceMetadataUrl: params.resource_metadata,
|
|
552
|
+
status: 401,
|
|
553
|
+
},
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/** Wrap a generic MCP failure (non-401) in an AdkError so CLI callers get a typed error. */
|
|
558
|
+
function throwRegistryCallError(
|
|
559
|
+
serverUrl: string,
|
|
560
|
+
status: number,
|
|
561
|
+
body: string,
|
|
562
|
+
): never {
|
|
563
|
+
throw new AdkError({
|
|
564
|
+
code: "registry_call_failed",
|
|
565
|
+
message: `Registry at ${serverUrl} returned ${status}.`,
|
|
566
|
+
hint: "Check the registry URL and that the server is reachable.",
|
|
567
|
+
details: { url: serverUrl, status, body: body.slice(0, 500) },
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
|
|
489
571
|
/**
|
|
490
572
|
* Call a tool on a direct MCP server.
|
|
491
573
|
*/
|
|
@@ -515,12 +597,19 @@ async function callMcpTool(
|
|
|
515
597
|
params: { name: toolName, arguments: params },
|
|
516
598
|
}),
|
|
517
599
|
});
|
|
600
|
+
if (res.status === 401) throwRegistryAuthError(serverUrl, res);
|
|
518
601
|
if (!res.ok) {
|
|
519
|
-
|
|
602
|
+
const body = await res.text().catch(() => "");
|
|
603
|
+
throwRegistryCallError(serverUrl, res.status, body);
|
|
520
604
|
}
|
|
521
605
|
const json = (await res.json()) as { result?: unknown; error?: { message: string } };
|
|
522
606
|
if (json.error) {
|
|
523
|
-
throw new
|
|
607
|
+
throw new AdkError({
|
|
608
|
+
code: "registry_rpc_error",
|
|
609
|
+
message: `Registry at ${serverUrl} returned an RPC error: ${json.error.message}`,
|
|
610
|
+
hint: "Check the tool name and parameters, or inspect the registry.",
|
|
611
|
+
details: { url: serverUrl, rpcError: json.error.message },
|
|
612
|
+
});
|
|
524
613
|
}
|
|
525
614
|
|
|
526
615
|
// Extract text content
|
|
@@ -950,19 +1039,19 @@ export async function createRegistryConsumer(
|
|
|
950
1039
|
},
|
|
951
1040
|
|
|
952
1041
|
async browse(registryUrl?: string, query?: string): Promise<AgentListEntry[]> {
|
|
953
|
-
// List agents from a specific registry, or all registries if not specified
|
|
1042
|
+
// List agents from a specific registry, or all registries if not specified.
|
|
1043
|
+
// Errors from any target propagate — previously allSettled silently
|
|
1044
|
+
// dropped rejections (including 401s), so browsing an unreachable or
|
|
1045
|
+
// unauthenticated registry returned 0 agents with no indication why.
|
|
954
1046
|
const targets = registryUrl
|
|
955
1047
|
? resolvedRegistries.filter(
|
|
956
1048
|
(r) => r.url === registryUrl || r.name === registryUrl,
|
|
957
1049
|
)
|
|
958
1050
|
: resolvedRegistries;
|
|
959
|
-
|
|
960
|
-
const results = await Promise.allSettled(
|
|
1051
|
+
const results = await Promise.all(
|
|
961
1052
|
targets.map((t) => listFromRegistry(t, query)),
|
|
962
1053
|
);
|
|
963
|
-
return results.
|
|
964
|
-
r.status === "fulfilled" ? r.value : [],
|
|
965
|
-
);
|
|
1054
|
+
return results.flat();
|
|
966
1055
|
},
|
|
967
1056
|
|
|
968
1057
|
async inspect(
|
package/src/server.ts
CHANGED
|
@@ -206,6 +206,17 @@ export interface AgentServerOptions {
|
|
|
206
206
|
features?: string[];
|
|
207
207
|
/** OAuth callback URL for shared OAuth flows */
|
|
208
208
|
oauthCallbackUrl?: string;
|
|
209
|
+
/**
|
|
210
|
+
* Announce that ref operations for agents sourced from this registry
|
|
211
|
+
* should be forwarded to a server-side adk-tools agent instead of
|
|
212
|
+
* running locally. Consumers pick this up during `registry.add` and
|
|
213
|
+
* auto-populate `RegistryEntry.proxy` — no user flag needed.
|
|
214
|
+
*/
|
|
215
|
+
proxy?: {
|
|
216
|
+
mode: "required" | "optional";
|
|
217
|
+
/** Agent path to forward to. Defaults to '@config'. */
|
|
218
|
+
agent?: string;
|
|
219
|
+
};
|
|
209
220
|
};
|
|
210
221
|
/**
|
|
211
222
|
* Structured logger for server-side errors (tool-call failures, JWT
|
|
@@ -591,6 +602,14 @@ export function createAgentServer(
|
|
|
591
602
|
...(options.registry.oauthCallbackUrl && {
|
|
592
603
|
oauthCallbackUrl: options.registry.oauthCallbackUrl,
|
|
593
604
|
}),
|
|
605
|
+
...(options.registry.proxy && {
|
|
606
|
+
proxy: {
|
|
607
|
+
mode: options.registry.proxy.mode,
|
|
608
|
+
...(options.registry.proxy.agent && {
|
|
609
|
+
agent: options.registry.proxy.agent,
|
|
610
|
+
}),
|
|
611
|
+
},
|
|
612
|
+
}),
|
|
594
613
|
},
|
|
595
614
|
}),
|
|
596
615
|
},
|