modality-mcp-kit 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,4 +3,4 @@ export { setupAITools, ModalityFastMCP } from "./util_mcp_tools_converter";
3
3
  export type { AITools, AITool, FastMCPTool, } from "./schemas/schemas_tool_config";
4
4
  export type { FastMCPCompatible, BasePrompt, } from "./util_mcp_tools_converter";
5
5
  export { FastHonoMcp } from "./FastHonoMcp";
6
- export { mcpProxyHandler, mcpProxyListHandler, mcpProxyCacheHandler, type McpProxyConfig, } from "./util_mcp_proxy";
6
+ export { mcpProxyHandler, mcpProxyListHandler, mcpProxyCacheHandler, type McpProxyConfig, type OAuthAllowAccessFn, } from "./util_mcp_proxy";
@@ -12,12 +12,16 @@ interface MCPServerConfig {
12
12
  description?: string;
13
13
  }
14
14
  export type McpProxyConfig = Record<string, MCPServerConfig>;
15
+ export type OAuthAllowAccessFn = (serverUrl: string, mcpName: string) => Promise<{
16
+ status: string;
17
+ message?: string;
18
+ }>;
15
19
  /**
16
20
  * Hono handler for MCP proxy
17
21
  * @param c - Hono context with mcpName param
18
22
  * @returns Response proxied from MCP server
19
23
  */
20
- export declare const mcpProxyHandler: (MCP_SERVERS: McpProxyConfig) => (c: any) => Promise<any>;
24
+ export declare const mcpProxyHandler: (MCP_SERVERS: McpProxyConfig, oauthAllowAccess?: OAuthAllowAccessFn) => (c: any) => Promise<any>;
21
25
  /**
22
26
  * Hono handler for listing available MCP servers
23
27
  * @param c - Hono context
@@ -8,6 +8,10 @@
8
8
  * Example: /proxy/figma → http://127.0.0.1:3845/mcp
9
9
  */
10
10
  import { SimpleCache } from "modality-kit";
11
+ import { createHash } from "node:crypto";
12
+ import { readFileSync, writeFileSync } from "node:fs";
13
+ import { homedir } from "node:os";
14
+ import { join } from "node:path";
11
15
  // ============================================
12
16
  // CACHE CONFIGURATION
13
17
  // ============================================
@@ -195,14 +199,39 @@ function getAnyCacheForMethod(cache, method) {
195
199
  }
196
200
  }
197
201
  // ============================================
198
- // HONO HANDLERS
202
+ // OAUTH TOKEN HELPERS
199
203
  // ============================================
204
+ function getOAuthCachePath(serverUrl) {
205
+ const key = createHash("sha1").update(serverUrl).digest("hex").slice(0, 12);
206
+ return join(homedir(), ".cache", "counter", `${key}.json`);
207
+ }
208
+ function getStoredOAuthToken(serverUrl) {
209
+ try {
210
+ const data = JSON.parse(readFileSync(getOAuthCachePath(serverUrl), "utf8"));
211
+ return data.tokens?.access_token ?? null;
212
+ }
213
+ catch {
214
+ return null;
215
+ }
216
+ }
217
+ function clearStoredOAuthTokens(serverUrl) {
218
+ const cachePath = getOAuthCachePath(serverUrl);
219
+ try {
220
+ const data = JSON.parse(readFileSync(cachePath, "utf8"));
221
+ delete data.tokens;
222
+ writeFileSync(cachePath, JSON.stringify(data, null, 2));
223
+ return true;
224
+ }
225
+ catch {
226
+ return false;
227
+ }
228
+ }
200
229
  /**
201
230
  * Hono handler for MCP proxy
202
231
  * @param c - Hono context with mcpName param
203
232
  * @returns Response proxied from MCP server
204
233
  */
