@mcp-use/inspector 20.0.0-beta.15 → 20.0.0-beta.17
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 +25 -58
- package/dist/app/inspector.css +2 -0
- package/dist/{cdn → app}/inspector.js +8249 -7972
- package/dist/cli.js +292 -286
- package/dist/client/auth/manufact-auth.d.ts +1 -1
- package/dist/client/auth/manufact-auth.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/server/{chunk-XCNS5CIW.js → chunk-2K7OKCVV.js} +67 -67
- package/dist/server/{chunk-UJLXHNQP.js → chunk-EKD2XITW.js} +3 -2
- package/dist/server/{chunk-WNQGHIZ3.js → chunk-EPPZCSJQ.js} +23 -77
- package/dist/server/chunk-JWDVSQED.js +156 -0
- package/dist/server/{chunk-M5TZA42I.js → chunk-PZF2PUDE.js} +15 -11
- package/dist/server/{chunk-XKCUJ6ZJ.js → chunk-RFJAZOK7.js} +27 -8
- package/dist/server/{chunk-OWR2BIBW.js → chunk-RKFQG67Z.js} +1 -3
- package/dist/server/{chunk-K2F2AJOI.js → chunk-V5A5XLFJ.js} +148 -5
- package/dist/server/cli.js +11 -16
- package/dist/server/create-dev-api-app.d.ts.map +1 -1
- package/dist/server/create-dev-api-app.js +4 -4
- package/dist/server/favicon-links.d.ts +2 -2
- package/dist/server/favicon-links.d.ts.map +1 -1
- package/dist/server/index.d.ts +2 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +8 -13
- package/dist/server/{cdn-shell.d.ts → inspector-shell.d.ts} +7 -5
- package/dist/server/inspector-shell.d.ts.map +1 -0
- package/dist/server/inspector-shell.js +10 -0
- package/dist/server/middleware.d.ts +52 -27
- package/dist/server/middleware.d.ts.map +1 -1
- package/dist/server/middleware.js +8 -10
- package/dist/server/proxy/index.js +2 -2
- package/dist/server/proxy/mcp-proxy.d.ts.map +1 -1
- package/dist/server/proxy/mcp-proxy.js +2 -2
- package/dist/server/proxy/oauth-proxy.d.ts +2 -0
- package/dist/server/proxy/oauth-proxy.d.ts.map +1 -1
- package/dist/server/proxy/oauth-proxy.js +1 -1
- package/dist/server/proxy-routes.d.ts.map +1 -1
- package/dist/server/proxy-routes.js +3 -3
- package/dist/server/server.js +4 -4
- package/dist/server/static-assets.d.ts +3 -3
- package/dist/server/static-assets.d.ts.map +1 -1
- package/dist/server/static-assets.js +3 -3
- package/dist/server/version.js +1 -1
- package/dist/server/vite-dev-api-plugin.js +4 -4
- package/dist/web/assets/index-Dj_E94k4.css +1 -0
- package/dist/web/assets/{index-Bqfsh_Rp.js → index-O3gsi3le.js} +91 -91
- package/dist/web/index.html +3 -3
- package/package.json +30 -11
- package/dist/cdn/inspector.css +0 -2
- package/dist/server/asset-urls.d.ts +0 -15
- package/dist/server/asset-urls.d.ts.map +0 -1
- package/dist/server/asset-urls.js +0 -13
- package/dist/server/cdn-shell.d.ts.map +0 -1
- package/dist/server/cdn-shell.js +0 -12
- package/dist/server/chunk-J4FNYLBF.js +0 -54
- package/dist/server/chunk-JZT7N2BJ.js +0 -58
- package/dist/server/chunk-ZPGABKXL.js +0 -72
- package/dist/server/favicon-static.d.ts +0 -4
- package/dist/server/favicon-static.d.ts.map +0 -1
- package/dist/server/favicon-static.js +0 -8
- package/dist/web/assets/index-bCIH1Sdx.css +0 -1
- /package/dist/{cdn → app}/apple-touch-icon.png +0 -0
- /package/dist/{cdn → app}/favicon-96x96.png +0 -0
- /package/dist/{cdn → app}/favicon-black.svg +0 -0
- /package/dist/{cdn → app}/favicon-white.svg +0 -0
- /package/dist/{cdn → app}/favicon.ico +0 -0
- /package/dist/{cdn → app}/favicon.svg +0 -0
- /package/dist/{cdn → app}/providers/anthropic.png +0 -0
- /package/dist/{cdn → app}/providers/google.png +0 -0
- /package/dist/{cdn → app}/providers/openai.png +0 -0
- /package/dist/{cdn → app}/site.webmanifest +0 -0
- /package/dist/{cdn → app}/web-app-manifest-192x192.png +0 -0
- /package/dist/{cdn → app}/web-app-manifest-512x512.png +0 -0
package/dist/cli.js
CHANGED
|
@@ -4,132 +4,21 @@
|
|
|
4
4
|
import { serve } from "@hono/node-server";
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import { cors as cors2 } from "hono/cors";
|
|
7
|
-
import { logger as logger2 } from "hono/logger";
|
|
8
7
|
import open from "open";
|
|
9
8
|
|
|
10
|
-
// src/server/asset-urls.ts
|
|
11
|
-
var JSDELIVR_INSPECTOR_TAG = "beta";
|
|
12
|
-
var DEFAULT_REMOTE_ASSETS_URL = `https://cdn.jsdelivr.net/npm/@mcp-use/inspector@${JSDELIVR_INSPECTOR_TAG}/dist/cdn/inspector.js`;
|
|
13
|
-
function inspectorStylesUrl(assetsUrl) {
|
|
14
|
-
return assetsUrl.replace(/\.js(?=$|[?#])/, ".css");
|
|
15
|
-
}
|
|
16
|
-
function resolveInspectorAssetUrls(inspectorMode2, basePath) {
|
|
17
|
-
const envUrl = process.env.INSPECTOR_ASSETS_URL ?? process.env.MCP_USE_INSPECTOR_ASSETS_URL;
|
|
18
|
-
if (envUrl) {
|
|
19
|
-
return {
|
|
20
|
-
jsUrl: envUrl,
|
|
21
|
-
cssUrl: inspectorStylesUrl(envUrl),
|
|
22
|
-
useLocal: false,
|
|
23
|
-
resolveLatest: false
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
const legacyBase = process.env.INSPECTOR_CDN_BASE;
|
|
27
|
-
if (legacyBase) {
|
|
28
|
-
const base = legacyBase.replace(/\/$/, "");
|
|
29
|
-
if (/\.js(?:$|[?#])/.test(base)) {
|
|
30
|
-
return {
|
|
31
|
-
jsUrl: base,
|
|
32
|
-
cssUrl: inspectorStylesUrl(base),
|
|
33
|
-
useLocal: false,
|
|
34
|
-
resolveLatest: false
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
return {
|
|
38
|
-
jsUrl: `${base}/inspector.js`,
|
|
39
|
-
cssUrl: `${base}/inspector.css`,
|
|
40
|
-
useLocal: false,
|
|
41
|
-
resolveLatest: false
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
if (inspectorMode2 === "standalone") {
|
|
45
|
-
const prefix = basePath;
|
|
46
|
-
return {
|
|
47
|
-
jsUrl: `${prefix}/dist/cdn/inspector.js`,
|
|
48
|
-
cssUrl: `${prefix}/dist/cdn/inspector.css`,
|
|
49
|
-
useLocal: true,
|
|
50
|
-
resolveLatest: false
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
const jsUrl = DEFAULT_REMOTE_ASSETS_URL;
|
|
54
|
-
return {
|
|
55
|
-
jsUrl,
|
|
56
|
-
cssUrl: inspectorStylesUrl(jsUrl),
|
|
57
|
-
useLocal: false,
|
|
58
|
-
resolveLatest: true
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
9
|
// src/server/favicon-links.ts
|
|
63
10
|
var FAVICON_CACHE_VERSION = "4";
|
|
64
|
-
var INSPECTOR_FAVICON_ASSETS = [
|
|
65
|
-
"favicon.svg",
|
|
66
|
-
"favicon-96x96.png",
|
|
67
|
-
"favicon.ico",
|
|
68
|
-
"apple-touch-icon.png",
|
|
69
|
-
"site.webmanifest",
|
|
70
|
-
"web-app-manifest-192x192.png",
|
|
71
|
-
"web-app-manifest-512x512.png",
|
|
72
|
-
"favicon-black.svg",
|
|
73
|
-
"favicon-white.svg"
|
|
74
|
-
];
|
|
75
11
|
function renderInspectorFaviconLinks(basePath = "") {
|
|
76
12
|
const prefix = basePath.replace(/\/$/, "");
|
|
77
13
|
const href = `${prefix}/favicon-black.svg?v=${FAVICON_CACHE_VERSION}`;
|
|
78
14
|
return `<link rel="icon" type="image/svg+xml" href="${href}" />`;
|
|
79
15
|
}
|
|
80
16
|
|
|
81
|
-
// src/server/
|
|
17
|
+
// src/server/static-assets.ts
|
|
82
18
|
import { existsSync, readFileSync } from "fs";
|
|
83
19
|
import path from "path";
|
|
84
20
|
import { fileURLToPath } from "url";
|
|
85
21
|
var CONTENT_TYPES = {
|
|
86
|
-
".svg": "image/svg+xml",
|
|
87
|
-
".png": "image/png",
|
|
88
|
-
".ico": "image/x-icon",
|
|
89
|
-
".webmanifest": "application/manifest+json"
|
|
90
|
-
};
|
|
91
|
-
function resolveFaviconDir() {
|
|
92
|
-
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
93
|
-
for (const dir of [
|
|
94
|
-
path.resolve(here, "cdn"),
|
|
95
|
-
// dist/cli.js (bundled)
|
|
96
|
-
path.resolve(here, "../cdn"),
|
|
97
|
-
// dist/server/*.js
|
|
98
|
-
path.resolve(here, "../../public")
|
|
99
|
-
// src/server/*.ts (dev)
|
|
100
|
-
]) {
|
|
101
|
-
if (existsSync(path.join(dir, "favicon-black.svg"))) {
|
|
102
|
-
return dir;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
throw new Error(
|
|
106
|
-
"Inspector favicon assets not found (expected dist/cdn or public/)"
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
function registerInspectorFaviconStatic(app2, basePath = "") {
|
|
110
|
-
const faviconDir = resolveFaviconDir();
|
|
111
|
-
const p = (suffix) => `${basePath}${suffix}`;
|
|
112
|
-
for (const file of INSPECTOR_FAVICON_ASSETS) {
|
|
113
|
-
app2.get(p(`/${file}`), (c) => {
|
|
114
|
-
try {
|
|
115
|
-
const data = readFileSync(path.join(faviconDir, file));
|
|
116
|
-
const ext = path.extname(file);
|
|
117
|
-
return c.body(data, 200, {
|
|
118
|
-
"Content-Type": CONTENT_TYPES[ext] ?? "application/octet-stream",
|
|
119
|
-
"Cache-Control": "public, max-age=3600"
|
|
120
|
-
});
|
|
121
|
-
} catch {
|
|
122
|
-
return c.notFound();
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// src/server/static-assets.ts
|
|
129
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
130
|
-
import path2 from "path";
|
|
131
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
132
|
-
var CONTENT_TYPES2 = {
|
|
133
22
|
".js": "application/javascript",
|
|
134
23
|
".css": "text/css",
|
|
135
24
|
".svg": "image/svg+xml",
|
|
@@ -137,40 +26,44 @@ var CONTENT_TYPES2 = {
|
|
|
137
26
|
".ico": "image/x-icon",
|
|
138
27
|
".webmanifest": "application/manifest+json"
|
|
139
28
|
};
|
|
140
|
-
function
|
|
141
|
-
const here =
|
|
29
|
+
function resolveInspectorAppDir() {
|
|
30
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
142
31
|
for (const dir of [
|
|
143
|
-
|
|
32
|
+
path.resolve(here, "app"),
|
|
144
33
|
// dist/cli.js (bundled)
|
|
145
|
-
|
|
34
|
+
path.resolve(here, "../app"),
|
|
146
35
|
// dist/server/*.js
|
|
36
|
+
path.resolve(here, "../../dist/app")
|
|
37
|
+
// src/server/*.ts (workspace dev)
|
|
147
38
|
]) {
|
|
148
|
-
if (
|
|
39
|
+
if (existsSync(path.join(dir, "inspector.js"))) {
|
|
149
40
|
return dir;
|
|
150
41
|
}
|
|
151
42
|
}
|
|
152
43
|
throw new Error(
|
|
153
|
-
"Inspector bundle not found (expected dist/
|
|
44
|
+
"Inspector bundle not found (expected dist/app/inspector.js)"
|
|
154
45
|
);
|
|
155
46
|
}
|
|
156
|
-
function registerInspectorStaticAssets(app2,
|
|
157
|
-
const
|
|
158
|
-
const mountPath = `${basePath}/dist/cdn`;
|
|
47
|
+
function registerInspectorStaticAssets(app2, mountPath = "/inspector/assets") {
|
|
48
|
+
const appDir = resolveInspectorAppDir();
|
|
159
49
|
app2.get(`${mountPath}/*`, (c) => {
|
|
160
50
|
const subPath = c.req.path.slice(mountPath.length);
|
|
161
51
|
const relative = subPath.startsWith("/") ? subPath.slice(1) : subPath;
|
|
162
52
|
if (!relative || relative.includes("..")) {
|
|
163
53
|
return c.notFound();
|
|
164
54
|
}
|
|
165
|
-
const file =
|
|
166
|
-
const root =
|
|
167
|
-
if (!file.startsWith(root) || !
|
|
55
|
+
const file = path.resolve(appDir, relative);
|
|
56
|
+
const root = appDir.endsWith(path.sep) ? appDir : `${appDir}${path.sep}`;
|
|
57
|
+
if (!file.startsWith(root) || !existsSync(file)) {
|
|
168
58
|
return c.notFound();
|
|
169
59
|
}
|
|
170
|
-
const ext =
|
|
171
|
-
return c.body(
|
|
172
|
-
"Content-Type":
|
|
173
|
-
|
|
60
|
+
const ext = path.extname(file);
|
|
61
|
+
return c.body(readFileSync(file), 200, {
|
|
62
|
+
"Content-Type": CONTENT_TYPES[ext] ?? "application/octet-stream",
|
|
63
|
+
// Standalone assets use stable URLs across CLI restarts. Revalidate them
|
|
64
|
+
// so a rebuilt or upgraded Inspector cannot keep running an hour-old UI
|
|
65
|
+
// bundle that predates its storage migrations or proxy contract.
|
|
66
|
+
"Cache-Control": "no-cache"
|
|
174
67
|
});
|
|
175
68
|
});
|
|
176
69
|
}
|
|
@@ -179,7 +72,7 @@ function registerInspectorStaticAssets(app2, basePath = "") {
|
|
|
179
72
|
var package_default = {
|
|
180
73
|
name: "@mcp-use/inspector",
|
|
181
74
|
type: "module",
|
|
182
|
-
version: "20.0.0-beta.
|
|
75
|
+
version: "20.0.0-beta.16",
|
|
183
76
|
description: "MCP Inspector - A tool for inspecting and debugging MCP servers",
|
|
184
77
|
author: "",
|
|
185
78
|
license: "MIT",
|
|
@@ -201,7 +94,13 @@ var package_default = {
|
|
|
201
94
|
exports: {
|
|
202
95
|
".": {
|
|
203
96
|
types: "./dist/server/index.d.ts",
|
|
204
|
-
import: "./dist/server/index.js"
|
|
97
|
+
import: "./dist/server/index.js",
|
|
98
|
+
default: "./dist/server/index.js"
|
|
99
|
+
},
|
|
100
|
+
"./dev": {
|
|
101
|
+
types: "./dist/server/index.d.ts",
|
|
102
|
+
import: "./dist/server/index.js",
|
|
103
|
+
default: "./dist/server/index.js"
|
|
205
104
|
},
|
|
206
105
|
"./proxy": {
|
|
207
106
|
types: "./dist/server/proxy-routes.d.ts",
|
|
@@ -225,12 +124,13 @@ var package_default = {
|
|
|
225
124
|
"dev:client": "pnpm run dev",
|
|
226
125
|
"dev:server": "VITE_DEV=true tsx watch src/server/server.ts",
|
|
227
126
|
"dev:standalone": "tsx watch src/server/server.ts",
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
"
|
|
231
|
-
"
|
|
232
|
-
"
|
|
233
|
-
"
|
|
127
|
+
clean: "rimraf dist",
|
|
128
|
+
build: "npm run clean && npm run build:client && npm run build:client-exports && npm run build:server && npm run build:cli && npm run build:app",
|
|
129
|
+
"build:app": "vite build --config vite.app.config.ts",
|
|
130
|
+
"watch:app": "vite build --watch --config vite.app.config.ts",
|
|
131
|
+
"preview:app": "vite preview --config vite.app.config.ts --host 127.0.0.1",
|
|
132
|
+
"serve:app": "pnpm build:app && pnpm preview:app",
|
|
133
|
+
"analyze:app": "ANALYZE=true vite build --config vite.app.config.ts",
|
|
234
134
|
"build:client": "vite build",
|
|
235
135
|
"build:client-exports": "tsup --config tsup.client.ts && tsc -p tsconfig.client.json --emitDeclarationOnly --declaration",
|
|
236
136
|
"build:server": "tsup 'src/server/**/*.ts' --format esm --out-dir dist/server && tsc -p tsconfig.server.json --emitDeclarationOnly --declaration",
|
|
@@ -270,8 +170,20 @@ var package_default = {
|
|
|
270
170
|
"react-router": "^7.12.0"
|
|
271
171
|
},
|
|
272
172
|
peerDependenciesMeta: {
|
|
173
|
+
"@mcp-use/client": {
|
|
174
|
+
optional: true
|
|
175
|
+
},
|
|
273
176
|
express: {
|
|
274
177
|
optional: true
|
|
178
|
+
},
|
|
179
|
+
react: {
|
|
180
|
+
optional: true
|
|
181
|
+
},
|
|
182
|
+
"react-dom": {
|
|
183
|
+
optional: true
|
|
184
|
+
},
|
|
185
|
+
"react-router": {
|
|
186
|
+
optional: true
|
|
275
187
|
}
|
|
276
188
|
},
|
|
277
189
|
dependencies: {
|
|
@@ -325,63 +237,14 @@ function getInspectorVersion() {
|
|
|
325
237
|
return VERSION;
|
|
326
238
|
}
|
|
327
239
|
|
|
328
|
-
// src/server/
|
|
240
|
+
// src/server/inspector-shell.ts
|
|
329
241
|
var INSPECTOR_VERSION = getInspectorVersion();
|
|
330
|
-
var INSPECTOR_VERSION_RESOLVER_URL = "https://data.jsdelivr.com/v1/packages/npm/@mcp-use/inspector/resolved?specifier=beta";
|
|
331
|
-
var INSPECTOR_CDN_PACKAGE_URL = "https://cdn.jsdelivr.net/npm/@mcp-use/inspector";
|
|
332
242
|
var OAUTH_POPUP_CLOSED_HTML = `<!doctype html>
|
|
333
243
|
<html lang="en"><head><meta charset="utf-8"><title>Signed in</title><meta name="robots" content="noindex"><style>html,body{margin:0;height:100%;display:flex;align-items:center;justify-content:center;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;color:#4b5563;background:#fff}</style></head>
|
|
334
244
|
<body><div>Signed in. You can close this window.</div>
|
|
335
245
|
<script>try{if(window.opener&&!window.opener.closed)window.opener.postMessage({type:"manufact:oauth-complete"},"*")}catch(e){}try{window.close()}catch(e){}</script>
|
|
336
246
|
</body></html>`;
|
|
337
|
-
function
|
|
338
|
-
return `<script type="module">
|
|
339
|
-
const resolverUrl = ${JSON.stringify(INSPECTOR_VERSION_RESOLVER_URL)};
|
|
340
|
-
const packageUrl = ${JSON.stringify(INSPECTOR_CDN_PACKAGE_URL)};
|
|
341
|
-
const versionPattern = /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$/;
|
|
342
|
-
|
|
343
|
-
try {
|
|
344
|
-
const controller = new AbortController();
|
|
345
|
-
const timeout = setTimeout(() => controller.abort(), 10_000);
|
|
346
|
-
let response;
|
|
347
|
-
try {
|
|
348
|
-
response = await fetch(resolverUrl, { signal: controller.signal });
|
|
349
|
-
} finally {
|
|
350
|
-
clearTimeout(timeout);
|
|
351
|
-
}
|
|
352
|
-
if (!response.ok) {
|
|
353
|
-
throw new Error(\`Inspector version resolver returned HTTP \${response.status}\`);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const resolved = await response.json();
|
|
357
|
-
if (
|
|
358
|
-
resolved?.name !== "@mcp-use/inspector" ||
|
|
359
|
-
typeof resolved.version !== "string" ||
|
|
360
|
-
!versionPattern.test(resolved.version)
|
|
361
|
-
) {
|
|
362
|
-
throw new Error("Inspector version resolver returned an invalid release");
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const version = resolved.version;
|
|
366
|
-
const assetBase = \`\${packageUrl}@\${version}/dist/cdn\`;
|
|
367
|
-
const stylesheet = document.createElement("link");
|
|
368
|
-
stylesheet.rel = "stylesheet";
|
|
369
|
-
stylesheet.href = \`\${assetBase}/inspector.css\`;
|
|
370
|
-
document.head.append(stylesheet);
|
|
371
|
-
|
|
372
|
-
window.__INSPECTOR_VERSION__ = version;
|
|
373
|
-
await import(\`\${assetBase}/inspector.js\`);
|
|
374
|
-
} catch (error) {
|
|
375
|
-
console.error("[Inspector] Failed to load the latest Inspector release:", error);
|
|
376
|
-
document.querySelector(".mcp-boot-spinner")?.remove();
|
|
377
|
-
const label = document.querySelector(".mcp-boot-label");
|
|
378
|
-
if (label) {
|
|
379
|
-
label.textContent = "Unable to load Inspector. Reload to try again.";
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
</script>`;
|
|
383
|
-
}
|
|
384
|
-
function generateCdnShellHtml(config, basePath, assets) {
|
|
247
|
+
function generateInspectorShellHtml(config, basePath, assets) {
|
|
385
248
|
const scripts = [];
|
|
386
249
|
if (config?.basePath !== void 0) {
|
|
387
250
|
scripts.push(
|
|
@@ -416,22 +279,20 @@ function generateCdnShellHtml(config, basePath, assets) {
|
|
|
416
279
|
`<script>window.__MCP_USE_ANONYMIZED_TELEMETRY__ = false;try{localStorage.setItem("MCP_USE_ANONYMIZED_TELEMETRY","false");}catch(e){}</script>`
|
|
417
280
|
);
|
|
418
281
|
}
|
|
282
|
+
if (process.env.MCP_USE_DEV_CLI === "1") {
|
|
283
|
+
scripts.push(`<script>window.__MCP_DEV_CLI__ = true;</script>`);
|
|
284
|
+
}
|
|
419
285
|
const runtimeScripts = scripts.join("\n ");
|
|
420
|
-
const assetPreconnects = assets.resolveLatest ? ` <link rel="preconnect" href="https://data.jsdelivr.com" crossorigin />
|
|
421
|
-
<link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />` : "";
|
|
422
|
-
const stylesheetTag = assets.resolveLatest ? "" : ` <link rel="stylesheet" href="${assets.cssUrl}" />`;
|
|
423
|
-
const assetLoader = assets.resolveLatest ? renderDefaultInspectorAssetLoader() : `<script type="module" src="${assets.jsUrl}"></script>`;
|
|
424
286
|
return `<!doctype html>
|
|
425
287
|
<html lang="en">
|
|
426
288
|
<head>
|
|
427
289
|
<meta charset="UTF-8" />
|
|
428
|
-
${renderInspectorFaviconLinks(basePath)}
|
|
290
|
+
${renderInspectorFaviconLinks(`${basePath}/inspector/assets`)}
|
|
429
291
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
430
292
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
431
293
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
432
|
-
${assetPreconnects}
|
|
433
294
|
<link href="https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;500;700&display=swap" rel="stylesheet" />
|
|
434
|
-
${
|
|
295
|
+
<link rel="stylesheet" href="${assets.cssUrl}" />
|
|
435
296
|
<title>Inspector | mcp-use</title>
|
|
436
297
|
<meta name="description" content="Free, open-source MCP Inspector by mcp-use. Connect to any MCP server, test tools, prompts, and resources, inspect RPC logs, and debug MCP apps \u2014 all in your browser." />
|
|
437
298
|
<style>
|
|
@@ -511,12 +372,17 @@ ${stylesheetTag}
|
|
|
511
372
|
</div>
|
|
512
373
|
</div>
|
|
513
374
|
</div>
|
|
514
|
-
${
|
|
375
|
+
<script type="module" src="${assets.jsUrl}"></script>
|
|
515
376
|
</body>
|
|
516
377
|
</html>`;
|
|
517
378
|
}
|
|
518
|
-
function
|
|
519
|
-
const
|
|
379
|
+
function registerInspectorShell(app2, config, basePath = "") {
|
|
380
|
+
const assetsPath = `${basePath}/inspector/assets`;
|
|
381
|
+
const version = encodeURIComponent(INSPECTOR_VERSION);
|
|
382
|
+
const assets = {
|
|
383
|
+
jsUrl: `${assetsPath}/inspector.js?v=${version}`,
|
|
384
|
+
cssUrl: `${assetsPath}/inspector.css?v=${version}`
|
|
385
|
+
};
|
|
520
386
|
const p = (suffix) => `${basePath}${suffix}`;
|
|
521
387
|
const effectiveConfig = {
|
|
522
388
|
...config,
|
|
@@ -525,10 +391,11 @@ function registerInspectorCdnShell(app2, config, basePath = "") {
|
|
|
525
391
|
disableTelemetry: config?.disableTelemetry ?? process.env.MCP_USE_ANONYMIZED_TELEMETRY === "false"
|
|
526
392
|
};
|
|
527
393
|
const serveShell = (c) => {
|
|
528
|
-
|
|
529
|
-
|
|
394
|
+
return c.html(
|
|
395
|
+
generateInspectorShellHtml(effectiveConfig, basePath, assets)
|
|
396
|
+
);
|
|
530
397
|
};
|
|
531
|
-
|
|
398
|
+
registerInspectorStaticAssets(app2, assetsPath);
|
|
532
399
|
app2.get(
|
|
533
400
|
p("/inspector/oauth-popup-closed.html"),
|
|
534
401
|
(c) => c.html(OAUTH_POPUP_CLOSED_HTML)
|
|
@@ -548,15 +415,12 @@ function registerInspectorCdnShell(app2, config, basePath = "") {
|
|
|
548
415
|
}
|
|
549
416
|
return serveShell(c);
|
|
550
417
|
});
|
|
551
|
-
if (basePath === "") {
|
|
418
|
+
if (basePath === "" && config?.rootRedirect !== false) {
|
|
552
419
|
app2.get("/", (c) => {
|
|
553
420
|
const url = new URL(c.req.url);
|
|
554
421
|
return c.redirect(`${p("/inspector")}${url.search}`);
|
|
555
422
|
});
|
|
556
423
|
}
|
|
557
|
-
if (assets.useLocal) {
|
|
558
|
-
registerInspectorStaticAssets(app2, basePath);
|
|
559
|
-
}
|
|
560
424
|
}
|
|
561
425
|
|
|
562
426
|
// src/server/proxy/mcp-proxy.ts
|
|
@@ -565,6 +429,7 @@ import { logger } from "hono/logger";
|
|
|
565
429
|
|
|
566
430
|
// src/server/proxy/oauth-proxy.ts
|
|
567
431
|
import { lookup } from "dns/promises";
|
|
432
|
+
import { Buffer } from "buffer";
|
|
568
433
|
import { isIP } from "net";
|
|
569
434
|
var SAFE_REQUEST_HEADERS = /* @__PURE__ */ new Set([
|
|
570
435
|
"accept",
|
|
@@ -589,6 +454,8 @@ var ENDPOINT_FIELDS = [
|
|
|
589
454
|
];
|
|
590
455
|
var BINDING_TTL_MS = 10 * 60 * 1e3;
|
|
591
456
|
var MAX_BINDINGS = 100;
|
|
457
|
+
var CONFIDENTIAL_CLIENT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
458
|
+
var MAX_CONFIDENTIAL_CLIENTS = 500;
|
|
592
459
|
function mountOAuthProxy(app2, options = {}) {
|
|
593
460
|
const {
|
|
594
461
|
basePath = "/oauth",
|
|
@@ -597,15 +464,17 @@ function mountOAuthProxy(app2, options = {}) {
|
|
|
597
464
|
timeoutMs = 1e4,
|
|
598
465
|
maxRequestBodyBytes = 64 * 1024,
|
|
599
466
|
maxResponseBodyBytes = 1024 * 1024,
|
|
467
|
+
callbackPath = "/oauth/callback",
|
|
600
468
|
enableLogging = true,
|
|
601
469
|
authenticate,
|
|
602
470
|
validateServerUrl
|
|
603
471
|
} = options;
|
|
604
472
|
const origins = new Set(allowedOrigins.map(normalizeOrigin));
|
|
605
473
|
const bindings = /* @__PURE__ */ new Map();
|
|
474
|
+
const confidentialClients = /* @__PURE__ */ new Map();
|
|
606
475
|
app2.use(`${basePath}/*`, async (c, next) => {
|
|
607
476
|
const origin = c.req.header("Origin");
|
|
608
|
-
if (origin && !isAllowedOrigin(origin, c
|
|
477
|
+
if (origin && !isAllowedOrigin(origin, c, origins)) {
|
|
609
478
|
return c.json({ error: "Origin not allowed" }, 403);
|
|
610
479
|
}
|
|
611
480
|
if (c.req.method === "OPTIONS") {
|
|
@@ -762,7 +631,24 @@ function mountOAuthProxy(app2, options = {}) {
|
|
|
762
631
|
}
|
|
763
632
|
try {
|
|
764
633
|
const headers = filterRequestHeaders(request.headers);
|
|
765
|
-
|
|
634
|
+
let body = serializeBody(request.body, headers);
|
|
635
|
+
if (endpointKind === "registration") {
|
|
636
|
+
const publicOrigin = normalizeOrigin(
|
|
637
|
+
c.req.header("Origin") ?? new URL(c.req.url).origin
|
|
638
|
+
);
|
|
639
|
+
body = enforceRegistrationRedirectUri(
|
|
640
|
+
body,
|
|
641
|
+
headers,
|
|
642
|
+
new URL(callbackPath, publicOrigin).toString()
|
|
643
|
+
);
|
|
644
|
+
} else {
|
|
645
|
+
body = applyConfidentialClientAuthentication({
|
|
646
|
+
body,
|
|
647
|
+
headers,
|
|
648
|
+
bindingKey,
|
|
649
|
+
clients: confidentialClients
|
|
650
|
+
});
|
|
651
|
+
}
|
|
766
652
|
if (new TextEncoder().encode(body ?? "").byteLength > maxRequestBodyBytes) {
|
|
767
653
|
return c.json({ error: "OAuth request body too large" }, 413);
|
|
768
654
|
}
|
|
@@ -790,6 +676,14 @@ function mountOAuthProxy(app2, options = {}) {
|
|
|
790
676
|
} catch {
|
|
791
677
|
}
|
|
792
678
|
}
|
|
679
|
+
if (endpointKind === "registration" && upstream.ok && responseBody && typeof responseBody === "object" && !Array.isArray(responseBody)) {
|
|
680
|
+
responseBody = retainConfidentialClient({
|
|
681
|
+
responseBody,
|
|
682
|
+
binding,
|
|
683
|
+
bindingKey,
|
|
684
|
+
clients: confidentialClients
|
|
685
|
+
});
|
|
686
|
+
}
|
|
793
687
|
return c.json({
|
|
794
688
|
status: upstream.status,
|
|
795
689
|
statusText: upstream.statusText,
|
|
@@ -859,18 +753,18 @@ function classifyMetadataTarget(serverUrl, target, binding) {
|
|
|
859
753
|
return void 0;
|
|
860
754
|
}
|
|
861
755
|
function protectedResourceMetadataUrls(serverUrl) {
|
|
862
|
-
const
|
|
756
|
+
const path2 = serverUrl.pathname === "/" ? "" : serverUrl.pathname;
|
|
863
757
|
return [
|
|
864
|
-
new URL(`/.well-known/oauth-protected-resource${
|
|
758
|
+
new URL(`/.well-known/oauth-protected-resource${path2}`, serverUrl.origin),
|
|
865
759
|
new URL("/.well-known/oauth-protected-resource", serverUrl.origin)
|
|
866
760
|
];
|
|
867
761
|
}
|
|
868
762
|
function authorizationServerMetadataUrls(issuer) {
|
|
869
|
-
const
|
|
763
|
+
const path2 = issuer.pathname === "/" ? "" : issuer.pathname;
|
|
870
764
|
return [
|
|
871
|
-
new URL(`/.well-known/oauth-authorization-server${
|
|
872
|
-
new URL(`/.well-known/openid-configuration${
|
|
873
|
-
new URL(`${
|
|
765
|
+
new URL(`/.well-known/oauth-authorization-server${path2}`, issuer.origin),
|
|
766
|
+
new URL(`/.well-known/openid-configuration${path2}`, issuer.origin),
|
|
767
|
+
new URL(`${path2 || ""}/.well-known/openid-configuration`, issuer.origin)
|
|
874
768
|
];
|
|
875
769
|
}
|
|
876
770
|
async function bindProtectedResource(metadata, serverUrl, binding, allowLoopback) {
|
|
@@ -896,6 +790,7 @@ async function bindProtectedResource(metadata, serverUrl, binding, allowLoopback
|
|
|
896
790
|
}
|
|
897
791
|
binding.authorizationServers = issuers;
|
|
898
792
|
binding.endpoints.clear();
|
|
793
|
+
binding.tokenEndpointAuthMethods.clear();
|
|
899
794
|
}
|
|
900
795
|
async function bindAuthorizationServer(metadata, expectedIssuer, binding, allowLoopback) {
|
|
901
796
|
if (typeof metadata.issuer !== "string" || canonicalUrl(new URL(metadata.issuer)) !== expectedIssuer) {
|
|
@@ -904,6 +799,11 @@ async function bindAuthorizationServer(metadata, expectedIssuer, binding, allowL
|
|
|
904
799
|
);
|
|
905
800
|
}
|
|
906
801
|
binding.endpoints.clear();
|
|
802
|
+
binding.tokenEndpointAuthMethods = new Set(
|
|
803
|
+
Array.isArray(metadata.token_endpoint_auth_methods_supported) ? metadata.token_endpoint_auth_methods_supported.filter(
|
|
804
|
+
(method) => typeof method === "string"
|
|
805
|
+
) : []
|
|
806
|
+
);
|
|
907
807
|
for (const [field, kind] of ENDPOINT_FIELDS) {
|
|
908
808
|
const value = metadata[field];
|
|
909
809
|
if (value === void 0) continue;
|
|
@@ -1000,7 +900,17 @@ function filterResponseHeaders(headers) {
|
|
|
1000
900
|
}
|
|
1001
901
|
function serializeBody(body, headers) {
|
|
1002
902
|
if (body === void 0 || body === null) return void 0;
|
|
1003
|
-
if (typeof body === "string")
|
|
903
|
+
if (typeof body === "string") {
|
|
904
|
+
if (!headers.has("content-type")) {
|
|
905
|
+
try {
|
|
906
|
+
JSON.parse(body);
|
|
907
|
+
headers.set("content-type", "application/json");
|
|
908
|
+
} catch {
|
|
909
|
+
headers.set("content-type", "application/x-www-form-urlencoded");
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return body;
|
|
913
|
+
}
|
|
1004
914
|
const contentType = headers.get("content-type") ?? "";
|
|
1005
915
|
if (contentType.includes("application/x-www-form-urlencoded") && typeof body === "object" && !Array.isArray(body)) {
|
|
1006
916
|
const params = new URLSearchParams();
|
|
@@ -1016,6 +926,77 @@ function serializeBody(body, headers) {
|
|
|
1016
926
|
}
|
|
1017
927
|
return JSON.stringify(body);
|
|
1018
928
|
}
|
|
929
|
+
function confidentialClientKey(bindingKey, clientId) {
|
|
930
|
+
return `${bindingKey}\0${clientId}`;
|
|
931
|
+
}
|
|
932
|
+
function retainConfidentialClient(options) {
|
|
933
|
+
const { responseBody, binding, bindingKey, clients } = options;
|
|
934
|
+
const clientId = responseBody.client_id;
|
|
935
|
+
const clientSecret = responseBody.client_secret;
|
|
936
|
+
if (typeof clientId !== "string" || typeof clientSecret !== "string") {
|
|
937
|
+
return responseBody;
|
|
938
|
+
}
|
|
939
|
+
const returnedMethod = responseBody.token_endpoint_auth_method;
|
|
940
|
+
const authMethod = returnedMethod === "client_secret_post" || returnedMethod === "client_secret_basic" ? returnedMethod : binding.tokenEndpointAuthMethods.has("client_secret_basic") ? "client_secret_basic" : "client_secret_post";
|
|
941
|
+
const upstreamExpiry = responseBody.client_secret_expires_at;
|
|
942
|
+
const expiresAt = upstreamExpiry === 0 ? Number.POSITIVE_INFINITY : typeof upstreamExpiry === "number" && upstreamExpiry > 0 ? upstreamExpiry * 1e3 : Date.now() + CONFIDENTIAL_CLIENT_TTL_MS;
|
|
943
|
+
pruneConfidentialClients(clients);
|
|
944
|
+
clients.set(confidentialClientKey(bindingKey, clientId), {
|
|
945
|
+
clientSecret,
|
|
946
|
+
authMethod,
|
|
947
|
+
expiresAt
|
|
948
|
+
});
|
|
949
|
+
while (clients.size > MAX_CONFIDENTIAL_CLIENTS) {
|
|
950
|
+
const oldest = clients.keys().next().value;
|
|
951
|
+
if (!oldest) break;
|
|
952
|
+
clients.delete(oldest);
|
|
953
|
+
}
|
|
954
|
+
const browserSafe = { ...responseBody };
|
|
955
|
+
delete browserSafe.client_secret;
|
|
956
|
+
delete browserSafe.client_secret_expires_at;
|
|
957
|
+
browserSafe.token_endpoint_auth_method = "none";
|
|
958
|
+
return browserSafe;
|
|
959
|
+
}
|
|
960
|
+
function applyConfidentialClientAuthentication(options) {
|
|
961
|
+
const { body, headers, bindingKey, clients } = options;
|
|
962
|
+
if (!body || !(headers.get("content-type") ?? "").includes(
|
|
963
|
+
"application/x-www-form-urlencoded"
|
|
964
|
+
)) {
|
|
965
|
+
return body;
|
|
966
|
+
}
|
|
967
|
+
const params = new URLSearchParams(body);
|
|
968
|
+
const clientId = params.get("client_id");
|
|
969
|
+
if (!clientId) return body;
|
|
970
|
+
const key = confidentialClientKey(bindingKey, clientId);
|
|
971
|
+
const client = clients.get(key);
|
|
972
|
+
if (!client) return body;
|
|
973
|
+
if (client.expiresAt <= Date.now()) {
|
|
974
|
+
clients.delete(key);
|
|
975
|
+
return body;
|
|
976
|
+
}
|
|
977
|
+
params.delete("client_secret");
|
|
978
|
+
if (client.authMethod === "client_secret_basic") {
|
|
979
|
+
const encoded = Buffer.from(
|
|
980
|
+
`${encodeOAuthClientCredential(clientId)}:${encodeOAuthClientCredential(client.clientSecret)}`,
|
|
981
|
+
"utf8"
|
|
982
|
+
).toString("base64");
|
|
983
|
+
headers.set("authorization", `Basic ${encoded}`);
|
|
984
|
+
} else {
|
|
985
|
+
headers.delete("authorization");
|
|
986
|
+
params.set("client_id", clientId);
|
|
987
|
+
params.set("client_secret", client.clientSecret);
|
|
988
|
+
}
|
|
989
|
+
return params.toString();
|
|
990
|
+
}
|
|
991
|
+
function encodeOAuthClientCredential(value) {
|
|
992
|
+
return new URLSearchParams({ value }).toString().slice("value=".length);
|
|
993
|
+
}
|
|
994
|
+
function pruneConfidentialClients(clients) {
|
|
995
|
+
const now = Date.now();
|
|
996
|
+
for (const [key, client] of clients) {
|
|
997
|
+
if (client.expiresAt <= now) clients.delete(key);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1019
1000
|
async function readRequestCapped(request, maxBytes) {
|
|
1020
1001
|
const declared = Number(request.headers.get("content-length"));
|
|
1021
1002
|
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
@@ -1075,6 +1056,7 @@ function getBinding(bindings, key) {
|
|
|
1075
1056
|
return {
|
|
1076
1057
|
authorizationServers: /* @__PURE__ */ new Set(),
|
|
1077
1058
|
endpoints: /* @__PURE__ */ new Map(),
|
|
1059
|
+
tokenEndpointAuthMethods: /* @__PURE__ */ new Set(),
|
|
1078
1060
|
updatedAt: Date.now()
|
|
1079
1061
|
};
|
|
1080
1062
|
}
|
|
@@ -1097,6 +1079,23 @@ function canonicalUrl(url) {
|
|
|
1097
1079
|
}
|
|
1098
1080
|
return copy.toString().replace(/\/$/, "");
|
|
1099
1081
|
}
|
|
1082
|
+
function enforceRegistrationRedirectUri(body, headers, callbackUrl) {
|
|
1083
|
+
if (!body) return body;
|
|
1084
|
+
const contentType = headers.get("content-type") ?? "";
|
|
1085
|
+
if (!contentType.includes("json")) return body;
|
|
1086
|
+
try {
|
|
1087
|
+
const parsed = JSON.parse(body);
|
|
1088
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1089
|
+
return body;
|
|
1090
|
+
}
|
|
1091
|
+
return JSON.stringify({
|
|
1092
|
+
...parsed,
|
|
1093
|
+
redirect_uris: [callbackUrl]
|
|
1094
|
+
});
|
|
1095
|
+
} catch {
|
|
1096
|
+
return body;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1100
1099
|
function normalizeOrigin(origin) {
|
|
1101
1100
|
const url = new URL(origin);
|
|
1102
1101
|
if (url.pathname !== "/" || url.search || url.hash) {
|
|
@@ -1104,10 +1103,18 @@ function normalizeOrigin(origin) {
|
|
|
1104
1103
|
}
|
|
1105
1104
|
return url.origin;
|
|
1106
1105
|
}
|
|
1107
|
-
function isAllowedOrigin(origin,
|
|
1106
|
+
function isAllowedOrigin(origin, c, allowed) {
|
|
1108
1107
|
try {
|
|
1109
1108
|
const normalized = normalizeOrigin(origin);
|
|
1110
|
-
|
|
1109
|
+
if (normalized === new URL(c.req.url).origin || allowed.has(normalized)) {
|
|
1110
|
+
return true;
|
|
1111
|
+
}
|
|
1112
|
+
const originUrl = new URL(normalized);
|
|
1113
|
+
const forwardedHost = c.req.header("x-forwarded-host")?.split(",")[0]?.trim();
|
|
1114
|
+
const requestHost = forwardedHost || c.req.header("host");
|
|
1115
|
+
if (!requestHost || originUrl.host !== requestHost) return false;
|
|
1116
|
+
const forwardedProto = c.req.header("x-forwarded-proto")?.split(",")[0]?.trim();
|
|
1117
|
+
return !forwardedProto || `${forwardedProto}:` === originUrl.protocol;
|
|
1111
1118
|
} catch {
|
|
1112
1119
|
return false;
|
|
1113
1120
|
}
|
|
@@ -1179,6 +1186,7 @@ var InvalidUpstreamError = class extends Error {
|
|
|
1179
1186
|
};
|
|
1180
1187
|
|
|
1181
1188
|
// src/server/proxy/mcp-proxy.ts
|
|
1189
|
+
var MAX_REDIRECTS = 3;
|
|
1182
1190
|
function isOpenEndedSseResponse(contentType, contentLength) {
|
|
1183
1191
|
return contentType.includes("text/event-stream") && !contentLength;
|
|
1184
1192
|
}
|
|
@@ -1262,81 +1270,48 @@ function mountMcpProxy(app2, options = {}) {
|
|
|
1262
1270
|
const requestHeaders = c.req.header();
|
|
1263
1271
|
for (const [key, value] of Object.entries(requestHeaders)) {
|
|
1264
1272
|
const lowerKey = key.toLowerCase();
|
|
1265
|
-
if (!lowerKey.startsWith("x-proxy-") && !lowerKey.startsWith("x-target-") && !lowerKey.startsWith("x-mcp-") && !lowerKey.startsWith("x-forwarded-") && !lowerKey.startsWith("cf-") && lowerKey !== "x-original-host" && lowerKey !== "host" && lowerKey !== "accept-encoding" && lowerKey !== "cdn-loop") {
|
|
1273
|
+
if (!lowerKey.startsWith("x-proxy-") && !lowerKey.startsWith("x-target-") && !lowerKey.startsWith("x-mcp-") && !lowerKey.startsWith("x-forwarded-") && !lowerKey.startsWith("cf-") && lowerKey !== "x-original-host" && lowerKey !== "host" && lowerKey !== "origin" && lowerKey !== "referer" && lowerKey !== "cookie" && lowerKey !== "proxy-authorization" && lowerKey !== "accept-encoding" && lowerKey !== "cdn-loop" && !lowerKey.startsWith("sec-fetch-")) {
|
|
1266
1274
|
headers[key] = value;
|
|
1267
1275
|
}
|
|
1268
1276
|
}
|
|
1269
1277
|
headers["Accept-Encoding"] = "identity";
|
|
1270
|
-
try {
|
|
1271
|
-
const targetUrlObj = new URL(targetUrl);
|
|
1272
|
-
headers.Host = targetUrlObj.host;
|
|
1273
|
-
} catch {
|
|
1274
|
-
return c.json({ error: "Invalid target URL" }, 400);
|
|
1275
|
-
}
|
|
1276
1278
|
const body = method !== "GET" && method !== "HEAD" ? new Uint8Array(await c.req.arrayBuffer()).slice() : void 0;
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1279
|
+
let currentUrl = targetUrl;
|
|
1280
|
+
let currentMethod = method;
|
|
1281
|
+
let currentBody = body;
|
|
1282
|
+
for (let redirectCount = 0; ; redirectCount += 1) {
|
|
1283
|
+
const response = await fetch(currentUrl, {
|
|
1284
|
+
method: currentMethod,
|
|
1285
|
+
headers,
|
|
1286
|
+
body: currentBody,
|
|
1287
|
+
redirect: "manual"
|
|
1288
|
+
});
|
|
1284
1289
|
const location = response.headers.get("location");
|
|
1285
|
-
if (location) {
|
|
1286
|
-
|
|
1287
|
-
if (!await isSafeProxyTarget(
|
|
1288
|
-
redirectUrl,
|
|
1289
|
-
options.allowLoopback ?? false
|
|
1290
|
-
) || options.validateRequest && !await options.validateRequest(redirectUrl, c)) {
|
|
1291
|
-
return c.json({ error: "Redirect target is not allowed" }, 403);
|
|
1292
|
-
}
|
|
1293
|
-
const redirectResponse = await fetch(redirectUrl, {
|
|
1294
|
-
method,
|
|
1295
|
-
headers,
|
|
1296
|
-
body,
|
|
1297
|
-
redirect: "manual"
|
|
1298
|
-
});
|
|
1299
|
-
const redirectHeaders = {};
|
|
1300
|
-
redirectResponse.headers.forEach((value, key) => {
|
|
1301
|
-
const lowerKey = key.toLowerCase();
|
|
1302
|
-
if (lowerKey !== "content-encoding" && lowerKey !== "transfer-encoding" && lowerKey !== "content-length") {
|
|
1303
|
-
redirectHeaders[key] = value;
|
|
1304
|
-
}
|
|
1305
|
-
});
|
|
1306
|
-
return new Response(redirectResponse.body, {
|
|
1307
|
-
status: redirectResponse.status,
|
|
1308
|
-
statusText: redirectResponse.statusText,
|
|
1309
|
-
headers: redirectHeaders
|
|
1310
|
-
});
|
|
1290
|
+
if (!(response.status >= 300 && response.status < 400 && location)) {
|
|
1291
|
+
return proxyResponse(response);
|
|
1311
1292
|
}
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
response.headers.forEach((value, key) => {
|
|
1315
|
-
const lowerKey = key.toLowerCase();
|
|
1316
|
-
if (lowerKey !== "content-encoding" && lowerKey !== "transfer-encoding" && lowerKey !== "content-length") {
|
|
1317
|
-
responseHeaders[key] = value;
|
|
1293
|
+
if (redirectCount >= MAX_REDIRECTS) {
|
|
1294
|
+
return c.json({ error: "Too many upstream redirects" }, 502);
|
|
1318
1295
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1296
|
+
const redirectUrl = new URL(location, currentUrl);
|
|
1297
|
+
if (!await isSafeProxyTarget(
|
|
1298
|
+
redirectUrl.toString(),
|
|
1299
|
+
options.allowLoopback ?? false
|
|
1300
|
+
) || options.validateRequest && !await options.validateRequest(redirectUrl.toString(), c)) {
|
|
1301
|
+
return c.json({ error: "Redirect target is not allowed" }, 403);
|
|
1302
|
+
}
|
|
1303
|
+
if (redirectUrl.origin !== new URL(currentUrl).origin) {
|
|
1304
|
+
deleteHeader(headers, "authorization");
|
|
1305
|
+
deleteHeader(headers, "proxy-authorization");
|
|
1306
|
+
}
|
|
1307
|
+
if (response.status === 303 || (response.status === 301 || response.status === 302) && currentMethod === "POST") {
|
|
1308
|
+
currentMethod = "GET";
|
|
1309
|
+
currentBody = void 0;
|
|
1310
|
+
deleteHeader(headers, "content-type");
|
|
1311
|
+
deleteHeader(headers, "content-length");
|
|
1312
|
+
}
|
|
1313
|
+
currentUrl = redirectUrl.toString();
|
|
1332
1314
|
}
|
|
1333
|
-
const bodyBuffer = await response.arrayBuffer();
|
|
1334
|
-
responseHeaders["Content-Length"] = String(bodyBuffer.byteLength);
|
|
1335
|
-
return new Response(bodyBuffer, {
|
|
1336
|
-
status: response.status,
|
|
1337
|
-
statusText: response.statusText,
|
|
1338
|
-
headers: responseHeaders
|
|
1339
|
-
});
|
|
1340
1315
|
} catch (error) {
|
|
1341
1316
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1342
1317
|
const targetUrl = c.req.header("X-Target-URL");
|
|
@@ -1366,6 +1341,38 @@ function mountMcpProxy(app2, options = {}) {
|
|
|
1366
1341
|
}
|
|
1367
1342
|
});
|
|
1368
1343
|
}
|
|
1344
|
+
function deleteHeader(headers, name) {
|
|
1345
|
+
for (const key of Object.keys(headers)) {
|
|
1346
|
+
if (key.toLowerCase() === name) delete headers[key];
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
async function proxyResponse(response) {
|
|
1350
|
+
const responseHeaders = {};
|
|
1351
|
+
response.headers.forEach((value, key) => {
|
|
1352
|
+
const lowerKey = key.toLowerCase();
|
|
1353
|
+
if (lowerKey !== "content-encoding" && lowerKey !== "transfer-encoding" && lowerKey !== "content-length" && lowerKey !== "set-cookie") {
|
|
1354
|
+
responseHeaders[key] = value;
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
const isTrueStream = isOpenEndedSseResponse(
|
|
1358
|
+
response.headers.get("content-type") || "",
|
|
1359
|
+
response.headers.get("content-length")
|
|
1360
|
+
);
|
|
1361
|
+
if (isTrueStream) {
|
|
1362
|
+
return new Response(response.body, {
|
|
1363
|
+
status: response.status,
|
|
1364
|
+
statusText: response.statusText,
|
|
1365
|
+
headers: responseHeaders
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
const bodyBuffer = await response.arrayBuffer();
|
|
1369
|
+
responseHeaders["Content-Length"] = String(bodyBuffer.byteLength);
|
|
1370
|
+
return new Response(bodyBuffer, {
|
|
1371
|
+
status: response.status,
|
|
1372
|
+
statusText: response.statusText,
|
|
1373
|
+
headers: responseHeaders
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1369
1376
|
|
|
1370
1377
|
// src/server/proxy-routes.ts
|
|
1371
1378
|
function registerInspectorProxyRoutes(app2, config, basePath = "") {
|
|
@@ -1387,6 +1394,7 @@ function registerInspectorProxyRoutes(app2, config, basePath = "") {
|
|
|
1387
1394
|
if (mountOAuth) {
|
|
1388
1395
|
mountOAuthProxy(app2, {
|
|
1389
1396
|
basePath: p("/inspector/api/oauth"),
|
|
1397
|
+
callbackPath: p("/inspector/oauth/callback"),
|
|
1390
1398
|
enableLogging: true,
|
|
1391
1399
|
allowedOrigins: config?.oauthProxyAllowedOrigins ?? [],
|
|
1392
1400
|
allowLoopback
|
|
@@ -1502,15 +1510,13 @@ app.use(
|
|
|
1502
1510
|
exposeHeaders: ["*"]
|
|
1503
1511
|
})
|
|
1504
1512
|
);
|
|
1505
|
-
app.use("/inspector/api/proxy/*", logger2());
|
|
1506
|
-
var inspectorMode = process.env.MCP_INSPECTOR_MODE ?? (process.env.RAILWAY_ENVIRONMENT_NAME ? "cloud" : "standalone");
|
|
1507
1513
|
registerInspectorProxyRoutes(app, {
|
|
1508
1514
|
autoConnectUrl: mcpUrl,
|
|
1509
1515
|
oauthProxyAllowedOrigins: [],
|
|
1510
|
-
oauthProxyAllowLoopback:
|
|
1516
|
+
oauthProxyAllowLoopback: true
|
|
1511
1517
|
});
|
|
1512
|
-
|
|
1513
|
-
inspectorMode,
|
|
1518
|
+
registerInspectorShell(app, {
|
|
1519
|
+
inspectorMode: "standalone",
|
|
1514
1520
|
manufactChatUrl: process.env.MANUFACT_CHAT_URL
|
|
1515
1521
|
});
|
|
1516
1522
|
async function startServer() {
|