Skip to content

feat(auth): inject MCP_INSPECTOR_API_TOKEN into served index.html (#1378)#1382

Merged
cliffhall merged 2 commits into
v2/mainfrom
v2/auto-inject-api-token
May 31, 2026
Merged

feat(auth): inject MCP_INSPECTOR_API_TOKEN into served index.html (#1378)#1382
cliffhall merged 2 commits into
v2/mainfrom
v2/auto-inject-api-token

Conversation

@cliffhall
Copy link
Copy Markdown
Member

Closes #1378.

Problem

Reloading the web client at the bare URL (no ?MCP_INSPECTOR_API_TOKEN=… query string) with an empty sessionStorage made every /api/* request 401 — the browser had no way to recover the backend's auth token. Same failure for bookmarks and hand-typed URLs.

Change (Option 1 from the issue — HTML template injection)

The backend already knows the token at index.html serve time, so it now embeds it into the page on every load:

  • New shared helper clients/web/server/inject-auth-token.ts — inserts <script>window.__INSPECTOR_API_TOKEN__ = "…"</script> (before </head>, falling back to </body> then prepend). Escapes </script> so a user-supplied token can't break out of the tag. No-op for an empty token (DANGEROUSLY_OMIT_AUTH).
  • Dev (vite-hono-plugin.ts) — injects via Vite's transformIndexHtml hook.
  • Prod (server.ts) — injects on the / route.
  • App.tsx getAuthToken() — reads window.__INSPECTOR_API_TOKEN__ first, then the ?MCP_INSPECTOR_API_TOKEN=… query string, then sessionStorage. Both fallbacks are preserved, so pasted full URLs keep working.
  • Shared global name lives in INSPECTOR_API_TOKEN_GLOBAL (core/mcp/remote/constants.ts).

Acceptance criteria

  • Reload at bare URL + empty sessionStorage loads the app and calls /api/* without 401 — verified against a live npm run dev: root injects the global, unauthenticated /api/config → 401, injected token → 200.
  • Launcher banner URL still works (query-string path retained as fallback).
  • Integration test exercises the real prod server's //api/* flow (server-token-injection.test.ts) plus helper unit coverage (inject-auth-token.test.ts).
  • AGENTS.md documents the token-recovery order and the new file.

Testing

npm run validate, npm run test:integration (491 passed), and npm run test:storybook (322 passed) all green.

🤖 Generated with Claude Code

)

Reloading the web client at the bare URL (no `?MCP_INSPECTOR_API_TOKEN=…`
query string) with empty sessionStorage made every `/api/*` request 401 —
the browser had no way to recover the backend's auth token.

Embed the token into `index.html` on every page load so the browser no
longer depends on the query string surviving navigation:

- New shared helper `clients/web/server/inject-auth-token.ts` embeds
  `<script>window.__INSPECTOR_API_TOKEN__ = "…"</script>` (escaped against
  `</script>` injection; no-op when auth is dangerously omitted).
- Dev: the Vite plugin injects via `transformIndexHtml`.
- Prod: the Hono server injects on the `/` route.
- `App.tsx` `getAuthToken()` now reads the injected global first, then the
  query string, then sessionStorage (both fallbacks preserved).
- Shared global name lives in `INSPECTOR_API_TOKEN_GLOBAL`
  (`core/mcp/remote/constants.ts`).

Tests: helper unit coverage + an integration test exercising the real
prod server's `/` → `/api/*` flow (injected token authenticates; missing
token 401s). AGENTS.md documents the token-recovery order.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cliffhall
Copy link
Copy Markdown
Member Author

@claude review

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 30, 2026

Claude finished @cliffhall's task in 2m 55s —— View job


Code Review

  • Read changed files
  • Analyze injection helper for security/correctness
  • Review dev (Vite plugin) and prod (Hono server) integration
  • Review App.tsx token resolution order
  • Review test coverage
  • Post review

Summary

Clean, well-scoped fix. The XSS hardening (JSON-stringify + </script> escape), the dev/prod symmetry (single shared helper), and the documented three-tier recovery order in App.tsx/AGENTS.md are all solid. Tests cover the helper unit-level plus an end-to-end / → /api/* round-trip against the real Hono server. Nothing here looks blocking.

A few observations worth thinking about before/after merge:


1. Latent: prod serveStatic SPA fallback serves an un-injected index.html

clients/web/server/server.ts:64-90 registers app.get("/", …) (injects) ahead of app.use("/*", serveStatic({ rewriteRequestPath: …→ "/index.html" })). The / route is fine, but any other path with no dot and no /api prefix (e.g. /oauth/callback — the browser OAuth landing URL, core/auth/browser/providers.ts:40) falls through to serveStatic, which rewrites to /index.html and serves the raw file straight off disk — bypassing the injection helper entirely.

For the OAuth round-trip specifically it likely still works in practice, because the token was saved to sessionStorage on the original launcher-banner load and the App.tsx:105-109 sessionStorage backstop picks it up. But that means the PR's "bare-URL reload works" property is /-only — a bookmark or hand-typed reload at /oauth/callback (or any future SPA route) with empty sessionStorage would 401 again.

Cheapest fix: read the file once, run it through injectAuthToken, and serve it from a small SPA-fallback handler before serveStatic (or hook a transformIndexHtml-equivalent into the static middleware). Fix this →

2. Minor: getAuthToken() doesn't persist fromGlobal into sessionStorage

clients/web/src/App.tsx:84-110 saves to sessionStorage only on the URL-param branch (lines 96-103). If a user only ever loads through injection (the new common case), sessionStorage stays empty — so the third-tier backstop never gets primed, and any later navigation that loses the global (e.g. point #1 above, or an iframe load that doesn't get the inject) has nothing to fall back to.

One-line fix: after if (typeof fromGlobal === "string" && fromGlobal), do the same best-effort sessionStorage.setItem(STORAGE_KEY, fromGlobal) you do for the URL path, then return. Keeps the comment block accurate ("URL value is persisted… so a later navigation that drops it still authenticates" — that intent applies to the injected global too). Fix this →

3. Minor: no Cache-Control on the injected / response

server.ts:64-77 returns c.html(injectAuthToken(html, resolvedAuthToken)) with no cache headers. The token is regenerated on every server start (randomBytes(32) at line 45 when no env override), so a browser- or proxy-cached page carrying a stale token would 401 after a server restart until the user hard-reloads. Low impact for a local dev tool, but a Cache-Control: no-store on this response would be cheap insurance and matches the "fresh token per page load" intent.

4. Nice catches I want to call out positively

  • serializeTokenForScript (inject-auth-token.ts:22-24) is the right combination — JSON.stringify for quote/backslash/control-char safety, plus the << escape to defuse </script> early-close. The test at inject-auth-token.test.ts:41-51 is exactly the case I'd worry about and it's exercised.
  • apply: "serve" + VITEST early-return in vite-hono-plugin.ts:48-58 keeps the plugin inert during Storybook/component tests. Good guard.
  • INSPECTOR_API_TOKEN_GLOBAL centralizes the magic string across helper, constants, App.tsx, and the two test files. No drift risk.
  • dangerouslyOmitAuth path correctly produces an empty resolvedAuthToken and injectAuthToken("…", "") is a documented no-op — matches the "no banner URL token" behavior so the global stays absent when auth is off.

• Branch: v2/auto-inject-api-token

… + no-store (#1378)

Addresses code-review feedback on the token-injection PR:

- Prod `server.ts` SPA deep-link fallback (e.g. `/oauth/callback`) previously
  served the raw index.html off disk via serveStatic, bypassing injection — so
  a bookmark/reload at a non-`/` route with empty sessionStorage would 401.
  Route the SPA fallback through the same `serveIndexHtml` (inject) helper;
  real static assets (paths with a dot) still serve verbatim. Dev already
  injected on every HTML serve via Vite `transformIndexHtml`.
- `getAuthToken()` now persists the injected `window.__INSPECTOR_API_TOKEN__`
  to sessionStorage (not just the URL-param branch), priming the backstop for
  any later navigation that loses the global.
- Injected HTML responses now send `Cache-Control: no-store`, so a page
  carrying a token isn't cached and served stale after a restart regenerates
  the token.

Integration tests added: SPA fallback (`/oauth/callback`) carries the token,
`Cache-Control: no-store` on injected HTML, real assets served verbatim, and
unknown `/api` routes 404 rather than falling through to the HTML shell.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cliffhall
Copy link
Copy Markdown
Member Author

Thanks for the review — addressed all three points in 025f06a.

1. Prod serveStatic SPA fallback served un-injected index.html — Fixed. Refactored server.ts to a shared serveIndexHtml helper and added an app.get("*") SPA fallback after serveStatic: real assets (paths with a dot) still serve verbatim, but any non-/api route that doesn't resolve to a file (e.g. /oauth/callback) now serves the injected HTML. Removed the old rewriteRequestPath that routed SPA paths to the raw file. Added integration tests: /oauth/callback carries the token, real asset.js serves verbatim, and unknown /api/* routes 404 rather than falling through to the shell. (Good catch that this is material for the OAuth landing URL — it's why #1379 stacks on this.)

2. getAuthToken() didn't persist fromGlobal — Fixed. Extracted a persist() helper and call it on both the injected-global and URL-param branches, so the sessionStorage backstop is primed even when the token only ever arrives via injection. Updated the comment to match.

3. No Cache-Control on injected / — Fixed. serveIndexHtml sets Cache-Control: no-store on every injected HTML response (both / and the SPA fallback), so a page carrying a token isn't served stale after a restart regenerates it. Added a test asserting the header on both routes.

Thanks also for the positive callouts on serializeTokenForScript, the VITEST/apply: "serve" guards, and the centralized INSPECTOR_API_TOKEN_GLOBAL — kept all of those as-is.

Note: this is the base of a 4-PR stack (#1383#1385#1387 build on it). I'll propagate these base changes up the stack when we merge down, or sooner if you'd prefer — let me know.

@cliffhall cliffhall merged commit 881ba4b into v2/main May 31, 2026
1 check passed
@cliffhall cliffhall deleted the v2/auto-inject-api-token branch May 31, 2026 00:10
@cliffhall cliffhall restored the v2/auto-inject-api-token branch May 31, 2026 00:11
@cliffhall cliffhall deleted the v2/auto-inject-api-token branch May 31, 2026 00:11
cliffhall added a commit that referenced this pull request May 31, 2026
… 401 match (#1379)

Code-review feedback on the OAuth-wiring PR:

- Callback effect: split completeOAuthFlow vs connect() into separate
  try/catch blocks. A token-exchange failure now reads "OAuth token exchange
  failed … Please try connecting again." (the single-use code is spent and the
  URL was cleared, so a reload can't retry); a post-OAuth connect failure reads
  "Failed to connect" since OAuth actually succeeded and re-clicking Connect
  reuses the persisted tokens.
- isUnauthorizedError: anchor the message fallback on the transport's
  `failed …(401)` wording instead of a bare `(401)`, so an unrelated `(401)`
  spliced into an error message can't trip the OAuth flow. Added a test.
- Documented that clearing the pending id + URL before the server lookup is
  intentional (deleted/renamed server mid-flow → require a fresh Connect).

Also merges the squash-merged #1382 base from v2/main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cliffhall added a commit that referenced this pull request May 31, 2026
…1383)

* feat(auth): inject MCP_INSPECTOR_API_TOKEN into served index.html (#1378)

Reloading the web client at the bare URL (no `?MCP_INSPECTOR_API_TOKEN=…`
query string) with empty sessionStorage made every `/api/*` request 401 —
the browser had no way to recover the backend's auth token.

Embed the token into `index.html` on every page load so the browser no
longer depends on the query string surviving navigation:

- New shared helper `clients/web/server/inject-auth-token.ts` embeds
  `<script>window.__INSPECTOR_API_TOKEN__ = "…"</script>` (escaped against
  `</script>` injection; no-op when auth is dangerously omitted).
- Dev: the Vite plugin injects via `transformIndexHtml`.
- Prod: the Hono server injects on the `/` route.
- `App.tsx` `getAuthToken()` now reads the injected global first, then the
  query string, then sessionStorage (both fallbacks preserved).
- Shared global name lives in `INSPECTOR_API_TOKEN_GLOBAL`
  (`core/mcp/remote/constants.ts`).

Tests: helper unit coverage + an integration test exercising the real
prod server's `/` → `/api/*` flow (injected token authenticates; missing
token 401s). AGENTS.md documents the token-recovery order.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(auth): wire OAuth authorization-code flow into App.tsx (#1379)

OAuth-protected MCP servers could not be connected to from the v2 web
client: the core OAuth pipeline exists, but App.tsx never invoked it, so a
connect attempt 401'd and surfaced "Remote send failed (401): … Missing
Authorization header" as a toast.

Wire the two missing entry points (all core primitives already in place):

- Auto-trigger on 401: in onToggleConnection's catch, detect an upstream
  401 (isUnauthorizedError) and call client.authenticate(), which runs
  discovery + DCR (backend-proxied) and redirects the page to the auth
  server via BrowserNavigation. The initiating server id is persisted to
  sessionStorage first, since the OAuth `state` carries only mode+authId
  and the full-page redirect wipes React state.
- /oauth/callback handler: a mount effect that, once `servers` hydrate,
  parses the callback params, recovers the pending server, rebuilds its
  InspectorClient, runs completeOAuthFlow(code) (PKCE verifier + DCR client
  info survive in BrowserOAuthStorage), replaceState("/") so a reload can't
  replay the single-use code, then connect(). An `error=` callback toasts
  instead of retrying.

connect() already attaches the OAuth provider to the transport
(inspectorClient.ts), so once tokens land in BrowserOAuthStorage the
outbound request carries the bearer token.

Extracted the pure pieces (constants + isUnauthorizedError) to
src/utils/oauthFlow.ts with unit tests. Verified end-to-end in a real
browser against the MCP SDK demo OAuth server: Connect -> redirect ->
auto-approve -> callback -> Connected, with the access token shown in the
Connection Info modal (#1377).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(auth): inject token into prod SPA fallback + prime sessionStorage + no-store (#1378)

Addresses code-review feedback on the token-injection PR:

- Prod `server.ts` SPA deep-link fallback (e.g. `/oauth/callback`) previously
  served the raw index.html off disk via serveStatic, bypassing injection — so
  a bookmark/reload at a non-`/` route with empty sessionStorage would 401.
  Route the SPA fallback through the same `serveIndexHtml` (inject) helper;
  real static assets (paths with a dot) still serve verbatim. Dev already
  injected on every HTML serve via Vite `transformIndexHtml`.
- `getAuthToken()` now persists the injected `window.__INSPECTOR_API_TOKEN__`
  to sessionStorage (not just the URL-param branch), priming the backstop for
  any later navigation that loses the global.
- Injected HTML responses now send `Cache-Control: no-store`, so a page
  carrying a token isn't cached and served stale after a restart regenerates
  the token.

Integration tests added: SPA fallback (`/oauth/callback`) carries the token,
`Cache-Control: no-store` on injected HTML, real assets served verbatim, and
unknown `/api` routes 404 rather than falling through to the HTML shell.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(auth): address #1383 review — split OAuth/connect toasts, tighten 401 match (#1379)

Code-review feedback on the OAuth-wiring PR:

- Callback effect: split completeOAuthFlow vs connect() into separate
  try/catch blocks. A token-exchange failure now reads "OAuth token exchange
  failed … Please try connecting again." (the single-use code is spent and the
  URL was cleared, so a reload can't retry); a post-OAuth connect failure reads
  "Failed to connect" since OAuth actually succeeded and re-clicking Connect
  reuses the persisted tokens.
- isUnauthorizedError: anchor the message fallback on the transport's
  `failed …(401)` wording instead of a bare `(401)`, so an unrelated `(401)`
  spliced into an error message can't trip the OAuth flow. Added a test.
- Documented that clearing the pending id + URL before the server lookup is
  intentional (deleted/renamed server mid-flow → require a fresh Connect).

Also merges the squash-merged #1382 base from v2/main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cliffhall added a commit that referenced this pull request May 31, 2026
…#1384) (#1385)

* feat(auth): inject MCP_INSPECTOR_API_TOKEN into served index.html (#1378)

Reloading the web client at the bare URL (no `?MCP_INSPECTOR_API_TOKEN=…`
query string) with empty sessionStorage made every `/api/*` request 401 —
the browser had no way to recover the backend's auth token.

Embed the token into `index.html` on every page load so the browser no
longer depends on the query string surviving navigation:

- New shared helper `clients/web/server/inject-auth-token.ts` embeds
  `<script>window.__INSPECTOR_API_TOKEN__ = "…"</script>` (escaped against
  `</script>` injection; no-op when auth is dangerously omitted).
- Dev: the Vite plugin injects via `transformIndexHtml`.
- Prod: the Hono server injects on the `/` route.
- `App.tsx` `getAuthToken()` now reads the injected global first, then the
  query string, then sessionStorage (both fallbacks preserved).
- Shared global name lives in `INSPECTOR_API_TOKEN_GLOBAL`
  (`core/mcp/remote/constants.ts`).

Tests: helper unit coverage + an integration test exercising the real
prod server's `/` → `/api/*` flow (injected token authenticates; missing
token 401s). AGENTS.md documents the token-recovery order.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(auth): wire OAuth authorization-code flow into App.tsx (#1379)

OAuth-protected MCP servers could not be connected to from the v2 web
client: the core OAuth pipeline exists, but App.tsx never invoked it, so a
connect attempt 401'd and surfaced "Remote send failed (401): … Missing
Authorization header" as a toast.

Wire the two missing entry points (all core primitives already in place):

- Auto-trigger on 401: in onToggleConnection's catch, detect an upstream
  401 (isUnauthorizedError) and call client.authenticate(), which runs
  discovery + DCR (backend-proxied) and redirects the page to the auth
  server via BrowserNavigation. The initiating server id is persisted to
  sessionStorage first, since the OAuth `state` carries only mode+authId
  and the full-page redirect wipes React state.
- /oauth/callback handler: a mount effect that, once `servers` hydrate,
  parses the callback params, recovers the pending server, rebuilds its
  InspectorClient, runs completeOAuthFlow(code) (PKCE verifier + DCR client
  info survive in BrowserOAuthStorage), replaceState("/") so a reload can't
  replay the single-use code, then connect(). An `error=` callback toasts
  instead of retrying.

connect() already attaches the OAuth provider to the transport
(inspectorClient.ts), so once tokens land in BrowserOAuthStorage the
outbound request carries the bearer token.

Extracted the pure pieces (constants + isUnauthorizedError) to
src/utils/oauthFlow.ts with unit tests. Verified end-to-end in a real
browser against the MCP SDK demo OAuth server: Connect -> redirect ->
auto-approve -> callback -> Connected, with the access token shown in the
Connection Info modal (#1377).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(auth): persist OAuth pre-redirect Network log across the redirect (#1384)

After #1379, the Network tab showed only the post-redirect auth HTTP
(discovery re-run + POST /token); the pre-redirect discovery and the DCR
POST /register that run during authenticate() were lost when the page
navigated to the auth server.

Root causes:

1. Ordering — BrowserNavigation set `window.location.href` before the
   client's `saveSession` event fired (OAuthManager calls onBeforeOAuthRedirect
   *after* auth() already navigated), so the save raced the unload and was
   dropped. Fix: BrowserNavigation now runs a synchronous `beforeNavigate`
   hook immediately before assigning location.href; App wires it through
   createWebEnvironment to flush the active fetch log to RemoteInspectorClient
   Storage (keyed by the authId parsed from the auth URL) via a keepalive POST
   that outlives the unload.

2. Illegal invocation — RemoteInspectorClientStorage defaulted to
   `this.fetchFn = globalThis.fetch` and called `this.fetchFn(...)`, which
   re-binds `this` and makes native fetch throw "Illegal invocation"
   (swallowed by the catch). This silently broke *all* session save/load.
   Fix: default to a wrapper that preserves the global receiver.

3. Restore race — hydrateFetchRequests replaced the list, so a load that
   resolved after the resuming connect appended live entries would clobber
   them. Fix: merge restored (older) entries ahead of live ones, dedupe by id.

saveSession also now uses keepalive: true.

Verified end-to-end against the MCP SDK demo OAuth server: the connected
page's Network tab shows the full handshake — pre-redirect discovery + DCR
/register plus post-redirect discovery + /token as `auth`, alongside
`transport`. Added unit tests for the beforeNavigate ordering and the
hydrate merge/dedupe.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(auth): inject token into prod SPA fallback + prime sessionStorage + no-store (#1378)

Addresses code-review feedback on the token-injection PR:

- Prod `server.ts` SPA deep-link fallback (e.g. `/oauth/callback`) previously
  served the raw index.html off disk via serveStatic, bypassing injection — so
  a bookmark/reload at a non-`/` route with empty sessionStorage would 401.
  Route the SPA fallback through the same `serveIndexHtml` (inject) helper;
  real static assets (paths with a dot) still serve verbatim. Dev already
  injected on every HTML serve via Vite `transformIndexHtml`.
- `getAuthToken()` now persists the injected `window.__INSPECTOR_API_TOKEN__`
  to sessionStorage (not just the URL-param branch), priming the backstop for
  any later navigation that loses the global.
- Injected HTML responses now send `Cache-Control: no-store`, so a page
  carrying a token isn't cached and served stale after a restart regenerates
  the token.

Integration tests added: SPA fallback (`/oauth/callback`) carries the token,
`Cache-Control: no-store` on injected HTML, real assets served verbatim, and
unknown `/api` routes 404 rather than falling through to the HTML shell.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(auth): address #1383 review — split OAuth/connect toasts, tighten 401 match (#1379)

Code-review feedback on the OAuth-wiring PR:

- Callback effect: split completeOAuthFlow vs connect() into separate
  try/catch blocks. A token-exchange failure now reads "OAuth token exchange
  failed … Please try connecting again." (the single-use code is spent and the
  URL was cleared, so a reload can't retry); a post-OAuth connect failure reads
  "Failed to connect" since OAuth actually succeeded and re-clicking Connect
  reuses the persisted tokens.
- isUnauthorizedError: anchor the message fallback on the transport's
  `failed …(401)` wording instead of a bare `(401)`, so an unrelated `(401)`
  spliced into an error message can't trip the OAuth flow. Added a test.
- Documented that clearing the pending id + URL before the server lookup is
  intentional (deleted/renamed server mid-flow → require a fresh Connect).

Also merges the squash-merged #1382 base from v2/main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(auth): address #1385 review — clarify double-save + keepalive cap + test fetch default (#1384)

Code-review feedback on the OAuth Network-log persistence PR:

- Documented the double-save: `FetchRequestLogState`'s `saveSession` listener
  is the backstop; `BrowserNavigation`'s `beforeNavigate` hook is the primary
  flush for the redirect case. Notes the listener may lose the navigation race
  and is harmless when it duplicates (last-writer-wins, identical payload).
- Reworded the keepalive comment in `RemoteInspectorClientStorage.saveSession`:
  the 64KB cap is general (the method is also reachable from the listener with
  the full session log), so a long session could exceed it and drop silently —
  acceptable since the persisted log is best-effort, not load-bearing.
- Added a regression test that constructs `RemoteInspectorClientStorage`
  without a `fetchFn`, stubs `globalThis.fetch`, and asserts the default
  wrapper calls it (locks in the "Illegal invocation" fix, which callers
  otherwise swallow).

Optional items (logger.warn on swallowed save errors; setupClientForServer dep
churn) acknowledged on the PR, not changed.

Also merges the squash-merged #1383 base from v2/main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2 Issues and PRs for v2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Auto-inject MCP_INSPECTOR_API_TOKEN so reloads don't 401 without the query string

1 participant