Skip to content

Add nansen mcp install/verify for local MCP clients (Cursor first) - #511

Closed
gulshngill wants to merge 1 commit into
mainfrom
feat/mcp-install-command
Closed

Add nansen mcp install/verify for local MCP clients (Cursor first)#511
gulshngill wants to merge 1 commit into
mainfrom
feat/mcp-install-command

Conversation

@gulshngill

Copy link
Copy Markdown
Contributor

What

Adds a real one-command MCP setup to the CLI — the command the docs always wanted to exist (see nansen-api#1803, API-317):

nansen login --api-key <key>
nansen mcp install cursor
nansen mcp verify cursor
  • nansen mcp install cursor merges a native url + headers Nansen entry into ~/.cursor/mcp.json (or --config-path), using the saved login key, NANSEN_API_KEY, or --api-key. It preserves other configured servers, refuses to clobber a file that isn't valid JSON, and chmods the file to 0600 since it embeds a credential.
  • nansen mcp verify cursor proves the setup in two steps: a streamable-HTTP initialize handshake against the configured URL (transport), then a credit-free account check with the configured key (auth). The second step exists because the MCP server accepts initialize and tools/list unauthenticated — a handshake alone never proves the key works.

The client registry is a map, so adding e.g. claude-desktop later is one entry.

Why

API-317 flagged that the documented Cursor "one-click" deep link shipped a placeholder key inside an opaque base64 payload. nansen-api#1803 replaces it with copy-paste JSON; this PR gives users the actual one-command path and a machine-checkable verify step.

Testing

  • 21 new unit tests (src/__tests__/mcp.test.js); full suite passes (2115 passed, 2 skipped), eslint clean.
  • Verified live against mcp.nansen.ai:
    • happy path with a valid key → transport: ok, auth: ok, server nansen-mcp 3.2.4, exit 0
    • invalid key → handshake passes, auth step fails with structured INVALID_API_KEY + resolution steps, exit 1
    • merge behavior, invalid-JSON refusal, and 0600 permissions confirmed on disk

Follow-up

Once released, nansen-api's gitbook/mcp/connecting.md Cursor section can add these commands back (they were removed from #1803 because they didn't exist yet). README here already documents them.

🤖 Generated with Claude Code

`nansen mcp install cursor` merges a native url+headers Nansen entry into
~/.cursor/mcp.json using the saved login key (or --api-key), preserves other
configured servers, refuses to clobber invalid JSON, and chmods the file to
0600 since it embeds a credential. `nansen mcp verify cursor` proves the
endpoint with a streamable-HTTP initialize handshake, then validates the
configured key with a credit-free account check — necessary because the MCP
server accepts initialize and tools/list unauthenticated, so a handshake
alone never proves auth. --config-path overrides the client default for
workspace configs and tests.

Verified live against mcp.nansen.ai: happy path (transport ok, auth ok),
invalid-key path (INVALID_API_KEY with resolution steps, exit 1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@nansen-pr-reviewer

Copy link
Copy Markdown

pr-reviewer Summary for #19b9ecb

📝 1 finding

Review completed. Please address the findings below.

Findings by Severity

Severity Count
🟡 Medium 1

Review effort: 3/5 (Moderate)

Summary

This is a well-structured addition of nansen mcp install / nansen mcp verify commands. The credential-in-config concern is handled (parent-dir creation before write, chmod 0600, refusal to clobber invalid JSON). Error messages are actionable, the two-step verify design is correctly motivated in the PR description, and the 21-unit-test suite covers happy paths and the main failure modes. All AGENTS.md checklist items are satisfied: schema.json updated, changeset present with correct minor bump, no interactive prompts, no real network calls in tests.

Findings

src/commands/mcp.js — medium

Malformed JSON response from MCP server produces a misleading "endpoint unreachable" error

In verify, the call to parseMcpResponseBody (lines 179–183) lives inside the try block that wraps the fetch. When the server returns a well-formed HTTP 200 but with a body that isn't valid JSON (or isn't valid SSE), parseMcpResponseBody throws a SyntaxError. That SyntaxError is not a NansenError, so the instanceof NansenError guard on line 185 does not re-throw it — instead it falls through to:

const reason = error.name === 'AbortError'
  ? `timed out after ${timeoutMs}ms`
  : (error.cause?.code || error.message);
throw new NansenError(`MCP endpoint unreachable: ${url} (${reason})`, ErrorCode.NETWORK_ERROR);

The result is a message like MCP endpoint unreachable: hxxps://mcp[.]nansen[.]ai/ra/mcp (Unexpected token '<', "<!DOCTYPE "... is not valid JSON). The endpoint was reachable — it just returned garbage. This will confuse users (and agents) into diagnosing a network problem when the real issue is a bad server response.

Fix: catch parseMcpResponseBody errors separately, or add a guard before the generic wrap:

} catch (error) {
  if (error instanceof NansenError) throw error;
  if (error instanceof SyntaxError) {
    throw new NansenError(
      `MCP handshake failed: ${url} returned an unparseable response (${error.message})`,
      ErrorCode.UNKNOWN
    );
  }
  const reason = error.name === 'AbortError'
    ? `timed out after ${timeoutMs}ms`
    : (error.cause?.code || error.message);
  throw new NansenError(`MCP endpoint unreachable: ${url} (${reason})`, ErrorCode.NETWORK_ERROR);
}

Token usage: 4,137 input, 5,782 output, 653,935 cache read, 40,997 cache write | Usage Guide

New pushes are reviewed automatically with a 10-minute cooldown between reviews. To request a review at any time, comment @nansen-pr-reviewer re-review.


Note: Some URLs in this summary were sanitized for security. Defanged links (hxxp/[.]) are not clickable by design.

@gulshngill

Copy link
Copy Markdown
Contributor Author

Closing as a duplicate: this was opened without checking the existing in-flight MCP stack, which is more complete and pre-dates it — #487 (nansen mcp install for claude-code/claude-desktop/cursor, with atomic merge-writes, backups, --dry-run, uninstall, key redaction) and #502/#508 (nansen mcp verify via a real authenticated MCP tools/call, which is a stronger auth proof than the account-endpoint check here). One useful data point from this PR's live testing carries over: the MCP server accepts initialize and tools/list unauthenticated, so any verify must make a real data call — both #502 and #508 already do this.

@gulshngill gulshngill closed this Aug 21, 2026
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