Skip to content

API-285: Authenticated MCP setup verification (nansen mcp verify) - #502

Open
gulshngill wants to merge 2 commits into
feat/api-285-mcp-installfrom
feat/api-285-mcp-verify
Open

API-285: Authenticated MCP setup verification (nansen mcp verify)#502
gulshngill wants to merge 2 commits into
feat/api-285-mcp-installfrom
feat/api-285-mcp-verify

Conversation

@gulshngill

@gulshngill gulshngill commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds authenticated verification for the MCP setup.

nansen mcp verify            # checks the saved credential
nansen mcp verify <client>   # checks the credential installed in that client's config

Why

Some MCP protocol discovery operations can succeed without credentials, so a typo, rotated, or placeholder key may appear to install successfully and only fail on the first real data call. verify makes that call up front and maps failures to an actionable next step.

Design / security

  • Client mode verifies the credential in the client's configured server entry. It accepts hand-written entries that still target the official server URL/transport, warns about non-security-relevant differences, and refuses entries that would send the key elsewhere before making a network call.
  • Redirects are rejected so the credential cannot be forwarded to another URL.
  • Credentials are never printed; response bodies are redacted before being used in messages.
  • --dry-run is rejected up front so the credit-consuming path is never reached silently; credit cost is disclosed in output, usage, schema, and README.
  • Observed failure shapes map to actionable guidance, including missing credentials, invalid credentials, HTTP errors, malformed responses, connectivity failures, and timeouts.
  • The transport uses stateless streamable HTTP with the required content negotiation and response parsing.
  • install now points users to verify as the final setup step.

Tests

  • npm test: 54 files, 2,010 passed / 2 skipped
  • npm run lint: clean
  • Coverage includes request shape, redirects, response classification, SSE response matching, status-family mapping, config-drift refusal, dry-run rejection, credential redaction, and CLI registration.

🤖 Generated with Claude Code

…API-285)

Closes the verification half of API-285 (child API-318): tools/list and
even some free tools succeed without a key, so a broken credential setup
looks fine until the first paid data call. verify makes that call — one
JSON-RPC tools/call of token_info against the hosted server — and maps
every observed failure mode to an actionable next step.

Two modes: bare verify checks the nansen login / NANSEN_API_KEY
credential; verify <client> checks the key actually installed in that
client's config, refusing (before any network call) entries that drift
from what install writes, so the key can never be sent to a non-official
URL. Redirects are refused (redirect: 'error'), the key is never
printed, response bodies are redacted, --dry-run is rejected on the
credit-consuming path, and per-status-family HTTP guidance covers
401/403, 429, 5xx, and unexpected responses.

Live-verified against mcp.nansen.ai (server nansen-mcp 3.2.4): valid
key → success; invalid key → actionable 401 guidance; install→verify
round trip in a clean HOME.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@gulshngill

gulshngill commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

Independent review

Verdict: REQUEST_CHANGES

Protocol behavior, credential redaction, endpoint pinning, live verification, and the existing test suite were checked and are generally sound. Two fixes are needed:

  1. Medium — src/commands/mcp.js:104-112, used at 376-379: extractInstalledKey requires deep equality with the exact config produced by the installer. Valid manually-created or deep-link configs with the official URL/header but extra or reordered fields are rejected, even though the verification request uses the module’s constant official URL and never trusts the config URL. Gate on the security-relevant fields instead: official URL (or the pinned official desktop transport) plus a non-empty key; warn about extra fields rather than treating them as drift.

  2. Medium — missing handler-level failure coverage around src/commands/mcp.js:437-440: the primary live failure shape is HTTP 200 with result.isError and an invalid-key message. Classification helpers are tested, but the command path is not. Add a test that drives this response through the command and asserts exit 1/actionable output without echoing the key. The current coverage claim that all classification branches are covered is not accurate.

Follow-ups noted by the reviewer: classify insufficient credits separately, verify the installed desktop transport rather than only direct HTTP, and tighten the success gate. These are non-blocking relative to the two fixes above.

…match

Addresses the two blocking findings on #502.

extractInstalledKey deep-equalled buildServerEntry(), so valid official
configs — hand-written, deep-link, or simply carrying an extra field —
were refused with "re-run install" even though verify sends the key to
the NANSEN_MCP_URL constant and never to the config's URL. It now gates
on what actually decides where the key goes:

- HTTP clients: the official URL (trailing slash tolerated), no
  command/args (which would make the client spawn a process and hand it
  the key), and exactly one case-insensitive NANSEN-API-KEY header.
- Claude Desktop: npx running the pinned mcp-remote itself (an npm
  execution override such as --package= keeps every token while running
  another binary), aimed at the official URL and no other, the key passed
  by the exact ${NANSEN_API_KEY} reference after --header with no
  competing key header (mcp-remote's last assignment wins), and no env
  variable beyond the key — env is npx's execution surface.

Everything else is now a warning from the new entryDriftNotes(), which
names the missing/changed/extra fields and never their values, so a note
cannot carry the key.

Test coverage for the primary live failure shape: an HTTP 200 body with
result.isError and an invalid-key message, driven through the command and
through runCLI — exit 1, actionable guidance, key absent from the error
and from all output even when the server echoes it back.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant