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.
package/dist/types/index.d.ts
CHANGED
|
@@ -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
|
package/dist/util_mcp_proxy.js
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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