Add nansen mcp install/verify for local MCP clients (Cursor first) - #511
Add nansen mcp install/verify for local MCP clients (Cursor first)#511gulshngill wants to merge 1 commit into
Found 1 finding within acceptable thresholds
Review Status
✅ Passed
Findings
| Severity | Count |
|---|---|
| 🟡 Medium | 1 |
Review effort: 3/5 (Moderate)
Details
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: https://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);
}Note: Claude suggested: APPROVE_WITH_COMMENTS. Final status determined by severity thresholds.