second-opinion
Cross-checks CoinMarketCap market data against itself — aggregated prices versus the DEX pools and centralised pairs behind them, quote timestamps, order-book/pool depth, and tokenised real-world-asset wrappers — to produce a trust score and an ACT/CAUTION/DO_NOT_ACT verdict with the cited endpoint answers behind every claim. Exposes tools to assess an asset or RWA token, preflight a trade size against available pool depth, and explain each of the seven checks and the endpoints and thresholds it uses.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@second-opinionsecond opinion on PAXG before I buy $25k — is it safe to act?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Second Opinion
A trust layer on top of the CoinMarketCap API. It cross-checks CMC data against itself — the aggregated price against the DEX pools and the centralised pairs behind it, the timestamps against the answer that carried them, the depth an order would meet, a tokenised real-world asset against the other wrappers of the same underlying — scores what it finds out of 100 and answers ACT, CAUTION or DO_NOT_ACT with the endpoint answers behind every statement.
It is built for the moment before an agent acts on a price. It places no order, signs nothing and holds no wallet.
The problem
Market data is read as though it were one fact. It is not. An aggregated price, a pool on a decentralised exchange and a tokenised claim on a real-world asset are three measurements of the same thing, taken at different moments by different venues, and an answer that carries one of them says nothing about whether it agrees with the other two. Nothing in a well-formed response says that the quote is hours old, that the venue behind the price holds less than the order about to be sent, or that the ticker resolved to a different asset from the one that was meant.
An agent is the worst placed to notice. It reads one number, has no second reading to hold it against, and acts. The demonstration in this repository is that failure exactly. The order is a reasonable one:
Buy 25,000 USD of tokenised gold. The ticker is XAU.XAU is the ISO code of an ounce of gold, so it is the ticker such an instruction reaches for. On the answers
recorded live on 2026-09-26, the symbol resolves to XAU9999 Meme (CMC 37470), a token priced around 1e-11 USD, and
25,000 USD is more than twice the depth of the deepest pool behind that price. Both readings come out of
CoinMarketCap's own answers; neither is visible in the one field an agent would have read.
Second Opinion is the reading that makes them visible, built from the same API. It crosses CMC's endpoints against one another, scores what it finds, and cites the recorded answer behind every statement.
npm run demoreplays that order and the same order on a wrapper the checks can follow — one refused, one acted on — with no key, no network and no credit.
Related MCP server: rwa-attest
Requirements
Node.js 20.19 or later
A CoinMarketCap API key. Every measurement in this repository was taken on the Startup plan; which endpoints that plan answers and which it refuses is recorded in docs/ENDPOINTS.md.
Quick start
npm install
cp .env.example .env # paste your key into CMC_API_KEY
npm run check:env # checks the key is readable; never prints it
npm run check -- PAXGOffline, with no key and no network: the two check runs recorded live on 2026-09-25 are replayed out of
fixtures/check.
npm run check -- PAXG --replay=fixtures/check
npm run check -- BTC --replay=fixtures/checkUse it from an MCP host
The server speaks MCP over stdio and registers four tools. Build it once, then point the host at the built entry point:
npm run build # writes dist/mcp/server.jsClaude Desktop
Settings → Developer → Edit Config opens claude_desktop_config.json. Add the server to it:
{
"mcpServers": {
"second-opinion": {
"command": "node",
"args": ["/absolute/path/to/second-opinion/dist/mcp/server.js"],
"env": { "CMC_API_KEY": "your-CoinMarketCap-key" }
}
}
}Restart Claude Desktop. The server appears as second-opinion with its four tools.
Claude Code
claude mcp add second-opinion -e CMC_API_KEY=your-CoinMarketCap-key -- node /absolute/path/to/second-opinion/dist/mcp/server.jsclaude mcp list then shows whether the server answered, and /mcp inside a session lists its tools.
Three things about that configuration
Launch
node dist/mcp/server.js, notnpm run mcp.npm runprints its own banner (> second-opinion@0.1.0 mcp) on stdout, and stdout is where the JSON-RPC stream lives: the banner breaks the handshake before it starts. Themcpscript is for a quick look from a terminal, where the banner is harmless; a host gets the barenodecommand. Everything the server has to say about itself goes to stderr, which the host shows as a server log.The path has to be absolute. A host starts the server from its own working directory, not from this one.
envis optional. At start-up the server loads the.envfile at the root of this repository, so a key already in.envis enough. A value given in the host'senvblock takes precedence over the one in.env.
Offline demo: no key, no network, no credit
The same server, pointed at the recorded answers:
{
"mcpServers": {
"second-opinion": {
"command": "node",
"args": [
"/absolute/path/to/second-opinion/dist/mcp/server.js",
"--replay=/absolute/path/to/second-opinion/fixtures/check"
]
}
}
}In this mode the tools answer for PAXG and BTC, the two assets those runs cover, and for anything else they name the
call that found no recorded answer. A third mode, --record[=dir], makes live calls and writes every answer to a
fixture with the key masked.
The four tools
Tool | Arguments | What it answers |
|
| The verdict, the score out of 100, what each of the seven checks observed or why it could not run, and the endpoints read |
|
| The same assessment with the real-world-asset check in full: how far this wrapper sits from the average tokenized price over the wrappers of the same underlying, and how widely they spread |
|
| The same assessment plus the share of the deepest pool read that an order of this size would take |
|
| What that check measures, the endpoints it reads, the limits it is read against and the recorded measurements those limits were settled on. Needs no API key |
Two notes an agent should read with the answers. side is echoed and changes no measurement: no recorded answer
splits the depth of a venue into bid and ask, so the share of a pool an order takes is the same either way. And the
two order-size limits preflight_trade reads are the only thresholds the calibration does not exercise — the share
is measured, where the line belongs is not. Both are set out in docs/DECISIONS.md.
Checking the configuration before trusting it
tests/mcp-config-doc.test.ts reads the JSON above out of this README, launches the server exactly as it describes
and drives it over stdio with the MCP SDK's own client:
npm test -- mcp-config-docThe web interface
Three pages: a search field, the verdict for one asset with every check and the endpoint answers behind it, and the
published API audit. The API key is read on the server only — there is no client component in the application, and a
deployment that puts a credential under a NEXT_PUBLIC_ name refuses to start.
npm run web:install # once: the interface is its own package (see docs/DECISIONS.md, D15)
npm run web:dev # builds the engine, then starts Next.js on http://localhost:3000With no CMC_API_KEY in the environment it serves the recorded answers of fixtures/ and says so under the verdict,
so the interface runs with no key, no network and no credit.
Deploy it to Vercel
The application lives in web/ and reads three things above its own directory — config/checks.json,
fixtures/ and docs/api_audit.json — so the repository, not web/, is the root the build has to see.
web/vercel.json carries the build settings; two project settings have to be set in the dashboard, because
vercel.json cannot express either:
Project setting | Value | Why |
Root Directory |
| The Next.js app is there, not at the repository root. Vercel reads |
Include source files outside of the Root Directory in the Build Step | enabled | The build reaches |
web/vercel.json then sets:
{
"framework": "nextjs",
"installCommand": "npm install --prefix .. && npm install",
"buildCommand": "npm run build --prefix .. && npm run build"
}Both run from web/ and take the same two steps in the same order as npm run web:build: the engine is installed
and compiled to dist/ first, then the application is built against it. What Vercel would otherwise pick for a
Next.js app — npm install then next build, both inside web/ — would build the application against a dist/
that does not exist yet, which is why both are overridden. Nothing else is: the output stays at web/.next, and
engines.node (>=20.19) leaves Vercel on its current default of Node 24.x.
Environment variables to set on the deployment
Set these under Settings → Environment Variables. Only the first is a secret, and none of them may be given a
NEXT_PUBLIC_ prefix — that prefix is inlined into the browser bundle, and the interface refuses to start if a
credential appears under it.
Variable | Set it to | What happens when it is unset |
| your CoinMarketCap key | The deployment serves recorded answers instead of calling the API |
|
|
|
| a directory of recorded answers |
|
| another audit report to publish |
|
| a cached token-to-RWA index | The index is rebuilt in memory, once per process, from the recorded walk in |
| a writable directory | The system temporary directory, which a serverless host can write to — unlike the |
| the directory holding | Found from the module's own location, or by walking up from the working directory. Needed only on a host that matches neither |
The client settings of .env.example — CMC_CREDIT_BUDGET, CACHE_TTL_SECONDS, CMC_TIMEOUT_MS and
the rest — are read here too, and keep their defaults when unset.
One last thing about a live deployment: a verdict reads several endpoints, so each visit spends credits.
CMC_CREDIT_BUDGET caps a single request and the cache above makes a repeated question free, but a public URL on a
Startup plan is better pointed at SECOND_OPINION_MODE=replay unless the key is meant to be spent.
The seven checks
Id | What it reads against what |
C1 | Aggregated price against the DEX token price |
C2 | Aggregated price against centralised exchange pairs |
C3 | Freshness of the timestamps |
C4 | Liquidity against reported volume |
C5 | RWA wrapper against the average tokenized price |
C6 | Consistency between endpoints |
C7 | Fields the verdict reads that could not be read |
A check that cannot run says why instead of scoring zero, and the score is formed on the checks that did run.
explain gives the same account of any of them, with the limits the run was configured with.
Architecture
One engine, four ways in. The MCP server, the command line, the demonstration agent and the web interface all run the same assessment, so a verdict cannot depend on who asked for it.
src/cmc/— the only code that speaks HTTP to CoinMarketCap: the key read from the environment, retries, CMC's own status codes, a disk cache, a credit counter with a ceiling per run, and the three modes —live,record,replay— that let everything above it run against recorded answers instead of the network.src/normalize/— one normaliser per verified endpoint, all producing the same shape: CMC ID, symbol, USD price, timestamp, volume, liquidity, and the answer each was read from. Units and epoch formats are settled here, so no check ever parses an API response.src/checks/— one file per check, C1 to C7, each returning the same result: a severity, what it measured, the limit it was read against, and the sources behind it.assess.tsruns the call plan for one asset and hands the seven results on.src/score/— weighs the checks that ran into one number out of 100 and reads the verdict off it. A check that could not run is not scored as a zero; it is left out, and the coverage is reported beside the score.src/rwa/— the token-to-asset index C5 needs, built by walking the issuer catalogue once and cached on disk.src/mcp/,src/cli/,src/web/andsrc/demo/are the four front ends;src/audit/andsrc/calibration/measure the engine rather than serve it.
Thresholds live in config/checks.json, never in the code. Recorded answers live in fixtures/, with the key
masked in every one. The Next.js application under web/ is a separate package holding markup and routing only —
everything it decides is decided in src/web/.
The CMC endpoints it reads
Seventeen, every one of them called with a real key and recorded before any code was written against it. Four more were called and refused by the plan — E04 and E21, the per-exchange pairs, which is why C2 reports instead of measuring, plus E12 and E15 — and the client cannot call them at all.
ID | Endpoint | Checks it feeds | Read for |
E01 |
| C7 | The CMC ID a symbol names, which every later call carries |
E02 |
| C1, C3, C6, C7 | The aggregated price, volume and timestamp: the reading the others are held against |
E03 |
| — | The calibration panel, in one call: the first assets by market capitalisation ( |
E05 |
| C7 | The token contract, which decides whether an asset has DEX venues to read at all |
E06 |
| C3, C6, C7 | A second aggregated price for the same asset |
E07 |
| — | A third aggregated price. Recorded and normalised; read by |
E08 |
| — | DEX pairs at a named venue. Recorded and normalised; read by |
E09 |
| — | One DEX pool, quoted. Recorded and normalised; read by |
E10 |
| C1, C3, C4, C7 | The price of the token at its DEX venues, and the liquidity behind it |
E11 |
| C4, C7 | The pools behind that price, and how deep each one is |
E13 |
| — | An RWA symbol to its |
E14 |
| C3, C5, C6, C7 | The wrappers of one real-world asset and their average tokenized price — C5 reads every sibling out of this single answer |
E16 |
| — | The catalogue of tokenized assets. Read by |
E17 |
| — | What a real-world asset is. Read by |
E18 |
| — | The issuer catalogue, walked once by |
E19 |
| — | The tokens of each issuer: what maps a wrapper symbol back to its asset ( |
E20 |
| — | The credit counter and the plan's limits, at no credit cost |
A cold verdict costs 5 credits on a token and 3 on a coin with no contract; E01, E13 and E20 cost none, and a repeated question costs nothing while the cache holds it. The call plan behind those numbers — three stages, what runs in parallel, and the latency each was measured at — is docs/DECISIONS.md (D1), and the status, cost, latency and observed fields of all twenty-one endpoints are in docs/ENDPOINTS.md, each line backed by the answer it was read from.
Calibration
On the first fifty cryptocurrencies by market capitalisation, read live on 2026-09-26, 94 % reach ACT — above
the 90 % the specification asks for — while three assets stay below it on a critical observation. The panel, every
threshold and the measurements each one was settled on are in docs/CALIBRATION.md; the
thresholds themselves live in config/checks.json, each beside the note that justifies it.
Documentation
docs/ENDPOINTS.md — every candidate endpoint, what the Startup plan answered, its cost in credits and the recorded fixture proving it
docs/DECISIONS.md — what each check reads and why, and what was dropped for lack of access
docs/CALIBRATION.md — the calibration panel and the thresholds it settled
Credits, cache and secrets
One run never spends more than CMC_CREDIT_BUDGET credits (500 by default) and every answer is cached on disk under
.cache/cmc for the lifetimes set in .env.example, so a repeated question costs nothing. The API key lives only in
.env, which is never committed; scripts/check-secrets.sh refuses a commit that carries one, and every recorded
fixture has it masked.
That script takes one scope at a time, and refuses a mistyped one rather than answering for the default:
scripts/check-secrets.sh # the staged changes — what the pre-commit hook runs
scripts/check-secrets.sh --worktree # every file a clone would carry, committed or not
scripts/check-secrets.sh --history # every commit of every branchVerifying a clone
npm run lint && npm run typecheck && npm testThe whole suite runs offline: the network is disabled while it runs, and every CMC answer it reads is a recorded one.
To check the clone the way a reader receives it, rather than the way this working copy happens to be:
npm run check:clean # add -- --with-web to cover the Next.js application tooIt copies the repository-visible files into a temporary directory — so node_modules/, dist/, .cache/ and
.env stay behind — then runs npm install, npm run build, npm run lint, npm run typecheck and npm test
there, and finishes with the quick start above. That last part is the point: the copy has no key, so a run that
reached the API would fail instead of quietly using yours.
This server cannot be deployed
Maintenance
Related MCP Connectors
Evidence-first trust verdicts for AI-agent services — query one before you transact.
Evidence-backed crypto due diligence with sources, freshness, and a runtime receipt on every call.
Trademark clearance (USPTO+TMview) and self-graded stock signals for AI agents. JSON verdicts.
Pre-purchase trust checks for AI agents: recalls, scam signals, proceed/caution/avoid verdict.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to query cryptocurrency data including prices, market cap, rankings, categories, and global metrics from CoinMarketCap via MCP tools.147 npmMIT
- AlicenseAqualityDmaintenanceProvides cryptographically verifiable RWA trust attestations and multi-chain DeFi data (TVL, top protocols, positioning scorecards) via MCP tools for AI assistants.111MIT
- AlicenseCqualityDmaintenanceEnables access to CoinMarketCap API data, including latest cryptocurrency listings and token quotes, through natural language.2MIT
- FlicenseNot gradedqualityDmaintenanceEnables AI agents to access Coinmarketcap data through real API endpoints with no authentication required, supporting crypto and DeFi queries.-