@mcp-use/inspector 20.0.0-beta.16 → 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.
Files changed (73) hide show
  1. package/README.md +25 -58
  2. package/dist/app/inspector.css +2 -0
  3. package/dist/{cdn → app}/inspector.js +7855 -7629
  4. package/dist/cli.js +292 -286
  5. package/dist/client/auth/manufact-auth.d.ts +1 -1
  6. package/dist/client/auth/manufact-auth.d.ts.map +1 -1
  7. package/dist/client/index.js +1 -1
  8. package/dist/server/{chunk-XCNS5CIW.js → chunk-2K7OKCVV.js} +67 -67
  9. package/dist/server/{chunk-UJLXHNQP.js → chunk-EKD2XITW.js} +3 -2
  10. package/dist/server/{chunk-FHC5ZGMM.js → chunk-EPPZCSJQ.js} +23 -77
  11. package/dist/server/chunk-JWDVSQED.js +156 -0
  12. package/dist/server/{chunk-M5TZA42I.js → chunk-PZF2PUDE.js} +15 -11
  13. package/dist/server/{chunk-R5ZEVRE5.js → chunk-RFJAZOK7.js} +27 -8
  14. package/dist/server/{chunk-OWR2BIBW.js → chunk-RKFQG67Z.js} +1 -3
  15. package/dist/server/{chunk-K2F2AJOI.js → chunk-V5A5XLFJ.js} +148 -5
  16. package/dist/server/cli.js +11 -16
  17. package/dist/server/create-dev-api-app.d.ts.map +1 -1
  18. package/dist/server/create-dev-api-app.js +4 -4
  19. package/dist/server/favicon-links.d.ts +2 -2
  20. package/dist/server/favicon-links.d.ts.map +1 -1
  21. package/dist/server/index.d.ts +2 -3
  22. package/dist/server/index.d.ts.map +1 -1
  23. package/dist/server/index.js +8 -13
  24. package/dist/server/{cdn-shell.d.ts → inspector-shell.d.ts} +7 -5
  25. package/dist/server/inspector-shell.d.ts.map +1 -0
  26. package/dist/server/inspector-shell.js +10 -0
  27. package/dist/server/middleware.d.ts +52 -27
  28. package/dist/server/middleware.d.ts.map +1 -1
  29. package/dist/server/middleware.js +8 -10
  30. package/dist/server/proxy/index.js +2 -2
  31. package/dist/server/proxy/mcp-proxy.d.ts.map +1 -1
  32. package/dist/server/proxy/mcp-proxy.js +2 -2
  33. package/dist/server/proxy/oauth-proxy.d.ts +2 -0
  34. package/dist/server/proxy/oauth-proxy.d.ts.map +1 -1
  35. package/dist/server/proxy/oauth-proxy.js +1 -1
  36. package/dist/server/proxy-routes.d.ts.map +1 -1
  37. package/dist/server/proxy-routes.js +3 -3
  38. package/dist/server/server.js +4 -4
  39. package/dist/server/static-assets.d.ts +3 -3
  40. package/dist/server/static-assets.d.ts.map +1 -1
  41. package/dist/server/static-assets.js +3 -3
  42. package/dist/server/version.js +1 -1
  43. package/dist/server/vite-dev-api-plugin.js +4 -4
  44. package/dist/web/assets/index-Dj_E94k4.css +1 -0
  45. package/dist/web/assets/index-O3gsi3le.js +643 -0
  46. package/dist/web/index.html +3 -3
  47. package/package.json +30 -11
  48. package/dist/cdn/inspector.css +0 -2
  49. package/dist/server/asset-urls.d.ts +0 -15
  50. package/dist/server/asset-urls.d.ts.map +0 -1
  51. package/dist/server/asset-urls.js +0 -13
  52. package/dist/server/cdn-shell.d.ts.map +0 -1
  53. package/dist/server/cdn-shell.js +0 -12
  54. package/dist/server/chunk-J4FNYLBF.js +0 -54
  55. package/dist/server/chunk-JZT7N2BJ.js +0 -58
  56. package/dist/server/chunk-QXOCY5ZE.js +0 -72
  57. package/dist/server/favicon-static.d.ts +0 -4
  58. package/dist/server/favicon-static.d.ts.map +0 -1
  59. package/dist/server/favicon-static.js +0 -8
  60. package/dist/web/assets/index-BgHcVJO3.js +0 -643
  61. package/dist/web/assets/index-bCIH1Sdx.css +0 -1
  62. /package/dist/{cdn → app}/apple-touch-icon.png +0 -0
  63. /package/dist/{cdn → app}/favicon-96x96.png +0 -0
  64. /package/dist/{cdn → app}/favicon-black.svg +0 -0
  65. /package/dist/{cdn → app}/favicon-white.svg +0 -0
  66. /package/dist/{cdn → app}/favicon.ico +0 -0
  67. /package/dist/{cdn → app}/favicon.svg +0 -0
  68. /package/dist/{cdn → app}/providers/anthropic.png +0 -0
  69. /package/dist/{cdn → app}/providers/google.png +0 -0
  70. /package/dist/{cdn → app}/providers/openai.png +0 -0
  71. /package/dist/{cdn → app}/site.webmanifest +0 -0
  72. /package/dist/{cdn → app}/web-app-manifest-192x192.png +0 -0
  73. /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/favicon-static.ts
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 resolveDistCdnDir() {
141
- const here = path2.dirname(fileURLToPath2(import.meta.url));
29
+ function resolveInspectorAppDir() {
30
+ const here = path.dirname(fileURLToPath(import.meta.url));
142
31
  for (const dir of [
143
- path2.resolve(here, "cdn"),
32
+ path.resolve(here, "app"),
144
33
  // dist/cli.js (bundled)
145
- path2.resolve(here, "../cdn")
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 (existsSync2(path2.join(dir, "inspector.js"))) {
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/cdn/inspector.js)"
44
+ "Inspector bundle not found (expected dist/app/inspector.js)"
154
45
  );
155
46
  }
156
- function registerInspectorStaticAssets(app2, basePath = "") {
157
- const cdnDir = resolveDistCdnDir();
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 = path2.resolve(cdnDir, relative);
166
- const root = cdnDir.endsWith(path2.sep) ? cdnDir : `${cdnDir}${path2.sep}`;
167
- if (!file.startsWith(root) || !existsSync2(file)) {
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 = path2.extname(file);
171
- return c.body(readFileSync2(file), 200, {
172
- "Content-Type": CONTENT_TYPES2[ext] ?? "application/octet-stream",
173
- "Cache-Control": "public, max-age=3600"
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.15",
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
- build: "npm run build:client && npm run build:client-exports && npm run build:server && npm run build:cli && npm run build:cdn",
229
- "build:cdn": "vite build --config vite.cdn.config.ts",
230
- "watch:cdn": "vite build --watch --config vite.cdn.config.ts",
231
- "preview:cdn": "vite preview --config vite.cdn.config.ts --host 127.0.0.1",
232
- "serve:cdn": "pnpm build:cdn && pnpm preview:cdn",
233
- "analyze:cdn": "ANALYZE=true vite build --config vite.cdn.config.ts",
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/cdn-shell.ts
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 renderDefaultInspectorAssetLoader() {
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
- ${stylesheetTag}
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
- ${assetLoader}
375
+ <script type="module" src="${assets.jsUrl}"></script>
515
376
  </body>
516
377
  </html>`;
517
378
  }
518
- function registerInspectorCdnShell(app2, config, basePath = "") {
519
- const assets = resolveInspectorAssetUrls(config?.inspectorMode, basePath);
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
- const assets2 = resolveInspectorAssetUrls(config?.inspectorMode, basePath);
529
- return c.html(generateCdnShellHtml(effectiveConfig, basePath, assets2));
394
+ return c.html(
395
+ generateInspectorShellHtml(effectiveConfig, basePath, assets)
396
+ );
530
397
  };
531
- registerInspectorFaviconStatic(app2, basePath);
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.req.url, origins)) {
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
- const body = serializeBody(request.body, headers);
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 path3 = serverUrl.pathname === "/" ? "" : serverUrl.pathname;
756
+ const path2 = serverUrl.pathname === "/" ? "" : serverUrl.pathname;
863
757
  return [
864
- new URL(`/.well-known/oauth-protected-resource${path3}`, serverUrl.origin),
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 path3 = issuer.pathname === "/" ? "" : issuer.pathname;
763
+ const path2 = issuer.pathname === "/" ? "" : issuer.pathname;
870
764
  return [
871
- new URL(`/.well-known/oauth-authorization-server${path3}`, issuer.origin),
872
- new URL(`/.well-known/openid-configuration${path3}`, issuer.origin),
873
- new URL(`${path3 || ""}/.well-known/openid-configuration`, issuer.origin)
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") return body;
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, requestUrl, allowed) {
1106
+ function isAllowedOrigin(origin, c, allowed) {
1108
1107
  try {
1109
1108
  const normalized = normalizeOrigin(origin);
1110
- return normalized === new URL(requestUrl).origin || allowed.has(normalized);
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
- const response = await fetch(targetUrl, {
1278
- method,
1279
- headers,
1280
- body,
1281
- redirect: "manual"
1282
- });
1283
- if (response.status >= 300 && response.status < 400) {
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
- const redirectUrl = new URL(location, targetUrl).toString();
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
- const responseHeaders = {};
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
- const contentType = response.headers.get("content-type") || "";
1321
- const upstreamContentLength = response.headers.get("content-length");
1322
- const isTrueStream = isOpenEndedSseResponse(
1323
- contentType,
1324
- upstreamContentLength
1325
- );
1326
- if (isTrueStream) {
1327
- return new Response(response.body, {
1328
- status: response.status,
1329
- statusText: response.statusText,
1330
- headers: responseHeaders
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: inspectorMode === "standalone"
1516
+ oauthProxyAllowLoopback: true
1511
1517
  });
1512
- registerInspectorCdnShell(app, {
1513
- inspectorMode,
1518
+ registerInspectorShell(app, {
1519
+ inspectorMode: "standalone",
1514
1520
  manufactChatUrl: process.env.MANUFACT_CHAT_URL
1515
1521
  });
1516
1522
  async function startServer() {