205
- export const mcpProxyHandler = (MCP_SERVERS) => async (c) => {
234
+ export const mcpProxyHandler = (MCP_SERVERS, oauthAllowAccess) => async (c) => {
206
235
  const mcpName = c.req.param("mcpName");
207
236
  if (!mcpName) {
208
237
  return c.json({
@@ -210,6 +239,57 @@ export const mcpProxyHandler = (MCP_SERVERS) => async (c) => {
210
239
  availableServers: Object.keys(MCP_SERVERS),
211
240
  }, 400);
212
241
  }
242
+ // Handle /_allow sub-route (OAuth access flow)
243
+ if (c.req.path.endsWith("/_allow")) {
244
+ const serverConfig = MCP_SERVERS[mcpName];
245
+ if (!serverConfig) {
246
+ return c.json({ error: "MCP server not found", availableServers: Object.keys(MCP_SERVERS) }, 404);
247
+ }
248
+ if (!oauthAllowAccess) {
249
+ return c.json({ error: "OAuth not configured for this proxy" }, 501);
250
+ }
251
+ try {
252
+ const result = await oauthAllowAccess(serverConfig.url, mcpName);
253
+ return c.json(result);
254
+ }
255
+ catch (err) {
256
+ return c.json({ error: "OAuth failed", message: err.message }, 500);
257
+ }
258
+ }
259
+ // Handle /_clear-auth sub-route
260
+ if (c.req.path.endsWith("/_clear-auth")) {
261
+ const serverConfig = MCP_SERVERS[mcpName];
262
+ if (!serverConfig) {
263
+ return c.json({ error: "MCP server not found", availableServers: Object.keys(MCP_SERVERS) }, 404);
264
+ }
265
+ const cleared = clearStoredOAuthTokens(serverConfig.url);
266
+ return c.json({
267
+ status: cleared ? "cleared" : "no_cache",
268
+ mcpName,
269
+ message: cleared
270
+ ? "OAuth tokens cleared"
271
+ : "No cached OAuth state found",
272
+ });
273
+ }
274
+ // Handle /_cache sub-routes (matched via app.use prefix routing)
275
+ const cachePathMatch = c.req.path.match(/\/_cache(?:\/(.+))?$/);
276
+ if (cachePathMatch) {
277
+ const cache = serverCaches.get(mcpName);
278
+ if (!cache) {
279
+ return c.json({ error: "No cache for this MCP server", keys: [] });
280
+ }
281
+ const cacheKey = cachePathMatch[1]
282
+ ? decodeURIComponent(cachePathMatch[1])
283
+ : undefined;
284
+ if (cacheKey) {
285
+ const entry = cache.get(cacheKey, true);
286
+ if (!entry) {
287
+ return c.json({ error: "Cache key not found", cacheKey }, 404);
288
+ }
289
+ return c.json({ cacheKey, value: entry });
290
+ }
291
+ return c.json({ keys: cache.keys() });
292
+ }
213
293
  // Handle CORS preflight
214
294
  if (c.req.method === "OPTIONS") {
215
295
  return new Response(null, {
@@ -253,10 +333,18 @@ export const mcpProxyHandler = (MCP_SERVERS) => async (c) => {
253
333
  upstreamHeaders[header] = value;
254
334
  }
255
335
  }
336
+ // Auto-inject stored OAuth token if no Authorization header present
337
+ if (!upstreamHeaders.authorization) {
338
+ const storedToken = getStoredOAuthToken(serverConfig.url);
339
+ if (storedToken) {
340
+ upstreamHeaders.authorization = `Bearer ${storedToken}`;
341
+ }
342
+ }
256
343
  try {
257
344
  const upstreamResponse = await fetch(serverConfig.url, {
258
345
  method: "GET",
259
346
  headers: upstreamHeaders,
347
+ signal: c.req.raw.signal,
260
348
  });
261
349
  // Forward response headers
262
350
  const responseHeaders = new Headers();
@@ -289,11 +377,22 @@ export const mcpProxyHandler = (MCP_SERVERS) => async (c) => {
289
377
  }
290
378
  // Regular GET - return server info
291
379
  const cache = getServerCache(mcpName);
380
+ const basePath = `/proxy/${mcpName}`;
292
381
  return c.json({
293
382
  mcpName,
294
- ...serverConfig,
295
- cacheKeys: cache.keys(),
296
- cacheSize: cache.keys().length,
383
+ upstream: serverConfig.url,
384
+ description: serverConfig.description,
385
+ endpoints: {
386
+ sse: `${basePath} (GET, Accept: text/event-stream)`,
387
+ rpc: `${basePath} (POST)`,
388
+ allow: `${basePath}/_allow`,
389
+ clearAuth: `${basePath}/_clear-auth`,
390
+ cache: `${basePath}/_cache`,
391
+ },
392
+ cache: {
393
+ keys: cache.keys(),
394
+ size: cache.keys().length,
395
+ },
297
396
  });
298
397
  }
299
398
  // POST request - proxy to MCP server with streaming support
@@ -365,6 +464,14 @@ export const mcpProxyHandler = (MCP_SERVERS) => async (c) => {
365
464
  console.log(`[MCP-PROXY] Forwarding header: ${header}=${value.substring(0, 50)}`);
366
465
  }
367
466
  }
467
+ // Auto-inject stored OAuth token if no Authorization header present
468
+ if (!upstreamHeaders.authorization) {
469
+ const storedToken = getStoredOAuthToken(serverConfig.url);
470
+ if (storedToken) {
471
+ upstreamHeaders.authorization = `Bearer ${storedToken}`;
472
+ console.log(`[MCP-PROXY] Using stored OAuth token for ${mcpName}`);
473
+ }
474
+ }
368
475
  console.log(`[MCP-PROXY] Upstream headers:`, Object.keys(upstreamHeaders));
369
476
  const upstreamResponse = await fetch(serverConfig.url, {
370
477
  method: "POST",
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.5.0",
2
+ "version": "1.6.0",
3
3
  "name": "modality-mcp-kit",
4
4
  "repository": {
5
5
  "type": "git",