modality-mcp-kit 1.6.0 → 1.6.2

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.
@@ -226,6 +226,93 @@ function clearStoredOAuthTokens(serverUrl) {
226
226
  return false;
227
227
  }
228
228
  }
229
+ // ============================================
230
+ // TOOL PREFETCH HELPERS
231
+ // ============================================
232
+ function parseToolsFromBody(body) {
233
+ try {
234
+ const parsed = JSON.parse(body);
235
+ if (parsed?.result?.tools)
236
+ return parsed.result.tools;
237
+ }
238
+ catch {
239
+ // Try SSE format
240
+ const lines = body.split("\n");
241
+ for (const line of lines) {
242
+ if (line.startsWith("data: ")) {
243
+ try {
244
+ const parsed = JSON.parse(line.substring(6));
245
+ if (parsed?.result?.tools)
246
+ return parsed.result.tools;
247
+ }
248
+ catch { }
249
+ }
250
+ }
251
+ }
252
+ return null;
253
+ }
254
+ async function prefetchAndCacheTools(mcpName, serverUrl, cache, storedToken) {
255
+ const cacheKey = "tools/list";
256
+ const cached = cache.get(cacheKey);
257
+ if (cached) {
258
+ const dataLine = cached.match(/^data: (.+)$/m)?.[1] ?? cached;
259
+ return { tools: parseToolsFromBody(dataLine), fromCache: true };
260
+ }
261
+ try {
262
+ const headers = {
263
+ "Content-Type": "application/json",
264
+ Accept: "application/json, text/event-stream",
265
+ };
266
+ if (storedToken) {
267
+ headers.authorization = `Bearer ${storedToken}`;
268
+ }
269
+ // Step 1: initialize session (required by MCP protocol before tools/list)
270
+ const initResponse = await fetch(serverUrl, {
271
+ method: "POST",
272
+ headers,
273
+ body: JSON.stringify({
274
+ jsonrpc: "2.0",
275
+ id: 1,
276
+ method: "initialize",
277
+ params: {
278
+ protocolVersion: "2024-11-05",
279
+ capabilities: {},
280
+ clientInfo: { name: "mcp-proxy", version: "1.0" },
281
+ },
282
+ }),
283
+ });
284
+ if (!initResponse.ok) {
285
+ console.warn(`[MCP-PROXY] Initialize failed for ${mcpName} — status: ${initResponse.status}`);
286
+ return { tools: null, fromCache: false };
287
+ }
288
+ const sessionId = initResponse.headers.get("mcp-session-id");
289
+ if (sessionId) {
290
+ headers["mcp-session-id"] = sessionId;
291
+ }
292
+ // Step 2: fetch tools/list
293
+ const response = await fetch(serverUrl, {
294
+ method: "POST",
295
+ headers,
296
+ body: JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} }),
297
+ });
298
+ const body = await response.text();
299
+ const tools = parseToolsFromBody(body);
300
+ if (response.ok && tools !== null) {
301
+ const ttl = METHOD_TTL_MS["tools/list"];
302
+ const cacheValue = body.includes("event:") ? body : `event: message\ndata: ${body}\n\n`;
303
+ cache.set(cacheKey, cacheValue, ttl);
304
+ console.log(`[MCP-PROXY] Prefetched and cached tools/list for ${mcpName}`);
305
+ }
306
+ else {
307
+ console.warn(`[MCP-PROXY] Skipping cache for ${mcpName} tools/list — status: ${response.status}, tools parsed: ${tools !== null}`);
308
+ }
309
+ return { tools, fromCache: false };
310
+ }
311
+ catch (e) {
312
+ console.error(`[MCP-PROXY] Failed to prefetch tools for ${mcpName}:`, e);
313
+ return { tools: null, fromCache: false };
314
+ }
315
+ }
229
316
  /**
230
317
  * Hono handler for MCP proxy
231
318
  * @param c - Hono context with mcpName param
@@ -271,6 +358,26 @@ export const mcpProxyHandler = (MCP_SERVERS, oauthAllowAccess) => async (c) => {
271
358
  : "No cached OAuth state found",
272
359
  });
273
360
  }
361
+ // Handle /_tools sub-route — prefetch and return cached tool list
362
+ if (c.req.path.endsWith("/_tools")) {
363
+ const serverConfig = MCP_SERVERS[mcpName];
364
+ if (!serverConfig) {
365
+ return c.json({ error: "MCP server not found", availableServers: Object.keys(MCP_SERVERS) }, 404);
366
+ }
367
+ const cache = getServerCache(mcpName);
368
+ const storedToken = getStoredOAuthToken(serverConfig.url);
369
+ const { tools, fromCache } = await prefetchAndCacheTools(mcpName, serverConfig.url, cache, storedToken);
370
+ const namesOnly = c.req.query("names") !== undefined;
371
+ if (namesOnly) {
372
+ return c.json(tools?.map((t) => t.name) ?? []);
373
+ }
374
+ return c.json({
375
+ mcpName,
376
+ fromCache,
377
+ count: tools?.length ?? 0,
378
+ tools: tools ?? [],
379
+ });
380
+ }
274
381
  // Handle /_cache sub-routes (matched via app.use prefix routing)
275
382
  const cachePathMatch = c.req.path.match(/\/_cache(?:\/(.+))?$/);
276
383
  if (cachePathMatch) {
@@ -385,6 +492,7 @@ export const mcpProxyHandler = (MCP_SERVERS, oauthAllowAccess) => async (c) => {
385
492
  endpoints: {
386
493
  sse: `${basePath} (GET, Accept: text/event-stream)`,
387
494
  rpc: `${basePath} (POST)`,
495
+ tools: `${basePath}/_tools`,
388
496
  allow: `${basePath}/_allow`,
389
497
  clearAuth: `${basePath}/_clear-auth`,
390
498
  cache: `${basePath}/_cache`,
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.6.0",
2
+ "version": "1.6.2",
3
3
  "name": "modality-mcp-kit",
4
4
  "repository": {
5
5
  "type": "git",