Releases the pending v1 security and hygiene fixes: the js-yaml
override correction (#2072), the remaining audit advisories (#2074),
and the esbuild/fsevents install-script denials (#2079). Together they
take `npm audit` from 8 advisories to 0.
Version fields only — no functional change.
The lockfile version fields were edited directly rather than via
`npm run update-version`, which finishes with a bare `npm install`
that re-resolves the whole tree; on this branch that churns 164
packages including production deps (@modelcontextprotocol/sdk, hono,
@hono/node-server 1.x -> 2.x major, jose, ws).
Fixes#2080
Claude-Session: https://claude.ai/code/session_01Gq5jMmxRUphrVbfNbYVmQH
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
npm 11.19 gates dependency lifecycle install scripts behind an
allowScripts allowlist and warns about any package not yet reviewed.
Three were pending: esbuild's postinstall and two copies of fsevents.
None of them is needed. esbuild resolves its platform binary through
optionalDependencies (@esbuild/darwin-*), so install.js is only a
validation/fallback-download step, and both fsevents copies ship a
prebuilt fsevents.node — their install script is just a node-gyp
rebuild fallback. Verified: builds and tests pass with the scripts
skipped.
Denying rather than approving keeps the warning quiet without granting
a standing "run this package's arbitrary install script" permission.
Claude-Session: https://claude.ai/code/session_01Gq5jMmxRUphrVbfNbYVmQH
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds overrides for the five advisories present in the production
dependency tree, plus two dev-only stragglers, bringing `npm audit`
to zero.
Only ip-address is meaningfully reachable: server/src/index.ts builds
sandboxRateLimiter from express-rate-limit, which parses IPs via
Address6 for subnet keying. Impact here is a rate-limit key bypass on
/sandbox rather than the SSRF the advisory titles imply, since req.ip
derives from the socket and the proxy binds localhost.
The other four are installed but unexercised: hono middleware is never
wired up, @hono/node-server's serve-static is never imported (only
getRequestListener), fast-uri is reached only for ajv format checks
whose result is never trusted for a host decision, and brace-expansion
expands serve-handler's config-supplied pattern rather than the
attacker-supplied path. nanoid and postcss are dev-only.
Every patched release exists within the current major line.
@hono/node-server is deliberately held at 1.19.17 rather than 2.x — a
major bump has no place on a deprecated branch.
Lock entries are patched in place rather than re-resolved, keeping the
diff to the affected packages.
Fixes#2074
Claude-Session: https://claude.ai/code/session_01Gq5jMmxRUphrVbfNbYVmQH
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The root `overrides` block pinned js-yaml to versions inside the
advisory range (3.15.0 and 4.2.0), so `npm audit fix` reported a fix
as available and then changed nothing — overrides take precedence.
Bumps both pins to the patched `v3-legacy` / `v4-legacy` releases,
addressing GHSA-52cp-r559-cp3m and GHSA-5p4m-2wfm-xmqj.
Exposure is development-only: `npm ls js-yaml --omit=dev` is empty and
both copies are reachable only via eslint and ts-jest/istanbul, neither
of which ships in the published package.
Both patched versions have an identical dependency and bin shape, so
the lock entries were patched in place — 6 lines, no transitive churn.
Fixes#2072
Claude-Session: https://claude.ai/code/session_01Gq5jMmxRUphrVbfNbYVmQH
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
`e2e_tests.yml` filtered on `branches: [main]` for both `push` and
`pull_request` — the same leftover as main.yml, but with a sharper
consequence: `main` never matches on this branch, so the filter matched
nothing and the suite has never run on `v1/main` at all. The branch's full
run history contains zero `Playwright Tests` runs, only
`Run install, format, lint, build, and test on every push` and `CLI Tests`.
Repointed both filters at `v1/main` so the gate actually gates. Unlike the
main.yml change this is a real behavior change — it turns a dead workflow
back on — so it is deliberately a separate commit.
Refs #2065
Signed-off-by: cliffhall <cliff@futurescale.com>
The push filter still listed `main`, and `pull_request` carried no branch
filter at all — both leftovers from when the v1 line was itself `main`.
Neither describes the topology now: `v1/main` publishes straight to the
`v1-latest` dist-tag and never merges into `main`, which carries v2 and
publishes `latest`.
No behavior change on any path that runs today. For `push`, GitHub runs the
workflow file from the pushed commit, so this file was only ever consulted
for `v1/main`; `main` could not match unless a commit on that branch carried
a v1-shaped tree. The same resolution rule made the unfiltered
`pull_request` harmless, since a PR's workflow comes from its merge ref.
The `release` trigger is untouched, so publishing is unaffected — it is
gated on the event type and the release's target commit, never on these
filters.
The value is that the file now states its intent, and the stale `main`
entry can no longer become live if anything is ever restored onto that
branch.
Closes#2065
Signed-off-by: cliffhall <cliff@futurescale.com>
Addresses the round-2 Copilot review (both suppressed comments).
- createPinnedAgent now takes the whole validated address list instead of
a single IP, so Node's address-family fallback still works: a
dual-stack host whose first address is unreachable (IPv6 on an
IPv4-only network) connects via a later one. The set net.connect may
choose from is exactly the prevalidated set, so the TOCTOU guarantee is
unchanged. Empty input throws ProxyTargetError rather than pinning
undefined.
- Tests: dual-stack pass-through, the empty-list guard, and an
end-to-end request where the first pinned address is a black hole
(2001:db8::1) and the connection must complete via the second.
- server/tsconfig.json excludes src/**/__tests__/** so the suite is no
longer emitted into build/ and published with the package (verified:
build/__tests__ is gone). tsconfig.test.json keeps the suite
type-checked, and `npm test` runs it before vitest.
Signed-off-by: cliffhall <cliff@futurescale.com>
Addresses the round-1 Copilot review.
- createPinnedAgent: Node's net.connect runs with autoSelectFamily on
(Node >= 20), so it calls the lookup hook with `{ all: true }` and
requires an ARRAY of `{ address, family }`. The scalar-only callback
failed every hostname request with ERR_INVALID_IP_ADDRESS, so /fetch
could only reach literal-IP targets. Both callback shapes are now
supported. Reproduced against Node 22/26 before and after.
- Tests: cover the `{ all: true }` shape for IPv4 and IPv6, and add an
end-to-end request through the agent over the production path
(node-fetch -> http.Agent -> net.connect) using an unresolvable
hostname, so the request can only succeed via the pinned IP. Verified
these three fail without the fix. Replaced the `Function`-typed casts
with a typed pinnedLookup() helper.
- CI: run the new server suite (.github/workflows/main.yml) and add a
root `test-server` script, so this regression coverage is enforced.
- package-lock.json: sync the server workspace manifest mirror with the
added @types/node and vitest devDependencies.
- index.ts: drop the `agent as any` cast and its eslint-disable — the
node-fetch RequestInit already types `agent`, and tsc is clean without
the suppression.
- Formatting: prettier-check now passes (it failed on the two new files).
Signed-off-by: cliffhall <cliff@futurescale.com>
#1828 pinned v1 publishes to `--tag v1`, following the runbook (#1804 §1). That
tag name is not legal: npm rejects any dist-tag that parses as a SemVer range,
and `v1` parses as `1.x`.
npm error Tag name must not be a valid SemVer range: v1
Found while running the companion `npm dist-tag add` step, which failed the
same way. So the merged flag would not have "published to the wrong tag" — the
next v1 security release would have failed to publish at all, and only at
release time.
`v1-latest` is accepted (verified against semver.validRange, along with
`legacy`, which was the other candidate). The workflow comment now records both
failure modes so neither the flag nor the specific tag name gets "cleaned up"
later.
Claude-Session: https://claude.ai/code/session_01Txmv2qqv3yeKgRzoqXytzD
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
`publish-all` had no `--tag`, so npm assigned `latest` to whatever it
published. That was correct for 1.0.1 — the deprecation release had to take
`latest` to reach npx users — but it becomes a trap the moment 2.0.0 ships: a
1.0.2 security fix published from `v1/main` would move `latest` back to v1, and
every `npx @modelcontextprotocol/inspector` in the world would silently get the
deprecated line.
`--tag v1` on both halves covers all four package names — `--workspaces` fans
out to inspector-client/-server/-cli, and the second invocation is the root.
Nothing enforces this flag, and the cost of dropping it surfaces months later
in a way that looks like an npm bug rather than a missing argument, so the
workflow carries a comment at the point of use.
Phase 3 of the v2 go-live runbook (#1804 §1).
Claude-Session: https://claude.ai/code/session_01Txmv2qqv3yeKgRzoqXytzD
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* chore: v1 deprecation notices in all four packages, bump to 1.0.1
Phase 2 of the v2 go-live runbook (#1804 §4). v1 publishes four separately
installable names, so each gets its own notice — a root-README-only change is
invisible to anyone who installed a sub-package. `cli/` and `server/` ship no
README at all, so for those two the runtime banner plus the forthcoming
`npm deprecate` (#1816) are the only notice their users ever get.
Notices, all using identical wording so the runtime banner and the npm warning
agree:
- root `README.md` and `client/README.md`
- `cli/src/cli.ts` — stderr banner in `main()`. Also covers the root
`mcp-inspector` bin, which is `cli/build/cli.js`. Deliberately **stderr**:
`--cli` mode emits JSON on stdout and callers pipe it, so a banner there
would corrupt output. Verified against a real server — stdout stays
parseable JSON while the banner goes to stderr.
- `server/src/index.ts` — alongside the existing proxy listening log
- `client/bin/start.js` — alongside the existing startup URL log
- `client/src/components/DeprecationBanner.tsx` — dismissible in-app banner,
dismissal persisted in localStorage with both read and write guarded so a
storage failure degrades to "banner reappears" rather than breaking the app
AGENTS.md is replaced with a hard stop: v1 takes security fixes only, by issue
only, and agents must never open a PR against v1/main.
Version bumped to 1.0.1 across all four package.json files plus the lockfile
(`npm run update-version`); `check-version` passes. The lockfile diff is
restricted to the version fields — the `libc` churn a newer npm wanted to
introduce was reverted as unrelated.
`publish-all` is deliberately NOT changed here: 1.0.1 must take `latest`, which
is the entire point of shipping this notice to npx users. Adding `--tag v1` is
#1816, immediately after this publishes.
Note for review: `client/src/App.tsx` shows ~836 changed lines but `git diff -w`
shows 8 — wrapping the layout in a flex column added one nesting level and
prettier re-indented the subtree. Review it with `-w`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Txmv2qqv3yeKgRzoqXytzD
* docs: replace the README screenshot with one showing the deprecation banner
Captured from this branch's build: v1.0.1 in the sidebar heading, the
dismissible deprecation banner across the top, connected to the everything
server with tools listed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Txmv2qqv3yeKgRzoqXytzD
* docs: pin the README screenshot to v1/main instead of main
The image URL must stay absolute — a relative path breaks on the npm package
page, which renders the README outside any repo context. But it was pinned to
`main`, so the v1 README rendered whatever `main` happened to hold: today the
pre-deprecation screenshot, and after the v2 tree swap (#1817) either the v2
screenshot or a 404 if that path doesn't exist there.
Pinning to `v1/main` keeps it absolute for npm, shows the deprecation banner
once this merges, and survives the swap. Verified all candidate refs resolve
200 — GitHub disambiguates the slash in `v1/main` as the ref without needing a
`refs/heads/` prefix.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Txmv2qqv3yeKgRzoqXytzD
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Previously assertSafeProxyTarget resolved the target hostname and validated
the IPs, but safeProxyFetch then passed the raw URL to node-fetch, which
performed its own DNS lookup. In the window between the two resolutions an
attacker who controls the domain's TTL could flip the record to
169.254.169.254 (cloud-metadata), causing fetch() to connect to the instance-
metadata service even though the block-list check passed.
Fix: assertSafeProxyTarget now returns the validated IP addresses. On each
hop of safeProxyFetch we call createPinnedAgent() to build an http/https.Agent
whose lookup hook unconditionally returns the pre-validated IP. node-fetch
uses that hook instead of the OS resolver, closing the TOCTOU window entirely.
Refactoring: isBlockedProxyAddress, assertSafeProxyTarget, and the new
createPinnedAgent are extracted to server/src/proxy-security.ts so they can
be unit-tested in isolation. A vitest suite is added to the server package
(the first unit tests for this package) with 26 cases covering the block-list,
DNS validation, IP-pinning, and the TOCTOU guarantee itself.
* fix(server): guard the /fetch proxy against SSRF to link-local/metadata
The /fetch proxy forwarded any user-supplied http/https URL straight into
fetch(), so a malicious remote MCP server could advertise OAuth metadata
pointing at the cloud metadata endpoint (169.254.169.254) and have the proxy
exfiltrate instance credentials (CodeQL js/request-forgery, alert #53).
Add a targeted SSRF guard: resolve the target host and reject link-local /
cloud-metadata addresses (169.254.0.0/16, IPv6 link-local, AWS IPv6 IMDS).
Loopback and RFC1918/private LAN targets stay reachable, since connecting to
local and self-hosted MCP servers is a core Inspector use case. Redirects are
now followed manually so every hop is re-validated, closing the
redirect-to-internal bypass. Returns 403 on a blocked target.
Adds endpoint tests for a direct metadata target, a redirect into a blocked
address, and normal redirects between allowed hosts.
Resolves code-scanning alert #53. Part of #1706.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HiccCEh9mwCfVzE8qYcop1
* fix(server): block IPv4-mapped IPv6 form of metadata IP in /fetch guard
The IPv4-mapped IPv6 branch of isBlockedProxyAddress only matched the
dotted-decimal form (::ffff:169.254.169.254), but the WHATWG URL parser
serializes IPv4-mapped addresses in hex (::ffff:a9fe:a9fe), so
http://[::ffff:169.254.169.254]/ resolved past the guard and still reached
the cloud metadata service on dual-stack hosts — the exact vector this guard
targets.
Extract the embedded IPv4 from both the hex and dotted forms and re-check it.
Adds a regression test for the IPv4-mapped IPv6 metadata address.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HiccCEh9mwCfVzE8qYcop1
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
npm does not reliably apply these transitive overrides to an existing
lockfile (bare and targeted overrides via install/update leave residual
vulnerable copies). This regenerates the lockfile from scratch with
version-targeted overrides so every copy of the affected packages
resolves to a patched version.
Resolves Dependabot alerts:
- esbuild #121 (-> 0.28.1)
- minimatch #69, #66, #61 (3.x -> 3.1.5)
- @babel/core #132 (-> 7.29.7; completes the partial fix from #1712)
Also pins js-yaml overrides (3.15.0 / 4.2.0) to prevent regression.
Supersedes #1713, #1714, #1715. Part of #1706.
Verified: 0 vulnerable copies across all advisories; build + client (535)
+ cli tests pass; lockfile internally consistent.
Claude-Session: https://claude.ai/code/session_01HiccCEh9mwCfVzE8qYcop1
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolves Dependabot alert #132. Uses a version-targeted override
(@babel/core@<=7.29.0) so all copies of the vulnerable range are bumped.
Part of #1706.
Claude-Session: https://claude.ai/code/session_01HiccCEh9mwCfVzE8qYcop1
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bumps shell-quote from ^1.8.3 to ^1.8.4 in the root and server
packages to resolve a critical security advisory. Adds an npm
override so concurrency's pinned shell-quote@1.8.3 also resolves to
a patched version, leaving no vulnerable copy in the tree.
Claude-Session: https://claude.ai/code/session_01HiccCEh9mwCfVzE8qYcop1
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bumps the client's vite dev dependency from ^7.1.11 to ^7.3.5 to
resolve Dependabot security alerts.
- #123 (HIGH): vite server.fs.deny bypass, patched in 7.3.5
- #129 (MEDIUM): vulnerability in launch-editor, a transitive dep of
vite; the newer vite no longer pulls in launch-editor, clearing it
Claude-Session: https://claude.ai/code/session_01HiccCEh9mwCfVzE8qYcop1
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bumps vitest from ^4.0.17 to ^4.1.0 in the cli workspace to resolve
Dependabot alert #119 (CRITICAL). Resolves via vitest@4.1.10.
Claude-Session: https://claude.ai/code/session_01HiccCEh9mwCfVzE8qYcop1
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "Check formatting" step in main.yml ran `npx prettier --check .`
before `npm install`, so npx fetched the LATEST prettier rather than the
version pinned in package-lock.json. This made the format check
non-deterministic and red on open PRs whenever the latest published
prettier differed from the pinned one.
- Pin prettier to exact 3.9.5 in devDependencies and update the lockfile
- Reformat the repo with the pinned prettier (expected source churn)
- Move the format check after `npm install --no-package-lock` and run it
via `npm run prettier-check` so CI uses the locally-installed pinned
prettier
Claude-Session: https://claude.ai/code/session_01HiccCEh9mwCfVzE8qYcop1
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Fix resource refresh button not triggering re-fetch (#1120)
The readResource function had an early return when the resource URI
was already cached, preventing the Refresh button from re-fetching.
Add a force parameter to bypass the cache on refresh, and update the
cache-hit path to still sync the displayed content when switching
between previously viewed resources.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: don't cache failed resource reads and rename force to bypassCache
Failed resource reads were cached in resourceContentMap, causing stale
errors to be served on re-click instead of retrying. Errors now only
set the display content without polluting the cache.
Also renamed the `force` boolean parameter to a `{ bypassCache }` options
object for self-documenting call sites.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: remove flaky resource-refresh e2e test
The test depends on npx downloading @modelcontextprotocol/server-everything
at runtime and uses waitForTimeout, making it unreliable in CI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: invalidate stale cache on refresh failure and rename opts to options
Clear cached resource content when a bypassCache refresh fails, so
subsequent clicks retry the fetch instead of serving stale data.
Rename the inline wrapper parameter from opts to options for consistency.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: surface read errors for resource_link in Tool results
Previously, failed resource_link reads left resourceContentMap[uri]
empty, so ResourceLinkView rendered an empty expansion with no error
feedback when a read failed in the Tools tab. Track errors in a
separate resourceErrorMap and thread it through ToolsTab/ToolResults
to ResourceLinkView so the user sees the error message. The existing
cache-skip behavior is preserved (errors don't pollute the content
map, so retries still work).
Also adds a ResourcesTab test asserting the Refresh button passes
{ bypassCache: true } to readResource, to prevent regression of #1120.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: preserve cached content on refresh failure and stop writing error JSON to resourceContent
Don't delete resourceContentMap[uri] when a bypassCache refresh fails;
a transient network error should not wipe the last-known-good content.
To prevent serving stale content after a known error, the cache-hit
guard now also skips cache when resourceErrorMap[uri] is set, forcing
a re-fetch until a successful read clears the error.
Also stop writing JSON.stringify({ error }) into resourceContent in
the catch block. sendMCPRequest already sets errors.resources (which
the ResourcesTab Alert displays), and the Tools tab shows errors via
resourceErrorMap. Writing error JSON into resourceContent was both
redundant and could bleed through as JsonView content after callers
cleared errors.resources before the next read.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use presence checks instead of truthiness for resource error state
Switch from truthiness checks to presence checks in the resource
error/content handling so an empty-string error message is still
treated as an error. readResource's cache-hit guard now uses
`uri in resourceContentMap` / `!(uri in resourceErrorMap)`,
ToolResults passes `resourceError?.[item.uri]` (possibly undefined)
through to ResourceLinkView, and ResourceLinkView renders the error
branch when `resourceError !== undefined`.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use hasOwnProperty instead of `in` for resource cache checks
The `in` operator also matches inherited properties like `toString`
and `constructor`, which would cause false cache hits if a server ever
returns a URI with one of those names. Switch the cache-hit guard and
the error-map cleanup to Object.prototype.hasOwnProperty.call so only
real cached entries match.
Object.hasOwn would be cleaner but requires ES2022 lib support which
the current tsconfig doesn't include.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: clear resource error on read start, not only on success
Previously, resourceErrorMap[uri] was only cleared when a read
succeeded. During an in-flight retry (e.g., after clicking Refresh on
a previously-failed resource), the stale error would remain in the
map and render in ResourceLinkView while the retry was still pending.
Clear the error entry after the cache-hit check passes, right before
sending the request, so the UI shows the last-known-good content (or
a loading state) during the retry instead of the prior error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use || instead of ?? for error message fallback
The ?? operator only falls back on null/undefined, so an Error with
an empty message (new Error("")) would produce an empty string and
render a blank error block. Switch to || so empty strings also fall
through to String(error), which produces a non-empty representation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: only sync resourceContent on cache hit from Resources tab
The cache-hit path called setResourceContent unconditionally, which
meant expanding a resource_link in the Tools tab could overwrite the
Resources tab's displayed content without updating selectedResource.
Guard the setResourceContent call with a currentTabRef check so only
cache hits from the Resources tab update the display.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per the MCP spec (2025-11-25), when a client observes input_required
status during task polling it must call tasks/result to give the server
a chance to deliver queued elicitation/sampling requests. Previously the
polling loop only called tasks/result after completed status, leaving
the server unable to trigger elicitation.
After elicitation is handled the task transitions back to working so
taskCompleted is not set — polling continues until a terminal status
is reached.
* feat: add URL-mode elicitation support
The MCP spec (2025-11-25) added URL-mode elicitation alongside form mode,
allowing servers to direct users to external URLs for out-of-band
interactions (auth flows, payments, sensitive data). This implements
client-side support for rendering and handling URL-mode elicitation
requests, and announces the capability during initialization.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add consent dialog for URL elicitation and separate Accept button
Replace the clickable URL link with a consent dialog that displays the
URL as plain text when "Open URL" is clicked, letting the user confirm
before navigating. Add a separate "Accept" button that resolves the
elicitation without the content field, per the MCP spec.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: preserve structured McpError data in request history
When a tool call returns an MCP error (e.g. -32042 URLElicitationRequired),
the catch block was only capturing error.message (a string), discarding the
structured error.code and error.data fields.
Now McpError instances push the full JSON-RPC error structure to history:
{ error: { code, message, data } }
This means the Response panel will show the complete error including
data.elicitations for -32042, rather than just a message string.
* Revert "fix: preserve structured McpError data in request history"
This reverts commit 86f6d093f6596d6a9a52b85df24b66fa2e354d07.
* fix: enforce HTTPS-only for URL elicitation per SEP-1036
Address Claude review on #1423:
- Validate the server-supplied URL before opening it; refuse non-https
schemes (http/file/javascript/data) and malformed URLs, disabling the
"Open URL" button and showing an error state instead of calling
window.open (SEP-1036 URL Security Requirements, a spec MUST).
- Display the parsed destination host prominently in the consent dialog
to surface look-alike/embedded-credential URLs.
- Document that elicitation/complete is surfaced via the notification
panel and resolved manually (no auto-correlation), a conscious choice
per the spec's MAY/SHOULD.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: evalstate <1936278+evalstate@users.noreply.github.com>
Resolves 15 of 16 Dependabot alerts via lockfile-only updates (no
breaking major bumps): handlebars (critical), fast-uri, flatted, hono,
path-to-regexp, rollup, vite, express-rate-limit, @hono/node-server,
ajv, brace-expansion, ip-address, picomatch, postcss, qs, ws, yaml,
and @tootallnate/once.
The remaining minimatch@3.1.2 advisory comes from @eslint/config-array
(dev/lint-only, not shipped) and will clear when ESLint bumps it.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per review feedback on PR #1270: replace the three-way `==` OR chain in
each of the four trigger branches with a single contains(fromJSON(...))
check against a shared list. Same semantics, less duplication, and the
allowed-roles list is now defined once per branch instead of three times.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the workflow's `if:` only checked the event type and the
presence of `@claude` in the body. The job would start for any commenter
and only later fail when the action attempted privileged operations on
behalf of a user without write access. That produced noisy red runs and
wasted runner minutes for what is effectively an authorization rejection.
Add an `author_association` check (OWNER, MEMBER, or COLLABORATOR) to
each of the four trigger branches (issue_comment, pull_request_review_comment,
pull_request_review, issues), so the job is short-circuited at workflow
evaluation time and never starts for unauthorized invokers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous fix only added repository.url to the root package.json, but npm
publish --workspaces publishes each workspace using its own package.json.
The provenance check runs per-package, so each published workspace package
also needs repository.url to match the GitHub repo URL. Also adds type: "git"
to the root and directory fields to the workspaces for correct monorepo
linking on npm/GitHub.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Upgrade npm to ^11.5.1 in the publish job (Node 22 bundles npm 10.x,
which lacks OIDC trusted publishing support)
- Drop NODE_AUTH_TOKEN / NPM_TOKEN secret in favor of OIDC
- Enable provenance attestation via NPM_CONFIG_PROVENANCE
The publish job already had id-token: write and registry-url configured.
* fix(server): sanitize error responses to prevent stack trace exposure
Replace raw error objects passed to res.json() with generic sanitized
messages so internal error details are not leaked to clients. Full
errors continue to be logged server-side via console.error.
Also adds a missing return in the /sse ECONNREFUSED branch, which
previously fell through and attempted a second response after headers
had already been sent.
Resolves CodeQL js/stack-trace-exposure alerts in server/src/index.ts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(server): address review feedback on error sanitization
- Sanitize /fetch route catch block: previously leaked error.message to
clients and silently swallowed errors with no server-side logging.
Now logs via console.error and returns a generic sanitized response.
- Replace JSON.stringify(error).includes("ECONNREFUSED") in the /sse
handler with a check against error.message and String(error.cause).
Error fields are non-enumerable so JSON.stringify(new Error(...))
produces "{}" — the old check only worked by accident when the error
happened to carry enumerable properties.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Ola Hungerford <olahungerford@gmail.com>
* fix: validate serverInfo.websiteUrl before rendering as link
A connected MCP server's serverInfo.websiteUrl was rendered directly
as an <a href> with no protocol check, allowing a malicious server to
supply javascript: (or other unsafe) URLs that would execute on click.
Run the value through validateRedirectUrl and, on failure, show a
warning-colored "Potentially malicious websiteURL field" message
instead of a link. The offending URL is still surfaced via tooltip
for inspection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: render websiteUrl warning below server name
Move the "Potentially malicious websiteURL field" message onto its
own row beneath the icon+name row so it doesn't crowd the server
name horizontally in the narrow sidebar.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>