@slashfi/agents-sdk 0.39.0 → 0.40.1
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.d.ts +13 -21
- package/dist/adk.d.ts.map +1 -1
- package/dist/adk.js +354 -51
- package/dist/adk.js.map +1 -1
- package/dist/cjs/codegen.js +1 -1
- package/dist/cjs/codegen.js.map +1 -1
- package/dist/cjs/config-store.js +727 -0
- package/dist/cjs/config-store.js.map +1 -0
- package/dist/cjs/index.js +7 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/local-fs.js +63 -0
- package/dist/cjs/local-fs.js.map +1 -0
- package/dist/cjs/registry.js +3 -0
- package/dist/cjs/registry.js.map +1 -1
- package/dist/codegen.d.ts +1 -0
- package/dist/codegen.d.ts.map +1 -1
- package/dist/codegen.js +1 -1
- package/dist/codegen.js.map +1 -1
- package/dist/config-store.d.ts +137 -0
- package/dist/config-store.d.ts.map +1 -0
- package/dist/config-store.js +691 -0
- package/dist/config-store.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/local-fs.d.ts +18 -0
- package/dist/local-fs.d.ts.map +1 -0
- package/dist/local-fs.js +59 -0
- package/dist/local-fs.js.map +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +3 -0
- package/dist/registry.js.map +1 -1
- package/dist/validate.d.ts +4 -4
- package/package.json +1 -1
- package/src/adk.ts +313 -53
- package/src/codegen.ts +2 -2
- package/src/config-store.ts +910 -0
- package/src/index.ts +14 -0
- package/src/local-fs.ts +68 -0
- package/src/registry.ts +3 -0
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ADK Config Store — programmatic API for managing registries and refs.
|
|
4
|
+
*
|
|
5
|
+
* Provides a `createAdk(fs, options?)` factory that returns an object
|
|
6
|
+
* with `registry.*` and `ref.*` namespaces. Backed by a pluggable
|
|
7
|
+
* FsStore so it works with local filesystem (CLI) or VCS (atlas).
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { createAdk, createLocalFsStore } from '@slashfi/agents-sdk';
|
|
12
|
+
*
|
|
13
|
+
* const adk = createAdk(createLocalFsStore());
|
|
14
|
+
* await adk.registry.add({ url: 'https://registry.slash.com', name: 'slash' });
|
|
15
|
+
* await adk.registry.browse('slash');
|
|
16
|
+
* await adk.ref.add({ ref: 'notion', registry: 'slash' });
|
|
17
|
+
* await adk.ref.call('notion', 'notion-search', { query: 'hello' });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
23
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
24
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
25
|
+
}
|
|
26
|
+
Object.defineProperty(o, k2, desc);
|
|
27
|
+
}) : (function(o, m, k, k2) {
|
|
28
|
+
if (k2 === undefined) k2 = k;
|
|
29
|
+
o[k2] = m[k];
|
|
30
|
+
}));
|
|
31
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
32
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
33
|
+
}) : function(o, v) {
|
|
34
|
+
o["default"] = v;
|
|
35
|
+
});
|
|
36
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
37
|
+
var ownKeys = function(o) {
|
|
38
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
39
|
+
var ar = [];
|
|
40
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
41
|
+
return ar;
|
|
42
|
+
};
|
|
43
|
+
return ownKeys(o);
|
|
44
|
+
};
|
|
45
|
+
return function (mod) {
|
|
46
|
+
if (mod && mod.__esModule) return mod;
|
|
47
|
+
var result = {};
|
|
48
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
49
|
+
__setModuleDefault(result, mod);
|
|
50
|
+
return result;
|
|
51
|
+
};
|
|
52
|
+
})();
|
|
53
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
+
exports.createAdk = createAdk;
|
|
55
|
+
const define_config_js_1 = require("./define-config.js");
|
|
56
|
+
const registry_consumer_js_1 = require("./registry-consumer.js");
|
|
57
|
+
const crypto_js_1 = require("./crypto.js");
|
|
58
|
+
const mcp_client_js_1 = require("./mcp-client.js");
|
|
59
|
+
const CONFIG_PATH = "consumer-config.json";
|
|
60
|
+
const SECRET_PREFIX = "secret:";
|
|
61
|
+
// ============================================
|
|
62
|
+
// Internal helpers
|
|
63
|
+
// ============================================
|
|
64
|
+
function refName(entry) {
|
|
65
|
+
return (0, define_config_js_1.normalizeRef)(entry).name;
|
|
66
|
+
}
|
|
67
|
+
function registryDisplayName(r) {
|
|
68
|
+
return typeof r === "string" ? r : (r.name ?? r.url);
|
|
69
|
+
}
|
|
70
|
+
function registryUrl(r) {
|
|
71
|
+
return typeof r === "string" ? r : r.url;
|
|
72
|
+
}
|
|
73
|
+
function findRegistry(registries, nameOrUrl) {
|
|
74
|
+
return registries.find((r) => {
|
|
75
|
+
if (typeof r === "string")
|
|
76
|
+
return r === nameOrUrl;
|
|
77
|
+
return (r.name ?? r.url) === nameOrUrl || r.url === nameOrUrl;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Walk an object and decrypt any string values starting with "secret:".
|
|
82
|
+
*/
|
|
83
|
+
async function decryptConfigSecrets(obj, encryptionKey) {
|
|
84
|
+
const result = {};
|
|
85
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
86
|
+
if (typeof value === "string" && value.startsWith(SECRET_PREFIX)) {
|
|
87
|
+
result[key] = await (0, crypto_js_1.decryptSecret)(value.slice(SECRET_PREFIX.length), encryptionKey);
|
|
88
|
+
}
|
|
89
|
+
else if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
90
|
+
result[key] = await decryptConfigSecrets(value, encryptionKey);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
result[key] = value;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
// ============================================
|
|
99
|
+
// Factory
|
|
100
|
+
// ============================================
|
|
101
|
+
function createAdk(fs, options = {}) {
|
|
102
|
+
async function readConfig() {
|
|
103
|
+
const content = await fs.readFile(CONFIG_PATH);
|
|
104
|
+
if (!content)
|
|
105
|
+
return {};
|
|
106
|
+
try {
|
|
107
|
+
return JSON.parse(content);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return {};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function writeConfig(config) {
|
|
114
|
+
await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Store a secret value in a ref's config, encrypted if encryptionKey is set.
|
|
118
|
+
* The value is stored inline as "secret:<encrypted>" in consumer-config.json.
|
|
119
|
+
*/
|
|
120
|
+
async function storeRefSecret(name, key, value) {
|
|
121
|
+
const stored = options.encryptionKey
|
|
122
|
+
? `${SECRET_PREFIX}${await (0, crypto_js_1.encryptSecret)(value, options.encryptionKey)}`
|
|
123
|
+
: value;
|
|
124
|
+
const config = await readConfig();
|
|
125
|
+
const refs = (config.refs ?? []).map((r) => {
|
|
126
|
+
if (refName(r) !== name)
|
|
127
|
+
return r;
|
|
128
|
+
return { ...r, config: { ...r.config, [key]: stored } };
|
|
129
|
+
});
|
|
130
|
+
await writeConfig({ ...config, refs });
|
|
131
|
+
}
|
|
132
|
+
async function readRefSecret(name, key) {
|
|
133
|
+
const config = await readConfig();
|
|
134
|
+
const entry = (config.refs ?? []).find((r) => refName(r) === name);
|
|
135
|
+
const value = entry?.config?.[key];
|
|
136
|
+
if (typeof value !== "string")
|
|
137
|
+
return null;
|
|
138
|
+
if (value.startsWith(SECRET_PREFIX) && options.encryptionKey) {
|
|
139
|
+
return (0, crypto_js_1.decryptSecret)(value.slice(SECRET_PREFIX.length), options.encryptionKey);
|
|
140
|
+
}
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
const PENDING_OAUTH_PATH = "pending-oauth.json";
|
|
144
|
+
async function readPendingOAuth() {
|
|
145
|
+
const content = await fs.readFile(PENDING_OAUTH_PATH);
|
|
146
|
+
if (!content)
|
|
147
|
+
return {};
|
|
148
|
+
try {
|
|
149
|
+
return JSON.parse(content);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return {};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async function writePendingOAuth(pending) {
|
|
156
|
+
await fs.writeFile(PENDING_OAUTH_PATH, JSON.stringify(pending, null, 2));
|
|
157
|
+
}
|
|
158
|
+
async function storePendingOAuth(state, data) {
|
|
159
|
+
const pending = await readPendingOAuth();
|
|
160
|
+
pending[state] = data;
|
|
161
|
+
await writePendingOAuth(pending);
|
|
162
|
+
}
|
|
163
|
+
async function consumePendingOAuth(state) {
|
|
164
|
+
const pending = await readPendingOAuth();
|
|
165
|
+
const data = pending[state] ?? null;
|
|
166
|
+
if (data) {
|
|
167
|
+
delete pending[state];
|
|
168
|
+
await writePendingOAuth(pending);
|
|
169
|
+
}
|
|
170
|
+
return data;
|
|
171
|
+
}
|
|
172
|
+
/** Call an MCP server directly with a bearer token (bypasses registry). */
|
|
173
|
+
async function callMcpDirect(serverUrl, toolName, params, token) {
|
|
174
|
+
const url = serverUrl.replace(/\/$/, "");
|
|
175
|
+
const headers = {
|
|
176
|
+
"Content-Type": "application/json",
|
|
177
|
+
Accept: "application/json, text/event-stream",
|
|
178
|
+
Authorization: `Bearer ${token}`,
|
|
179
|
+
};
|
|
180
|
+
let reqId = 0;
|
|
181
|
+
let sessionId;
|
|
182
|
+
async function rpc(method, rpcParams) {
|
|
183
|
+
const reqHeaders = { ...headers, ...(sessionId ? { "Mcp-Session-Id": sessionId } : {}) };
|
|
184
|
+
const res = await globalThis.fetch(url, {
|
|
185
|
+
method: "POST",
|
|
186
|
+
headers: reqHeaders,
|
|
187
|
+
body: JSON.stringify({
|
|
188
|
+
jsonrpc: "2.0",
|
|
189
|
+
id: ++reqId,
|
|
190
|
+
method,
|
|
191
|
+
...(rpcParams && { params: rpcParams }),
|
|
192
|
+
}),
|
|
193
|
+
});
|
|
194
|
+
if (!res.ok) {
|
|
195
|
+
throw new Error(`MCP ${method} failed (${res.status}): ${await res.text().catch(() => "unknown")}`);
|
|
196
|
+
}
|
|
197
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
198
|
+
// Capture session ID from response
|
|
199
|
+
const newSessionId = res.headers.get("mcp-session-id");
|
|
200
|
+
if (newSessionId)
|
|
201
|
+
sessionId = newSessionId;
|
|
202
|
+
// SSE response — parse events to find the JSON-RPC result
|
|
203
|
+
if (contentType.includes("text/event-stream")) {
|
|
204
|
+
const text = await res.text();
|
|
205
|
+
const lines = text.split("\n");
|
|
206
|
+
for (const line of lines) {
|
|
207
|
+
if (line.startsWith("data: ")) {
|
|
208
|
+
try {
|
|
209
|
+
const json = JSON.parse(line.slice(6));
|
|
210
|
+
if (json.id === reqId) {
|
|
211
|
+
if (json.error)
|
|
212
|
+
throw new Error(`MCP RPC error: ${json.error.message}`);
|
|
213
|
+
return json.result;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
if (e instanceof Error && e.message.startsWith("MCP RPC"))
|
|
218
|
+
throw e;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
const json = await res.json();
|
|
225
|
+
if (json.error)
|
|
226
|
+
throw new Error(`MCP RPC error: ${json.error.message}`);
|
|
227
|
+
return json.result;
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
await rpc("initialize", {
|
|
231
|
+
protocolVersion: "2024-11-05",
|
|
232
|
+
capabilities: {},
|
|
233
|
+
clientInfo: { name: "adk", version: "1.0.0" },
|
|
234
|
+
});
|
|
235
|
+
await rpc("notifications/initialized").catch(() => { });
|
|
236
|
+
const result = await rpc("tools/call", { name: toolName, arguments: params });
|
|
237
|
+
const textContent = result?.content?.find((c) => c.type === "text");
|
|
238
|
+
if (textContent?.text) {
|
|
239
|
+
try {
|
|
240
|
+
return { success: true, result: JSON.parse(textContent.text) };
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return { success: true, result: textContent.text };
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return { success: true, result };
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function callbackUrl() {
|
|
253
|
+
const port = options.oauthCallbackPort ?? 8919;
|
|
254
|
+
return options.oauthCallbackUrl ?? `http://localhost:${port}/callback`;
|
|
255
|
+
}
|
|
256
|
+
/** Try fetching a URL directly as OAuth metadata (it may already be a discovery URL). */
|
|
257
|
+
async function tryFetchOAuthMetadata(url) {
|
|
258
|
+
try {
|
|
259
|
+
const res = await globalThis.fetch(url);
|
|
260
|
+
if (!res.ok)
|
|
261
|
+
return null;
|
|
262
|
+
const data = await res.json();
|
|
263
|
+
if (data.authorization_endpoint && data.token_endpoint) {
|
|
264
|
+
return data;
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Build a registryConsumer from the current config.
|
|
274
|
+
* Decrypts secret: values in registry headers/auth before connecting.
|
|
275
|
+
*/
|
|
276
|
+
async function buildConsumer(registryFilter) {
|
|
277
|
+
const config = await readConfig();
|
|
278
|
+
let registries = config.registries ?? [];
|
|
279
|
+
if (registryFilter) {
|
|
280
|
+
const target = findRegistry(registries, registryFilter);
|
|
281
|
+
if (!target) {
|
|
282
|
+
throw new Error(`Registry "${registryFilter}" not found. Available: ${registries.map(registryDisplayName).join(", ")}`);
|
|
283
|
+
}
|
|
284
|
+
registries = [target];
|
|
285
|
+
}
|
|
286
|
+
// Decrypt secret: values in registry entries if encryption key is set
|
|
287
|
+
const resolved = options.encryptionKey
|
|
288
|
+
? await Promise.all(registries.map(async (r) => {
|
|
289
|
+
if (typeof r === "string")
|
|
290
|
+
return r;
|
|
291
|
+
const decrypted = await decryptConfigSecrets(r, options.encryptionKey);
|
|
292
|
+
return decrypted;
|
|
293
|
+
}))
|
|
294
|
+
: registries;
|
|
295
|
+
return (0, registry_consumer_js_1.createRegistryConsumer)({ registries: resolved, refs: config.refs ?? [] }, { token: options.token });
|
|
296
|
+
}
|
|
297
|
+
// ==========================================
|
|
298
|
+
// Registry API
|
|
299
|
+
// ==========================================
|
|
300
|
+
const registry = {
|
|
301
|
+
async add(entry) {
|
|
302
|
+
const config = await readConfig();
|
|
303
|
+
const alias = entry.name ?? entry.url;
|
|
304
|
+
const registries = (config.registries ?? []).filter((r) => registryDisplayName(r) !== alias);
|
|
305
|
+
registries.push(entry);
|
|
306
|
+
await writeConfig({ ...config, registries });
|
|
307
|
+
},
|
|
308
|
+
async remove(nameOrUrl) {
|
|
309
|
+
const config = await readConfig();
|
|
310
|
+
if (!config.registries?.length)
|
|
311
|
+
return false;
|
|
312
|
+
const before = config.registries.length;
|
|
313
|
+
const registries = config.registries.filter((r) => registryDisplayName(r) !== nameOrUrl && registryUrl(r) !== nameOrUrl);
|
|
314
|
+
if (registries.length === before)
|
|
315
|
+
return false;
|
|
316
|
+
await writeConfig({ ...config, registries });
|
|
317
|
+
return true;
|
|
318
|
+
},
|
|
319
|
+
async list() {
|
|
320
|
+
const config = await readConfig();
|
|
321
|
+
return (config.registries ?? []).map((r) => typeof r === "string" ? { url: r } : r);
|
|
322
|
+
},
|
|
323
|
+
async get(name) {
|
|
324
|
+
const config = await readConfig();
|
|
325
|
+
const target = findRegistry(config.registries ?? [], name);
|
|
326
|
+
if (!target)
|
|
327
|
+
return null;
|
|
328
|
+
return typeof target === "string" ? { url: target } : target;
|
|
329
|
+
},
|
|
330
|
+
async update(name, updates) {
|
|
331
|
+
const config = await readConfig();
|
|
332
|
+
if (!config.registries?.length)
|
|
333
|
+
return false;
|
|
334
|
+
let found = false;
|
|
335
|
+
const registries = config.registries.map((r) => {
|
|
336
|
+
const rName = registryDisplayName(r);
|
|
337
|
+
if (rName !== name && registryUrl(r) !== name)
|
|
338
|
+
return r;
|
|
339
|
+
found = true;
|
|
340
|
+
const existing = typeof r === "string" ? { url: r } : { ...r };
|
|
341
|
+
if (updates.url)
|
|
342
|
+
existing.url = updates.url;
|
|
343
|
+
if (updates.name)
|
|
344
|
+
existing.name = updates.name;
|
|
345
|
+
if (updates.auth)
|
|
346
|
+
existing.auth = updates.auth;
|
|
347
|
+
if (updates.headers)
|
|
348
|
+
existing.headers = { ...existing.headers, ...updates.headers };
|
|
349
|
+
return existing;
|
|
350
|
+
});
|
|
351
|
+
if (!found)
|
|
352
|
+
return false;
|
|
353
|
+
await writeConfig({ ...config, registries });
|
|
354
|
+
return true;
|
|
355
|
+
},
|
|
356
|
+
async browse(name, query) {
|
|
357
|
+
const consumer = await buildConsumer(name);
|
|
358
|
+
const config = await readConfig();
|
|
359
|
+
const target = findRegistry(config.registries ?? [], name);
|
|
360
|
+
const url = target ? registryUrl(target) : name;
|
|
361
|
+
return consumer.browse(url, query);
|
|
362
|
+
},
|
|
363
|
+
async inspect(name) {
|
|
364
|
+
const consumer = await buildConsumer(name);
|
|
365
|
+
const config = await readConfig();
|
|
366
|
+
const target = findRegistry(config.registries ?? [], name);
|
|
367
|
+
const url = target ? registryUrl(target) : name;
|
|
368
|
+
return consumer.discover(url);
|
|
369
|
+
},
|
|
370
|
+
async test(name) {
|
|
371
|
+
const config = await readConfig();
|
|
372
|
+
const registries = config.registries ?? [];
|
|
373
|
+
const targets = name
|
|
374
|
+
? registries.filter((r) => registryDisplayName(r) === name || registryUrl(r) === name)
|
|
375
|
+
: registries;
|
|
376
|
+
const results = await Promise.allSettled(targets.map(async (r) => {
|
|
377
|
+
const url = registryUrl(r);
|
|
378
|
+
const rName = registryDisplayName(r);
|
|
379
|
+
try {
|
|
380
|
+
const consumer = await (0, registry_consumer_js_1.createRegistryConsumer)({ registries: [r] }, { token: options.token });
|
|
381
|
+
const disc = await consumer.discover(url);
|
|
382
|
+
return { name: rName, url, status: "active", issuer: disc.issuer };
|
|
383
|
+
}
|
|
384
|
+
catch (err) {
|
|
385
|
+
const msg = err instanceof Error ? err.message : "unknown";
|
|
386
|
+
return { name: rName, url, status: "error", error: msg };
|
|
387
|
+
}
|
|
388
|
+
}));
|
|
389
|
+
return results.map((r) => r.status === "fulfilled"
|
|
390
|
+
? r.value
|
|
391
|
+
: { name: "unknown", url: "unknown", status: "error", error: "unknown" });
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
// ==========================================
|
|
395
|
+
// Ref API
|
|
396
|
+
// ==========================================
|
|
397
|
+
const ref = {
|
|
398
|
+
async add(entry) {
|
|
399
|
+
const config = await readConfig();
|
|
400
|
+
const name = refName(entry);
|
|
401
|
+
const refs = (config.refs ?? []).filter((r) => refName(r) !== name);
|
|
402
|
+
refs.push(entry);
|
|
403
|
+
await writeConfig({ ...config, refs });
|
|
404
|
+
// Check security requirements
|
|
405
|
+
let security = null;
|
|
406
|
+
try {
|
|
407
|
+
const consumer = await buildConsumer();
|
|
408
|
+
const info = await consumer.inspect(entry.ref);
|
|
409
|
+
if (info?.security)
|
|
410
|
+
security = info.security;
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
// Non-fatal — registry might be unreachable
|
|
414
|
+
}
|
|
415
|
+
return { security };
|
|
416
|
+
},
|
|
417
|
+
async remove(name) {
|
|
418
|
+
const config = await readConfig();
|
|
419
|
+
if (!config.refs?.length)
|
|
420
|
+
return false;
|
|
421
|
+
const before = config.refs.length;
|
|
422
|
+
const refs = config.refs.filter((r) => refName(r) !== name);
|
|
423
|
+
if (refs.length === before)
|
|
424
|
+
return false;
|
|
425
|
+
await writeConfig({ ...config, refs });
|
|
426
|
+
return true;
|
|
427
|
+
},
|
|
428
|
+
async list() {
|
|
429
|
+
const config = await readConfig();
|
|
430
|
+
return (config.refs ?? []).map(define_config_js_1.normalizeRef);
|
|
431
|
+
},
|
|
432
|
+
async get(name) {
|
|
433
|
+
const config = await readConfig();
|
|
434
|
+
return (config.refs ?? []).find((r) => refName(r) === name) ?? null;
|
|
435
|
+
},
|
|
436
|
+
async update(name, updates) {
|
|
437
|
+
const config = await readConfig();
|
|
438
|
+
if (!config.refs?.length)
|
|
439
|
+
return false;
|
|
440
|
+
let found = false;
|
|
441
|
+
const refs = config.refs.map((r) => {
|
|
442
|
+
if (refName(r) !== name)
|
|
443
|
+
return r;
|
|
444
|
+
found = true;
|
|
445
|
+
const updated = { ...r };
|
|
446
|
+
if (updates.url)
|
|
447
|
+
updated.url = updates.url;
|
|
448
|
+
if (updates.as)
|
|
449
|
+
updated.as = updates.as;
|
|
450
|
+
if (updates.scheme)
|
|
451
|
+
updated.scheme = updates.scheme;
|
|
452
|
+
if (updates.config)
|
|
453
|
+
updated.config = { ...updated.config, ...updates.config };
|
|
454
|
+
if (updates.sourceRegistry)
|
|
455
|
+
updated.sourceRegistry = updates.sourceRegistry;
|
|
456
|
+
return updated;
|
|
457
|
+
});
|
|
458
|
+
if (!found)
|
|
459
|
+
return false;
|
|
460
|
+
await writeConfig({ ...config, refs });
|
|
461
|
+
return true;
|
|
462
|
+
},
|
|
463
|
+
async inspect(name, opts) {
|
|
464
|
+
const config = await readConfig();
|
|
465
|
+
const entry = (config.refs ?? []).find((r) => refName(r) === name);
|
|
466
|
+
if (!entry)
|
|
467
|
+
throw new Error(`Ref "${name}" not found`);
|
|
468
|
+
const consumer = await buildConsumer();
|
|
469
|
+
return consumer.inspect(entry.ref, undefined, opts);
|
|
470
|
+
},
|
|
471
|
+
async call(name, tool, params) {
|
|
472
|
+
const config = await readConfig();
|
|
473
|
+
const entry = (config.refs ?? []).find((r) => refName(r) === name);
|
|
474
|
+
if (!entry)
|
|
475
|
+
throw new Error(`Ref "${name}" not found`);
|
|
476
|
+
const accessToken = await readRefSecret(name, "access_token");
|
|
477
|
+
// If we have a direct access_token from OAuth, call the agent's MCP server
|
|
478
|
+
// directly instead of going through the registry
|
|
479
|
+
if (accessToken) {
|
|
480
|
+
const info = await ref.inspect(name);
|
|
481
|
+
// Use upstream URL from registry if available, fall back to deriving from OAuth URL
|
|
482
|
+
const upstream = info?.upstream;
|
|
483
|
+
const security = info?.security;
|
|
484
|
+
const mcpUrl = upstream
|
|
485
|
+
?? (security?.flows?.authorizationCode?.authorizationUrl
|
|
486
|
+
? `${new URL(security.flows.authorizationCode.authorizationUrl).origin}/mcp`
|
|
487
|
+
: null);
|
|
488
|
+
if (mcpUrl) {
|
|
489
|
+
return callMcpDirect(mcpUrl, tool, params ?? {}, accessToken);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const consumer = await buildConsumer();
|
|
493
|
+
const reg = consumer.registries()[0];
|
|
494
|
+
if (!reg)
|
|
495
|
+
throw new Error("No registry available");
|
|
496
|
+
return consumer.callRegistry(reg, {
|
|
497
|
+
action: "execute_tool",
|
|
498
|
+
path: entry.ref,
|
|
499
|
+
tool,
|
|
500
|
+
params: params ?? {},
|
|
501
|
+
});
|
|
502
|
+
},
|
|
503
|
+
async resources(name) {
|
|
504
|
+
const config = await readConfig();
|
|
505
|
+
const entry = (config.refs ?? []).find((r) => refName(r) === name);
|
|
506
|
+
if (!entry)
|
|
507
|
+
throw new Error(`Ref "${name}" not found`);
|
|
508
|
+
const consumer = await buildConsumer();
|
|
509
|
+
const reg = consumer.registries()[0];
|
|
510
|
+
if (!reg)
|
|
511
|
+
throw new Error("No registry available");
|
|
512
|
+
return consumer.callRegistry(reg, {
|
|
513
|
+
action: "list_resources",
|
|
514
|
+
path: entry.ref,
|
|
515
|
+
});
|
|
516
|
+
},
|
|
517
|
+
async read(name, uris) {
|
|
518
|
+
const config = await readConfig();
|
|
519
|
+
const entry = (config.refs ?? []).find((r) => refName(r) === name);
|
|
520
|
+
if (!entry)
|
|
521
|
+
throw new Error(`Ref "${name}" not found`);
|
|
522
|
+
const consumer = await buildConsumer();
|
|
523
|
+
const reg = consumer.registries()[0];
|
|
524
|
+
if (!reg)
|
|
525
|
+
throw new Error("No registry available");
|
|
526
|
+
return consumer.callRegistry(reg, {
|
|
527
|
+
action: "read_resources",
|
|
528
|
+
path: entry.ref,
|
|
529
|
+
uris,
|
|
530
|
+
});
|
|
531
|
+
},
|
|
532
|
+
async authStatus(name) {
|
|
533
|
+
const config = await readConfig();
|
|
534
|
+
const entry = (config.refs ?? []).find((r) => refName(r) === name);
|
|
535
|
+
if (!entry)
|
|
536
|
+
throw new Error(`Ref "${name}" not found`);
|
|
537
|
+
let security = null;
|
|
538
|
+
try {
|
|
539
|
+
const consumer = await buildConsumer();
|
|
540
|
+
const info = await consumer.inspect(entry.ref);
|
|
541
|
+
if (info?.security)
|
|
542
|
+
security = info.security;
|
|
543
|
+
}
|
|
544
|
+
catch {
|
|
545
|
+
// Can't reach registry
|
|
546
|
+
}
|
|
547
|
+
const configKeys = Object.keys(entry.config ?? {});
|
|
548
|
+
if (!security || security.type === "none") {
|
|
549
|
+
return { name, security, complete: true, missing: [], present: configKeys };
|
|
550
|
+
}
|
|
551
|
+
const requiredFields = (() => {
|
|
552
|
+
switch (security.type) {
|
|
553
|
+
case "oauth2": return ["access_token"];
|
|
554
|
+
case "apiKey": return ["api_key"];
|
|
555
|
+
case "http": return ["token"];
|
|
556
|
+
default: return [];
|
|
557
|
+
}
|
|
558
|
+
})();
|
|
559
|
+
const missing = requiredFields.filter((f) => !configKeys.includes(f));
|
|
560
|
+
const present = requiredFields.filter((f) => configKeys.includes(f));
|
|
561
|
+
return { name, security, complete: missing.length === 0, missing, present };
|
|
562
|
+
},
|
|
563
|
+
async auth(name, opts) {
|
|
564
|
+
const status = await ref.authStatus(name);
|
|
565
|
+
const security = status.security;
|
|
566
|
+
if (!security || security.type === "none") {
|
|
567
|
+
return { type: "none", complete: true };
|
|
568
|
+
}
|
|
569
|
+
if (security.type === "apiKey") {
|
|
570
|
+
if (!opts?.apiKey)
|
|
571
|
+
return { type: "apiKey", complete: false };
|
|
572
|
+
await storeRefSecret(name, "api_key", opts.apiKey);
|
|
573
|
+
return { type: "apiKey", complete: true };
|
|
574
|
+
}
|
|
575
|
+
if (security.type === "http") {
|
|
576
|
+
if (!opts?.apiKey)
|
|
577
|
+
return { type: "http", complete: false };
|
|
578
|
+
await storeRefSecret(name, "token", opts.apiKey);
|
|
579
|
+
return { type: "http", complete: true };
|
|
580
|
+
}
|
|
581
|
+
if (security.type === "oauth2") {
|
|
582
|
+
const flows = security.flows;
|
|
583
|
+
const authCodeFlow = flows?.authorizationCode;
|
|
584
|
+
if (!authCodeFlow?.authorizationUrl) {
|
|
585
|
+
return { type: "oauth2", complete: false };
|
|
586
|
+
}
|
|
587
|
+
// The authorizationUrl might be the discovery URL itself or a base URL
|
|
588
|
+
const authUrl = authCodeFlow.authorizationUrl;
|
|
589
|
+
let metadata = await tryFetchOAuthMetadata(authUrl);
|
|
590
|
+
if (!metadata) {
|
|
591
|
+
// Try base origin
|
|
592
|
+
const origin = new URL(authUrl).origin;
|
|
593
|
+
metadata = await (0, mcp_client_js_1.discoverOAuthMetadata)(origin);
|
|
594
|
+
}
|
|
595
|
+
if (!metadata) {
|
|
596
|
+
throw new Error(`Could not discover OAuth metadata from ${authUrl}`);
|
|
597
|
+
}
|
|
598
|
+
const redirectUri = callbackUrl();
|
|
599
|
+
// Dynamic client registration if supported
|
|
600
|
+
let clientId;
|
|
601
|
+
let clientSecret;
|
|
602
|
+
if (metadata.registration_endpoint) {
|
|
603
|
+
const supportedAuthMethods = metadata.token_endpoint_auth_methods_supported ?? ["none"];
|
|
604
|
+
const preferredMethod = supportedAuthMethods.includes("none")
|
|
605
|
+
? "none"
|
|
606
|
+
: supportedAuthMethods[0] ?? "client_secret_post";
|
|
607
|
+
const securityClientName = security.clientName;
|
|
608
|
+
const reg = await (0, mcp_client_js_1.dynamicClientRegistration)(metadata.registration_endpoint, {
|
|
609
|
+
clientName: securityClientName ?? options.oauthClientName ?? "adk",
|
|
610
|
+
redirectUris: [redirectUri],
|
|
611
|
+
grantTypes: ["authorization_code"],
|
|
612
|
+
tokenEndpointAuthMethod: preferredMethod,
|
|
613
|
+
});
|
|
614
|
+
clientId = reg.clientId;
|
|
615
|
+
clientSecret = reg.clientSecret;
|
|
616
|
+
await storeRefSecret(name, "client_id", clientId);
|
|
617
|
+
if (clientSecret) {
|
|
618
|
+
await storeRefSecret(name, "client_secret", clientSecret);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
const stored = await readRefSecret(name, "client_id");
|
|
623
|
+
if (!stored) {
|
|
624
|
+
throw new Error("OAuth server doesn't support dynamic client registration. " +
|
|
625
|
+
"Store a client_id first.");
|
|
626
|
+
}
|
|
627
|
+
clientId = stored;
|
|
628
|
+
}
|
|
629
|
+
// State ties the callback back to this ref
|
|
630
|
+
const state = `${name}:${Date.now()}`;
|
|
631
|
+
const { url: authorizeUrl, codeVerifier } = await (0, mcp_client_js_1.buildOAuthAuthorizeUrl)({
|
|
632
|
+
authorizationEndpoint: metadata.authorization_endpoint,
|
|
633
|
+
clientId,
|
|
634
|
+
redirectUri,
|
|
635
|
+
scopes: metadata.scopes_supported,
|
|
636
|
+
state,
|
|
637
|
+
});
|
|
638
|
+
// Persist pending state so handleCallback works across processes
|
|
639
|
+
await storePendingOAuth(state, {
|
|
640
|
+
refName: name,
|
|
641
|
+
codeVerifier,
|
|
642
|
+
clientId,
|
|
643
|
+
clientSecret,
|
|
644
|
+
tokenEndpoint: metadata.token_endpoint,
|
|
645
|
+
redirectUri,
|
|
646
|
+
createdAt: Date.now(),
|
|
647
|
+
});
|
|
648
|
+
return { type: "oauth2", complete: false, authorizeUrl };
|
|
649
|
+
}
|
|
650
|
+
return { type: security.type, complete: false };
|
|
651
|
+
},
|
|
652
|
+
async authLocal(name, opts) {
|
|
653
|
+
const result = await ref.auth(name);
|
|
654
|
+
if (result.complete)
|
|
655
|
+
return { complete: true };
|
|
656
|
+
if (result.type !== "oauth2" || !result.authorizeUrl) {
|
|
657
|
+
throw new Error(`authLocal only handles OAuth2. Auth type: ${result.type}`);
|
|
658
|
+
}
|
|
659
|
+
if (opts?.onAuthorizeUrl) {
|
|
660
|
+
opts.onAuthorizeUrl(result.authorizeUrl);
|
|
661
|
+
}
|
|
662
|
+
// Spin up local callback server
|
|
663
|
+
const port = options.oauthCallbackPort ?? 8919;
|
|
664
|
+
const timeout = opts?.timeoutMs ?? 300_000;
|
|
665
|
+
const { createServer } = await Promise.resolve().then(() => __importStar(require("node:http")));
|
|
666
|
+
return new Promise((resolve, reject) => {
|
|
667
|
+
const server = createServer(async (req, res) => {
|
|
668
|
+
const reqUrl = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
669
|
+
if (reqUrl.pathname !== "/callback")
|
|
670
|
+
return;
|
|
671
|
+
const code = reqUrl.searchParams.get("code");
|
|
672
|
+
const state = reqUrl.searchParams.get("state");
|
|
673
|
+
if (!code || !state) {
|
|
674
|
+
const error = reqUrl.searchParams.get("error") ?? "missing code/state";
|
|
675
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
676
|
+
res.end(`<h1>Error</h1><p>${error}</p>`);
|
|
677
|
+
server.close();
|
|
678
|
+
reject(new Error(`OAuth denied: ${error}`));
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
try {
|
|
682
|
+
const cbResult = await handleCallback({ code, state });
|
|
683
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
684
|
+
res.end("<h1>Authorized!</h1><p>You can close this tab.</p>");
|
|
685
|
+
server.close();
|
|
686
|
+
resolve({ complete: cbResult.complete });
|
|
687
|
+
}
|
|
688
|
+
catch (err) {
|
|
689
|
+
res.writeHead(500, { "Content-Type": "text/html" });
|
|
690
|
+
res.end(`<h1>Error</h1><p>${err instanceof Error ? err.message : String(err)}</p>`);
|
|
691
|
+
server.close();
|
|
692
|
+
reject(err);
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
server.listen(port);
|
|
696
|
+
const timer = setTimeout(() => {
|
|
697
|
+
server.close();
|
|
698
|
+
reject(new Error("OAuth callback timed out"));
|
|
699
|
+
}, timeout);
|
|
700
|
+
server.on("close", () => clearTimeout(timer));
|
|
701
|
+
});
|
|
702
|
+
},
|
|
703
|
+
};
|
|
704
|
+
// ==========================================
|
|
705
|
+
// Top-level callback handler
|
|
706
|
+
// ==========================================
|
|
707
|
+
async function handleCallback(params) {
|
|
708
|
+
const pending = await consumePendingOAuth(params.state);
|
|
709
|
+
if (!pending) {
|
|
710
|
+
throw new Error(`No pending OAuth flow for state "${params.state}".`);
|
|
711
|
+
}
|
|
712
|
+
const tokens = await (0, mcp_client_js_1.exchangeCodeForTokens)(pending.tokenEndpoint, {
|
|
713
|
+
code: params.code,
|
|
714
|
+
codeVerifier: pending.codeVerifier,
|
|
715
|
+
clientId: pending.clientId,
|
|
716
|
+
clientSecret: pending.clientSecret,
|
|
717
|
+
redirectUri: pending.redirectUri,
|
|
718
|
+
});
|
|
719
|
+
await storeRefSecret(pending.refName, "access_token", tokens.accessToken);
|
|
720
|
+
if (tokens.refreshToken) {
|
|
721
|
+
await storeRefSecret(pending.refName, "refresh_token", tokens.refreshToken);
|
|
722
|
+
}
|
|
723
|
+
return { refName: pending.refName, complete: true };
|
|
724
|
+
}
|
|
725
|
+
return { registry, ref, readConfig, writeConfig, handleCallback };
|
|
726
|
+
}
|
|
727
|
+
//# sourceMappingURL=config-store.js.map
|