@phake/mcp 0.0.2 → 0.0.4
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/README.md +3 -5
- package/dist/{index-sbqy8kgq.js → index-ctd3bknx.js} +1119 -1447
- package/dist/index.d.ts +0 -1
- package/dist/index.js +60 -70
- package/dist/runtime/node/index.js +34307 -13
- package/dist/runtime/worker/index.js +1 -3
- package/package.json +2 -2
- package/dist/index-1zyem3xr.js +0 -14893
- package/dist/index-4f4xvtt9.js +0 -19552
|
@@ -1,19 +1,92 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
// src/shared/utils/base64.ts
|
|
2
|
+
function base64Encode(input) {
|
|
3
|
+
return btoa(input);
|
|
4
|
+
}
|
|
5
|
+
function base64Decode(input) {
|
|
6
|
+
return atob(input);
|
|
7
|
+
}
|
|
8
|
+
function base64UrlEncode(bytes) {
|
|
9
|
+
let binary = "";
|
|
10
|
+
for (let i = 0;i < bytes.length; i++) {
|
|
11
|
+
binary += String.fromCharCode(bytes[i]);
|
|
12
|
+
}
|
|
13
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
14
|
+
}
|
|
15
|
+
function base64UrlDecode(str) {
|
|
16
|
+
let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
17
|
+
const padLength = (4 - base64.length % 4) % 4;
|
|
18
|
+
base64 += "=".repeat(padLength);
|
|
19
|
+
const binary = atob(base64);
|
|
20
|
+
const bytes = new Uint8Array(binary.length);
|
|
21
|
+
for (let i = 0;i < binary.length; i++) {
|
|
22
|
+
bytes[i] = binary.charCodeAt(i);
|
|
23
|
+
}
|
|
24
|
+
return bytes;
|
|
25
|
+
}
|
|
26
|
+
function base64UrlEncodeString(input) {
|
|
27
|
+
return base64UrlEncode(new TextEncoder().encode(input));
|
|
28
|
+
}
|
|
29
|
+
function base64UrlDecodeString(input) {
|
|
30
|
+
return new TextDecoder().decode(base64UrlDecode(input));
|
|
31
|
+
}
|
|
32
|
+
function base64UrlEncodeJson(obj) {
|
|
33
|
+
try {
|
|
34
|
+
return base64UrlEncodeString(JSON.stringify(obj));
|
|
35
|
+
} catch {
|
|
36
|
+
return "";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function base64UrlDecodeJson(value) {
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(base64UrlDecodeString(value));
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/shared/crypto/aes-gcm.ts
|
|
48
|
+
var ALGORITHM = "AES-GCM";
|
|
49
|
+
var KEY_LENGTH = 256;
|
|
50
|
+
var IV_LENGTH = 12;
|
|
51
|
+
var TAG_LENGTH = 128;
|
|
52
|
+
async function deriveKey(secret) {
|
|
53
|
+
const keyBytes = base64UrlDecode(secret);
|
|
54
|
+
if (keyBytes.length !== 32) {
|
|
55
|
+
throw new Error(`Invalid key length: expected 32 bytes, got ${keyBytes.length}`);
|
|
56
|
+
}
|
|
57
|
+
return crypto.subtle.importKey("raw", keyBytes.buffer, { name: ALGORITHM, length: KEY_LENGTH }, false, ["encrypt", "decrypt"]);
|
|
58
|
+
}
|
|
59
|
+
async function encrypt(plaintext, secret) {
|
|
60
|
+
const key = await deriveKey(secret);
|
|
61
|
+
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
|
|
62
|
+
const plaintextBytes = new TextEncoder().encode(plaintext);
|
|
63
|
+
const ciphertext = await crypto.subtle.encrypt({ name: ALGORITHM, iv, tagLength: TAG_LENGTH }, key, plaintextBytes);
|
|
64
|
+
const combined = new Uint8Array(iv.length + ciphertext.byteLength);
|
|
65
|
+
combined.set(iv, 0);
|
|
66
|
+
combined.set(new Uint8Array(ciphertext), iv.length);
|
|
67
|
+
return base64UrlEncode(combined);
|
|
68
|
+
}
|
|
69
|
+
async function decrypt(ciphertext, secret) {
|
|
70
|
+
const key = await deriveKey(secret);
|
|
71
|
+
const combined = base64UrlDecode(ciphertext);
|
|
72
|
+
if (combined.length < IV_LENGTH + 16) {
|
|
73
|
+
throw new Error("Invalid ciphertext: too short");
|
|
74
|
+
}
|
|
75
|
+
const iv = combined.slice(0, IV_LENGTH);
|
|
76
|
+
const encrypted = combined.slice(IV_LENGTH);
|
|
77
|
+
const plaintextBytes = await crypto.subtle.decrypt({ name: ALGORITHM, iv, tagLength: TAG_LENGTH }, key, encrypted);
|
|
78
|
+
return new TextDecoder().decode(plaintextBytes);
|
|
79
|
+
}
|
|
80
|
+
function generateKey() {
|
|
81
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
82
|
+
return base64UrlEncode(bytes);
|
|
83
|
+
}
|
|
84
|
+
function createEncryptor(secret) {
|
|
85
|
+
return {
|
|
86
|
+
encrypt: (plaintext) => encrypt(plaintext, secret),
|
|
87
|
+
decrypt: (ciphertext) => decrypt(ciphertext, secret)
|
|
88
|
+
};
|
|
89
|
+
}
|
|
17
90
|
|
|
18
91
|
// src/shared/http/cors.ts
|
|
19
92
|
var DEFAULT_CORS = {
|
|
@@ -55,6 +128,354 @@ function buildCorsHeaders(options = {}) {
|
|
|
55
128
|
return headers;
|
|
56
129
|
}
|
|
57
130
|
|
|
131
|
+
// src/shared/storage/interface.ts
|
|
132
|
+
var MAX_SESSIONS_PER_API_KEY = 5;
|
|
133
|
+
|
|
134
|
+
// src/shared/storage/memory.ts
|
|
135
|
+
var DEFAULT_TXN_TTL_MS = 10 * 60 * 1000;
|
|
136
|
+
var DEFAULT_CODE_TTL_MS = 10 * 60 * 1000;
|
|
137
|
+
var DEFAULT_SESSION_TTL_MS = 24 * 60 * 60 * 1000;
|
|
138
|
+
var DEFAULT_RS_TOKEN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
139
|
+
var MAX_RS_RECORDS = 1e4;
|
|
140
|
+
var MAX_TRANSACTIONS = 1000;
|
|
141
|
+
var MAX_SESSIONS = 1e4;
|
|
142
|
+
var CLEANUP_INTERVAL_MS = 60000;
|
|
143
|
+
function evictOldest(map, maxSize, countToRemove = 1) {
|
|
144
|
+
if (map.size < maxSize)
|
|
145
|
+
return;
|
|
146
|
+
const entries = [...map.entries()].sort((a, b) => {
|
|
147
|
+
const aTime = a[1].created_at ?? a[1].createdAt ?? 0;
|
|
148
|
+
const bTime = b[1].created_at ?? b[1].createdAt ?? 0;
|
|
149
|
+
return aTime - bTime;
|
|
150
|
+
});
|
|
151
|
+
for (let i = 0;i < countToRemove && i < entries.length; i++) {
|
|
152
|
+
map.delete(entries[i][0]);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function cleanupExpired(map) {
|
|
156
|
+
const now = Date.now();
|
|
157
|
+
let removed = 0;
|
|
158
|
+
for (const [key, entry] of map) {
|
|
159
|
+
if (now >= entry.expiresAt) {
|
|
160
|
+
map.delete(key);
|
|
161
|
+
removed++;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return removed;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
class MemoryTokenStore {
|
|
168
|
+
rsAccessMap = new Map;
|
|
169
|
+
rsRefreshMap = new Map;
|
|
170
|
+
transactions = new Map;
|
|
171
|
+
codes = new Map;
|
|
172
|
+
cleanupIntervalId = null;
|
|
173
|
+
constructor() {
|
|
174
|
+
this.startCleanup();
|
|
175
|
+
}
|
|
176
|
+
startCleanup() {
|
|
177
|
+
if (this.cleanupIntervalId)
|
|
178
|
+
return;
|
|
179
|
+
this.cleanupIntervalId = setInterval(() => {
|
|
180
|
+
this.cleanup();
|
|
181
|
+
}, CLEANUP_INTERVAL_MS);
|
|
182
|
+
if (typeof this.cleanupIntervalId === "object" && "unref" in this.cleanupIntervalId) {
|
|
183
|
+
this.cleanupIntervalId.unref();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
stopCleanup() {
|
|
187
|
+
if (this.cleanupIntervalId) {
|
|
188
|
+
clearInterval(this.cleanupIntervalId);
|
|
189
|
+
this.cleanupIntervalId = null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
cleanup() {
|
|
193
|
+
const now = Date.now();
|
|
194
|
+
let tokensRemoved = 0;
|
|
195
|
+
for (const [key, entry] of this.rsAccessMap) {
|
|
196
|
+
if (now >= entry.expiresAt) {
|
|
197
|
+
this.rsAccessMap.delete(key);
|
|
198
|
+
tokensRemoved++;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
for (const [key, entry] of this.rsRefreshMap) {
|
|
202
|
+
if (now >= entry.expiresAt) {
|
|
203
|
+
this.rsRefreshMap.delete(key);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const transactionsRemoved = cleanupExpired(this.transactions);
|
|
207
|
+
const codesRemoved = cleanupExpired(this.codes);
|
|
208
|
+
return {
|
|
209
|
+
tokens: tokensRemoved,
|
|
210
|
+
transactions: transactionsRemoved,
|
|
211
|
+
codes: codesRemoved
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
async storeRsMapping(rsAccess, provider, rsRefresh, ttlMs = DEFAULT_RS_TOKEN_TTL_MS) {
|
|
215
|
+
const now = Date.now();
|
|
216
|
+
const expiresAt = now + ttlMs;
|
|
217
|
+
evictOldest(this.rsAccessMap, MAX_RS_RECORDS, 10);
|
|
218
|
+
if (rsRefresh) {
|
|
219
|
+
const existing = this.rsRefreshMap.get(rsRefresh);
|
|
220
|
+
if (existing) {
|
|
221
|
+
this.rsAccessMap.delete(existing.rs_access_token);
|
|
222
|
+
existing.rs_access_token = rsAccess;
|
|
223
|
+
existing.provider = { ...provider };
|
|
224
|
+
existing.expiresAt = expiresAt;
|
|
225
|
+
this.rsAccessMap.set(rsAccess, existing);
|
|
226
|
+
return existing;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const record = {
|
|
230
|
+
rs_access_token: rsAccess,
|
|
231
|
+
rs_refresh_token: rsRefresh ?? crypto.randomUUID(),
|
|
232
|
+
provider: { ...provider },
|
|
233
|
+
created_at: now,
|
|
234
|
+
expiresAt
|
|
235
|
+
};
|
|
236
|
+
this.rsAccessMap.set(record.rs_access_token, record);
|
|
237
|
+
this.rsRefreshMap.set(record.rs_refresh_token, record);
|
|
238
|
+
return record;
|
|
239
|
+
}
|
|
240
|
+
async getByRsAccess(rsAccess) {
|
|
241
|
+
const entry = this.rsAccessMap.get(rsAccess);
|
|
242
|
+
if (!entry)
|
|
243
|
+
return null;
|
|
244
|
+
if (Date.now() >= entry.expiresAt) {
|
|
245
|
+
this.rsAccessMap.delete(rsAccess);
|
|
246
|
+
this.rsRefreshMap.delete(entry.rs_refresh_token);
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
return entry;
|
|
250
|
+
}
|
|
251
|
+
async getByRsRefresh(rsRefresh) {
|
|
252
|
+
const entry = this.rsRefreshMap.get(rsRefresh);
|
|
253
|
+
if (!entry)
|
|
254
|
+
return null;
|
|
255
|
+
if (Date.now() >= entry.expiresAt) {
|
|
256
|
+
this.rsAccessMap.delete(entry.rs_access_token);
|
|
257
|
+
this.rsRefreshMap.delete(rsRefresh);
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
return entry;
|
|
261
|
+
}
|
|
262
|
+
async updateByRsRefresh(rsRefresh, provider, maybeNewRsAccess, ttlMs = DEFAULT_RS_TOKEN_TTL_MS) {
|
|
263
|
+
const rec = this.rsRefreshMap.get(rsRefresh);
|
|
264
|
+
if (!rec)
|
|
265
|
+
return null;
|
|
266
|
+
const now = Date.now();
|
|
267
|
+
if (maybeNewRsAccess) {
|
|
268
|
+
this.rsAccessMap.delete(rec.rs_access_token);
|
|
269
|
+
rec.rs_access_token = maybeNewRsAccess;
|
|
270
|
+
rec.created_at = now;
|
|
271
|
+
}
|
|
272
|
+
rec.provider = { ...provider };
|
|
273
|
+
rec.expiresAt = now + ttlMs;
|
|
274
|
+
this.rsAccessMap.set(rec.rs_access_token, rec);
|
|
275
|
+
this.rsRefreshMap.set(rsRefresh, rec);
|
|
276
|
+
return rec;
|
|
277
|
+
}
|
|
278
|
+
async saveTransaction(txnId, txn, ttlSeconds) {
|
|
279
|
+
const ttlMs = ttlSeconds ? ttlSeconds * 1000 : DEFAULT_TXN_TTL_MS;
|
|
280
|
+
const now = Date.now();
|
|
281
|
+
evictOldest(this.transactions, MAX_TRANSACTIONS, 10);
|
|
282
|
+
this.transactions.set(txnId, {
|
|
283
|
+
value: txn,
|
|
284
|
+
expiresAt: now + ttlMs,
|
|
285
|
+
createdAt: now
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
async getTransaction(txnId) {
|
|
289
|
+
const entry = this.transactions.get(txnId);
|
|
290
|
+
if (!entry)
|
|
291
|
+
return null;
|
|
292
|
+
if (Date.now() >= entry.expiresAt) {
|
|
293
|
+
this.transactions.delete(txnId);
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
return entry.value;
|
|
297
|
+
}
|
|
298
|
+
async deleteTransaction(txnId) {
|
|
299
|
+
this.transactions.delete(txnId);
|
|
300
|
+
}
|
|
301
|
+
async saveCode(code, txnId, ttlSeconds) {
|
|
302
|
+
const ttlMs = ttlSeconds ? ttlSeconds * 1000 : DEFAULT_CODE_TTL_MS;
|
|
303
|
+
const now = Date.now();
|
|
304
|
+
this.codes.set(code, {
|
|
305
|
+
value: txnId,
|
|
306
|
+
expiresAt: now + ttlMs,
|
|
307
|
+
createdAt: now
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
async getTxnIdByCode(code) {
|
|
311
|
+
const entry = this.codes.get(code);
|
|
312
|
+
if (!entry)
|
|
313
|
+
return null;
|
|
314
|
+
if (Date.now() >= entry.expiresAt) {
|
|
315
|
+
this.codes.delete(code);
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
return entry.value;
|
|
319
|
+
}
|
|
320
|
+
async deleteCode(code) {
|
|
321
|
+
this.codes.delete(code);
|
|
322
|
+
}
|
|
323
|
+
getStats() {
|
|
324
|
+
return {
|
|
325
|
+
rsTokens: this.rsAccessMap.size,
|
|
326
|
+
transactions: this.transactions.size,
|
|
327
|
+
codes: this.codes.size
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
class MemorySessionStore {
|
|
333
|
+
sessions = new Map;
|
|
334
|
+
cleanupIntervalId = null;
|
|
335
|
+
constructor() {
|
|
336
|
+
this.startCleanup();
|
|
337
|
+
}
|
|
338
|
+
startCleanup() {
|
|
339
|
+
if (this.cleanupIntervalId)
|
|
340
|
+
return;
|
|
341
|
+
this.cleanupIntervalId = setInterval(() => {
|
|
342
|
+
this.cleanup();
|
|
343
|
+
}, CLEANUP_INTERVAL_MS);
|
|
344
|
+
if (typeof this.cleanupIntervalId === "object" && "unref" in this.cleanupIntervalId) {
|
|
345
|
+
this.cleanupIntervalId.unref();
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
stopCleanup() {
|
|
349
|
+
if (this.cleanupIntervalId) {
|
|
350
|
+
clearInterval(this.cleanupIntervalId);
|
|
351
|
+
this.cleanupIntervalId = null;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
cleanup() {
|
|
355
|
+
const now = Date.now();
|
|
356
|
+
let removed = 0;
|
|
357
|
+
for (const [sessionId, session] of this.sessions) {
|
|
358
|
+
if (now >= session.expiresAt) {
|
|
359
|
+
this.sessions.delete(sessionId);
|
|
360
|
+
removed++;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return removed;
|
|
364
|
+
}
|
|
365
|
+
async create(sessionId, apiKey, ttlMs = DEFAULT_SESSION_TTL_MS) {
|
|
366
|
+
const count = await this.countByApiKey(apiKey);
|
|
367
|
+
if (count >= MAX_SESSIONS_PER_API_KEY) {
|
|
368
|
+
await this.deleteOldestByApiKey(apiKey);
|
|
369
|
+
}
|
|
370
|
+
if (this.sessions.size >= MAX_SESSIONS) {
|
|
371
|
+
const oldest = [...this.sessions.entries()].sort((a, b) => a[1].created_at - b[1].created_at)[0];
|
|
372
|
+
if (oldest) {
|
|
373
|
+
this.sessions.delete(oldest[0]);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
const now = Date.now();
|
|
377
|
+
const record = {
|
|
378
|
+
sessionId,
|
|
379
|
+
apiKey,
|
|
380
|
+
created_at: now,
|
|
381
|
+
last_accessed: now,
|
|
382
|
+
initialized: false,
|
|
383
|
+
expiresAt: now + ttlMs
|
|
384
|
+
};
|
|
385
|
+
this.sessions.set(sessionId, record);
|
|
386
|
+
return record;
|
|
387
|
+
}
|
|
388
|
+
async get(sessionId) {
|
|
389
|
+
const session = this.sessions.get(sessionId);
|
|
390
|
+
if (!session)
|
|
391
|
+
return null;
|
|
392
|
+
const now = Date.now();
|
|
393
|
+
if (now >= session.expiresAt) {
|
|
394
|
+
this.sessions.delete(sessionId);
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
session.last_accessed = now;
|
|
398
|
+
return session;
|
|
399
|
+
}
|
|
400
|
+
async update(sessionId, data) {
|
|
401
|
+
const session = this.sessions.get(sessionId);
|
|
402
|
+
if (!session)
|
|
403
|
+
return;
|
|
404
|
+
const now = Date.now();
|
|
405
|
+
Object.assign(session, data, { last_accessed: now });
|
|
406
|
+
}
|
|
407
|
+
async delete(sessionId) {
|
|
408
|
+
this.sessions.delete(sessionId);
|
|
409
|
+
}
|
|
410
|
+
async getByApiKey(apiKey) {
|
|
411
|
+
const results = [];
|
|
412
|
+
const now = Date.now();
|
|
413
|
+
for (const session of this.sessions.values()) {
|
|
414
|
+
if (session.apiKey === apiKey && now < session.expiresAt) {
|
|
415
|
+
results.push(session);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return results.sort((a, b) => b.last_accessed - a.last_accessed);
|
|
419
|
+
}
|
|
420
|
+
async countByApiKey(apiKey) {
|
|
421
|
+
let count = 0;
|
|
422
|
+
const now = Date.now();
|
|
423
|
+
for (const session of this.sessions.values()) {
|
|
424
|
+
if (session.apiKey === apiKey && now < session.expiresAt) {
|
|
425
|
+
count++;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return count;
|
|
429
|
+
}
|
|
430
|
+
async deleteOldestByApiKey(apiKey) {
|
|
431
|
+
let oldest = null;
|
|
432
|
+
const now = Date.now();
|
|
433
|
+
for (const session of this.sessions.values()) {
|
|
434
|
+
if (session.apiKey === apiKey && now < session.expiresAt) {
|
|
435
|
+
if (!oldest || session.last_accessed < oldest.last_accessed) {
|
|
436
|
+
oldest = session;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (oldest) {
|
|
441
|
+
this.sessions.delete(oldest.sessionId);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
async ensure(sessionId, ttlMs = DEFAULT_SESSION_TTL_MS) {
|
|
445
|
+
const existing = this.sessions.get(sessionId);
|
|
446
|
+
if (existing) {
|
|
447
|
+
existing.expiresAt = Date.now() + ttlMs;
|
|
448
|
+
existing.last_accessed = Date.now();
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (this.sessions.size >= MAX_SESSIONS) {
|
|
452
|
+
const oldest = [...this.sessions.entries()].sort((a, b) => a[1].created_at - b[1].created_at)[0];
|
|
453
|
+
if (oldest) {
|
|
454
|
+
this.sessions.delete(oldest[0]);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const now = Date.now();
|
|
458
|
+
this.sessions.set(sessionId, {
|
|
459
|
+
sessionId,
|
|
460
|
+
created_at: now,
|
|
461
|
+
last_accessed: now,
|
|
462
|
+
expiresAt: now + ttlMs
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
async put(sessionId, value, ttlMs = DEFAULT_SESSION_TTL_MS) {
|
|
466
|
+
const now = Date.now();
|
|
467
|
+
this.sessions.set(sessionId, {
|
|
468
|
+
...value,
|
|
469
|
+
sessionId,
|
|
470
|
+
last_accessed: value.last_accessed ?? now,
|
|
471
|
+
expiresAt: now + ttlMs
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
getSessionCount() {
|
|
475
|
+
return this.sessions.size;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
58
479
|
// src/shared/storage/kv.ts
|
|
59
480
|
function ttl(seconds) {
|
|
60
481
|
return Math.floor(Date.now() / 1000) + seconds;
|
|
@@ -357,6 +778,73 @@ function getSessionStore() {
|
|
|
357
778
|
return sessionStoreInstance;
|
|
358
779
|
}
|
|
359
780
|
|
|
781
|
+
// src/shared/utils/logger.ts
|
|
782
|
+
var LOG_LEVELS = {
|
|
783
|
+
debug: 0,
|
|
784
|
+
info: 1,
|
|
785
|
+
warning: 2,
|
|
786
|
+
error: 3
|
|
787
|
+
};
|
|
788
|
+
var currentLevel = "info";
|
|
789
|
+
function shouldLog(level) {
|
|
790
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];
|
|
791
|
+
}
|
|
792
|
+
function formatLog(level, logger, data) {
|
|
793
|
+
const timestamp = new Date().toISOString();
|
|
794
|
+
const { message, ...rest } = data;
|
|
795
|
+
const extra = Object.keys(rest).length > 0 ? ` ${JSON.stringify(rest)}` : "";
|
|
796
|
+
return `[${timestamp}] ${level.toUpperCase()} [${logger}] ${message}${extra}`;
|
|
797
|
+
}
|
|
798
|
+
function sanitize(data) {
|
|
799
|
+
const sanitized = { ...data };
|
|
800
|
+
const sensitiveKeys = [
|
|
801
|
+
"password",
|
|
802
|
+
"token",
|
|
803
|
+
"secret",
|
|
804
|
+
"key",
|
|
805
|
+
"authorization",
|
|
806
|
+
"access_token",
|
|
807
|
+
"refresh_token"
|
|
808
|
+
];
|
|
809
|
+
for (const key of Object.keys(sanitized)) {
|
|
810
|
+
if (sensitiveKeys.some((sk) => key.toLowerCase().includes(sk))) {
|
|
811
|
+
const value = sanitized[key];
|
|
812
|
+
if (typeof value === "string" && value.length > 8) {
|
|
813
|
+
sanitized[key] = `${value.substring(0, 8)}...`;
|
|
814
|
+
} else {
|
|
815
|
+
sanitized[key] = "[REDACTED]";
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
return sanitized;
|
|
820
|
+
}
|
|
821
|
+
var sharedLogger = {
|
|
822
|
+
setLevel(level) {
|
|
823
|
+
currentLevel = level;
|
|
824
|
+
},
|
|
825
|
+
debug(logger, data) {
|
|
826
|
+
if (shouldLog("debug")) {
|
|
827
|
+
console.log(formatLog("debug", logger, sanitize(data)));
|
|
828
|
+
}
|
|
829
|
+
},
|
|
830
|
+
info(logger, data) {
|
|
831
|
+
if (shouldLog("info")) {
|
|
832
|
+
console.log(formatLog("info", logger, sanitize(data)));
|
|
833
|
+
}
|
|
834
|
+
},
|
|
835
|
+
warning(logger, data) {
|
|
836
|
+
if (shouldLog("warning")) {
|
|
837
|
+
console.warn(formatLog("warning", logger, sanitize(data)));
|
|
838
|
+
}
|
|
839
|
+
},
|
|
840
|
+
error(logger, data) {
|
|
841
|
+
if (shouldLog("error")) {
|
|
842
|
+
console.error(formatLog("error", logger, sanitize(data)));
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
var logger = sharedLogger;
|
|
847
|
+
|
|
360
848
|
// src/shared/http/response.ts
|
|
361
849
|
function jsonResponse(data, options = {}) {
|
|
362
850
|
const { status = 200, headers = {}, cors = true } = options;
|
|
@@ -402,1442 +890,667 @@ var JsonRpcErrorCode = {
|
|
|
402
890
|
ServerError: -32000
|
|
403
891
|
};
|
|
404
892
|
|
|
405
|
-
// src/shared/
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
}
|
|
893
|
+
// src/shared/utils/cancellation.ts
|
|
894
|
+
class CancellationError extends Error {
|
|
895
|
+
constructor(message = "Operation was cancelled") {
|
|
896
|
+
super(message);
|
|
897
|
+
this.name = "CancellationError";
|
|
898
|
+
}
|
|
899
|
+
}
|
|
411
900
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
901
|
+
class CancellationToken {
|
|
902
|
+
_isCancelled = false;
|
|
903
|
+
_listeners = [];
|
|
904
|
+
get isCancelled() {
|
|
905
|
+
return this._isCancelled;
|
|
906
|
+
}
|
|
907
|
+
cancel() {
|
|
908
|
+
if (this._isCancelled)
|
|
909
|
+
return;
|
|
910
|
+
this._isCancelled = true;
|
|
911
|
+
this._listeners.forEach((listener) => {
|
|
912
|
+
try {
|
|
913
|
+
listener();
|
|
914
|
+
} catch (error) {
|
|
915
|
+
console.error("Error in cancellation listener:", error);
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
this._listeners.length = 0;
|
|
919
|
+
}
|
|
920
|
+
onCancelled(listener) {
|
|
921
|
+
if (this._isCancelled) {
|
|
922
|
+
listener();
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
this._listeners.push(listener);
|
|
926
|
+
}
|
|
927
|
+
throwIfCancelled() {
|
|
928
|
+
if (this._isCancelled) {
|
|
929
|
+
throw new CancellationError;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
function createCancellationToken() {
|
|
934
|
+
return new CancellationToken;
|
|
935
|
+
}
|
|
936
|
+
async function withCancellation(operation, token) {
|
|
937
|
+
token.throwIfCancelled();
|
|
938
|
+
return new Promise((resolve, reject) => {
|
|
939
|
+
let completed = false;
|
|
940
|
+
token.onCancelled(() => {
|
|
941
|
+
if (!completed) {
|
|
942
|
+
completed = true;
|
|
943
|
+
reject(new CancellationError);
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
operation(token).then((result) => {
|
|
947
|
+
if (!completed) {
|
|
948
|
+
completed = true;
|
|
949
|
+
resolve(result);
|
|
950
|
+
}
|
|
951
|
+
}).catch((error) => {
|
|
952
|
+
if (!completed) {
|
|
953
|
+
completed = true;
|
|
954
|
+
reject(error);
|
|
955
|
+
}
|
|
956
|
+
});
|
|
442
957
|
});
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// src/shared/types/provider.ts
|
|
961
|
+
function toProviderInfo(tokens) {
|
|
443
962
|
return {
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
name: ctx.config.title || serverMetadata.title,
|
|
449
|
-
version: ctx.config.version || "0.0.1"
|
|
450
|
-
},
|
|
451
|
-
instructions: ctx.config.instructions || serverMetadata.instructions
|
|
452
|
-
}
|
|
963
|
+
accessToken: tokens.access_token,
|
|
964
|
+
refreshToken: tokens.refresh_token,
|
|
965
|
+
expiresAt: tokens.expires_at,
|
|
966
|
+
scopes: tokens.scopes
|
|
453
967
|
};
|
|
454
968
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
},
|
|
463
|
-
...tool.annotations && { annotations: tool.annotations }
|
|
464
|
-
}));
|
|
465
|
-
return { result: { tools } };
|
|
969
|
+
function toProviderTokens(info) {
|
|
970
|
+
return {
|
|
971
|
+
access_token: info.accessToken,
|
|
972
|
+
refresh_token: info.refreshToken,
|
|
973
|
+
expires_at: info.expiresAt,
|
|
974
|
+
scopes: info.scopes
|
|
975
|
+
};
|
|
466
976
|
}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
if (requestId !== undefined && ctx.cancellationRegistry) {
|
|
473
|
-
ctx.cancellationRegistry.set(requestId, abortController);
|
|
977
|
+
|
|
978
|
+
// src/shared/tools/types.ts
|
|
979
|
+
function assertProviderToken(context) {
|
|
980
|
+
if (!context.providerToken) {
|
|
981
|
+
throw new Error("Authentication required");
|
|
474
982
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
983
|
+
}
|
|
984
|
+
function defineTool(def) {
|
|
985
|
+
return def;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// src/shared/tools/echo.ts
|
|
989
|
+
import { z } from "zod";
|
|
990
|
+
var echoInputSchema = z.object({
|
|
991
|
+
message: z.string().min(1).describe("Message to echo back"),
|
|
992
|
+
uppercase: z.boolean().optional().describe("Convert message to uppercase")
|
|
993
|
+
});
|
|
994
|
+
var echoTool = defineTool({
|
|
995
|
+
name: "echo",
|
|
996
|
+
title: "Echo",
|
|
997
|
+
description: "Echo back a message, optionally transformed",
|
|
998
|
+
inputSchema: echoInputSchema,
|
|
999
|
+
outputSchema: {
|
|
1000
|
+
echoed: z.string().describe("The echoed message"),
|
|
1001
|
+
length: z.number().describe("Message length")
|
|
1002
|
+
},
|
|
1003
|
+
annotations: {
|
|
1004
|
+
title: "Echo Message",
|
|
1005
|
+
readOnlyHint: true,
|
|
1006
|
+
destructiveHint: false,
|
|
1007
|
+
idempotentHint: true,
|
|
1008
|
+
openWorldHint: false
|
|
1009
|
+
},
|
|
1010
|
+
handler: async (args) => {
|
|
1011
|
+
const message = args.message;
|
|
1012
|
+
const echoed = args.uppercase ? message.toUpperCase() : message;
|
|
1013
|
+
const result = {
|
|
1014
|
+
echoed,
|
|
1015
|
+
length: echoed.length
|
|
1016
|
+
};
|
|
494
1017
|
return {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
message: "Authentication required. Please complete OAuth flow first."
|
|
498
|
-
}
|
|
1018
|
+
content: [{ type: "text", text: echoed }],
|
|
1019
|
+
structuredContent: result
|
|
499
1020
|
};
|
|
500
1021
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
// src/shared/tools/health.ts
|
|
1025
|
+
import { z as z2 } from "zod";
|
|
1026
|
+
var healthInputSchema = z2.object({
|
|
1027
|
+
verbose: z2.boolean().optional().describe("Include additional runtime details")
|
|
1028
|
+
});
|
|
1029
|
+
var healthTool = defineTool({
|
|
1030
|
+
name: "health",
|
|
1031
|
+
title: "Health Check",
|
|
1032
|
+
description: "Check server health, uptime, and runtime information",
|
|
1033
|
+
inputSchema: healthInputSchema,
|
|
1034
|
+
outputSchema: {
|
|
1035
|
+
status: z2.string().describe("Server status"),
|
|
1036
|
+
timestamp: z2.number().describe("Current timestamp"),
|
|
1037
|
+
runtime: z2.string().describe("Runtime environment"),
|
|
1038
|
+
uptime: z2.number().optional().describe("Uptime in seconds (if available)")
|
|
1039
|
+
},
|
|
1040
|
+
annotations: {
|
|
1041
|
+
title: "Server Health Check",
|
|
1042
|
+
readOnlyHint: true,
|
|
1043
|
+
destructiveHint: false,
|
|
1044
|
+
idempotentHint: true,
|
|
1045
|
+
openWorldHint: false
|
|
1046
|
+
},
|
|
1047
|
+
handler: async (args) => {
|
|
1048
|
+
const verbose = Boolean(args.verbose);
|
|
1049
|
+
const g = globalThis;
|
|
1050
|
+
const isWorkers = typeof g.caches !== "undefined" && !("process" in g);
|
|
1051
|
+
const runtime = isWorkers ? "cloudflare-workers" : "node";
|
|
1052
|
+
const result = {
|
|
1053
|
+
status: "ok",
|
|
1054
|
+
timestamp: Date.now(),
|
|
1055
|
+
runtime
|
|
528
1056
|
};
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
1057
|
+
if (verbose) {
|
|
1058
|
+
if (!isWorkers && typeof process !== "undefined") {
|
|
1059
|
+
result.uptime = Math.floor(process.uptime());
|
|
1060
|
+
result.nodeVersion = process.version;
|
|
1061
|
+
result.memoryUsage = process.memoryUsage().heapUsed;
|
|
1062
|
+
}
|
|
532
1063
|
}
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
async function handleResourcesList() {
|
|
536
|
-
return { result: { resources: [] } };
|
|
537
|
-
}
|
|
538
|
-
async function handleResourcesTemplatesList() {
|
|
539
|
-
return { result: { resourceTemplates: [] } };
|
|
540
|
-
}
|
|
541
|
-
async function handlePromptsList() {
|
|
542
|
-
return { result: { prompts: [] } };
|
|
543
|
-
}
|
|
544
|
-
async function handlePing() {
|
|
545
|
-
return { result: {} };
|
|
546
|
-
}
|
|
547
|
-
var currentLogLevel = "info";
|
|
548
|
-
async function handleLoggingSetLevel(params) {
|
|
549
|
-
const level = params?.level;
|
|
550
|
-
const validLevels = [
|
|
551
|
-
"debug",
|
|
552
|
-
"info",
|
|
553
|
-
"notice",
|
|
554
|
-
"warning",
|
|
555
|
-
"error",
|
|
556
|
-
"critical",
|
|
557
|
-
"alert",
|
|
558
|
-
"emergency"
|
|
559
|
-
];
|
|
560
|
-
if (!level || !validLevels.includes(level)) {
|
|
561
1064
|
return {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
message: `Invalid log level. Must be one of: ${validLevels.join(", ")}`
|
|
565
|
-
}
|
|
1065
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
1066
|
+
structuredContent: result
|
|
566
1067
|
};
|
|
567
1068
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
function getLogLevel() {
|
|
576
|
-
return currentLogLevel;
|
|
1069
|
+
});
|
|
1070
|
+
|
|
1071
|
+
// src/runtime/node/context.ts
|
|
1072
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1073
|
+
var authContextStorage = new AsyncLocalStorage;
|
|
1074
|
+
function getCurrentAuthContext() {
|
|
1075
|
+
return authContextStorage.getStore();
|
|
577
1076
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
1077
|
+
|
|
1078
|
+
class ContextRegistry {
|
|
1079
|
+
contexts = new Map;
|
|
1080
|
+
create(requestId, sessionId, authData) {
|
|
1081
|
+
const context = {
|
|
1082
|
+
sessionId,
|
|
1083
|
+
cancellationToken: createCancellationToken(),
|
|
1084
|
+
requestId,
|
|
1085
|
+
timestamp: Date.now(),
|
|
1086
|
+
authStrategy: authData?.authStrategy,
|
|
1087
|
+
authHeaders: authData?.authHeaders,
|
|
1088
|
+
resolvedHeaders: authData?.resolvedHeaders,
|
|
1089
|
+
rsToken: authData?.rsToken,
|
|
1090
|
+
providerToken: authData?.providerToken,
|
|
1091
|
+
provider: authData?.provider,
|
|
1092
|
+
serviceToken: authData?.serviceToken ?? authData?.providerToken
|
|
585
1093
|
};
|
|
1094
|
+
this.contexts.set(requestId, context);
|
|
1095
|
+
return context;
|
|
586
1096
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
return handleInitialize(params, ctx);
|
|
590
|
-
case "tools/list":
|
|
591
|
-
return handleToolsList(ctx);
|
|
592
|
-
case "tools/call":
|
|
593
|
-
return handleToolsCall(params, ctx, requestId);
|
|
594
|
-
case "resources/list":
|
|
595
|
-
return handleResourcesList();
|
|
596
|
-
case "resources/templates/list":
|
|
597
|
-
return handleResourcesTemplatesList();
|
|
598
|
-
case "prompts/list":
|
|
599
|
-
return handlePromptsList();
|
|
600
|
-
case "ping":
|
|
601
|
-
return handlePing();
|
|
602
|
-
case "logging/setLevel":
|
|
603
|
-
return handleLoggingSetLevel(params);
|
|
604
|
-
default:
|
|
605
|
-
sharedLogger.debug("mcp_dispatch", { message: "Unknown method", method });
|
|
606
|
-
return {
|
|
607
|
-
error: {
|
|
608
|
-
code: JsonRpcErrorCode2.MethodNotFound,
|
|
609
|
-
message: `Method not found: ${method}`
|
|
610
|
-
}
|
|
611
|
-
};
|
|
1097
|
+
get(requestId) {
|
|
1098
|
+
return this.contexts.get(requestId);
|
|
612
1099
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
message: "Client initialized",
|
|
622
|
-
sessionId: ctx.sessionId
|
|
623
|
-
});
|
|
1100
|
+
getCancellationToken(requestId) {
|
|
1101
|
+
return this.contexts.get(requestId)?.cancellationToken;
|
|
1102
|
+
}
|
|
1103
|
+
cancel(requestId, _reason) {
|
|
1104
|
+
const context = this.contexts.get(requestId);
|
|
1105
|
+
if (!context)
|
|
1106
|
+
return false;
|
|
1107
|
+
context.cancellationToken.cancel();
|
|
624
1108
|
return true;
|
|
625
1109
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
reason: cancelParams?.reason,
|
|
636
|
-
sessionId: ctx.sessionId
|
|
637
|
-
});
|
|
638
|
-
controller.abort(cancelParams?.reason ?? "Client requested cancellation");
|
|
639
|
-
return true;
|
|
1110
|
+
delete(requestId) {
|
|
1111
|
+
return this.contexts.delete(requestId);
|
|
1112
|
+
}
|
|
1113
|
+
deleteBySession(sessionId) {
|
|
1114
|
+
let deleted = 0;
|
|
1115
|
+
for (const [requestId, context] of this.contexts.entries()) {
|
|
1116
|
+
if (context.sessionId === sessionId) {
|
|
1117
|
+
this.contexts.delete(requestId);
|
|
1118
|
+
deleted++;
|
|
640
1119
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
1120
|
+
}
|
|
1121
|
+
if (deleted > 0) {
|
|
1122
|
+
sharedLogger.debug("context_registry", {
|
|
1123
|
+
message: "Cleaned up contexts for session",
|
|
1124
|
+
sessionId,
|
|
1125
|
+
count: deleted
|
|
645
1126
|
});
|
|
646
1127
|
}
|
|
647
|
-
return
|
|
1128
|
+
return deleted;
|
|
648
1129
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
method,
|
|
652
|
-
sessionId: ctx.sessionId
|
|
653
|
-
});
|
|
654
|
-
return false;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
// node_modules/oauth4webapi/build/index.js
|
|
658
|
-
var USER_AGENT;
|
|
659
|
-
if (typeof navigator === "undefined" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) {
|
|
660
|
-
const NAME = "oauth4webapi";
|
|
661
|
-
const VERSION = "v3.8.5";
|
|
662
|
-
USER_AGENT = `${NAME}/${VERSION}`;
|
|
663
|
-
}
|
|
664
|
-
function looseInstanceOf(input, expected) {
|
|
665
|
-
if (input == null) {
|
|
666
|
-
return false;
|
|
667
|
-
}
|
|
668
|
-
try {
|
|
669
|
-
return input instanceof expected || Object.getPrototypeOf(input)[Symbol.toStringTag] === expected.prototype[Symbol.toStringTag];
|
|
670
|
-
} catch {
|
|
671
|
-
return false;
|
|
1130
|
+
get size() {
|
|
1131
|
+
return this.contexts.size;
|
|
672
1132
|
}
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
}
|
|
681
|
-
var allowInsecureRequests = Symbol();
|
|
682
|
-
var clockSkew = Symbol();
|
|
683
|
-
var clockTolerance = Symbol();
|
|
684
|
-
var customFetch = Symbol();
|
|
685
|
-
var modifyAssertion = Symbol();
|
|
686
|
-
var jweDecrypt = Symbol();
|
|
687
|
-
var jwksCache = Symbol();
|
|
688
|
-
var encoder = new TextEncoder;
|
|
689
|
-
var decoder = new TextDecoder;
|
|
690
|
-
function buf(input) {
|
|
691
|
-
if (typeof input === "string") {
|
|
692
|
-
return encoder.encode(input);
|
|
693
|
-
}
|
|
694
|
-
return decoder.decode(input);
|
|
695
|
-
}
|
|
696
|
-
var encodeBase64Url;
|
|
697
|
-
if (Uint8Array.prototype.toBase64) {
|
|
698
|
-
encodeBase64Url = (input) => {
|
|
699
|
-
if (input instanceof ArrayBuffer) {
|
|
700
|
-
input = new Uint8Array(input);
|
|
701
|
-
}
|
|
702
|
-
return input.toBase64({ alphabet: "base64url", omitPadding: true });
|
|
703
|
-
};
|
|
704
|
-
} else {
|
|
705
|
-
const CHUNK_SIZE = 32768;
|
|
706
|
-
encodeBase64Url = (input) => {
|
|
707
|
-
if (input instanceof ArrayBuffer) {
|
|
708
|
-
input = new Uint8Array(input);
|
|
709
|
-
}
|
|
710
|
-
const arr = [];
|
|
711
|
-
for (let i = 0;i < input.byteLength; i += CHUNK_SIZE) {
|
|
712
|
-
arr.push(String.fromCharCode.apply(null, input.subarray(i, i + CHUNK_SIZE)));
|
|
713
|
-
}
|
|
714
|
-
return btoa(arr.join("")).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
715
|
-
};
|
|
716
|
-
}
|
|
717
|
-
var decodeBase64Url;
|
|
718
|
-
if (Uint8Array.fromBase64) {
|
|
719
|
-
decodeBase64Url = (input) => {
|
|
720
|
-
try {
|
|
721
|
-
return Uint8Array.fromBase64(input, { alphabet: "base64url" });
|
|
722
|
-
} catch (cause) {
|
|
723
|
-
throw CodedTypeError("The input to be decoded is not correctly encoded.", ERR_INVALID_ARG_VALUE, cause);
|
|
724
|
-
}
|
|
725
|
-
};
|
|
726
|
-
} else {
|
|
727
|
-
decodeBase64Url = (input) => {
|
|
728
|
-
try {
|
|
729
|
-
const binary = atob(input.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, ""));
|
|
730
|
-
const bytes = new Uint8Array(binary.length);
|
|
731
|
-
for (let i = 0;i < binary.length; i++) {
|
|
732
|
-
bytes[i] = binary.charCodeAt(i);
|
|
1133
|
+
cleanupExpired(maxAgeMs = 10 * 60 * 1000) {
|
|
1134
|
+
const now = Date.now();
|
|
1135
|
+
let cleaned = 0;
|
|
1136
|
+
for (const [requestId, context] of this.contexts.entries()) {
|
|
1137
|
+
if (now - context.timestamp > maxAgeMs) {
|
|
1138
|
+
this.contexts.delete(requestId);
|
|
1139
|
+
cleaned++;
|
|
733
1140
|
}
|
|
734
|
-
return bytes;
|
|
735
|
-
} catch (cause) {
|
|
736
|
-
throw CodedTypeError("The input to be decoded is not correctly encoded.", ERR_INVALID_ARG_VALUE, cause);
|
|
737
1141
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
1142
|
+
if (cleaned > 0) {
|
|
1143
|
+
sharedLogger.warning("context_registry", {
|
|
1144
|
+
message: "Cleaned up expired contexts (this indicates missing cleanup calls)",
|
|
1145
|
+
count: cleaned,
|
|
1146
|
+
maxAgeMs
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
return cleaned;
|
|
743
1150
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
class UnsupportedOperationError extends Error {
|
|
748
|
-
code;
|
|
749
|
-
constructor(message, options) {
|
|
750
|
-
super(message, options);
|
|
751
|
-
this.name = this.constructor.name;
|
|
752
|
-
this.code = UNSUPPORTED_OPERATION;
|
|
753
|
-
Error.captureStackTrace?.(this, this.constructor);
|
|
1151
|
+
clear() {
|
|
1152
|
+
this.contexts.clear();
|
|
754
1153
|
}
|
|
755
1154
|
}
|
|
1155
|
+
var contextRegistry = new ContextRegistry;
|
|
756
1156
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
this.name = this.constructor.name;
|
|
762
|
-
if (options?.code) {
|
|
763
|
-
this.code = options?.code;
|
|
764
|
-
}
|
|
765
|
-
Error.captureStackTrace?.(this, this.constructor);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
function OPE(message, code, cause) {
|
|
769
|
-
return new OperationProcessingError(message, { code, cause });
|
|
770
|
-
}
|
|
771
|
-
async function calculateJwkThumbprint(jwk) {
|
|
772
|
-
let components;
|
|
773
|
-
switch (jwk.kty) {
|
|
774
|
-
case "EC":
|
|
775
|
-
components = {
|
|
776
|
-
crv: jwk.crv,
|
|
777
|
-
kty: jwk.kty,
|
|
778
|
-
x: jwk.x,
|
|
779
|
-
y: jwk.y
|
|
780
|
-
};
|
|
781
|
-
break;
|
|
782
|
-
case "OKP":
|
|
783
|
-
components = {
|
|
784
|
-
crv: jwk.crv,
|
|
785
|
-
kty: jwk.kty,
|
|
786
|
-
x: jwk.x
|
|
787
|
-
};
|
|
788
|
-
break;
|
|
789
|
-
case "AKP":
|
|
790
|
-
components = {
|
|
791
|
-
alg: jwk.alg,
|
|
792
|
-
kty: jwk.kty,
|
|
793
|
-
pub: jwk.pub
|
|
794
|
-
};
|
|
795
|
-
break;
|
|
796
|
-
case "RSA":
|
|
797
|
-
components = {
|
|
798
|
-
e: jwk.e,
|
|
799
|
-
kty: jwk.kty,
|
|
800
|
-
n: jwk.n
|
|
801
|
-
};
|
|
802
|
-
break;
|
|
803
|
-
default:
|
|
804
|
-
throw new UnsupportedOperationError("unsupported JWK key type", { cause: jwk });
|
|
1157
|
+
// src/shared/tools/registry.ts
|
|
1158
|
+
function getSchemaShape(schema) {
|
|
1159
|
+
if ("shape" in schema && typeof schema.shape === "object") {
|
|
1160
|
+
return schema.shape;
|
|
805
1161
|
}
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1162
|
+
if ("_def" in schema && schema._def && typeof schema._def === "object") {
|
|
1163
|
+
const def = schema._def;
|
|
1164
|
+
if (def.schema) {
|
|
1165
|
+
return getSchemaShape(def.schema);
|
|
1166
|
+
}
|
|
1167
|
+
if (def.innerType) {
|
|
1168
|
+
return getSchemaShape(def.innerType);
|
|
1169
|
+
}
|
|
811
1170
|
}
|
|
1171
|
+
return;
|
|
812
1172
|
}
|
|
813
|
-
function
|
|
814
|
-
|
|
815
|
-
if (key.type !== "private") {
|
|
816
|
-
throw CodedTypeError(`${it} must be a private CryptoKey`, ERR_INVALID_ARG_VALUE);
|
|
817
|
-
}
|
|
1173
|
+
function asRegisteredTool(tool) {
|
|
1174
|
+
return tool;
|
|
818
1175
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1176
|
+
var sharedTools = [
|
|
1177
|
+
asRegisteredTool(healthTool),
|
|
1178
|
+
asRegisteredTool(echoTool)
|
|
1179
|
+
];
|
|
1180
|
+
function getSharedTool(name) {
|
|
1181
|
+
return sharedTools.find((t) => t.name === name);
|
|
824
1182
|
}
|
|
825
|
-
function
|
|
826
|
-
|
|
827
|
-
return false;
|
|
828
|
-
}
|
|
829
|
-
return true;
|
|
1183
|
+
function getSharedToolNames() {
|
|
1184
|
+
return sharedTools.map((t) => t.name);
|
|
830
1185
|
}
|
|
831
|
-
function
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
if (headers.has("authorization")) {
|
|
840
|
-
throw CodedTypeError('"options.headers" must not include the "authorization" header name', ERR_INVALID_ARG_VALUE);
|
|
841
|
-
}
|
|
842
|
-
return headers;
|
|
843
|
-
}
|
|
844
|
-
function signal(url, value) {
|
|
845
|
-
if (value !== undefined) {
|
|
846
|
-
if (typeof value === "function") {
|
|
847
|
-
value = value(url.href);
|
|
848
|
-
}
|
|
849
|
-
if (!(value instanceof AbortSignal)) {
|
|
850
|
-
throw CodedTypeError('"options.signal" must return or be an instance of AbortSignal', ERR_INVALID_ARG_TYPE);
|
|
851
|
-
}
|
|
852
|
-
return value;
|
|
1186
|
+
async function executeSharedTool(name, args, context, tools) {
|
|
1187
|
+
const toolList = tools ?? sharedTools;
|
|
1188
|
+
const tool = toolList.find((t) => t.name === name);
|
|
1189
|
+
if (!tool) {
|
|
1190
|
+
return {
|
|
1191
|
+
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
1192
|
+
isError: true
|
|
1193
|
+
};
|
|
853
1194
|
}
|
|
854
|
-
return;
|
|
855
|
-
}
|
|
856
|
-
function assertNumber(input, allow0, it, code, cause) {
|
|
857
1195
|
try {
|
|
858
|
-
if (
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
if (allow0) {
|
|
864
|
-
if (input !== 0) {
|
|
865
|
-
throw CodedTypeError(`${it} must be a non-negative number`, ERR_INVALID_ARG_VALUE, cause);
|
|
866
|
-
}
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
throw CodedTypeError(`${it} must be a positive number`, ERR_INVALID_ARG_VALUE, cause);
|
|
870
|
-
} catch (err) {
|
|
871
|
-
if (code) {
|
|
872
|
-
throw OPE(err.message, code, cause);
|
|
1196
|
+
if (context.signal?.aborted) {
|
|
1197
|
+
return {
|
|
1198
|
+
content: [{ type: "text", text: "Operation was cancelled" }],
|
|
1199
|
+
isError: true
|
|
1200
|
+
};
|
|
873
1201
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
1202
|
+
const parseResult = tool.inputSchema.safeParse(args);
|
|
1203
|
+
if (!parseResult.success) {
|
|
1204
|
+
const errors = parseResult.error.issues.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ");
|
|
1205
|
+
return {
|
|
1206
|
+
content: [{ type: "text", text: `Invalid input: ${errors}` }],
|
|
1207
|
+
isError: true
|
|
1208
|
+
};
|
|
881
1209
|
}
|
|
882
|
-
|
|
883
|
-
|
|
1210
|
+
const result = await tool.handler(parseResult.data, context);
|
|
1211
|
+
if (tool.outputSchema && !result.isError) {
|
|
1212
|
+
if (!result.structuredContent) {
|
|
1213
|
+
return {
|
|
1214
|
+
content: [
|
|
1215
|
+
{
|
|
1216
|
+
type: "text",
|
|
1217
|
+
text: "Tool with outputSchema must return structuredContent (unless isError is true)"
|
|
1218
|
+
}
|
|
1219
|
+
],
|
|
1220
|
+
isError: true
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
884
1223
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1224
|
+
return result;
|
|
1225
|
+
} catch (error) {
|
|
1226
|
+
if (context.signal?.aborted) {
|
|
1227
|
+
return {
|
|
1228
|
+
content: [{ type: "text", text: "Operation was cancelled" }],
|
|
1229
|
+
isError: true
|
|
1230
|
+
};
|
|
888
1231
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
}
|
|
895
|
-
function notJson(response, ...types) {
|
|
896
|
-
let msg = '"response" content-type must be ';
|
|
897
|
-
if (types.length > 2) {
|
|
898
|
-
const last = types.pop();
|
|
899
|
-
msg += `${types.join(", ")}, or ${last}`;
|
|
900
|
-
} else if (types.length === 2) {
|
|
901
|
-
msg += `${types[0]} or ${types[1]}`;
|
|
902
|
-
} else {
|
|
903
|
-
msg += types[0];
|
|
904
|
-
}
|
|
905
|
-
return OPE(msg, RESPONSE_IS_NOT_JSON, response);
|
|
906
|
-
}
|
|
907
|
-
function assertContentType(response, contentType) {
|
|
908
|
-
if (getContentType(response) !== contentType) {
|
|
909
|
-
throw notJson(response, contentType);
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
function randomBytes() {
|
|
913
|
-
return b64u(crypto.getRandomValues(new Uint8Array(32)));
|
|
914
|
-
}
|
|
915
|
-
async function calculatePKCECodeChallenge(codeVerifier) {
|
|
916
|
-
assertString(codeVerifier, "codeVerifier");
|
|
917
|
-
return b64u(await crypto.subtle.digest("SHA-256", buf(codeVerifier)));
|
|
918
|
-
}
|
|
919
|
-
function psAlg(key) {
|
|
920
|
-
switch (key.algorithm.hash.name) {
|
|
921
|
-
case "SHA-256":
|
|
922
|
-
return "PS256";
|
|
923
|
-
case "SHA-384":
|
|
924
|
-
return "PS384";
|
|
925
|
-
case "SHA-512":
|
|
926
|
-
return "PS512";
|
|
927
|
-
default:
|
|
928
|
-
throw new UnsupportedOperationError("unsupported RsaHashedKeyAlgorithm hash name", {
|
|
929
|
-
cause: key
|
|
930
|
-
});
|
|
1232
|
+
return {
|
|
1233
|
+
content: [
|
|
1234
|
+
{ type: "text", text: `Tool error: ${error.message}` }
|
|
1235
|
+
],
|
|
1236
|
+
isError: true
|
|
1237
|
+
};
|
|
931
1238
|
}
|
|
932
1239
|
}
|
|
933
|
-
function
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
return "RS512";
|
|
941
|
-
default:
|
|
942
|
-
throw new UnsupportedOperationError("unsupported RsaHashedKeyAlgorithm hash name", {
|
|
943
|
-
cause: key
|
|
1240
|
+
function registerTools(server, contextResolver) {
|
|
1241
|
+
for (const tool of sharedTools) {
|
|
1242
|
+
const inputSchemaShape = getSchemaShape(tool.inputSchema);
|
|
1243
|
+
if (!inputSchemaShape) {
|
|
1244
|
+
logger.error("tools", {
|
|
1245
|
+
message: "Failed to extract schema shape",
|
|
1246
|
+
toolName: tool.name
|
|
944
1247
|
});
|
|
1248
|
+
throw new Error(`Failed to extract schema shape for tool: ${tool.name}`);
|
|
1249
|
+
}
|
|
1250
|
+
server.registerTool(tool.name, {
|
|
1251
|
+
description: tool.description,
|
|
1252
|
+
inputSchema: inputSchemaShape,
|
|
1253
|
+
...tool.outputSchema && { outputSchema: tool.outputSchema },
|
|
1254
|
+
...tool.annotations && { annotations: tool.annotations }
|
|
1255
|
+
}, async (args, extra) => {
|
|
1256
|
+
const authContext = extra.requestId && contextResolver ? contextResolver(extra.requestId) : undefined;
|
|
1257
|
+
const ctx = authContext ?? getCurrentAuthContext();
|
|
1258
|
+
const authCtx = ctx;
|
|
1259
|
+
const providerInfo = authCtx?.provider ? toProviderInfo(authCtx.provider) : undefined;
|
|
1260
|
+
const context = {
|
|
1261
|
+
sessionId: extra.sessionId ?? crypto.randomUUID(),
|
|
1262
|
+
signal: extra.signal,
|
|
1263
|
+
meta: {
|
|
1264
|
+
progressToken: extra._meta?.progressToken,
|
|
1265
|
+
requestId: extra.requestId?.toString()
|
|
1266
|
+
},
|
|
1267
|
+
authStrategy: authCtx?.authStrategy,
|
|
1268
|
+
providerToken: authCtx?.providerToken,
|
|
1269
|
+
provider: providerInfo,
|
|
1270
|
+
resolvedHeaders: authCtx?.resolvedHeaders
|
|
1271
|
+
};
|
|
1272
|
+
const result = await executeSharedTool(tool.name, args, context);
|
|
1273
|
+
return result;
|
|
1274
|
+
});
|
|
945
1275
|
}
|
|
1276
|
+
logger.info("tools", { message: `Registered ${sharedTools.length} tools` });
|
|
946
1277
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
return esAlg(key);
|
|
967
|
-
case "Ed25519":
|
|
968
|
-
case "ML-DSA-44":
|
|
969
|
-
case "ML-DSA-65":
|
|
970
|
-
case "ML-DSA-87":
|
|
971
|
-
return key.algorithm.name;
|
|
972
|
-
case "EdDSA":
|
|
973
|
-
return "Ed25519";
|
|
974
|
-
default:
|
|
975
|
-
throw new UnsupportedOperationError("unsupported CryptoKey algorithm name", { cause: key });
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
function getClockSkew(client) {
|
|
979
|
-
const skew = client?.[clockSkew];
|
|
980
|
-
return typeof skew === "number" && Number.isFinite(skew) ? skew : 0;
|
|
981
|
-
}
|
|
982
|
-
function getClockTolerance(client) {
|
|
983
|
-
const tolerance = client?.[clockTolerance];
|
|
984
|
-
return typeof tolerance === "number" && Number.isFinite(tolerance) && Math.sign(tolerance) !== -1 ? tolerance : 30;
|
|
985
|
-
}
|
|
986
|
-
function epochTime() {
|
|
987
|
-
return Math.floor(Date.now() / 1000);
|
|
988
|
-
}
|
|
989
|
-
function assertAs(as) {
|
|
990
|
-
if (typeof as !== "object" || as === null) {
|
|
991
|
-
throw CodedTypeError('"as" must be an object', ERR_INVALID_ARG_TYPE);
|
|
992
|
-
}
|
|
993
|
-
assertString(as.issuer, '"as.issuer"');
|
|
994
|
-
}
|
|
995
|
-
function assertClient(client) {
|
|
996
|
-
if (typeof client !== "object" || client === null) {
|
|
997
|
-
throw CodedTypeError('"client" must be an object', ERR_INVALID_ARG_TYPE);
|
|
998
|
-
}
|
|
999
|
-
assertString(client.client_id, '"client.client_id"');
|
|
1000
|
-
}
|
|
1001
|
-
function ClientSecretPost(clientSecret) {
|
|
1002
|
-
assertString(clientSecret, '"clientSecret"');
|
|
1003
|
-
return (_as, client, body, _headers) => {
|
|
1004
|
-
body.set("client_id", client.client_id);
|
|
1005
|
-
body.set("client_secret", clientSecret);
|
|
1278
|
+
|
|
1279
|
+
// src/shared/mcp/dispatcher.ts
|
|
1280
|
+
import { z as z3 } from "zod";
|
|
1281
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
1282
|
+
|
|
1283
|
+
// src/runtime/node/capabilities.ts
|
|
1284
|
+
function buildCapabilities() {
|
|
1285
|
+
return {
|
|
1286
|
+
logging: {},
|
|
1287
|
+
prompts: {
|
|
1288
|
+
listChanged: true
|
|
1289
|
+
},
|
|
1290
|
+
resources: {
|
|
1291
|
+
listChanged: true,
|
|
1292
|
+
subscribe: true
|
|
1293
|
+
},
|
|
1294
|
+
tools: {
|
|
1295
|
+
listChanged: true
|
|
1296
|
+
}
|
|
1006
1297
|
};
|
|
1007
1298
|
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
return `${input}.${signature}`;
|
|
1015
|
-
}
|
|
1016
|
-
var jwkCache;
|
|
1017
|
-
async function getSetPublicJwkCache(key, alg) {
|
|
1018
|
-
const { kty, e, n, x, y, crv, pub } = await crypto.subtle.exportKey("jwk", key);
|
|
1019
|
-
const jwk = { kty, e, n, x, y, crv, pub };
|
|
1020
|
-
if (kty === "AKP")
|
|
1021
|
-
jwk.alg = alg;
|
|
1022
|
-
jwkCache.set(key, jwk);
|
|
1023
|
-
return jwk;
|
|
1024
|
-
}
|
|
1025
|
-
async function publicJwk(key, alg) {
|
|
1026
|
-
jwkCache ||= new WeakMap;
|
|
1027
|
-
return jwkCache.get(key) || getSetPublicJwkCache(key, alg);
|
|
1028
|
-
}
|
|
1029
|
-
var URLParse = URL.parse ? (url, base) => URL.parse(url, base) : (url, base) => {
|
|
1030
|
-
try {
|
|
1031
|
-
return new URL(url, base);
|
|
1032
|
-
} catch {
|
|
1033
|
-
return null;
|
|
1034
|
-
}
|
|
1299
|
+
|
|
1300
|
+
// src/shared/config/metadata.ts
|
|
1301
|
+
var serverMetadata = {
|
|
1302
|
+
title: "MCP Server",
|
|
1303
|
+
version: "0.0.1",
|
|
1304
|
+
instructions: "Use these tools to interact with the server."
|
|
1035
1305
|
};
|
|
1036
|
-
function checkProtocol(url, enforceHttps) {
|
|
1037
|
-
if (enforceHttps && url.protocol !== "https:") {
|
|
1038
|
-
throw OPE("only requests to HTTPS are allowed", HTTP_REQUEST_FORBIDDEN, url);
|
|
1039
|
-
}
|
|
1040
|
-
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
1041
|
-
throw OPE("only HTTP and HTTPS requests are allowed", REQUEST_PROTOCOL_FORBIDDEN, url);
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
function validateEndpoint(value, endpoint, useMtlsAlias, enforceHttps) {
|
|
1045
|
-
let url;
|
|
1046
|
-
if (typeof value !== "string" || !(url = URLParse(value))) {
|
|
1047
|
-
throw OPE(`authorization server metadata does not contain a valid ${useMtlsAlias ? `"as.mtls_endpoint_aliases.${endpoint}"` : `"as.${endpoint}"`}`, value === undefined ? MISSING_SERVER_METADATA : INVALID_SERVER_METADATA, { attribute: useMtlsAlias ? `mtls_endpoint_aliases.${endpoint}` : endpoint });
|
|
1048
|
-
}
|
|
1049
|
-
checkProtocol(url, enforceHttps);
|
|
1050
|
-
return url;
|
|
1051
|
-
}
|
|
1052
|
-
function resolveEndpoint(as, endpoint, useMtlsAlias, enforceHttps) {
|
|
1053
|
-
if (useMtlsAlias && as.mtls_endpoint_aliases && endpoint in as.mtls_endpoint_aliases) {
|
|
1054
|
-
return validateEndpoint(as.mtls_endpoint_aliases[endpoint], endpoint, useMtlsAlias, enforceHttps);
|
|
1055
|
-
}
|
|
1056
|
-
return validateEndpoint(as[endpoint], endpoint, useMtlsAlias, enforceHttps);
|
|
1057
|
-
}
|
|
1058
|
-
class DPoPHandler {
|
|
1059
|
-
#header;
|
|
1060
|
-
#privateKey;
|
|
1061
|
-
#publicKey;
|
|
1062
|
-
#clockSkew;
|
|
1063
|
-
#modifyAssertion;
|
|
1064
|
-
#map;
|
|
1065
|
-
#jkt;
|
|
1066
|
-
constructor(client, keyPair, options) {
|
|
1067
|
-
assertPrivateKey(keyPair?.privateKey, '"DPoP.privateKey"');
|
|
1068
|
-
assertPublicKey(keyPair?.publicKey, '"DPoP.publicKey"');
|
|
1069
|
-
if (!keyPair.publicKey.extractable) {
|
|
1070
|
-
throw CodedTypeError('"DPoP.publicKey.extractable" must be true', ERR_INVALID_ARG_VALUE);
|
|
1071
|
-
}
|
|
1072
|
-
this.#modifyAssertion = options?.[modifyAssertion];
|
|
1073
|
-
this.#clockSkew = getClockSkew(client);
|
|
1074
|
-
this.#privateKey = keyPair.privateKey;
|
|
1075
|
-
this.#publicKey = keyPair.publicKey;
|
|
1076
|
-
branded.add(this);
|
|
1077
|
-
}
|
|
1078
|
-
#get(key) {
|
|
1079
|
-
this.#map ||= new Map;
|
|
1080
|
-
let item = this.#map.get(key);
|
|
1081
|
-
if (item) {
|
|
1082
|
-
this.#map.delete(key);
|
|
1083
|
-
this.#map.set(key, item);
|
|
1084
|
-
}
|
|
1085
|
-
return item;
|
|
1086
|
-
}
|
|
1087
|
-
#set(key, val) {
|
|
1088
|
-
this.#map ||= new Map;
|
|
1089
|
-
this.#map.delete(key);
|
|
1090
|
-
if (this.#map.size === 100) {
|
|
1091
|
-
this.#map.delete(this.#map.keys().next().value);
|
|
1092
|
-
}
|
|
1093
|
-
this.#map.set(key, val);
|
|
1094
|
-
}
|
|
1095
|
-
async calculateThumbprint() {
|
|
1096
|
-
if (!this.#jkt) {
|
|
1097
|
-
const jwk = await crypto.subtle.exportKey("jwk", this.#publicKey);
|
|
1098
|
-
this.#jkt ||= await calculateJwkThumbprint(jwk);
|
|
1099
|
-
}
|
|
1100
|
-
return this.#jkt;
|
|
1101
|
-
}
|
|
1102
|
-
async addProof(url, headers, htm, accessToken) {
|
|
1103
|
-
const alg = keyToJws(this.#privateKey);
|
|
1104
|
-
this.#header ||= {
|
|
1105
|
-
alg,
|
|
1106
|
-
typ: "dpop+jwt",
|
|
1107
|
-
jwk: await publicJwk(this.#publicKey, alg)
|
|
1108
|
-
};
|
|
1109
|
-
const nonce = this.#get(url.origin);
|
|
1110
|
-
const now = epochTime() + this.#clockSkew;
|
|
1111
|
-
const payload = {
|
|
1112
|
-
iat: now,
|
|
1113
|
-
jti: randomBytes(),
|
|
1114
|
-
htm,
|
|
1115
|
-
nonce,
|
|
1116
|
-
htu: `${url.origin}${url.pathname}`,
|
|
1117
|
-
ath: accessToken ? b64u(await crypto.subtle.digest("SHA-256", buf(accessToken))) : undefined
|
|
1118
|
-
};
|
|
1119
|
-
this.#modifyAssertion?.(this.#header, payload);
|
|
1120
|
-
headers.set("dpop", await signJwt(this.#header, payload, this.#privateKey));
|
|
1121
|
-
}
|
|
1122
|
-
cacheNonce(response, url) {
|
|
1123
|
-
try {
|
|
1124
|
-
const nonce = response.headers.get("dpop-nonce");
|
|
1125
|
-
if (nonce) {
|
|
1126
|
-
this.#set(url.origin, nonce);
|
|
1127
|
-
}
|
|
1128
|
-
} catch {}
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
class ResponseBodyError extends Error {
|
|
1132
|
-
cause;
|
|
1133
|
-
code;
|
|
1134
|
-
error;
|
|
1135
|
-
status;
|
|
1136
|
-
error_description;
|
|
1137
|
-
response;
|
|
1138
|
-
constructor(message, options) {
|
|
1139
|
-
super(message, options);
|
|
1140
|
-
this.name = this.constructor.name;
|
|
1141
|
-
this.code = RESPONSE_BODY_ERROR;
|
|
1142
|
-
this.cause = options.cause;
|
|
1143
|
-
this.error = options.cause.error;
|
|
1144
|
-
this.status = options.response.status;
|
|
1145
|
-
this.error_description = options.cause.error_description;
|
|
1146
|
-
Object.defineProperty(this, "response", { enumerable: false, value: options.response });
|
|
1147
|
-
Error.captureStackTrace?.(this, this.constructor);
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
1306
|
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1307
|
+
// src/shared/mcp/dispatcher.ts
|
|
1308
|
+
var LATEST_PROTOCOL_VERSION = "2025-06-18";
|
|
1309
|
+
var SUPPORTED_PROTOCOL_VERSIONS = [
|
|
1310
|
+
"2025-06-18",
|
|
1311
|
+
"2025-03-26",
|
|
1312
|
+
"2024-11-05",
|
|
1313
|
+
"2024-10-07"
|
|
1314
|
+
];
|
|
1315
|
+
var JsonRpcErrorCode2 = {
|
|
1316
|
+
ParseError: -32700,
|
|
1317
|
+
InvalidRequest: -32600,
|
|
1318
|
+
MethodNotFound: -32601,
|
|
1319
|
+
InvalidParams: -32602,
|
|
1320
|
+
InternalError: -32603
|
|
1321
|
+
};
|
|
1322
|
+
async function handleInitialize(params, ctx) {
|
|
1323
|
+
const clientInfo = params?.clientInfo;
|
|
1324
|
+
const requestedVersion = String(params?.protocolVersion || LATEST_PROTOCOL_VERSION);
|
|
1325
|
+
const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion) ? requestedVersion : LATEST_PROTOCOL_VERSION;
|
|
1326
|
+
ctx.setSessionState({
|
|
1327
|
+
initialized: false,
|
|
1328
|
+
clientInfo,
|
|
1329
|
+
protocolVersion
|
|
1330
|
+
});
|
|
1331
|
+
sharedLogger.info("mcp_dispatch", {
|
|
1332
|
+
message: "Initialize request",
|
|
1333
|
+
sessionId: ctx.sessionId,
|
|
1334
|
+
clientInfo,
|
|
1335
|
+
requestedVersion,
|
|
1336
|
+
negotiatedVersion: protocolVersion
|
|
1337
|
+
});
|
|
1338
|
+
return {
|
|
1339
|
+
result: {
|
|
1340
|
+
protocolVersion,
|
|
1341
|
+
capabilities: buildCapabilities(),
|
|
1342
|
+
serverInfo: {
|
|
1343
|
+
name: ctx.config.title || serverMetadata.title,
|
|
1344
|
+
version: ctx.config.version || "0.0.1"
|
|
1345
|
+
},
|
|
1346
|
+
instructions: ctx.config.instructions || serverMetadata.instructions
|
|
1347
|
+
}
|
|
1348
|
+
};
|
|
1165
1349
|
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
var quotedMatch = '"((?:[^"\\\\]|\\\\[\\s\\S])*)"';
|
|
1186
|
-
var quotedParamMatcher = "(" + tokenMatch + ")\\s*=\\s*" + quotedMatch;
|
|
1187
|
-
var paramMatcher = "(" + tokenMatch + ")\\s*=\\s*(" + tokenMatch + ")";
|
|
1188
|
-
var schemeRE = new RegExp("^[,\\s]*(" + tokenMatch + ")");
|
|
1189
|
-
var quotedParamRE = new RegExp("^[,\\s]*" + quotedParamMatcher + "[,\\s]*(.*)");
|
|
1190
|
-
var unquotedParamRE = new RegExp("^[,\\s]*" + paramMatcher + "[,\\s]*(.*)");
|
|
1191
|
-
var token68ParamRE = new RegExp("^(" + token68Match + ")(?:$|[,\\s])(.*)");
|
|
1192
|
-
function parseWwwAuthenticateChallenges(response) {
|
|
1193
|
-
if (!looseInstanceOf(response, Response)) {
|
|
1194
|
-
throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
|
|
1195
|
-
}
|
|
1196
|
-
const header = response.headers.get("www-authenticate");
|
|
1197
|
-
if (header === null) {
|
|
1198
|
-
return;
|
|
1350
|
+
async function handleToolsList(ctx) {
|
|
1351
|
+
const tools = (ctx.tools ?? sharedTools).map((tool) => ({
|
|
1352
|
+
name: tool.name,
|
|
1353
|
+
description: tool.description,
|
|
1354
|
+
inputSchema: zodToJsonSchema(tool.inputSchema),
|
|
1355
|
+
...tool.outputSchema && {
|
|
1356
|
+
outputSchema: zodToJsonSchema(z3.object(tool.outputSchema))
|
|
1357
|
+
},
|
|
1358
|
+
...tool.annotations && { annotations: tool.annotations }
|
|
1359
|
+
}));
|
|
1360
|
+
return { result: { tools } };
|
|
1361
|
+
}
|
|
1362
|
+
async function handleToolsCall(params, ctx, requestId) {
|
|
1363
|
+
const toolName = String(params?.name || "");
|
|
1364
|
+
const toolArgs = params?.arguments || {};
|
|
1365
|
+
const meta = params?._meta;
|
|
1366
|
+
const abortController = new AbortController;
|
|
1367
|
+
if (requestId !== undefined && ctx.cancellationRegistry) {
|
|
1368
|
+
ctx.cancellationRegistry.set(requestId, abortController);
|
|
1199
1369
|
}
|
|
1200
|
-
const
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
}
|
|
1208
|
-
const afterScheme = rest.substring(match[0].length);
|
|
1209
|
-
if (afterScheme && !afterScheme.match(/^[\s,]/)) {
|
|
1210
|
-
return;
|
|
1211
|
-
}
|
|
1212
|
-
const spaceMatch = afterScheme.match(/^\s+(.*)$/);
|
|
1213
|
-
const hasParameters = !!spaceMatch;
|
|
1214
|
-
rest = spaceMatch ? spaceMatch[1] : undefined;
|
|
1215
|
-
const parameters = {};
|
|
1216
|
-
let token68;
|
|
1217
|
-
if (hasParameters) {
|
|
1218
|
-
while (rest) {
|
|
1219
|
-
let key;
|
|
1220
|
-
let value;
|
|
1221
|
-
if (match = rest.match(quotedParamRE)) {
|
|
1222
|
-
[, key, value, rest] = match;
|
|
1223
|
-
if (value.includes("\\")) {
|
|
1224
|
-
try {
|
|
1225
|
-
value = JSON.parse(`"${value}"`);
|
|
1226
|
-
} catch {}
|
|
1227
|
-
}
|
|
1228
|
-
parameters[key.toLowerCase()] = value;
|
|
1229
|
-
continue;
|
|
1230
|
-
}
|
|
1231
|
-
if (match = rest.match(unquotedParamRE)) {
|
|
1232
|
-
[, key, value, rest] = match;
|
|
1233
|
-
parameters[key.toLowerCase()] = value;
|
|
1234
|
-
continue;
|
|
1235
|
-
}
|
|
1236
|
-
if (match = rest.match(token68ParamRE)) {
|
|
1237
|
-
if (Object.keys(parameters).length) {
|
|
1238
|
-
break;
|
|
1239
|
-
}
|
|
1240
|
-
[, token68, rest] = match;
|
|
1241
|
-
break;
|
|
1242
|
-
}
|
|
1243
|
-
return;
|
|
1244
|
-
}
|
|
1245
|
-
} else {
|
|
1246
|
-
rest = afterScheme || undefined;
|
|
1247
|
-
}
|
|
1248
|
-
const challenge = { scheme, parameters };
|
|
1249
|
-
if (token68) {
|
|
1250
|
-
challenge.token68 = token68;
|
|
1370
|
+
const toolContext = {
|
|
1371
|
+
...ctx.auth,
|
|
1372
|
+
sessionId: ctx.sessionId,
|
|
1373
|
+
signal: abortController.signal,
|
|
1374
|
+
meta: {
|
|
1375
|
+
progressToken: meta?.progressToken,
|
|
1376
|
+
requestId: requestId !== undefined ? String(requestId) : undefined
|
|
1251
1377
|
}
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1378
|
+
};
|
|
1379
|
+
sharedLogger.debug("mcp_dispatch", {
|
|
1380
|
+
message: "Calling tool",
|
|
1381
|
+
tool: toolName,
|
|
1382
|
+
sessionId: ctx.sessionId,
|
|
1383
|
+
requestId,
|
|
1384
|
+
hasProviderToken: Boolean(ctx.auth.providerToken)
|
|
1385
|
+
});
|
|
1386
|
+
const toolList = ctx.tools ?? sharedTools;
|
|
1387
|
+
const tool = toolList.find((t) => t.name === toolName);
|
|
1388
|
+
if (tool?.requiresAuth && !ctx.auth.providerToken) {
|
|
1389
|
+
return {
|
|
1390
|
+
error: {
|
|
1391
|
+
code: JsonRpcErrorCode2.InvalidRequest,
|
|
1392
|
+
message: "Authentication required. Please complete OAuth flow first."
|
|
1267
1393
|
}
|
|
1268
|
-
}
|
|
1394
|
+
};
|
|
1269
1395
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
cause: err,
|
|
1280
|
-
response
|
|
1396
|
+
try {
|
|
1397
|
+
const result = await executeSharedTool(toolName, toolArgs, toolContext, ctx.tools);
|
|
1398
|
+
return { result };
|
|
1399
|
+
} catch (error) {
|
|
1400
|
+
if (abortController.signal.aborted) {
|
|
1401
|
+
sharedLogger.info("mcp_dispatch", {
|
|
1402
|
+
message: "Tool execution cancelled",
|
|
1403
|
+
tool: toolName,
|
|
1404
|
+
requestId
|
|
1281
1405
|
});
|
|
1406
|
+
return {
|
|
1407
|
+
error: {
|
|
1408
|
+
code: JsonRpcErrorCode2.InternalError,
|
|
1409
|
+
message: "Request was cancelled"
|
|
1410
|
+
}
|
|
1411
|
+
};
|
|
1282
1412
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
if (!branded.has(option)) {
|
|
1288
|
-
throw CodedTypeError('"options.DPoP" is not a valid DPoPHandle', ERR_INVALID_ARG_VALUE);
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
var skipSubjectCheck = Symbol();
|
|
1292
|
-
function getContentType(input) {
|
|
1293
|
-
return input.headers.get("content-type")?.split(";")[0];
|
|
1294
|
-
}
|
|
1295
|
-
async function authenticatedRequest(as, client, clientAuthentication, url, body, headers, options) {
|
|
1296
|
-
await clientAuthentication(as, client, body, headers);
|
|
1297
|
-
headers.set("content-type", "application/x-www-form-urlencoded;charset=UTF-8");
|
|
1298
|
-
return (options?.[customFetch] || fetch)(url.href, {
|
|
1299
|
-
body,
|
|
1300
|
-
headers: Object.fromEntries(headers.entries()),
|
|
1301
|
-
method: "POST",
|
|
1302
|
-
redirect: "manual",
|
|
1303
|
-
signal: signal(url, options?.signal)
|
|
1304
|
-
});
|
|
1305
|
-
}
|
|
1306
|
-
async function tokenEndpointRequest(as, client, clientAuthentication, grantType, parameters, options) {
|
|
1307
|
-
const url = resolveEndpoint(as, "token_endpoint", client.use_mtls_endpoint_aliases, options?.[allowInsecureRequests] !== true);
|
|
1308
|
-
parameters.set("grant_type", grantType);
|
|
1309
|
-
const headers = prepareHeaders(options?.headers);
|
|
1310
|
-
headers.set("accept", "application/json");
|
|
1311
|
-
if (options?.DPoP !== undefined) {
|
|
1312
|
-
assertDPoP(options.DPoP);
|
|
1313
|
-
await options.DPoP.addProof(url, headers, "POST");
|
|
1314
|
-
}
|
|
1315
|
-
const response = await authenticatedRequest(as, client, clientAuthentication, url, parameters, headers, options);
|
|
1316
|
-
options?.DPoP?.cacheNonce(response, url);
|
|
1317
|
-
return response;
|
|
1318
|
-
}
|
|
1319
|
-
async function refreshTokenGrantRequest(as, client, clientAuthentication, refreshToken, options) {
|
|
1320
|
-
assertAs(as);
|
|
1321
|
-
assertClient(client);
|
|
1322
|
-
assertString(refreshToken, '"refreshToken"');
|
|
1323
|
-
const parameters = new URLSearchParams(options?.additionalParameters);
|
|
1324
|
-
parameters.set("refresh_token", refreshToken);
|
|
1325
|
-
return tokenEndpointRequest(as, client, clientAuthentication, "refresh_token", parameters, options);
|
|
1326
|
-
}
|
|
1327
|
-
var idTokenClaims = new WeakMap;
|
|
1328
|
-
var jwtRefs = new WeakMap;
|
|
1329
|
-
function getValidatedIdTokenClaims(ref) {
|
|
1330
|
-
if (!ref.id_token) {
|
|
1331
|
-
return;
|
|
1332
|
-
}
|
|
1333
|
-
const claims = idTokenClaims.get(ref);
|
|
1334
|
-
if (!claims) {
|
|
1335
|
-
throw CodedTypeError('"ref" was already garbage collected or did not resolve from the proper sources', ERR_INVALID_ARG_VALUE);
|
|
1336
|
-
}
|
|
1337
|
-
return claims;
|
|
1338
|
-
}
|
|
1339
|
-
async function processGenericAccessTokenResponse(as, client, response, additionalRequiredIdTokenClaims, decryptFn, recognizedTokenTypes) {
|
|
1340
|
-
assertAs(as);
|
|
1341
|
-
assertClient(client);
|
|
1342
|
-
if (!looseInstanceOf(response, Response)) {
|
|
1343
|
-
throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
|
|
1344
|
-
}
|
|
1345
|
-
await checkOAuthBodyError(response, 200, "Token Endpoint");
|
|
1346
|
-
assertReadableResponse(response);
|
|
1347
|
-
const json = await getResponseJsonBody(response);
|
|
1348
|
-
assertString(json.access_token, '"response" body "access_token" property', INVALID_RESPONSE, {
|
|
1349
|
-
body: json
|
|
1350
|
-
});
|
|
1351
|
-
assertString(json.token_type, '"response" body "token_type" property', INVALID_RESPONSE, {
|
|
1352
|
-
body: json
|
|
1353
|
-
});
|
|
1354
|
-
json.token_type = json.token_type.toLowerCase();
|
|
1355
|
-
if (json.expires_in !== undefined) {
|
|
1356
|
-
let expiresIn = typeof json.expires_in !== "number" ? parseFloat(json.expires_in) : json.expires_in;
|
|
1357
|
-
assertNumber(expiresIn, true, '"response" body "expires_in" property', INVALID_RESPONSE, {
|
|
1358
|
-
body: json
|
|
1359
|
-
});
|
|
1360
|
-
json.expires_in = expiresIn;
|
|
1361
|
-
}
|
|
1362
|
-
if (json.refresh_token !== undefined) {
|
|
1363
|
-
assertString(json.refresh_token, '"response" body "refresh_token" property', INVALID_RESPONSE, {
|
|
1364
|
-
body: json
|
|
1365
|
-
});
|
|
1366
|
-
}
|
|
1367
|
-
if (json.scope !== undefined && typeof json.scope !== "string") {
|
|
1368
|
-
throw OPE('"response" body "scope" property must be a string', INVALID_RESPONSE, { body: json });
|
|
1369
|
-
}
|
|
1370
|
-
if (json.id_token !== undefined) {
|
|
1371
|
-
assertString(json.id_token, '"response" body "id_token" property', INVALID_RESPONSE, {
|
|
1372
|
-
body: json
|
|
1413
|
+
sharedLogger.error("mcp_dispatch", {
|
|
1414
|
+
message: "Tool execution failed",
|
|
1415
|
+
tool: toolName,
|
|
1416
|
+
error: error.message
|
|
1373
1417
|
});
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
if (client.default_max_age !== undefined) {
|
|
1379
|
-
assertNumber(client.default_max_age, true, '"client.default_max_age"');
|
|
1380
|
-
requiredClaims.push("auth_time");
|
|
1381
|
-
}
|
|
1382
|
-
if (additionalRequiredIdTokenClaims?.length) {
|
|
1383
|
-
requiredClaims.push(...additionalRequiredIdTokenClaims);
|
|
1384
|
-
}
|
|
1385
|
-
const { claims, jwt } = await validateJwt(json.id_token, checkSigningAlgorithm.bind(undefined, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported, "RS256"), getClockSkew(client), getClockTolerance(client), decryptFn).then(validatePresence.bind(undefined, requiredClaims)).then(validateIssuer.bind(undefined, as)).then(validateAudience.bind(undefined, client.client_id));
|
|
1386
|
-
if (Array.isArray(claims.aud) && claims.aud.length !== 1) {
|
|
1387
|
-
if (claims.azp === undefined) {
|
|
1388
|
-
throw OPE('ID Token "aud" (audience) claim includes additional untrusted audiences', JWT_CLAIM_COMPARISON, { claims, claim: "aud" });
|
|
1389
|
-
}
|
|
1390
|
-
if (claims.azp !== client.client_id) {
|
|
1391
|
-
throw OPE('unexpected ID Token "azp" (authorized party) claim value', JWT_CLAIM_COMPARISON, { expected: client.client_id, claims, claim: "azp" });
|
|
1418
|
+
return {
|
|
1419
|
+
error: {
|
|
1420
|
+
code: JsonRpcErrorCode2.InternalError,
|
|
1421
|
+
message: `Tool execution failed: ${error.message}`
|
|
1392
1422
|
}
|
|
1423
|
+
};
|
|
1424
|
+
} finally {
|
|
1425
|
+
if (requestId !== undefined && ctx.cancellationRegistry) {
|
|
1426
|
+
ctx.cancellationRegistry.delete(requestId);
|
|
1393
1427
|
}
|
|
1394
|
-
if (claims.auth_time !== undefined) {
|
|
1395
|
-
assertNumber(claims.auth_time, true, 'ID Token "auth_time" (authentication time)', INVALID_RESPONSE, { claims });
|
|
1396
|
-
}
|
|
1397
|
-
jwtRefs.set(response, jwt);
|
|
1398
|
-
idTokenClaims.set(json, claims);
|
|
1399
|
-
}
|
|
1400
|
-
if (recognizedTokenTypes?.[json.token_type] !== undefined) {
|
|
1401
|
-
recognizedTokenTypes[json.token_type](response, json);
|
|
1402
|
-
} else if (json.token_type !== "dpop" && json.token_type !== "bearer") {
|
|
1403
|
-
throw new UnsupportedOperationError("unsupported `token_type` value", { cause: { body: json } });
|
|
1404
|
-
}
|
|
1405
|
-
return json;
|
|
1406
|
-
}
|
|
1407
|
-
function checkAuthenticationChallenges(response) {
|
|
1408
|
-
let challenges;
|
|
1409
|
-
if (challenges = parseWwwAuthenticateChallenges(response)) {
|
|
1410
|
-
throw new WWWAuthenticateChallengeError("server responded with a challenge in the WWW-Authenticate HTTP Header", { cause: challenges, response });
|
|
1411
1428
|
}
|
|
1412
1429
|
}
|
|
1413
|
-
async function
|
|
1414
|
-
return
|
|
1430
|
+
async function handleResourcesList() {
|
|
1431
|
+
return { result: { resources: [] } };
|
|
1415
1432
|
}
|
|
1416
|
-
function
|
|
1417
|
-
|
|
1418
|
-
if (!result.claims.aud.includes(expected)) {
|
|
1419
|
-
throw OPE('unexpected JWT "aud" (audience) claim value', JWT_CLAIM_COMPARISON, {
|
|
1420
|
-
expected,
|
|
1421
|
-
claims: result.claims,
|
|
1422
|
-
claim: "aud"
|
|
1423
|
-
});
|
|
1424
|
-
}
|
|
1425
|
-
} else if (result.claims.aud !== expected) {
|
|
1426
|
-
throw OPE('unexpected JWT "aud" (audience) claim value', JWT_CLAIM_COMPARISON, {
|
|
1427
|
-
expected,
|
|
1428
|
-
claims: result.claims,
|
|
1429
|
-
claim: "aud"
|
|
1430
|
-
});
|
|
1431
|
-
}
|
|
1432
|
-
return result;
|
|
1433
|
+
async function handleResourcesTemplatesList() {
|
|
1434
|
+
return { result: { resourceTemplates: [] } };
|
|
1433
1435
|
}
|
|
1434
|
-
function
|
|
1435
|
-
|
|
1436
|
-
if (result.claims.iss !== expected) {
|
|
1437
|
-
throw OPE('unexpected JWT "iss" (issuer) claim value', JWT_CLAIM_COMPARISON, {
|
|
1438
|
-
expected,
|
|
1439
|
-
claims: result.claims,
|
|
1440
|
-
claim: "iss"
|
|
1441
|
-
});
|
|
1442
|
-
}
|
|
1443
|
-
return result;
|
|
1444
|
-
}
|
|
1445
|
-
var branded = new WeakSet;
|
|
1446
|
-
function brand(searchParams) {
|
|
1447
|
-
branded.add(searchParams);
|
|
1448
|
-
return searchParams;
|
|
1449
|
-
}
|
|
1450
|
-
var nopkce = Symbol();
|
|
1451
|
-
async function authorizationCodeGrantRequest(as, client, clientAuthentication, callbackParameters, redirectUri, codeVerifier, options) {
|
|
1452
|
-
assertAs(as);
|
|
1453
|
-
assertClient(client);
|
|
1454
|
-
if (!branded.has(callbackParameters)) {
|
|
1455
|
-
throw CodedTypeError('"callbackParameters" must be an instance of URLSearchParams obtained from "validateAuthResponse()", or "validateJwtAuthResponse()', ERR_INVALID_ARG_VALUE);
|
|
1456
|
-
}
|
|
1457
|
-
assertString(redirectUri, '"redirectUri"');
|
|
1458
|
-
const code = getURLSearchParameter(callbackParameters, "code");
|
|
1459
|
-
if (!code) {
|
|
1460
|
-
throw OPE('no authorization code in "callbackParameters"', INVALID_RESPONSE);
|
|
1461
|
-
}
|
|
1462
|
-
const parameters = new URLSearchParams(options?.additionalParameters);
|
|
1463
|
-
parameters.set("redirect_uri", redirectUri);
|
|
1464
|
-
parameters.set("code", code);
|
|
1465
|
-
if (codeVerifier !== nopkce) {
|
|
1466
|
-
assertString(codeVerifier, '"codeVerifier"');
|
|
1467
|
-
parameters.set("code_verifier", codeVerifier);
|
|
1468
|
-
}
|
|
1469
|
-
return tokenEndpointRequest(as, client, clientAuthentication, "authorization_code", parameters, options);
|
|
1470
|
-
}
|
|
1471
|
-
var jwtClaimNames = {
|
|
1472
|
-
aud: "audience",
|
|
1473
|
-
c_hash: "code hash",
|
|
1474
|
-
client_id: "client id",
|
|
1475
|
-
exp: "expiration time",
|
|
1476
|
-
iat: "issued at",
|
|
1477
|
-
iss: "issuer",
|
|
1478
|
-
jti: "jwt id",
|
|
1479
|
-
nonce: "nonce",
|
|
1480
|
-
s_hash: "state hash",
|
|
1481
|
-
sub: "subject",
|
|
1482
|
-
ath: "access token hash",
|
|
1483
|
-
htm: "http method",
|
|
1484
|
-
htu: "http uri",
|
|
1485
|
-
cnf: "confirmation",
|
|
1486
|
-
auth_time: "authentication time"
|
|
1487
|
-
};
|
|
1488
|
-
function validatePresence(required, result) {
|
|
1489
|
-
for (const claim of required) {
|
|
1490
|
-
if (result.claims[claim] === undefined) {
|
|
1491
|
-
throw OPE(`JWT "${claim}" (${jwtClaimNames[claim]}) claim missing`, INVALID_RESPONSE, {
|
|
1492
|
-
claims: result.claims
|
|
1493
|
-
});
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
return result;
|
|
1436
|
+
async function handlePromptsList() {
|
|
1437
|
+
return { result: { prompts: [] } };
|
|
1497
1438
|
}
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
async function processAuthorizationCodeResponse(as, client, response, options) {
|
|
1501
|
-
if (typeof options?.expectedNonce === "string" || typeof options?.maxAge === "number" || options?.requireIdToken) {
|
|
1502
|
-
return processAuthorizationCodeOpenIDResponse(as, client, response, options.expectedNonce, options.maxAge, options[jweDecrypt], options.recognizedTokenTypes);
|
|
1503
|
-
}
|
|
1504
|
-
return processAuthorizationCodeOAuth2Response(as, client, response, options?.[jweDecrypt], options?.recognizedTokenTypes);
|
|
1439
|
+
async function handlePing() {
|
|
1440
|
+
return { result: {} };
|
|
1505
1441
|
}
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
assertNumber(maxAge, true, '"maxAge" argument');
|
|
1527
|
-
additionalRequiredClaims.push("auth_time");
|
|
1442
|
+
var currentLogLevel = "info";
|
|
1443
|
+
async function handleLoggingSetLevel(params) {
|
|
1444
|
+
const level = params?.level;
|
|
1445
|
+
const validLevels = [
|
|
1446
|
+
"debug",
|
|
1447
|
+
"info",
|
|
1448
|
+
"notice",
|
|
1449
|
+
"warning",
|
|
1450
|
+
"error",
|
|
1451
|
+
"critical",
|
|
1452
|
+
"alert",
|
|
1453
|
+
"emergency"
|
|
1454
|
+
];
|
|
1455
|
+
if (!level || !validLevels.includes(level)) {
|
|
1456
|
+
return {
|
|
1457
|
+
error: {
|
|
1458
|
+
code: JsonRpcErrorCode2.InvalidParams,
|
|
1459
|
+
message: `Invalid log level. Must be one of: ${validLevels.join(", ")}`
|
|
1460
|
+
}
|
|
1461
|
+
};
|
|
1528
1462
|
}
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1463
|
+
currentLogLevel = level;
|
|
1464
|
+
sharedLogger.info("mcp_dispatch", {
|
|
1465
|
+
message: "Log level changed",
|
|
1466
|
+
level: currentLogLevel
|
|
1532
1467
|
});
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
expected: undefined,
|
|
1545
|
-
claims,
|
|
1546
|
-
claim: "nonce"
|
|
1547
|
-
});
|
|
1548
|
-
}
|
|
1549
|
-
} else if (claims.nonce !== expectedNonce) {
|
|
1550
|
-
throw OPE('unexpected ID Token "nonce" claim value', JWT_CLAIM_COMPARISON, {
|
|
1551
|
-
expected: expectedNonce,
|
|
1552
|
-
claims,
|
|
1553
|
-
claim: "nonce"
|
|
1554
|
-
});
|
|
1555
|
-
}
|
|
1556
|
-
return result;
|
|
1557
|
-
}
|
|
1558
|
-
async function processAuthorizationCodeOAuth2Response(as, client, response, decryptFn, recognizedTokenTypes) {
|
|
1559
|
-
const result = await processGenericAccessTokenResponse(as, client, response, undefined, decryptFn, recognizedTokenTypes);
|
|
1560
|
-
const claims = getValidatedIdTokenClaims(result);
|
|
1561
|
-
if (claims) {
|
|
1562
|
-
if (client.default_max_age !== undefined) {
|
|
1563
|
-
assertNumber(client.default_max_age, true, '"client.default_max_age"');
|
|
1564
|
-
const now = epochTime() + getClockSkew(client);
|
|
1565
|
-
const tolerance = getClockTolerance(client);
|
|
1566
|
-
if (claims.auth_time + client.default_max_age < now - tolerance) {
|
|
1567
|
-
throw OPE("too much time has elapsed since the last End-User authentication", JWT_TIMESTAMP_CHECK, { claims, now, tolerance, claim: "auth_time" });
|
|
1468
|
+
return { result: {} };
|
|
1469
|
+
}
|
|
1470
|
+
function getLogLevel() {
|
|
1471
|
+
return currentLogLevel;
|
|
1472
|
+
}
|
|
1473
|
+
async function dispatchMcpMethod(method, params, ctx, requestId) {
|
|
1474
|
+
if (!method) {
|
|
1475
|
+
return {
|
|
1476
|
+
error: {
|
|
1477
|
+
code: JsonRpcErrorCode2.InvalidRequest,
|
|
1478
|
+
message: "Missing method"
|
|
1568
1479
|
}
|
|
1569
|
-
}
|
|
1570
|
-
if (claims.nonce !== undefined) {
|
|
1571
|
-
throw OPE('unexpected ID Token "nonce" claim value', JWT_CLAIM_COMPARISON, {
|
|
1572
|
-
expected: undefined,
|
|
1573
|
-
claims,
|
|
1574
|
-
claim: "nonce"
|
|
1575
|
-
});
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
return result;
|
|
1579
|
-
}
|
|
1580
|
-
var WWW_AUTHENTICATE_CHALLENGE = "OAUTH_WWW_AUTHENTICATE_CHALLENGE";
|
|
1581
|
-
var RESPONSE_BODY_ERROR = "OAUTH_RESPONSE_BODY_ERROR";
|
|
1582
|
-
var UNSUPPORTED_OPERATION = "OAUTH_UNSUPPORTED_OPERATION";
|
|
1583
|
-
var AUTHORIZATION_RESPONSE_ERROR = "OAUTH_AUTHORIZATION_RESPONSE_ERROR";
|
|
1584
|
-
var PARSE_ERROR = "OAUTH_PARSE_ERROR";
|
|
1585
|
-
var INVALID_RESPONSE = "OAUTH_INVALID_RESPONSE";
|
|
1586
|
-
var RESPONSE_IS_NOT_JSON = "OAUTH_RESPONSE_IS_NOT_JSON";
|
|
1587
|
-
var RESPONSE_IS_NOT_CONFORM = "OAUTH_RESPONSE_IS_NOT_CONFORM";
|
|
1588
|
-
var HTTP_REQUEST_FORBIDDEN = "OAUTH_HTTP_REQUEST_FORBIDDEN";
|
|
1589
|
-
var REQUEST_PROTOCOL_FORBIDDEN = "OAUTH_REQUEST_PROTOCOL_FORBIDDEN";
|
|
1590
|
-
var JWT_TIMESTAMP_CHECK = "OAUTH_JWT_TIMESTAMP_CHECK_FAILED";
|
|
1591
|
-
var JWT_CLAIM_COMPARISON = "OAUTH_JWT_CLAIM_COMPARISON_FAILED";
|
|
1592
|
-
var MISSING_SERVER_METADATA = "OAUTH_MISSING_SERVER_METADATA";
|
|
1593
|
-
var INVALID_SERVER_METADATA = "OAUTH_INVALID_SERVER_METADATA";
|
|
1594
|
-
function assertReadableResponse(response) {
|
|
1595
|
-
if (response.bodyUsed) {
|
|
1596
|
-
throw CodedTypeError('"response" body has been used already', ERR_INVALID_ARG_VALUE);
|
|
1597
|
-
}
|
|
1598
|
-
}
|
|
1599
|
-
function checkRsaKeyAlgorithm(key) {
|
|
1600
|
-
const { algorithm } = key;
|
|
1601
|
-
if (typeof algorithm.modulusLength !== "number" || algorithm.modulusLength < 2048) {
|
|
1602
|
-
throw new UnsupportedOperationError(`unsupported ${algorithm.name} modulusLength`, {
|
|
1603
|
-
cause: key
|
|
1604
|
-
});
|
|
1480
|
+
};
|
|
1605
1481
|
}
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1482
|
+
switch (method) {
|
|
1483
|
+
case "initialize":
|
|
1484
|
+
return handleInitialize(params, ctx);
|
|
1485
|
+
case "tools/list":
|
|
1486
|
+
return handleToolsList(ctx);
|
|
1487
|
+
case "tools/call":
|
|
1488
|
+
return handleToolsCall(params, ctx, requestId);
|
|
1489
|
+
case "resources/list":
|
|
1490
|
+
return handleResourcesList();
|
|
1491
|
+
case "resources/templates/list":
|
|
1492
|
+
return handleResourcesTemplatesList();
|
|
1493
|
+
case "prompts/list":
|
|
1494
|
+
return handlePromptsList();
|
|
1495
|
+
case "ping":
|
|
1496
|
+
return handlePing();
|
|
1497
|
+
case "logging/setLevel":
|
|
1498
|
+
return handleLoggingSetLevel(params);
|
|
1616
1499
|
default:
|
|
1617
|
-
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
function keyToSubtle(key) {
|
|
1621
|
-
switch (key.algorithm.name) {
|
|
1622
|
-
case "ECDSA":
|
|
1500
|
+
sharedLogger.debug("mcp_dispatch", { message: "Unknown method", method });
|
|
1623
1501
|
return {
|
|
1624
|
-
|
|
1625
|
-
|
|
1502
|
+
error: {
|
|
1503
|
+
code: JsonRpcErrorCode2.MethodNotFound,
|
|
1504
|
+
message: `Method not found: ${method}`
|
|
1505
|
+
}
|
|
1626
1506
|
};
|
|
1627
|
-
case "RSA-PSS": {
|
|
1628
|
-
checkRsaKeyAlgorithm(key);
|
|
1629
|
-
switch (key.algorithm.hash.name) {
|
|
1630
|
-
case "SHA-256":
|
|
1631
|
-
case "SHA-384":
|
|
1632
|
-
case "SHA-512":
|
|
1633
|
-
return {
|
|
1634
|
-
name: key.algorithm.name,
|
|
1635
|
-
saltLength: parseInt(key.algorithm.hash.name.slice(-3), 10) >> 3
|
|
1636
|
-
};
|
|
1637
|
-
default:
|
|
1638
|
-
throw new UnsupportedOperationError("unsupported RSA-PSS hash name", { cause: key });
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
case "RSASSA-PKCS1-v1_5":
|
|
1642
|
-
checkRsaKeyAlgorithm(key);
|
|
1643
|
-
return key.algorithm.name;
|
|
1644
|
-
case "ML-DSA-44":
|
|
1645
|
-
case "ML-DSA-65":
|
|
1646
|
-
case "ML-DSA-87":
|
|
1647
|
-
case "Ed25519":
|
|
1648
|
-
return key.algorithm.name;
|
|
1649
|
-
}
|
|
1650
|
-
throw new UnsupportedOperationError("unsupported CryptoKey algorithm name", { cause: key });
|
|
1651
|
-
}
|
|
1652
|
-
async function validateJwt(jws, checkAlg, clockSkew2, clockTolerance2, decryptJwt) {
|
|
1653
|
-
let { 0: protectedHeader, 1: payload, length } = jws.split(".");
|
|
1654
|
-
if (length === 5) {
|
|
1655
|
-
if (decryptJwt !== undefined) {
|
|
1656
|
-
jws = await decryptJwt(jws);
|
|
1657
|
-
({ 0: protectedHeader, 1: payload, length } = jws.split("."));
|
|
1658
|
-
} else {
|
|
1659
|
-
throw new UnsupportedOperationError("JWE decryption is not configured", { cause: jws });
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
if (length !== 3) {
|
|
1663
|
-
throw OPE("Invalid JWT", INVALID_RESPONSE, jws);
|
|
1664
|
-
}
|
|
1665
|
-
let header;
|
|
1666
|
-
try {
|
|
1667
|
-
header = JSON.parse(buf(b64u(protectedHeader)));
|
|
1668
|
-
} catch (cause) {
|
|
1669
|
-
throw OPE("failed to parse JWT Header body as base64url encoded JSON", PARSE_ERROR, cause);
|
|
1670
|
-
}
|
|
1671
|
-
if (!isJsonObject(header)) {
|
|
1672
|
-
throw OPE("JWT Header must be a top level object", INVALID_RESPONSE, jws);
|
|
1673
|
-
}
|
|
1674
|
-
checkAlg(header);
|
|
1675
|
-
if (header.crit !== undefined) {
|
|
1676
|
-
throw new UnsupportedOperationError('no JWT "crit" header parameter extensions are supported', {
|
|
1677
|
-
cause: { header }
|
|
1678
|
-
});
|
|
1679
|
-
}
|
|
1680
|
-
let claims;
|
|
1681
|
-
try {
|
|
1682
|
-
claims = JSON.parse(buf(b64u(payload)));
|
|
1683
|
-
} catch (cause) {
|
|
1684
|
-
throw OPE("failed to parse JWT Payload body as base64url encoded JSON", PARSE_ERROR, cause);
|
|
1685
|
-
}
|
|
1686
|
-
if (!isJsonObject(claims)) {
|
|
1687
|
-
throw OPE("JWT Payload must be a top level object", INVALID_RESPONSE, jws);
|
|
1688
|
-
}
|
|
1689
|
-
const now = epochTime() + clockSkew2;
|
|
1690
|
-
if (claims.exp !== undefined) {
|
|
1691
|
-
if (typeof claims.exp !== "number") {
|
|
1692
|
-
throw OPE('unexpected JWT "exp" (expiration time) claim type', INVALID_RESPONSE, { claims });
|
|
1693
|
-
}
|
|
1694
|
-
if (claims.exp <= now - clockTolerance2) {
|
|
1695
|
-
throw OPE('unexpected JWT "exp" (expiration time) claim value, expiration is past current timestamp', JWT_TIMESTAMP_CHECK, { claims, now, tolerance: clockTolerance2, claim: "exp" });
|
|
1696
|
-
}
|
|
1697
1507
|
}
|
|
1698
|
-
if (claims.iat !== undefined) {
|
|
1699
|
-
if (typeof claims.iat !== "number") {
|
|
1700
|
-
throw OPE('unexpected JWT "iat" (issued at) claim type', INVALID_RESPONSE, { claims });
|
|
1701
|
-
}
|
|
1702
|
-
}
|
|
1703
|
-
if (claims.iss !== undefined) {
|
|
1704
|
-
if (typeof claims.iss !== "string") {
|
|
1705
|
-
throw OPE('unexpected JWT "iss" (issuer) claim type', INVALID_RESPONSE, { claims });
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
if (claims.nbf !== undefined) {
|
|
1709
|
-
if (typeof claims.nbf !== "number") {
|
|
1710
|
-
throw OPE('unexpected JWT "nbf" (not before) claim type', INVALID_RESPONSE, { claims });
|
|
1711
|
-
}
|
|
1712
|
-
if (claims.nbf > now + clockTolerance2) {
|
|
1713
|
-
throw OPE('unexpected JWT "nbf" (not before) claim value', JWT_TIMESTAMP_CHECK, {
|
|
1714
|
-
claims,
|
|
1715
|
-
now,
|
|
1716
|
-
tolerance: clockTolerance2,
|
|
1717
|
-
claim: "nbf"
|
|
1718
|
-
});
|
|
1719
|
-
}
|
|
1720
|
-
}
|
|
1721
|
-
if (claims.aud !== undefined) {
|
|
1722
|
-
if (typeof claims.aud !== "string" && !Array.isArray(claims.aud)) {
|
|
1723
|
-
throw OPE('unexpected JWT "aud" (audience) claim type', INVALID_RESPONSE, { claims });
|
|
1724
|
-
}
|
|
1725
|
-
}
|
|
1726
|
-
return { header, claims, jwt: jws };
|
|
1727
1508
|
}
|
|
1728
|
-
function
|
|
1729
|
-
if (
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
expected: client,
|
|
1734
|
-
reason: "client configuration"
|
|
1735
|
-
});
|
|
1736
|
-
}
|
|
1737
|
-
return;
|
|
1738
|
-
}
|
|
1739
|
-
if (Array.isArray(issuer)) {
|
|
1740
|
-
if (!issuer.includes(header.alg)) {
|
|
1741
|
-
throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
|
|
1742
|
-
header,
|
|
1743
|
-
expected: issuer,
|
|
1744
|
-
reason: "authorization server metadata"
|
|
1745
|
-
});
|
|
1746
|
-
}
|
|
1747
|
-
return;
|
|
1748
|
-
}
|
|
1749
|
-
if (fallback !== undefined) {
|
|
1750
|
-
if (typeof fallback === "string" ? header.alg !== fallback : typeof fallback === "function" ? !fallback(header.alg) : !fallback.includes(header.alg)) {
|
|
1751
|
-
throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
|
|
1752
|
-
header,
|
|
1753
|
-
expected: fallback,
|
|
1754
|
-
reason: "default value"
|
|
1755
|
-
});
|
|
1509
|
+
function handleMcpNotification(method, params, ctx) {
|
|
1510
|
+
if (method === "notifications/initialized") {
|
|
1511
|
+
const session = ctx.getSessionState();
|
|
1512
|
+
if (session) {
|
|
1513
|
+
ctx.setSessionState({ ...session, initialized: true });
|
|
1756
1514
|
}
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
}
|
|
1761
|
-
function getURLSearchParameter(parameters, name) {
|
|
1762
|
-
const { 0: value, length } = parameters.getAll(name);
|
|
1763
|
-
if (length > 1) {
|
|
1764
|
-
throw OPE(`"${name}" parameter must be provided only once`, INVALID_RESPONSE);
|
|
1765
|
-
}
|
|
1766
|
-
return value;
|
|
1767
|
-
}
|
|
1768
|
-
var skipStateCheck = Symbol();
|
|
1769
|
-
var expectNoState = Symbol();
|
|
1770
|
-
function validateAuthResponse(as, client, parameters, expectedState) {
|
|
1771
|
-
assertAs(as);
|
|
1772
|
-
assertClient(client);
|
|
1773
|
-
if (parameters instanceof URL) {
|
|
1774
|
-
parameters = parameters.searchParams;
|
|
1775
|
-
}
|
|
1776
|
-
if (!(parameters instanceof URLSearchParams)) {
|
|
1777
|
-
throw CodedTypeError('"parameters" must be an instance of URLSearchParams, or URL', ERR_INVALID_ARG_TYPE);
|
|
1778
|
-
}
|
|
1779
|
-
if (getURLSearchParameter(parameters, "response")) {
|
|
1780
|
-
throw OPE('"parameters" contains a JARM response, use validateJwtAuthResponse() instead of validateAuthResponse()', INVALID_RESPONSE, { parameters });
|
|
1781
|
-
}
|
|
1782
|
-
const iss = getURLSearchParameter(parameters, "iss");
|
|
1783
|
-
const state = getURLSearchParameter(parameters, "state");
|
|
1784
|
-
if (!iss && as.authorization_response_iss_parameter_supported) {
|
|
1785
|
-
throw OPE('response parameter "iss" (issuer) missing', INVALID_RESPONSE, { parameters });
|
|
1786
|
-
}
|
|
1787
|
-
if (iss && iss !== as.issuer) {
|
|
1788
|
-
throw OPE('unexpected "iss" (issuer) response parameter value', INVALID_RESPONSE, {
|
|
1789
|
-
expected: as.issuer,
|
|
1790
|
-
parameters
|
|
1515
|
+
sharedLogger.info("mcp_dispatch", {
|
|
1516
|
+
message: "Client initialized",
|
|
1517
|
+
sessionId: ctx.sessionId
|
|
1791
1518
|
});
|
|
1519
|
+
return true;
|
|
1792
1520
|
}
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1521
|
+
if (method === "notifications/cancelled") {
|
|
1522
|
+
const cancelParams = params;
|
|
1523
|
+
const requestId = cancelParams?.requestId;
|
|
1524
|
+
if (requestId !== undefined && ctx.cancellationRegistry) {
|
|
1525
|
+
const controller = ctx.cancellationRegistry.get(requestId);
|
|
1526
|
+
if (controller) {
|
|
1527
|
+
sharedLogger.info("mcp_dispatch", {
|
|
1528
|
+
message: "Cancelling request",
|
|
1529
|
+
requestId,
|
|
1530
|
+
reason: cancelParams?.reason,
|
|
1531
|
+
sessionId: ctx.sessionId
|
|
1800
1532
|
});
|
|
1533
|
+
controller.abort(cancelParams?.reason ?? "Client requested cancellation");
|
|
1534
|
+
return true;
|
|
1801
1535
|
}
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
}
|
|
1810
|
-
}
|
|
1811
|
-
const error = getURLSearchParameter(parameters, "error");
|
|
1812
|
-
if (error) {
|
|
1813
|
-
throw new AuthorizationResponseError("authorization response from the server is an error", {
|
|
1814
|
-
cause: parameters
|
|
1815
|
-
});
|
|
1816
|
-
}
|
|
1817
|
-
const id_token = getURLSearchParameter(parameters, "id_token");
|
|
1818
|
-
const token = getURLSearchParameter(parameters, "token");
|
|
1819
|
-
if (id_token !== undefined || token !== undefined) {
|
|
1820
|
-
throw new UnsupportedOperationError("implicit and hybrid flows are not supported");
|
|
1821
|
-
}
|
|
1822
|
-
return brand(new URLSearchParams(parameters));
|
|
1823
|
-
}
|
|
1824
|
-
async function getResponseJsonBody(response, check = assertApplicationJson) {
|
|
1825
|
-
let json;
|
|
1826
|
-
try {
|
|
1827
|
-
json = await response.json();
|
|
1828
|
-
} catch (cause) {
|
|
1829
|
-
check(response);
|
|
1830
|
-
throw OPE('failed to parse "response" body as JSON', PARSE_ERROR, cause);
|
|
1831
|
-
}
|
|
1832
|
-
if (!isJsonObject(json)) {
|
|
1833
|
-
throw OPE('"response" body must be a top level object', INVALID_RESPONSE, { body: json });
|
|
1536
|
+
sharedLogger.debug("mcp_dispatch", {
|
|
1537
|
+
message: "Cancellation request for unknown requestId",
|
|
1538
|
+
requestId,
|
|
1539
|
+
sessionId: ctx.sessionId
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
return true;
|
|
1834
1543
|
}
|
|
1835
|
-
|
|
1544
|
+
sharedLogger.debug("mcp_dispatch", {
|
|
1545
|
+
message: "Unhandled notification",
|
|
1546
|
+
method,
|
|
1547
|
+
sessionId: ctx.sessionId
|
|
1548
|
+
});
|
|
1549
|
+
return false;
|
|
1836
1550
|
}
|
|
1837
|
-
var _nodiscoverycheck = Symbol();
|
|
1838
|
-
var _expectedIssuer = Symbol();
|
|
1839
1551
|
|
|
1840
1552
|
// src/shared/oauth/refresh.ts
|
|
1553
|
+
import * as oauth from "oauth4webapi";
|
|
1841
1554
|
function buildProviderRefreshConfig(config) {
|
|
1842
1555
|
if (!config.PROVIDER_CLIENT_ID || !config.PROVIDER_CLIENT_SECRET || !config.PROVIDER_ACCOUNTS_URL) {
|
|
1843
1556
|
return;
|
|
@@ -1867,9 +1580,9 @@ async function refreshProviderToken(refreshToken, config) {
|
|
|
1867
1580
|
tokenUrl: authServer.token_endpoint
|
|
1868
1581
|
});
|
|
1869
1582
|
try {
|
|
1870
|
-
const clientAuth = ClientSecretPost(config.clientSecret);
|
|
1871
|
-
const response = await refreshTokenGrantRequest(authServer, client, clientAuth, refreshToken);
|
|
1872
|
-
const result = await processRefreshTokenResponse(authServer, client, response);
|
|
1583
|
+
const clientAuth = oauth.ClientSecretPost(config.clientSecret);
|
|
1584
|
+
const response = await oauth.refreshTokenGrantRequest(authServer, client, clientAuth, refreshToken);
|
|
1585
|
+
const result = await oauth.processRefreshTokenResponse(authServer, client, response);
|
|
1873
1586
|
const accessToken = result.access_token;
|
|
1874
1587
|
if (!accessToken) {
|
|
1875
1588
|
return {
|
|
@@ -1891,7 +1604,7 @@ async function refreshProviderToken(refreshToken, config) {
|
|
|
1891
1604
|
}
|
|
1892
1605
|
};
|
|
1893
1606
|
} catch (error) {
|
|
1894
|
-
if (error instanceof ResponseBodyError) {
|
|
1607
|
+
if (error instanceof oauth.ResponseBodyError) {
|
|
1895
1608
|
sharedLogger.error("oauth_refresh", {
|
|
1896
1609
|
message: "Provider refresh failed",
|
|
1897
1610
|
error: error.error,
|
|
@@ -2212,16 +1925,17 @@ function assertSsrfSafe(urlString, options) {
|
|
|
2212
1925
|
}
|
|
2213
1926
|
|
|
2214
1927
|
// src/shared/oauth/cimd.ts
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
1928
|
+
import { z as z4 } from "zod";
|
|
1929
|
+
var ClientMetadataSchema = z4.object({
|
|
1930
|
+
client_id: z4.string().url(),
|
|
1931
|
+
client_name: z4.string().optional(),
|
|
1932
|
+
redirect_uris: z4.array(z4.string().url()),
|
|
1933
|
+
client_uri: z4.string().url().optional(),
|
|
1934
|
+
logo_uri: z4.string().url().optional(),
|
|
1935
|
+
tos_uri: z4.string().url().optional(),
|
|
1936
|
+
policy_uri: z4.string().url().optional(),
|
|
1937
|
+
jwks_uri: z4.string().url().optional(),
|
|
1938
|
+
software_statement: z4.string().optional()
|
|
2225
1939
|
});
|
|
2226
1940
|
function isClientIdUrl(clientId) {
|
|
2227
1941
|
if (!clientId.startsWith("https://")) {
|
|
@@ -2374,6 +2088,7 @@ function validateRedirectUri(metadata, redirectUri) {
|
|
|
2374
2088
|
}
|
|
2375
2089
|
|
|
2376
2090
|
// src/shared/oauth/flow.ts
|
|
2091
|
+
import * as oauth2 from "oauth4webapi";
|
|
2377
2092
|
function generateOpaqueToken(bytes = 32) {
|
|
2378
2093
|
const array = new Uint8Array(bytes);
|
|
2379
2094
|
crypto.getRandomValues(array);
|
|
@@ -2588,18 +2303,18 @@ async function handleProviderCallback(input, store, providerConfig, oauthConfig,
|
|
|
2588
2303
|
});
|
|
2589
2304
|
const rawParams = new URLSearchParams;
|
|
2590
2305
|
rawParams.set("code", input.providerCode);
|
|
2591
|
-
const callbackParams = validateAuthResponse(authServer, client, rawParams, skipStateCheck);
|
|
2306
|
+
const callbackParams = oauth2.validateAuthResponse(authServer, client, rawParams, oauth2.skipStateCheck);
|
|
2592
2307
|
if (!providerConfig.clientSecret) {
|
|
2593
2308
|
throw new Error("Server misconfigured: PROVIDER_CLIENT_SECRET is not set");
|
|
2594
2309
|
}
|
|
2595
|
-
const clientAuth = ClientSecretPost(providerConfig.clientSecret);
|
|
2310
|
+
const clientAuth = oauth2.ClientSecretPost(providerConfig.clientSecret);
|
|
2596
2311
|
try {
|
|
2597
|
-
const response = await authorizationCodeGrantRequest(authServer, client, clientAuth, callbackParams, redirectUri, nopkce);
|
|
2312
|
+
const response = await oauth2.authorizationCodeGrantRequest(authServer, client, clientAuth, callbackParams, redirectUri, oauth2.nopkce);
|
|
2598
2313
|
sharedLogger.debug("oauth_callback", {
|
|
2599
2314
|
message: "Token response received",
|
|
2600
2315
|
status: response.status
|
|
2601
2316
|
});
|
|
2602
|
-
const result = await processAuthorizationCodeResponse(authServer, client, response);
|
|
2317
|
+
const result = await oauth2.processAuthorizationCodeResponse(authServer, client, response);
|
|
2603
2318
|
const accessToken = result.access_token;
|
|
2604
2319
|
if (!accessToken) {
|
|
2605
2320
|
sharedLogger.error("oauth_callback", {
|
|
@@ -2640,7 +2355,7 @@ async function handleProviderCallback(input, store, providerConfig, oauthConfig,
|
|
|
2640
2355
|
providerTokens
|
|
2641
2356
|
};
|
|
2642
2357
|
} catch (error) {
|
|
2643
|
-
if (error instanceof ResponseBodyError) {
|
|
2358
|
+
if (error instanceof oauth2.ResponseBodyError) {
|
|
2644
2359
|
sharedLogger.error("oauth_callback", {
|
|
2645
2360
|
message: "Provider token error",
|
|
2646
2361
|
error: error.error,
|
|
@@ -2733,7 +2448,7 @@ async function handleToken(input, store, providerConfig) {
|
|
|
2733
2448
|
throw new Error("invalid_grant");
|
|
2734
2449
|
}
|
|
2735
2450
|
const expected = txn.codeChallenge;
|
|
2736
|
-
const actual = await calculatePKCECodeChallenge(input.codeVerifier);
|
|
2451
|
+
const actual = await oauth2.calculatePKCECodeChallenge(input.codeVerifier);
|
|
2737
2452
|
if (expected !== actual) {
|
|
2738
2453
|
sharedLogger.error("oauth_token", {
|
|
2739
2454
|
message: "PKCE verification failed"
|
|
@@ -2749,7 +2464,7 @@ async function handleToken(input, store, providerConfig) {
|
|
|
2749
2464
|
if (txn.provider?.access_token) {
|
|
2750
2465
|
await store.storeRsMapping(rsAccess, txn.provider, rsRefresh);
|
|
2751
2466
|
sharedLogger.info("oauth_token", {
|
|
2752
|
-
message: "RS
|
|
2467
|
+
message: "RS→Provider mapping stored"
|
|
2753
2468
|
});
|
|
2754
2469
|
} else {
|
|
2755
2470
|
sharedLogger.warning("oauth_token", {
|
|
@@ -2867,51 +2582,8 @@ function buildFlowOptions(url, config, overrides = {}) {
|
|
|
2867
2582
|
};
|
|
2868
2583
|
}
|
|
2869
2584
|
|
|
2870
|
-
//
|
|
2871
|
-
|
|
2872
|
-
let a, s, n = new URL(e2.url), c = e2.query = { __proto__: null };
|
|
2873
|
-
for (let [e3, t3] of n.searchParams)
|
|
2874
|
-
c[e3] = c[e3] ? [].concat(c[e3], t3) : t3;
|
|
2875
|
-
e:
|
|
2876
|
-
try {
|
|
2877
|
-
for (let t3 of o.before || [])
|
|
2878
|
-
if ((a = await t3(e2.proxy ?? e2, ...r)) != null)
|
|
2879
|
-
break e;
|
|
2880
|
-
t:
|
|
2881
|
-
for (let [o2, c2, l, i] of t2)
|
|
2882
|
-
if ((o2 == e2.method || o2 == "ALL") && (s = n.pathname.match(c2))) {
|
|
2883
|
-
e2.params = s.groups || {}, e2.route = i;
|
|
2884
|
-
for (let t3 of l)
|
|
2885
|
-
if ((a = await t3(e2.proxy ?? e2, ...r)) != null)
|
|
2886
|
-
break t;
|
|
2887
|
-
}
|
|
2888
|
-
} catch (t3) {
|
|
2889
|
-
if (!o.catch)
|
|
2890
|
-
throw t3;
|
|
2891
|
-
a = await o.catch(t3, e2.proxy ?? e2, ...r);
|
|
2892
|
-
}
|
|
2893
|
-
try {
|
|
2894
|
-
for (let t3 of o.finally || [])
|
|
2895
|
-
a = await t3(a, e2.proxy ?? e2, ...r) ?? a;
|
|
2896
|
-
} catch (t3) {
|
|
2897
|
-
if (!o.catch)
|
|
2898
|
-
throw t3;
|
|
2899
|
-
a = await o.catch(t3, e2.proxy ?? e2, ...r);
|
|
2900
|
-
}
|
|
2901
|
-
return a;
|
|
2902
|
-
} });
|
|
2903
|
-
var o = (e = "text/plain; charset=utf-8", t2) => (o2, r = {}) => {
|
|
2904
|
-
if (o2 === undefined || o2 instanceof Response)
|
|
2905
|
-
return o2;
|
|
2906
|
-
const a = new Response(t2?.(o2) ?? o2, r.url ? undefined : r);
|
|
2907
|
-
return a.headers.set("content-type", e), a;
|
|
2908
|
-
};
|
|
2909
|
-
var r = o("application/json; charset=utf-8", JSON.stringify);
|
|
2910
|
-
var p = o("text/plain; charset=utf-8", String);
|
|
2911
|
-
var f = o("text/html");
|
|
2912
|
-
var u = o("image/jpeg");
|
|
2913
|
-
var h = o("image/png");
|
|
2914
|
-
var g = o("image/webp");
|
|
2585
|
+
// src/adapters/http-worker/index.ts
|
|
2586
|
+
import { Router } from "itty-router";
|
|
2915
2587
|
|
|
2916
2588
|
// src/adapters/http-worker/security.ts
|
|
2917
2589
|
async function checkAuthAndChallenge(request, store, config, sid) {
|
|
@@ -3421,16 +3093,16 @@ function initializeWorkerStorage(env, config) {
|
|
|
3421
3093
|
sharedTokenStore = new MemoryTokenStore;
|
|
3422
3094
|
sharedSessionStore = new MemorySessionStore;
|
|
3423
3095
|
}
|
|
3424
|
-
let
|
|
3425
|
-
let
|
|
3096
|
+
let encrypt2;
|
|
3097
|
+
let decrypt2;
|
|
3426
3098
|
if (env.RS_TOKENS_ENC_KEY) {
|
|
3427
3099
|
const encryptor = createEncryptor(env.RS_TOKENS_ENC_KEY);
|
|
3428
|
-
|
|
3429
|
-
|
|
3100
|
+
encrypt2 = encryptor.encrypt;
|
|
3101
|
+
decrypt2 = encryptor.decrypt;
|
|
3430
3102
|
sharedLogger.debug("worker_storage", { message: "KV encryption enabled" });
|
|
3431
3103
|
} else {
|
|
3432
|
-
|
|
3433
|
-
|
|
3104
|
+
encrypt2 = async (s) => s;
|
|
3105
|
+
decrypt2 = async (s) => s;
|
|
3434
3106
|
if (config.NODE_ENV === "production") {
|
|
3435
3107
|
sharedLogger.warning("worker_storage", {
|
|
3436
3108
|
message: "RS_TOKENS_ENC_KEY not set! KV data is unencrypted."
|
|
@@ -3438,13 +3110,13 @@ function initializeWorkerStorage(env, config) {
|
|
|
3438
3110
|
}
|
|
3439
3111
|
}
|
|
3440
3112
|
const tokenStore = new KvTokenStore(kvNamespace, {
|
|
3441
|
-
encrypt,
|
|
3442
|
-
decrypt,
|
|
3113
|
+
encrypt: encrypt2,
|
|
3114
|
+
decrypt: decrypt2,
|
|
3443
3115
|
fallback: sharedTokenStore
|
|
3444
3116
|
});
|
|
3445
3117
|
const sessionStore = new KvSessionStore(kvNamespace, {
|
|
3446
|
-
encrypt,
|
|
3447
|
-
decrypt,
|
|
3118
|
+
encrypt: encrypt2,
|
|
3119
|
+
decrypt: decrypt2,
|
|
3448
3120
|
fallback: sharedSessionStore
|
|
3449
3121
|
});
|
|
3450
3122
|
initializeStorage(tokenStore, sessionStore);
|
|
@@ -3452,7 +3124,7 @@ function initializeWorkerStorage(env, config) {
|
|
|
3452
3124
|
}
|
|
3453
3125
|
var MCP_ENDPOINT_PATH = "/mcp";
|
|
3454
3126
|
function createWorkerRouter(ctx) {
|
|
3455
|
-
const router =
|
|
3127
|
+
const router = Router();
|
|
3456
3128
|
const { tokenStore, sessionStore, config, tools } = ctx;
|
|
3457
3129
|
router.options("*", () => corsPreflightResponse());
|
|
3458
3130
|
attachDiscoveryRoutes(router, config);
|
|
@@ -3467,12 +3139,12 @@ function createWorkerRouter(ctx) {
|
|
|
3467
3139
|
return router;
|
|
3468
3140
|
}
|
|
3469
3141
|
function shimProcessEnv(env) {
|
|
3470
|
-
const
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
...
|
|
3142
|
+
const g = globalThis;
|
|
3143
|
+
g.process = g.process || {};
|
|
3144
|
+
g.process.env = {
|
|
3145
|
+
...g.process.env ?? {},
|
|
3474
3146
|
...env
|
|
3475
3147
|
};
|
|
3476
3148
|
}
|
|
3477
3149
|
|
|
3478
|
-
export { withCors, corsPreflightResponse, buildCorsHeaders, KvTokenStore, KvSessionStore, initializeStorage, getTokenStore, getSessionStore, JsonRpcErrorCode, LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, getLogLevel, dispatchMcpMethod, handleMcpNotification, buildProviderRefreshConfig, refreshProviderToken, isTokenExpiredOrExpiring, ensureFreshToken, validateOrigin, validateProtocolVersion, buildUnauthorizedChallenge, buildAuthorizationServerMetadata, buildProtectedResourceMetadata, createDiscoveryHandlers, workerDiscoveryStrategy, nodeDiscoveryStrategy, checkSsrfSafe, isSsrfSafe, assertSsrfSafe, ClientMetadataSchema, isClientIdUrl, fetchClientMetadata, validateRedirectUri, generateOpaqueToken, handleAuthorize, handleProviderCallback, handleToken, handleRegister, handleRevoke, parseAuthorizeInput, parseCallbackInput, parseTokenInput, buildTokenInput, buildProviderConfig, buildOAuthConfig, buildFlowOptions, initializeWorkerStorage, createWorkerRouter, shimProcessEnv };
|
|
3150
|
+
export { base64Encode, base64Decode, base64UrlEncode, base64UrlDecode, base64UrlEncodeString, base64UrlDecodeString, base64UrlEncodeJson, base64UrlDecodeJson, encrypt, decrypt, generateKey, createEncryptor, withCors, corsPreflightResponse, buildCorsHeaders, MAX_SESSIONS_PER_API_KEY, MemoryTokenStore, MemorySessionStore, KvTokenStore, KvSessionStore, initializeStorage, getTokenStore, getSessionStore, sharedLogger, logger, JsonRpcErrorCode, CancellationError, CancellationToken, createCancellationToken, withCancellation, toProviderInfo, toProviderTokens, assertProviderToken, defineTool, echoInputSchema, echoTool, healthInputSchema, healthTool, sharedTools, getSharedTool, getSharedToolNames, executeSharedTool, registerTools, LATEST_PROTOCOL_VERSION, SUPPORTED_PROTOCOL_VERSIONS, getLogLevel, dispatchMcpMethod, handleMcpNotification, buildProviderRefreshConfig, refreshProviderToken, isTokenExpiredOrExpiring, ensureFreshToken, validateOrigin, validateProtocolVersion, buildUnauthorizedChallenge, buildAuthorizationServerMetadata, buildProtectedResourceMetadata, createDiscoveryHandlers, workerDiscoveryStrategy, nodeDiscoveryStrategy, checkSsrfSafe, isSsrfSafe, assertSsrfSafe, ClientMetadataSchema, isClientIdUrl, fetchClientMetadata, validateRedirectUri, generateOpaqueToken, handleAuthorize, handleProviderCallback, handleToken, handleRegister, handleRevoke, parseAuthorizeInput, parseCallbackInput, parseTokenInput, buildTokenInput, buildProviderConfig, buildOAuthConfig, buildFlowOptions, initializeWorkerStorage, createWorkerRouter, shimProcessEnv };
|