@slashfi/agents-sdk 0.40.0 → 0.41.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-tools.d.ts +31 -0
- package/dist/adk-tools.d.ts.map +1 -0
- package/dist/adk-tools.js +140 -0
- package/dist/adk-tools.js.map +1 -0
- package/dist/adk.js +75 -4
- package/dist/adk.js.map +1 -1
- package/dist/call-agent-schema.d.ts +96 -96
- package/dist/cjs/adk-tools.js +143 -0
- package/dist/cjs/adk-tools.js.map +1 -0
- package/dist/cjs/config-store.js +124 -45
- package/dist/cjs/config-store.js.map +1 -1
- package/dist/cjs/define-config.js.map +1 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/registry-consumer.js +1 -0
- package/dist/cjs/registry-consumer.js.map +1 -1
- package/dist/cjs/registry.js +3 -0
- package/dist/cjs/registry.js.map +1 -1
- package/dist/config-store.d.ts +46 -7
- package/dist/config-store.d.ts.map +1 -1
- package/dist/config-store.js +124 -45
- package/dist/config-store.js.map +1 -1
- package/dist/define-config.d.ts +17 -0
- package/dist/define-config.d.ts.map +1 -1
- package/dist/define-config.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/registry-consumer.d.ts +2 -0
- package/dist/registry-consumer.d.ts.map +1 -1
- package/dist/registry-consumer.js +1 -0
- package/dist/registry-consumer.js.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +3 -0
- package/dist/registry.js.map +1 -1
- package/package.json +1 -1
- package/src/adk-tools.ts +156 -0
- package/src/adk.ts +73 -2
- package/src/config-store.ts +191 -54
- package/src/define-config.ts +23 -0
- package/src/index.ts +7 -0
- package/src/registry-consumer.ts +4 -0
- package/src/registry.ts +3 -0
package/src/adk-tools.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADK Tools — expose an Adk instance as MCP ToolDefinitions.
|
|
3
|
+
*
|
|
4
|
+
* Used by atlas-environments (@config agent) and atlas-runtime
|
|
5
|
+
* to register adk ref/registry operations as MCP tools.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // Standalone — no scopes
|
|
10
|
+
* const tools = createAdkTools({ resolveScope: () => adk });
|
|
11
|
+
*
|
|
12
|
+
* // Atlas — user/tenant scopes
|
|
13
|
+
* const tools = createAdkTools({
|
|
14
|
+
* resolveScope: (scope) => scope === 'tenant' ? tenantAdk : userAdk,
|
|
15
|
+
* scopes: ['user', 'tenant'],
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { defineTool } from "./define.js";
|
|
21
|
+
import type { ToolDefinition, ToolContext } from "./types.js";
|
|
22
|
+
import type { Adk } from "./config-store.js";
|
|
23
|
+
import type { RefEntry, RegistryEntry } from "./define-config.js";
|
|
24
|
+
|
|
25
|
+
export interface CreateAdkToolsOptions<TCtx extends ToolContext = ToolContext> {
|
|
26
|
+
/**
|
|
27
|
+
* Resolve an Adk instance from the scope string and tool context.
|
|
28
|
+
* Called per-operation. TCtx is your environment's context type.
|
|
29
|
+
*/
|
|
30
|
+
resolveScope: (scope: string | undefined, ctx: TCtx) => Adk | Promise<Adk>;
|
|
31
|
+
/** Allowed scope values. If set, added as enum to the JSON schema. */
|
|
32
|
+
scopes?: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function createAdkTools<TCtx extends ToolContext = ToolContext>(opts: CreateAdkToolsOptions<TCtx>): ToolDefinition<TCtx>[] {
|
|
36
|
+
const { resolveScope, scopes } = opts;
|
|
37
|
+
|
|
38
|
+
const scopeSchema = scopes
|
|
39
|
+
? { type: "string" as const, enum: scopes, description: "Config scope to operate on" }
|
|
40
|
+
: { type: "string" as const, description: "Config scope (optional)" };
|
|
41
|
+
|
|
42
|
+
const refTool = defineTool({
|
|
43
|
+
name: "ref",
|
|
44
|
+
description:
|
|
45
|
+
"Manage agent refs. Operations: add, remove, list, get, update, inspect, call, auth, auth-status, resources, read.",
|
|
46
|
+
inputSchema: {
|
|
47
|
+
type: "object" as const,
|
|
48
|
+
properties: {
|
|
49
|
+
operation: {
|
|
50
|
+
type: "string",
|
|
51
|
+
enum: ["add", "remove", "list", "get", "update", "inspect", "call", "auth", "auth-status", "resources", "read"],
|
|
52
|
+
},
|
|
53
|
+
scope: scopeSchema,
|
|
54
|
+
ref: { type: "string" },
|
|
55
|
+
name: { type: "string" },
|
|
56
|
+
scheme: { type: "string" },
|
|
57
|
+
url: { type: "string" },
|
|
58
|
+
as: { type: "string" },
|
|
59
|
+
sourceRegistry: { type: "object", properties: { url: { type: "string" }, agentPath: { type: "string" } } },
|
|
60
|
+
config: { type: "object" },
|
|
61
|
+
tool: { type: "string" },
|
|
62
|
+
params: { type: "object" },
|
|
63
|
+
full: { type: "boolean" },
|
|
64
|
+
uris: { type: "array", items: { type: "string" } },
|
|
65
|
+
apiKey: { type: "string" },
|
|
66
|
+
},
|
|
67
|
+
required: ["operation"],
|
|
68
|
+
},
|
|
69
|
+
execute: async (input: Record<string, unknown>, ctx) => {
|
|
70
|
+
const adk = await resolveScope(input.scope as string | undefined, ctx as TCtx);
|
|
71
|
+
const op = input.operation as string;
|
|
72
|
+
|
|
73
|
+
switch (op) {
|
|
74
|
+
case "add": {
|
|
75
|
+
const entry: Record<string, unknown> = { ref: input.ref };
|
|
76
|
+
if (input.scheme) entry.scheme = input.scheme;
|
|
77
|
+
if (input.url) entry.url = input.url;
|
|
78
|
+
if (input.as) entry.as = input.as;
|
|
79
|
+
if (input.sourceRegistry) entry.sourceRegistry = input.sourceRegistry;
|
|
80
|
+
if (input.config) entry.config = input.config;
|
|
81
|
+
return adk.ref.add(entry as unknown as RefEntry);
|
|
82
|
+
}
|
|
83
|
+
case "remove":
|
|
84
|
+
return { removed: await adk.ref.remove(input.name as string) };
|
|
85
|
+
case "list":
|
|
86
|
+
return { refs: await adk.ref.list() };
|
|
87
|
+
case "get":
|
|
88
|
+
return await adk.ref.get(input.name as string);
|
|
89
|
+
case "update":
|
|
90
|
+
return { updated: await adk.ref.update(input.name as string, input as unknown as Partial<RefEntry>) };
|
|
91
|
+
case "inspect":
|
|
92
|
+
return await adk.ref.inspect(input.name as string, { full: input.full as boolean });
|
|
93
|
+
case "call":
|
|
94
|
+
return await adk.ref.call(input.name as string, input.tool as string, input.params as Record<string, unknown>);
|
|
95
|
+
case "auth":
|
|
96
|
+
return await adk.ref.auth(input.name as string, { apiKey: input.apiKey as string });
|
|
97
|
+
case "auth-status":
|
|
98
|
+
return await adk.ref.authStatus(input.name as string);
|
|
99
|
+
case "resources":
|
|
100
|
+
return await adk.ref.resources(input.name as string);
|
|
101
|
+
case "read":
|
|
102
|
+
return await adk.ref.read(input.name as string, input.uris as string[]);
|
|
103
|
+
default:
|
|
104
|
+
throw new Error(`Unknown ref operation: ${op}`);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
}) as ToolDefinition<TCtx>;
|
|
108
|
+
|
|
109
|
+
const registryTool = defineTool({
|
|
110
|
+
name: "registry",
|
|
111
|
+
description:
|
|
112
|
+
"Manage registry connections. Operations: add, remove, list, browse, inspect, test.",
|
|
113
|
+
inputSchema: {
|
|
114
|
+
type: "object" as const,
|
|
115
|
+
properties: {
|
|
116
|
+
operation: {
|
|
117
|
+
type: "string",
|
|
118
|
+
enum: ["add", "remove", "list", "browse", "inspect", "test"],
|
|
119
|
+
},
|
|
120
|
+
scope: scopeSchema,
|
|
121
|
+
url: { type: "string" },
|
|
122
|
+
name: { type: "string" },
|
|
123
|
+
query: { type: "string" },
|
|
124
|
+
auth: { type: "object" },
|
|
125
|
+
},
|
|
126
|
+
required: ["operation"],
|
|
127
|
+
},
|
|
128
|
+
execute: async (input: Record<string, unknown>, ctx) => {
|
|
129
|
+
const adk = await resolveScope(input.scope as string | undefined, ctx as TCtx);
|
|
130
|
+
const op = input.operation as string;
|
|
131
|
+
|
|
132
|
+
switch (op) {
|
|
133
|
+
case "add": {
|
|
134
|
+
const entry: Record<string, unknown> = { url: input.url, name: input.name };
|
|
135
|
+
if (input.auth) entry.auth = input.auth;
|
|
136
|
+
await adk.registry.add(entry as unknown as RegistryEntry);
|
|
137
|
+
return { added: true, name: input.name, url: input.url };
|
|
138
|
+
}
|
|
139
|
+
case "remove":
|
|
140
|
+
return { removed: await adk.registry.remove(input.name as string) };
|
|
141
|
+
case "list":
|
|
142
|
+
return { registries: await adk.registry.list() };
|
|
143
|
+
case "browse":
|
|
144
|
+
return { agents: await adk.registry.browse(input.name as string, input.query as string) };
|
|
145
|
+
case "inspect":
|
|
146
|
+
return await adk.registry.inspect(input.name as string);
|
|
147
|
+
case "test":
|
|
148
|
+
return { results: await adk.registry.test(input.name as string) };
|
|
149
|
+
default:
|
|
150
|
+
throw new Error(`Unknown registry operation: ${op}`);
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return [refTool, registryTool as unknown as ToolDefinition<TCtx>];
|
|
156
|
+
}
|
package/src/adk.ts
CHANGED
|
@@ -78,6 +78,7 @@ function printUsage() {
|
|
|
78
78
|
adk — Agent Development Kit
|
|
79
79
|
|
|
80
80
|
Usage:
|
|
81
|
+
adk proxy <op> [options] Manage remote adk proxies
|
|
81
82
|
adk registry <op> [options] Manage registry connections
|
|
82
83
|
adk ref <op> [options] Manage agent refs
|
|
83
84
|
adk codegen [options] Generate agent from MCP server
|
|
@@ -87,6 +88,11 @@ Usage:
|
|
|
87
88
|
adk use <agent> [options] Execute a tool on a generated agent
|
|
88
89
|
adk list List all generated agents
|
|
89
90
|
|
|
91
|
+
Proxy operations:
|
|
92
|
+
adk proxy add <url> --name <name> [--type mcp|registry] [--agent @config] [--default]
|
|
93
|
+
adk proxy remove <name>
|
|
94
|
+
adk proxy list
|
|
95
|
+
|
|
90
96
|
Registry operations:
|
|
91
97
|
adk registry add <url> --name <name> [--auth-type bearer|api-key|none]
|
|
92
98
|
adk registry remove <name>
|
|
@@ -368,6 +374,59 @@ function runList() {
|
|
|
368
374
|
// Registry CLI
|
|
369
375
|
// ============================================
|
|
370
376
|
|
|
377
|
+
// ============================================
|
|
378
|
+
// Proxy CLI
|
|
379
|
+
// ============================================
|
|
380
|
+
|
|
381
|
+
async function runProxy() {
|
|
382
|
+
const op = args[1];
|
|
383
|
+
const adk = getAdk();
|
|
384
|
+
|
|
385
|
+
switch (op) {
|
|
386
|
+
case "add": {
|
|
387
|
+
const url = args[2];
|
|
388
|
+
const name = getArg("--name");
|
|
389
|
+
if (!url || !name) { console.error("Usage: adk proxy add <url> --name <name> [--type mcp|registry] [--agent @config] [--default]"); process.exit(1); }
|
|
390
|
+
const type = (getArg("--type") ?? (url.startsWith("http") ? "mcp" : "registry")) as "mcp" | "registry";
|
|
391
|
+
const agent = getArg("--agent");
|
|
392
|
+
const isDefault = hasFlag("--default");
|
|
393
|
+
await adk.proxy.add({ name, url, type, ...(agent && { agent }), ...(isDefault && { default: true }) });
|
|
394
|
+
console.log(`Added proxy: ${name} → ${url}${isDefault ? " (default)" : ""}`);
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
case "remove": {
|
|
398
|
+
const name = args[2];
|
|
399
|
+
if (!name) { console.error("Usage: adk proxy remove <name>"); process.exit(1); }
|
|
400
|
+
const removed = await adk.proxy.remove(name);
|
|
401
|
+
console.log(removed ? `Removed: ${name}` : `Not found: ${name}`);
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
case "list": {
|
|
405
|
+
const proxies = await adk.proxy.list();
|
|
406
|
+
if (proxies.length === 0) {
|
|
407
|
+
console.log("No proxies configured.");
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
console.log(`\n${proxies.length} proxy(s)\n`);
|
|
411
|
+
for (const p of proxies) {
|
|
412
|
+
const def = p.default ? " (default)" : "";
|
|
413
|
+
console.log(` ${p.name}${def}`);
|
|
414
|
+
console.log(` ${p.url} [${p.type}${p.agent ? ` → ${p.agent}` : ""}]`);
|
|
415
|
+
console.log();
|
|
416
|
+
}
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
default:
|
|
420
|
+
console.error(`Unknown proxy operation: ${op}`);
|
|
421
|
+
console.error("Operations: add, remove, list");
|
|
422
|
+
process.exit(1);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// ============================================
|
|
427
|
+
// Registry CLI
|
|
428
|
+
// ============================================
|
|
429
|
+
|
|
371
430
|
async function runRegistry() {
|
|
372
431
|
const op = args[1];
|
|
373
432
|
const adk = getAdk();
|
|
@@ -561,8 +620,17 @@ async function runRef() {
|
|
|
561
620
|
console.log(`\n${icon} ${status.name}`);
|
|
562
621
|
console.log(` auth type: ${status.security?.type ?? "none"}`);
|
|
563
622
|
console.log(` complete: ${status.complete}`);
|
|
564
|
-
|
|
565
|
-
|
|
623
|
+
for (const [field, info] of Object.entries(status.fields)) {
|
|
624
|
+
const fieldIcon = info.present ? "\x1b[32m\u2713\x1b[0m"
|
|
625
|
+
: info.resolvable ? "\x1b[36m\u2192\x1b[0m"
|
|
626
|
+
: info.automated ? "\x1b[33m~\x1b[0m"
|
|
627
|
+
: "\x1b[31m\u2717\x1b[0m";
|
|
628
|
+
const source = info.present ? "stored"
|
|
629
|
+
: info.resolvable ? "resolvable"
|
|
630
|
+
: info.automated ? "automated"
|
|
631
|
+
: "missing";
|
|
632
|
+
console.log(` ${fieldIcon} ${field}: ${source}${info.required ? "" : " (optional)"}`);
|
|
633
|
+
}
|
|
566
634
|
console.log();
|
|
567
635
|
break;
|
|
568
636
|
}
|
|
@@ -622,6 +690,9 @@ async function runRef() {
|
|
|
622
690
|
// ============================================
|
|
623
691
|
|
|
624
692
|
switch (command) {
|
|
693
|
+
case "proxy":
|
|
694
|
+
await runProxy();
|
|
695
|
+
break;
|
|
625
696
|
case "registry":
|
|
626
697
|
await runRegistry();
|
|
627
698
|
break;
|
package/src/config-store.ts
CHANGED
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import type { FsStore } from "./agent-definitions/config.js";
|
|
21
21
|
import type {
|
|
22
22
|
ConsumerConfig,
|
|
23
|
+
ProxyEntry,
|
|
23
24
|
RefEntry,
|
|
24
25
|
RegistryEntry,
|
|
25
26
|
ResolvedRef,
|
|
@@ -47,6 +48,29 @@ const SECRET_PREFIX = "secret:";
|
|
|
47
48
|
// Types
|
|
48
49
|
// ============================================
|
|
49
50
|
|
|
51
|
+
/** Context passed to the resolveCredentials callback */
|
|
52
|
+
export interface ResolveCredentialsContext {
|
|
53
|
+
/** Ref name */
|
|
54
|
+
ref: string;
|
|
55
|
+
/** Credential field being resolved (e.g. "client_id", "client_secret", "api_key") */
|
|
56
|
+
field: string;
|
|
57
|
+
/** The full ref entry from config */
|
|
58
|
+
entry: RefEntry;
|
|
59
|
+
/** Security scheme from the registry */
|
|
60
|
+
security: SecuritySchemeSummary | null;
|
|
61
|
+
/** OAuth metadata if available (from discovery) */
|
|
62
|
+
oauthMetadata?: import("./mcp-client.js").OAuthServerMetadata | null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Resolve a credential field for a ref.
|
|
67
|
+
* Called during auth() when the adk needs a credential it can't auto-obtain.
|
|
68
|
+
* Return the value, or null to indicate it's not available.
|
|
69
|
+
*/
|
|
70
|
+
export type ResolveCredentials = (
|
|
71
|
+
ctx: ResolveCredentialsContext,
|
|
72
|
+
) => Promise<string | null>;
|
|
73
|
+
|
|
50
74
|
export interface AdkOptions {
|
|
51
75
|
/** Passphrase for encrypting/decrypting secret: values */
|
|
52
76
|
encryptionKey?: string;
|
|
@@ -60,8 +84,14 @@ export interface AdkOptions {
|
|
|
60
84
|
oauthCallbackUrl?: string;
|
|
61
85
|
/** Port for local OAuth callback server (default 8919) */
|
|
62
86
|
oauthCallbackPort?: number;
|
|
63
|
-
/** Client name for OAuth dynamic client registration (default: "
|
|
87
|
+
/** Client name for OAuth dynamic client registration (default: "adk") */
|
|
64
88
|
oauthClientName?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Resolve preconfigured credentials for a ref.
|
|
91
|
+
* Used by atlas to inject platform-level or tenant-level credentials
|
|
92
|
+
* (e.g. client_id/client_secret) before the user auth flow runs.
|
|
93
|
+
*/
|
|
94
|
+
resolveCredentials?: ResolveCredentials;
|
|
65
95
|
}
|
|
66
96
|
|
|
67
97
|
export interface RegistryTestResult {
|
|
@@ -83,16 +113,25 @@ export interface AdkRegistryApi {
|
|
|
83
113
|
test(name?: string): Promise<RegistryTestResult[]>;
|
|
84
114
|
}
|
|
85
115
|
|
|
116
|
+
/** Describes a single credential field requirement */
|
|
117
|
+
export interface CredentialField {
|
|
118
|
+
required: boolean;
|
|
119
|
+
/** Can be obtained automatically (dynamic registration, OAuth flow) */
|
|
120
|
+
automated: boolean;
|
|
121
|
+
/** Already present in the ref's config */
|
|
122
|
+
present: boolean;
|
|
123
|
+
/** Available via resolveCredentials callback */
|
|
124
|
+
resolvable: boolean;
|
|
125
|
+
}
|
|
126
|
+
|
|
86
127
|
/** Describes what auth a ref needs and what's already provided */
|
|
87
128
|
export interface RefAuthStatus {
|
|
88
129
|
name: string;
|
|
89
130
|
security: SecuritySchemeSummary | null;
|
|
90
|
-
/** All required
|
|
131
|
+
/** All required fields are either present, resolvable, or automated */
|
|
91
132
|
complete: boolean;
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
/** Fields already stored */
|
|
95
|
-
present: string[];
|
|
133
|
+
/** Per-field breakdown */
|
|
134
|
+
fields: Record<string, CredentialField>;
|
|
96
135
|
}
|
|
97
136
|
|
|
98
137
|
export interface OAuthResult {
|
|
@@ -143,7 +182,14 @@ export interface AdkRefApi {
|
|
|
143
182
|
}): Promise<{ complete: boolean }>;
|
|
144
183
|
}
|
|
145
184
|
|
|
185
|
+
export interface AdkProxyApi {
|
|
186
|
+
add(entry: ProxyEntry): Promise<void>;
|
|
187
|
+
remove(name: string): Promise<boolean>;
|
|
188
|
+
list(): Promise<ProxyEntry[]>;
|
|
189
|
+
}
|
|
190
|
+
|
|
146
191
|
export interface Adk {
|
|
192
|
+
proxy: AdkProxyApi;
|
|
147
193
|
registry: AdkRegistryApi;
|
|
148
194
|
ref: AdkRefApi;
|
|
149
195
|
readConfig(): Promise<ConsumerConfig>;
|
|
@@ -547,21 +593,26 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
|
|
|
547
593
|
|
|
548
594
|
const ref: AdkRefApi = {
|
|
549
595
|
async add(entry: RefEntry): Promise<{ security: SecuritySchemeSummary | null }> {
|
|
550
|
-
|
|
551
|
-
const name = refName(entry);
|
|
552
|
-
const refs = (config.refs ?? []).filter((r) => refName(r) !== name);
|
|
553
|
-
refs.push(entry);
|
|
554
|
-
await writeConfig({ ...config, refs });
|
|
555
|
-
|
|
556
|
-
// Check security requirements
|
|
596
|
+
// Enrich ref with upstream info from the registry
|
|
557
597
|
let security: SecuritySchemeSummary | null = null;
|
|
558
598
|
try {
|
|
559
599
|
const consumer = await buildConsumer();
|
|
560
600
|
const info = await consumer.inspect(entry.ref);
|
|
561
601
|
if (info?.security) security = info.security;
|
|
602
|
+
if (info?.upstream && !entry.url) {
|
|
603
|
+
entry.url = info.upstream as string;
|
|
604
|
+
entry.scheme = entry.scheme ?? "mcp";
|
|
605
|
+
}
|
|
562
606
|
} catch {
|
|
563
607
|
// Non-fatal — registry might be unreachable
|
|
564
608
|
}
|
|
609
|
+
|
|
610
|
+
const config = await readConfig();
|
|
611
|
+
const name = refName(entry);
|
|
612
|
+
const refs = (config.refs ?? []).filter((r) => refName(r) !== name);
|
|
613
|
+
refs.push(entry);
|
|
614
|
+
await writeConfig({ ...config, refs });
|
|
615
|
+
|
|
565
616
|
return { security };
|
|
566
617
|
},
|
|
567
618
|
|
|
@@ -623,15 +674,8 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
|
|
|
623
674
|
|
|
624
675
|
// If we have a direct access_token from OAuth, call the agent's MCP server
|
|
625
676
|
// directly instead of going through the registry
|
|
626
|
-
if (accessToken) {
|
|
627
|
-
|
|
628
|
-
const security = info?.security as { type: string; flows?: { authorizationCode?: { authorizationUrl?: string } } } | undefined;
|
|
629
|
-
const authUrl = security?.flows?.authorizationCode?.authorizationUrl;
|
|
630
|
-
if (authUrl) {
|
|
631
|
-
// MCP endpoint is at /mcp under the OAuth origin
|
|
632
|
-
const mcpUrl = `${new URL(authUrl).origin}/mcp`;
|
|
633
|
-
return callMcpDirect(mcpUrl, tool, params ?? {}, accessToken);
|
|
634
|
-
}
|
|
677
|
+
if (accessToken && entry.url) {
|
|
678
|
+
return callMcpDirect(entry.url, tool, params ?? {}, accessToken);
|
|
635
679
|
}
|
|
636
680
|
|
|
637
681
|
const consumer = await buildConsumer();
|
|
@@ -691,46 +735,112 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
|
|
|
691
735
|
// Can't reach registry
|
|
692
736
|
}
|
|
693
737
|
|
|
738
|
+
if (!security || security.type === "none") {
|
|
739
|
+
return { name, security, complete: true, fields: {} };
|
|
740
|
+
}
|
|
741
|
+
|
|
694
742
|
const configKeys = Object.keys(entry.config ?? {});
|
|
743
|
+
const resolve = options.resolveCredentials;
|
|
695
744
|
|
|
696
|
-
|
|
697
|
-
|
|
745
|
+
async function canResolve(field: string, oauthMetadata?: import("./mcp-client.js").OAuthServerMetadata | null): Promise<boolean> {
|
|
746
|
+
if (!resolve || !entry) return false;
|
|
747
|
+
const val = await resolve({ ref: name, field, entry, security, oauthMetadata });
|
|
748
|
+
return val !== null;
|
|
698
749
|
}
|
|
699
750
|
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
751
|
+
const fields: Record<string, CredentialField> = {};
|
|
752
|
+
|
|
753
|
+
if (security.type === "oauth2") {
|
|
754
|
+
const securityExt = security as {
|
|
755
|
+
dynamicRegistration?: boolean;
|
|
756
|
+
discoveryUrl?: string;
|
|
757
|
+
};
|
|
758
|
+
const hasRegistration = !!securityExt.dynamicRegistration;
|
|
759
|
+
|
|
760
|
+
let oauthMetadata: import("./mcp-client.js").OAuthServerMetadata | null = null;
|
|
761
|
+
let needsSecret = false;
|
|
762
|
+
if (securityExt.discoveryUrl) {
|
|
763
|
+
oauthMetadata = await tryFetchOAuthMetadata(securityExt.discoveryUrl);
|
|
764
|
+
if (oauthMetadata) {
|
|
765
|
+
const authMethods = oauthMetadata.token_endpoint_auth_methods_supported ?? [];
|
|
766
|
+
needsSecret = !authMethods.includes("none");
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
fields.client_id = {
|
|
771
|
+
required: true,
|
|
772
|
+
automated: hasRegistration,
|
|
773
|
+
present: configKeys.includes("client_id"),
|
|
774
|
+
resolvable: await canResolve("client_id", oauthMetadata),
|
|
775
|
+
};
|
|
776
|
+
if (needsSecret) {
|
|
777
|
+
fields.client_secret = {
|
|
778
|
+
required: true,
|
|
779
|
+
automated: hasRegistration,
|
|
780
|
+
present: configKeys.includes("client_secret"),
|
|
781
|
+
resolvable: await canResolve("client_secret", oauthMetadata),
|
|
782
|
+
};
|
|
706
783
|
}
|
|
707
|
-
|
|
784
|
+
fields.access_token = {
|
|
785
|
+
required: true,
|
|
786
|
+
automated: true,
|
|
787
|
+
present: configKeys.includes("access_token"),
|
|
788
|
+
resolvable: false,
|
|
789
|
+
};
|
|
790
|
+
} else if (security.type === "apiKey") {
|
|
791
|
+
fields.api_key = {
|
|
792
|
+
required: true,
|
|
793
|
+
automated: false,
|
|
794
|
+
present: configKeys.includes("api_key"),
|
|
795
|
+
resolvable: await canResolve("api_key"),
|
|
796
|
+
};
|
|
797
|
+
} else if (security.type === "http") {
|
|
798
|
+
fields.token = {
|
|
799
|
+
required: true,
|
|
800
|
+
automated: false,
|
|
801
|
+
present: configKeys.includes("token"),
|
|
802
|
+
resolvable: await canResolve("token"),
|
|
803
|
+
};
|
|
804
|
+
}
|
|
708
805
|
|
|
709
|
-
const
|
|
710
|
-
|
|
806
|
+
const complete = Object.values(fields).every(
|
|
807
|
+
(f) => !f.required || f.present || f.resolvable || f.automated,
|
|
808
|
+
);
|
|
711
809
|
|
|
712
|
-
return { name, security, complete
|
|
810
|
+
return { name, security, complete, fields };
|
|
713
811
|
},
|
|
714
812
|
|
|
715
813
|
async auth(name: string, opts?: {
|
|
716
814
|
apiKey?: string;
|
|
717
815
|
}): Promise<AuthStartResult> {
|
|
816
|
+
const config = await readConfig();
|
|
817
|
+
const entry = (config.refs ?? []).find((r) => refName(r) === name);
|
|
818
|
+
if (!entry) throw new Error(`Ref "${name}" not found`);
|
|
819
|
+
|
|
718
820
|
const status = await ref.authStatus(name);
|
|
719
821
|
const security = status.security;
|
|
822
|
+
const resolve = options.resolveCredentials;
|
|
823
|
+
|
|
824
|
+
async function tryResolve(field: string, oauthMetadata?: import("./mcp-client.js").OAuthServerMetadata | null): Promise<string | null> {
|
|
825
|
+
if (!resolve) return null;
|
|
826
|
+
return resolve({ ref: name, field, entry: entry!, security, oauthMetadata });
|
|
827
|
+
}
|
|
720
828
|
|
|
721
829
|
if (!security || security.type === "none") {
|
|
722
830
|
return { type: "none", complete: true };
|
|
723
831
|
}
|
|
724
832
|
|
|
725
833
|
if (security.type === "apiKey") {
|
|
726
|
-
|
|
727
|
-
|
|
834
|
+
const key = opts?.apiKey ?? await tryResolve("api_key");
|
|
835
|
+
if (!key) return { type: "apiKey", complete: false };
|
|
836
|
+
await storeRefSecret(name, "api_key", key);
|
|
728
837
|
return { type: "apiKey", complete: true };
|
|
729
838
|
}
|
|
730
839
|
|
|
731
840
|
if (security.type === "http") {
|
|
732
|
-
|
|
733
|
-
|
|
841
|
+
const token = opts?.apiKey ?? await tryResolve("token");
|
|
842
|
+
if (!token) return { type: "http", complete: false };
|
|
843
|
+
await storeRefSecret(name, "token", token);
|
|
734
844
|
return { type: "http", complete: true };
|
|
735
845
|
}
|
|
736
846
|
|
|
@@ -741,11 +851,9 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
|
|
|
741
851
|
return { type: "oauth2", complete: false };
|
|
742
852
|
}
|
|
743
853
|
|
|
744
|
-
// The authorizationUrl might be the discovery URL itself or a base URL
|
|
745
854
|
const authUrl = authCodeFlow.authorizationUrl;
|
|
746
855
|
let metadata = await tryFetchOAuthMetadata(authUrl);
|
|
747
856
|
if (!metadata) {
|
|
748
|
-
// Try base origin
|
|
749
857
|
const origin = new URL(authUrl).origin;
|
|
750
858
|
metadata = await discoverOAuthMetadata(origin);
|
|
751
859
|
}
|
|
@@ -755,10 +863,14 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
|
|
|
755
863
|
|
|
756
864
|
const redirectUri = callbackUrl();
|
|
757
865
|
|
|
758
|
-
//
|
|
759
|
-
let clientId
|
|
760
|
-
|
|
761
|
-
|
|
866
|
+
// Resolve client credentials: callback → stored → dynamic registration
|
|
867
|
+
let clientId = await tryResolve("client_id", metadata)
|
|
868
|
+
?? await readRefSecret(name, "client_id");
|
|
869
|
+
let clientSecret = await tryResolve("client_secret", metadata)
|
|
870
|
+
?? await readRefSecret(name, "client_secret")
|
|
871
|
+
?? undefined;
|
|
872
|
+
|
|
873
|
+
if (!clientId && metadata.registration_endpoint) {
|
|
762
874
|
const supportedAuthMethods = metadata.token_endpoint_auth_methods_supported ?? ["none"];
|
|
763
875
|
const preferredMethod = supportedAuthMethods.includes("none")
|
|
764
876
|
? "none"
|
|
@@ -777,15 +889,12 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
|
|
|
777
889
|
if (clientSecret) {
|
|
778
890
|
await storeRefSecret(name, "client_secret", clientSecret);
|
|
779
891
|
}
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
);
|
|
787
|
-
}
|
|
788
|
-
clientId = stored;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
if (!clientId) {
|
|
895
|
+
throw new Error(
|
|
896
|
+
"Could not obtain client_id. Provide via resolveCredentials callback or store manually.",
|
|
897
|
+
);
|
|
789
898
|
}
|
|
790
899
|
|
|
791
900
|
// State ties the callback back to this ref
|
|
@@ -903,5 +1012,33 @@ export function createAdk(fs: FsStore, options: AdkOptions = {}): Adk {
|
|
|
903
1012
|
return { refName: pending.refName, complete: true };
|
|
904
1013
|
}
|
|
905
1014
|
|
|
906
|
-
|
|
1015
|
+
// ==========================================
|
|
1016
|
+
// Proxy API
|
|
1017
|
+
// ==========================================
|
|
1018
|
+
|
|
1019
|
+
const proxy: AdkProxyApi = {
|
|
1020
|
+
async add(entry: ProxyEntry): Promise<void> {
|
|
1021
|
+
const config = await readConfig();
|
|
1022
|
+
const proxies = (config.proxies ?? []).filter((p) => p.name !== entry.name);
|
|
1023
|
+
proxies.push(entry);
|
|
1024
|
+
await writeConfig({ ...config, proxies });
|
|
1025
|
+
},
|
|
1026
|
+
|
|
1027
|
+
async remove(name: string): Promise<boolean> {
|
|
1028
|
+
const config = await readConfig();
|
|
1029
|
+
if (!config.proxies?.length) return false;
|
|
1030
|
+
const before = config.proxies.length;
|
|
1031
|
+
const proxies = config.proxies.filter((p) => p.name !== name);
|
|
1032
|
+
if (proxies.length === before) return false;
|
|
1033
|
+
await writeConfig({ ...config, proxies });
|
|
1034
|
+
return true;
|
|
1035
|
+
},
|
|
1036
|
+
|
|
1037
|
+
async list(): Promise<ProxyEntry[]> {
|
|
1038
|
+
const config = await readConfig();
|
|
1039
|
+
return config.proxies ?? [];
|
|
1040
|
+
},
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
return { proxy, registry, ref, readConfig, writeConfig, handleCallback };
|
|
907
1044
|
}
|
package/src/define-config.ts
CHANGED
|
@@ -86,12 +86,35 @@ export type RefEntry = {
|
|
|
86
86
|
status?: 'active' | 'inactive' | 'error';
|
|
87
87
|
};
|
|
88
88
|
|
|
89
|
+
// ============================================
|
|
90
|
+
// Proxy Config
|
|
91
|
+
// ============================================
|
|
92
|
+
|
|
93
|
+
/** A proxy target — remote adk server that handles ref/registry operations */
|
|
94
|
+
export interface ProxyEntry {
|
|
95
|
+
/** Human-readable name */
|
|
96
|
+
name: string;
|
|
97
|
+
/** URL of the remote server */
|
|
98
|
+
url: string;
|
|
99
|
+
/** Connection type: 'mcp' (direct MCP server) or 'registry' (agent on a registry) */
|
|
100
|
+
type: 'mcp' | 'registry';
|
|
101
|
+
/** For type 'registry': the agent path that implements adk tools (e.g. '@config') */
|
|
102
|
+
agent?: string;
|
|
103
|
+
/** Auth for connecting to the proxy */
|
|
104
|
+
auth?: RegistryAuth;
|
|
105
|
+
/** Whether this is the default proxy when no local refs/registries exist */
|
|
106
|
+
default?: boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
89
109
|
// ============================================
|
|
90
110
|
// Consumer Config
|
|
91
111
|
// ============================================
|
|
92
112
|
|
|
93
113
|
/** The full consumer configuration */
|
|
94
114
|
export interface ConsumerConfig {
|
|
115
|
+
/** Remote adk proxies — forward operations to a remote server */
|
|
116
|
+
proxies?: ProxyEntry[];
|
|
117
|
+
|
|
95
118
|
/** Registries to connect to, in resolution order */
|
|
96
119
|
registries?: (string | RegistryEntry)[];
|
|
97
120
|
|