@coinpaprika/mcp 1.1.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  All notable changes to the CoinPaprika MCP Server will be documented in this file.
4
4
 
5
+ ## [1.3.0] - 2026-07-27
6
+
7
+ ### Changed
8
+
9
+ - **Intent-first tool descriptions.** Every tool description was rewritten so agents select the right tool from the description alone, each leading with what the tool returns plus `Use when asked '...'` cues (e.g. `getTickers` vs `getCoinById` vs `getContracts`). Matches the descriptions now served by the hosted worker at `mcp.coinpaprika.com`, so hosted and self-host read identically.
10
+ - `getCapabilities` description aligned with the hosted worker (onboarding-first wording; clarifies it describes the MCP server itself, not crypto data).
11
+
12
+ ### Added
13
+
14
+ - `server.json` manifest for publication to the official MCP Registry (`registry.modelcontextprotocol.io`).
15
+
5
16
  ## [1.1.1] - 2026-05-05
6
17
 
7
18
  ### Fixed
package/README.md CHANGED
@@ -12,7 +12,7 @@ npm install -g @coinpaprika/mcp
12
12
  coinpaprika-mcp
13
13
 
14
14
  # Or run directly without installation
15
- npx @coinpaprika/mcp
15
+ npx @coinpaprika/mcp@latest
16
16
  ```
17
17
 
18
18
  CoinPaprika MCP connects Claude to live crypto market data for 8,000+ coins. No API key required for free-tier features. [Installation](#installation) | [Configuration](#claude-desktop-integration) | [API Reference](https://api.coinpaprika.com)
@@ -25,7 +25,7 @@ CoinPaprika MCP connects Claude to live crypto market data for 8,000+ coins. No
25
25
  - **Coin Analysis Tools**: Deep-dive into any cryptocurrency — price, team, events, exchanges, and markets
26
26
  - **Price Trackers**: Track prices across multiple quote currencies with historical OHLCV data
27
27
  - **Exchange Comparisons**: Compare trading pairs and volumes across 200+ exchanges
28
- - **Portfolio Valuations**: Convert between currencies and track price changes over time
28
+ - **Portfolio Valuations**: Convert an amount between currencies ('0.5 BTC in USD') and track price changes over time
29
29
  - **Contract Lookup**: Find tokens by their smart contract address across multiple platforms
30
30
 
31
31
  ## Installation
@@ -59,7 +59,7 @@ Add the following to your Claude Desktop configuration file:
59
59
  "mcpServers": {
60
60
  "coinpaprika": {
61
61
  "command": "npx",
62
- "args": ["@coinpaprika/mcp"]
62
+ "args": ["@coinpaprika/mcp@latest"]
63
63
  }
64
64
  }
65
65
  }
@@ -72,7 +72,7 @@ Add the following to your Claude Desktop configuration file:
72
72
  "mcpServers": {
73
73
  "coinpaprika": {
74
74
  "command": "npx",
75
- "args": ["@coinpaprika/mcp"],
75
+ "args": ["@coinpaprika/mcp@latest"],
76
76
  "env": {
77
77
  "COINPAPRIKA_API_KEY": "your-api-key-here"
78
78
  }
@@ -85,10 +85,10 @@ Add the following to your Claude Desktop configuration file:
85
85
 
86
86
  ```bash
87
87
  # Add as MCP server in Claude Code
88
- claude mcp add coinpaprika -- npx @coinpaprika/mcp
88
+ claude mcp add coinpaprika -- npx @coinpaprika/mcp@latest
89
89
 
90
90
  # With API key
91
- COINPAPRIKA_API_KEY=your-key claude mcp add coinpaprika -- npx @coinpaprika/mcp
91
+ COINPAPRIKA_API_KEY=your-key claude mcp add coinpaprika -- npx @coinpaprika/mcp@latest
92
92
  ```
93
93
 
94
94
  ### Hosted Alternative (No Installation)
@@ -122,27 +122,27 @@ If you prefer zero setup, point any MCP-compatible client directly at the hosted
122
122
  |------|-------------|
123
123
  | `getCapabilities` | Server capabilities, workflow patterns, validation rules, and best practices. **Start here.** |
124
124
  | `status` | Server status and configuration |
125
- | `getGlobal` | Global crypto market overview (market cap, volume, BTC dominance) |
125
+ | `getGlobal` | Global market overview: total market cap, 24h volume, BTC dominance ('how is the market doing') |
126
126
 
127
127
  ### Coins
128
128
 
129
129
  | Tool | Description | Required Parameters |
130
130
  |------|-------------|---------------------|
131
- | `getCoins` | List all coins (id, name, symbol, rank) | — |
132
- | `getCoinById` | Coin details (description, team, links) | `coinId` |
131
+ | `getCoins` | List all coins (id, name, symbol, rank); no prices, use getTickers for prices | — |
132
+ | `getCoinById` | Coin project details (description, team, links); no price, use getTickersById | `coinId` |
133
133
  | `getCoinEvents` | Upcoming events for a coin | `coinId` |
134
134
  | `getCoinExchanges` | Exchanges listing a coin | `coinId` |
135
- | `getCoinMarkets` | Markets/trading pairs for a coin | `coinId` |
135
+ | `getCoinMarkets` | Markets/exchanges trading a coin ('where to buy X', 'X price on Binance') | `coinId` |
136
136
 
137
137
  ### Tickers & Prices
138
138
 
139
139
  | Tool | Description | Required Parameters |
140
140
  |------|-------------|---------------------|
141
- | `getTickers` | All tickers with price quotes | — |
142
- | `getTickersById` | Ticker for a specific coin | `coinId` |
143
- | `getCoinOHLCVLatest` | OHLCV for the last full day | `coinId` |
144
- | `getCoinOHLCVToday` | OHLCV for today (in progress) | `coinId` |
145
- | `priceConverter` | Convert between currencies | `baseCurrencyId`, `quoteCurrencyId` |
141
+ | `getTickers` | Current prices + market caps for top coins by market cap ('top 5 coins', market snapshot) | — |
142
+ | `getTickersById` | Current price, market cap & 24h change of one coin ('BTC price', 'how much is ETH') | `coinId` |
143
+ | `getCoinOHLCVLatest` | Latest full-day OHLC candle (not the live price) | `coinId` |
144
+ | `getCoinOHLCVToday` | Today's in-progress OHLC candle | `coinId` |
145
+ | `priceConverter` | Convert an amount between currencies ('0.5 BTC in USD') | `baseCurrencyId`, `quoteCurrencyId` |
146
146
 
147
147
  ### Exchanges
148
148
 
@@ -166,14 +166,14 @@ If you prefer zero setup, point any MCP-compatible client directly at the hosted
166
166
  |------|-------------|---------------------|
167
167
  | `getPlatforms` | List contract platforms | — |
168
168
  | `getContracts` | Contracts on a platform | `platformId` |
169
- | `getTickerByContract` | Ticker by contract address | `platformId`, `contractAddress` |
169
+ | `getTickerByContract` | Token price by contract address (on-chain/DeFi tokens) | `platformId`, `contractAddress` |
170
170
 
171
171
  ### Search & Resolution
172
172
 
173
173
  | Tool | Description | Required Parameters |
174
174
  |------|-------------|---------------------|
175
- | `search` | Search coins, exchanges, ICOs, people, tags | `q` |
176
- | `resolveId` | Resolve fuzzy query to canonical CoinPaprika IDs | `type`, `query` |
175
+ | `search` | Search coins/exchanges/people/tags by name; use FIRST to get an id | `q` |
176
+ | `resolveId` | Resolve a name/symbol to canonical id; call FIRST before price tools | `type`, `query` |
177
177
 
178
178
  ### Paid-Tier Tools
179
179
 
@@ -198,7 +198,7 @@ export COINPAPRIKA_API_KEY=your-api-key-here
198
198
  coinpaprika-mcp
199
199
 
200
200
  # Or pass inline
201
- COINPAPRIKA_API_KEY=your-api-key-here npx @coinpaprika/mcp
201
+ COINPAPRIKA_API_KEY=your-api-key-here npx @coinpaprika/mcp@latest
202
202
  ```
203
203
 
204
204
  Get an API key at [coinpaprika.com/api/pricing](https://coinpaprika.com/api/pricing/).
package/dist/index.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { createRequire } from 'module';
3
4
  import fetch from 'node-fetch';
4
5
  import { z } from 'zod';
5
6
 
6
- // Server version
7
- const SERVER_VERSION = '1.0.0';
7
+ // Server version, read from package.json so the banner, capabilities
8
+ // document, and status tool always report the published version.
9
+ const SERVER_VERSION = createRequire(import.meta.url)('../package.json').version;
8
10
 
9
11
  // Optional API key from environment
10
12
  const API_KEY = process.env.COINPAPRIKA_API_KEY || '';
@@ -32,6 +34,21 @@ const ErrorCodes = {
32
34
  // Coin ID validation regex
33
35
  const COIN_ID_REGEX = /^[a-z0-9]+-[a-z0-9-]+$/;
34
36
 
37
+ // Canonical-slug guidance shared by every tool that takes a coin id. Agents
38
+ // kept passing ticker symbols ("BTC") or guessing slugs, then retry-looping on
39
+ // "resource not found" (devrel#5). The slug is NOT derivable from the symbol,
40
+ // so steer them to search / resolveId first.
41
+ const COIN_ID_DESCRIPTION =
42
+ "Canonical CoinPaprika coin slug in 'symbol-name' format, e.g. 'btc-bitcoin', " +
43
+ "'eth-ethereum', 'ada-cardano'. Do NOT pass ticker symbols ('BTC', 'AAVE') " +
44
+ "or guess the slug — it is not derivable from the symbol (e.g. AAVE resolves " +
45
+ "to 'aave-new', which you could not guess). Call search or resolveId first to " +
46
+ "resolve a symbol or name to its canonical id.";
47
+
48
+ // Known misspellings, surfaced in getCapabilities so MCP catalogs / tool
49
+ // routers / agent frameworks can match fuzzy references back to us (devrel#6).
50
+ const SERVER_ALIASES = ["coinparika", "coinpapika", "coinaprika", "coin-paprika", "coin paprika"];
51
+
35
52
  // Structured error response builder
36
53
  function buildErrorResponse(code, message, retryable, suggestion, correctedExample, metadata) {
37
54
  const error = {
@@ -97,7 +114,7 @@ function parseAPIError(status, statusText, endpoint) {
97
114
  ErrorCodes.CP404_NOT_FOUND,
98
115
  'Resource not found',
99
116
  false,
100
- 'The requested resource does not exist. Check the ID format and try again',
117
+ 'No resource with that id exists — it may be misspelled, retired, or for a different entity. Call search or resolveId to get the current canonical id, then retry.',
101
118
  undefined,
102
119
  { endpoint }
103
120
  );
@@ -260,18 +277,19 @@ async function withGuidance(exec, hint) {
260
277
  function buildCapabilitiesDocument() {
261
278
  return {
262
279
  service: "coinpaprika",
280
+ aliases: SERVER_ALIASES,
263
281
  version: SERVER_VERSION,
264
- description: "Cryptocurrency market data for 8000+ coins",
282
+ description: "Real-time cryptocurrency prices and market data for 12,000+ coins",
265
283
  server: {
266
284
  name: "CoinPaprika MCP Server",
267
285
  version: SERVER_VERSION,
268
- description: "Cryptocurrency market data — prices, tickers, exchanges, OHLCV, and more",
286
+ description: "Real-time cryptocurrency prices, market caps, 24h changes and volumes for 12,000+ coins (Bitcoin, Ethereum, Solana, and more) and 350+ exchanges. Use for how much a crypto is worth, top coins by market cap, or live market data.",
269
287
  documentation_url: "https://api.coinpaprika.com",
270
288
  },
271
289
  tools: [
272
290
  {
273
291
  name: "status",
274
- description: "Server status and configuration",
292
+ description: "Get the CoinPaprika MCP server's status and configuration (uptime, mode, enabled features). Use when asked 'is the server up', 'what mode am I in', 'server status'. This reports the MCP server itself, not the crypto market; for market health use getGlobal.",
275
293
  category: "system",
276
294
  parameters: {},
277
295
  returns: { type: "object" },
@@ -279,7 +297,7 @@ function buildCapabilitiesDocument() {
279
297
  },
280
298
  {
281
299
  name: "getGlobal",
282
- description: "Get global cryptocurrency market statistics",
300
+ description: "Get a global crypto market overview: total market cap, total 24h volume, Bitcoin dominance, and market-wide 24h change. Use for 'how is the crypto market doing', 'total market cap', 'BTC dominance'. For a single coin's price use getTickersById.",
283
301
  category: "market",
284
302
  parameters: {},
285
303
  returns: { type: "object", properties: ["market_cap_usd", "volume_24h_usd", "bitcoin_dominance_percentage"] },
@@ -287,7 +305,7 @@ function buildCapabilitiesDocument() {
287
305
  },
288
306
  {
289
307
  name: "getCoins",
290
- description: "List all available coins",
308
+ description: "List available coins (id, name, symbol, rank). Use to discover coins or map a name to an id. Does NOT include prices; for prices use getTickers or getTickersById.",
291
309
  category: "coins",
292
310
  parameters: { limit: { type: "integer", required: false, default: 50, min: 1, max: 250 } },
293
311
  returns: { type: "array", items: "Coin" },
@@ -295,7 +313,7 @@ function buildCapabilitiesDocument() {
295
313
  },
296
314
  {
297
315
  name: "getTickers",
298
- description: "Get market data for all active cryptocurrencies",
316
+ description: "Get current prices, market caps and 24h changes for the top cryptocurrencies ranked by market cap. Use for 'top 5 coins', 'top crypto today', 'biggest cryptocurrencies', or a market snapshot. For one specific coin use getTickersById.",
299
317
  category: "market",
300
318
  parameters: {
301
319
  quotes: { type: "string", required: false, default: "USD", description: "Comma-separated quote currencies" },
@@ -306,7 +324,7 @@ function buildCapabilitiesDocument() {
306
324
  },
307
325
  {
308
326
  name: "getCoinOHLCVLatest",
309
- description: "Get OHLCV data for last full day",
327
+ description: "Get the latest full-day OHLC candle (open/high/low/close plus volume) for a coin. For the live spot price use getTickersById.",
310
328
  category: "coins",
311
329
  parameters: {
312
330
  coinId: { type: "string", required: true, format: "coin-id", example: "btc-bitcoin" },
@@ -317,7 +335,7 @@ function buildCapabilitiesDocument() {
317
335
  },
318
336
  {
319
337
  name: "getCoinOHLCVToday",
320
- description: "Get OHLCV data for today",
338
+ description: "Get today's in-progress OHLC candle for a coin. For the current spot price use getTickersById; for past candles use getCoinOHLCVHistorical.",
321
339
  category: "coins",
322
340
  parameters: {
323
341
  coinId: { type: "string", required: true, format: "coin-id" },
@@ -328,7 +346,7 @@ function buildCapabilitiesDocument() {
328
346
  },
329
347
  {
330
348
  name: "getCoinOHLCVHistorical",
331
- description: "Get historical OHLCV data (requires Starter+ plan)",
349
+ description: "Get OHLC candles (open/high/low/close plus volume) for a coin over a date range, for price charts, backtests and technical analysis. Use for 'daily chart for the last month', 'hourly candles since Jan 1'. For a single past date's spot value use getTickersHistoricalById; not for the current price. Requires Starter+ plan.",
332
350
  category: "coins",
333
351
  parameters: {
334
352
  coinId: { type: "string", required: true, format: "coin-id" },
@@ -343,7 +361,7 @@ function buildCapabilitiesDocument() {
343
361
  },
344
362
  {
345
363
  name: "getCoinById",
346
- description: "Get detailed information about a specific cryptocurrency",
364
+ description: "Get descriptive project information about a coin (description, links, team, tags). Does NOT include price; for price/market cap use getTickersById.",
347
365
  category: "coins",
348
366
  parameters: { coinId: { type: "string", required: true, format: "coin-id", example: "btc-bitcoin" } },
349
367
  returns: { type: "object", properties: ["id", "name", "symbol", "description", "links", "rank"] },
@@ -351,7 +369,7 @@ function buildCapabilitiesDocument() {
351
369
  },
352
370
  {
353
371
  name: "getCoinEvents",
354
- description: "Get events for a specific coin",
372
+ description: "Get the timeline of events for a coin (launches, partnerships, hard forks, conference talks) with dates and descriptions. Use when asked 'what is happening with X', 'upcoming events for Ethereum', 'X roadmap events'. For prices or market data use getTickersById.",
355
373
  category: "coins",
356
374
  parameters: {
357
375
  coinId: { type: "string", required: true, format: "coin-id" },
@@ -362,7 +380,7 @@ function buildCapabilitiesDocument() {
362
380
  },
363
381
  {
364
382
  name: "getCoinExchanges",
365
- description: "Get exchanges where a coin is traded",
383
+ description: "List the exchanges that LIST a specific coin (venue names only). Use for 'which exchanges list X', 'is X on Coinbase', 'where is X listed'. For where to BUY with per-market price and volume use getCoinMarkets; for the coin's live price use getTickersById.",
366
384
  category: "coins",
367
385
  parameters: {
368
386
  coinId: { type: "string", required: true, format: "coin-id" },
@@ -373,7 +391,7 @@ function buildCapabilitiesDocument() {
373
391
  },
374
392
  {
375
393
  name: "getCoinMarkets",
376
- description: "Get markets for a specific coin",
394
+ description: "Get the specific markets and trading pairs where a coin trades, with per-market price and 24h volume. Use for 'where can I buy X', 'X price on Binance', 'best market for X'. For just the list of exchange names use getCoinExchanges.",
377
395
  category: "coins",
378
396
  parameters: {
379
397
  coinId: { type: "string", required: true, format: "coin-id" },
@@ -385,7 +403,7 @@ function buildCapabilitiesDocument() {
385
403
  },
386
404
  {
387
405
  name: "getTickersById",
388
- description: "Get ticker data for a specific coin",
406
+ description: "Get the current price, market cap, 24h change and volume of a specific cryptocurrency in USD (or another quote currency). Use when asked 'what is the price of X', 'how much is Bitcoin worth', 'BTC to USD', 'ETH price now'. For past prices use getTickersHistoricalById.",
389
407
  category: "market",
390
408
  parameters: {
391
409
  coinId: { type: "string", required: true, format: "coin-id" },
@@ -396,7 +414,7 @@ function buildCapabilitiesDocument() {
396
414
  },
397
415
  {
398
416
  name: "getTickersHistoricalById",
399
- description: "Get historical ticker data (requires Starter+ plan)",
417
+ description: "Get a coin's price and market cap at a single past date or time (a point-in-time value). Use for 'price of BTC last Tuesday', 'ETH market cap on 2024-01-01'. For OHLC chart candles over a range use getCoinOHLCVHistorical, not this. Not for the current price (use getTickersById). Requires Starter+ plan.",
400
418
  category: "market",
401
419
  parameters: {
402
420
  coinId: { type: "string", required: true, format: "coin-id" },
@@ -411,7 +429,7 @@ function buildCapabilitiesDocument() {
411
429
  },
412
430
  {
413
431
  name: "getPeopleById",
414
- description: "Get information about a person in crypto space",
432
+ description: "Get profile information about a person in crypto (bio, roles, linked projects and accounts). Use when asked 'who is Vitalik Buterin', 'background on X founder', 'projects tied to this person'. Look up the person id first with search.",
415
433
  category: "people",
416
434
  parameters: { personId: { type: "string", required: true } },
417
435
  returns: { type: "object" },
@@ -419,7 +437,7 @@ function buildCapabilitiesDocument() {
419
437
  },
420
438
  {
421
439
  name: "getTags",
422
- description: "Get list of all tags",
440
+ description: "List all CoinPaprika tags (categories such as defi, stablecoin, memecoin, ai) used to group coins. Use when asked 'what categories exist', 'list crypto sectors', or to find a tag id before calling getTagById.",
423
441
  category: "tags",
424
442
  parameters: {
425
443
  additionalFields: { type: "string", required: false },
@@ -430,7 +448,7 @@ function buildCapabilitiesDocument() {
430
448
  },
431
449
  {
432
450
  name: "getTagById",
433
- description: "Get details about a specific tag",
451
+ description: "Get details for a specific tag: its description and the coins and ICOs grouped under it. Use when asked 'what coins are in the defi tag', 'show the stablecoin category', 'tokens tagged X'. Get the tag id first from getTags.",
434
452
  category: "tags",
435
453
  parameters: {
436
454
  tagId: { type: "string", required: true },
@@ -441,7 +459,7 @@ function buildCapabilitiesDocument() {
441
459
  },
442
460
  {
443
461
  name: "getExchanges",
444
- description: "Get list of all exchanges",
462
+ description: "List all exchanges tracked by CoinPaprika with rank, trust score and volume. Use when asked 'top exchanges', 'biggest crypto exchanges', 'list exchanges by volume', or to find an exchange id before calling getExchangeByID.",
445
463
  category: "exchanges",
446
464
  parameters: {
447
465
  quotes: { type: "string", required: false },
@@ -452,7 +470,7 @@ function buildCapabilitiesDocument() {
452
470
  },
453
471
  {
454
472
  name: "getExchangeByID",
455
- description: "Get details about a specific exchange",
473
+ description: "Get details for a specific exchange (volume, market count, trust score, links, fiat support). Use when asked 'tell me about Binance', 'Coinbase exchange stats', 'is X a trustworthy exchange'. For the pairs traded there use getExchangeMarkets; find the exchange id first with search or getExchanges.",
456
474
  category: "exchanges",
457
475
  parameters: {
458
476
  exchangeId: { type: "string", required: true },
@@ -463,7 +481,7 @@ function buildCapabilitiesDocument() {
463
481
  },
464
482
  {
465
483
  name: "getExchangeMarkets",
466
- description: "Get markets on a specific exchange",
484
+ description: "List the trading pairs (markets) on a specific exchange with price and 24h volume per pair. Use when asked 'what pairs trade on Binance', 'markets on Kraken', 'BTC pairs on X exchange'. For exchange-level stats use getExchangeByID.",
467
485
  category: "exchanges",
468
486
  parameters: {
469
487
  exchangeId: { type: "string", required: true },
@@ -475,7 +493,7 @@ function buildCapabilitiesDocument() {
475
493
  },
476
494
  {
477
495
  name: "getPlatforms",
478
- description: "Get list of contract platforms",
496
+ description: "List the smart-contract platforms (blockchains such as ethereum, bnb, polygon) for which CoinPaprika indexes token contracts. Use when asked 'which chains are supported for contract lookups', 'list contract platforms', or to pick a platform id before calling getContracts.",
479
497
  category: "contracts",
480
498
  parameters: { limit: { type: "integer", required: false, default: 50 } },
481
499
  returns: { type: "array", items: "Platform" },
@@ -483,7 +501,7 @@ function buildCapabilitiesDocument() {
483
501
  },
484
502
  {
485
503
  name: "getContracts",
486
- description: "Get contracts for a specific platform",
504
+ description: "List the token contract addresses tracked on a specific platform, mapping each contract to its CoinPaprika coin id. Use when asked 'contracts on ethereum', 'what tokens does X chain have', or to map a contract address to a coin id. Get the platform id first from getPlatforms; for a single contract's price use getTickerByContract.",
487
505
  category: "contracts",
488
506
  parameters: {
489
507
  platformId: { type: "string", required: true },
@@ -494,7 +512,7 @@ function buildCapabilitiesDocument() {
494
512
  },
495
513
  {
496
514
  name: "getTickerByContract",
497
- description: "Get ticker by contract address",
515
+ description: "Get the current price and market data of a token by its contract address (for on-chain and DeFi tokens). Use when you have a 0x... or contract address rather than a name. For history use getHistoricalTickerByContract.",
498
516
  category: "contracts",
499
517
  parameters: {
500
518
  platformId: { type: "string", required: true },
@@ -505,7 +523,7 @@ function buildCapabilitiesDocument() {
505
523
  },
506
524
  {
507
525
  name: "getHistoricalTickerByContract",
508
- description: "Get historical ticker by contract address (requires Starter+ plan)",
526
+ description: "Get historical price and market data of a token by its contract address. Not for the current price (use getTickerByContract). Requires Starter+ plan.",
509
527
  category: "contracts",
510
528
  parameters: {
511
529
  platformId: { type: "string", required: true },
@@ -521,7 +539,7 @@ function buildCapabilitiesDocument() {
521
539
  },
522
540
  {
523
541
  name: "search",
524
- description: "Search for currencies, exchanges, ICOs, people, and tags",
542
+ description: "Broadly search CoinPaprika across coins, exchanges, ICOs, people and tags by name or symbol when the entity type is unknown or you are exploring. Use for 'find anything called X'. If you specifically need a coin's canonical id to fetch its price, prefer resolveId.",
525
543
  category: "search",
526
544
  parameters: {
527
545
  q: { type: "string", required: true },
@@ -534,7 +552,7 @@ function buildCapabilitiesDocument() {
534
552
  },
535
553
  {
536
554
  name: "resolveId",
537
- description: "Resolve fuzzy query to canonical coin/exchange/people/tag IDs",
555
+ description: "Resolve a coin name or symbol ('bitcoin', 'BTC', 'AAVE') to its single canonical CoinPaprika id ('btc-bitcoin', 'aave-new'). Call this FIRST whenever you have a bare symbol or name and want the coin's price, since ids are not derivable from symbols; then pass the id to getTickersById.",
538
556
  category: "search",
539
557
  parameters: {
540
558
  type: { type: "string", required: true, enum: ["coin", "exchange", "people", "tags"] },
@@ -546,7 +564,7 @@ function buildCapabilitiesDocument() {
546
564
  },
547
565
  {
548
566
  name: "priceConverter",
549
- description: "Convert amount from base currency to quote currency",
567
+ description: "Convert an amount from one crypto or fiat to another at the current rate ('how much is 0.5 BTC in USD', 'convert 100 USDC to EUR'). For a plain coin price without an amount, use getTickersById.",
550
568
  category: "tools",
551
569
  parameters: {
552
570
  baseCurrencyId: { type: "string", required: true },
@@ -558,7 +576,7 @@ function buildCapabilitiesDocument() {
558
576
  },
559
577
  {
560
578
  name: "keyInfo",
561
- description: "Verify API key (requires Pro plan)",
579
+ description: "Get details and usage for the current CoinPaprika API key (plan, quota, requests remaining, expiry). Use when asked 'what plan am I on', 'how many requests are left', 'is my API key valid'. Requires a Pro plan.",
562
580
  category: "system",
563
581
  parameters: {},
564
582
  returns: { type: "object" },
@@ -566,7 +584,7 @@ function buildCapabilitiesDocument() {
566
584
  },
567
585
  {
568
586
  name: "getMappings",
569
- description: "Get API ID mappings (requires Business plan)",
587
+ description: "Get mappings between CoinPaprika ids and external or legacy identifiers so you can reconcile ids across systems. Use when asked 'map CoinPaprika ids to X', 'id crosswalk', 'external id mapping'. Requires a Business plan.",
570
588
  category: "system",
571
589
  parameters: {
572
590
  coinpaprika: { type: "string", required: false },
@@ -581,7 +599,7 @@ function buildCapabilitiesDocument() {
581
599
  },
582
600
  {
583
601
  name: "getChangelogIDs",
584
- description: "Get changelog IDs (requires Starter+ plan)",
602
+ description: "Get the ids of recent changes to CoinPaprika's coin and exchange listings (additions, renames, delistings) for syncing a local dataset. Use when asked 'what changed recently', 'new or delisted coins', 'listing changelog'. Requires a Starter+ plan.",
585
603
  category: "system",
586
604
  parameters: {
587
605
  page: { type: "integer", required: false, default: 1 },
@@ -652,7 +670,7 @@ function buildCapabilitiesDocument() {
652
670
  steps: [
653
671
  "getPlatforms - List available contract platforms",
654
672
  "getContracts - List contracts on a platform",
655
- "getTickerByContract - Get ticker by contract address",
673
+ "getTickerByContract - Get the current price and market data of a token by its contract address (for on-chain/DeFi tokens). Use when you have a 0x.../contract address rather than a name. For history use getHistoricalTickerByContract.",
656
674
  ],
657
675
  example_use_case: "Looking up an ERC-20 token by its Ethereum contract",
658
676
  },
@@ -721,6 +739,34 @@ const server = new McpServer({
721
739
  description: 'MCP server for accessing CoinPaprika cryptocurrency market data',
722
740
  });
723
741
 
742
+ // devrel#5 parity: validate coin/currency id format BEFORE the handler runs,
743
+ // returning the structured CP400_INVALID_COIN_ID guidance (matches the hosted
744
+ // MCP). Wrapping server.tool centralizes this for every id-bearing tool
745
+ // (coinId / baseCurrencyId / quoteCurrencyId) instead of repeating it in each
746
+ // handler — so a ticker symbol like "BTC" gets actionable guidance to call
747
+ // search/resolveId instead of a bare upstream 404.
748
+ const _registerTool = server.tool.bind(server);
749
+ server.tool = (...regArgs) => {
750
+ const handler = regArgs[regArgs.length - 1];
751
+ const schema = regArgs.length >= 4 && regArgs[2] && typeof regArgs[2] === 'object' ? regArgs[2] : null;
752
+ if (typeof handler === 'function' && schema) {
753
+ const idFields = ['coinId', 'baseCurrencyId', 'quoteCurrencyId'].filter((f) => f in schema);
754
+ if (idFields.length > 0) {
755
+ regArgs[regArgs.length - 1] = async (args, ...rest) => {
756
+ for (const f of idFields) {
757
+ const v = args && args[f];
758
+ if (typeof v === 'string') {
759
+ const err = validateCoinId(v);
760
+ if (err) return formatMcpError(err);
761
+ }
762
+ }
763
+ return handler(args, ...rest);
764
+ };
765
+ }
766
+ }
767
+ return _registerTool(...regArgs);
768
+ };
769
+
724
770
  // ─── Tool 1: status ───
725
771
  server.tool(
726
772
  'status',
@@ -801,7 +847,7 @@ server.tool(
801
847
  'getCoinOHLCVLatest',
802
848
  'OHLCV last full day',
803
849
  {
804
- coinId: z.string().describe("Coin ID in format: symbol-name (e.g., btc-bitcoin)"),
850
+ coinId: z.string().describe(COIN_ID_DESCRIPTION),
805
851
  quote: z.string().optional().default('usd').describe("Quote currency (default: usd)")
806
852
  },
807
853
  async ({ coinId, quote = 'usd' }) => {
@@ -819,7 +865,7 @@ server.tool(
819
865
  'getCoinOHLCVToday',
820
866
  'OHLCV today',
821
867
  {
822
- coinId: z.string().describe("Coin ID in format: symbol-name (e.g., btc-bitcoin)"),
868
+ coinId: z.string().describe(COIN_ID_DESCRIPTION),
823
869
  quote: z.string().optional().default('usd').describe("Quote currency (default: usd)")
824
870
  },
825
871
  async ({ coinId, quote = 'usd' }) => {
@@ -837,7 +883,7 @@ server.tool(
837
883
  'getCoinOHLCVHistorical',
838
884
  'OHLCV historical (plan-dependent)',
839
885
  {
840
- coinId: z.string().describe("Coin ID in format: symbol-name (e.g., btc-bitcoin)"),
886
+ coinId: z.string().describe(COIN_ID_DESCRIPTION),
841
887
  start: z.string().describe("Start date (ISO 8601 or yyyy-mm-dd)"),
842
888
  end: z.string().optional().describe("End date (ISO 8601 or yyyy-mm-dd)"),
843
889
  limit: z.number().optional().default(50).describe("Number of data points (default: 50, max: 250)"),
@@ -858,7 +904,7 @@ server.tool(
858
904
  server.tool(
859
905
  'getCoinById',
860
906
  'Coin details (descriptive info)',
861
- { coinId: z.string().describe("Coin ID in format: symbol-name (e.g., btc-bitcoin)") },
907
+ { coinId: z.string().describe(COIN_ID_DESCRIPTION) },
862
908
  async ({ coinId }) => {
863
909
  try {
864
910
  const response = await fetchFromAPI(`/coins/${encodeURIComponent(coinId)}`);
@@ -874,7 +920,7 @@ server.tool(
874
920
  'getCoinEvents',
875
921
  'Events for a coin',
876
922
  {
877
- coinId: z.string().describe("Coin ID in format: symbol-name (e.g., btc-bitcoin)"),
923
+ coinId: z.string().describe(COIN_ID_DESCRIPTION),
878
924
  limit: z.number().optional().default(50).describe("Number of events to return (default: 50, max: 250)")
879
925
  },
880
926
  async ({ coinId, limit = 50 }) => {
@@ -895,7 +941,7 @@ server.tool(
895
941
  'getCoinExchanges',
896
942
  'Exchanges for a coin',
897
943
  {
898
- coinId: z.string().describe("Coin ID in format: symbol-name (e.g., btc-bitcoin)"),
944
+ coinId: z.string().describe(COIN_ID_DESCRIPTION),
899
945
  limit: z.number().optional().default(50).describe("Number of exchanges to return (default: 50, max: 250)")
900
946
  },
901
947
  async ({ coinId, limit = 50 }) => {
@@ -916,7 +962,7 @@ server.tool(
916
962
  'getCoinMarkets',
917
963
  'Markets for a coin',
918
964
  {
919
- coinId: z.string().describe("Coin ID in format: symbol-name (e.g., btc-bitcoin)"),
965
+ coinId: z.string().describe(COIN_ID_DESCRIPTION),
920
966
  quotes: z.string().optional().describe("Comma-separated quote currencies"),
921
967
  limit: z.number().optional().default(50).describe("Number of markets to return (default: 50, max: 250)")
922
968
  },
@@ -939,7 +985,7 @@ server.tool(
939
985
  'getTickersById',
940
986
  'Ticker for a specific coin',
941
987
  {
942
- coinId: z.string().describe("Coin ID in format: symbol-name (e.g., btc-bitcoin)"),
988
+ coinId: z.string().describe(COIN_ID_DESCRIPTION),
943
989
  quotes: z.string().optional().describe("Comma-separated quote currencies")
944
990
  },
945
991
  async ({ coinId, quotes }) => {
@@ -958,7 +1004,7 @@ server.tool(
958
1004
  'getTickersHistoricalById',
959
1005
  'Historical ticks for a coin (plan dependent)',
960
1006
  {
961
- coinId: z.string().describe("Coin ID in format: symbol-name (e.g., btc-bitcoin)"),
1007
+ coinId: z.string().describe(COIN_ID_DESCRIPTION),
962
1008
  start: z.string().describe("Start date (ISO 8601 or yyyy-mm-dd)"),
963
1009
  end: z.string().optional().describe("End date"),
964
1010
  limit: z.number().optional().default(50).describe("Number of data points (default: 50, max: 250)"),
@@ -1228,8 +1274,8 @@ server.tool(
1228
1274
  'priceConverter',
1229
1275
  'Convert base -> quote',
1230
1276
  {
1231
- baseCurrencyId: z.string().describe("Base currency ID (e.g., btc-bitcoin)"),
1232
- quoteCurrencyId: z.string().describe("Quote currency ID (e.g., usd-us-dollars)"),
1277
+ baseCurrencyId: z.string().describe("Required. Source CoinPaprika coin slug (e.g., 'btc-bitcoin'). Full slugs only; ticker symbols like 'BTC' are rejected upstream. Call search or resolveId first."),
1278
+ quoteCurrencyId: z.string().describe("Required. Target CoinPaprika coin slug (e.g., 'usd-us-dollars', 'eth-ethereum'). Full slugs only; ticker symbols like 'USD' are rejected upstream."),
1233
1279
  amount: z.number().optional().default(0).describe("Amount to convert (default: 0)")
1234
1280
  },
1235
1281
  async ({ baseCurrencyId, quoteCurrencyId, amount = 0 }) => {
@@ -1280,7 +1326,10 @@ server.tool(
1280
1326
  },
1281
1327
  async (params) => {
1282
1328
  const qs = new URLSearchParams();
1283
- for (const [k, v] of Object.entries(params)) {
1329
+ // Allowlist the upstream-recognized mapping params only — never forward
1330
+ // MCP-only or unexpected fields, which the upstream rejects with a 400.
1331
+ for (const k of ['coinpaprika', 'coinmarketcap', 'coingecko', 'cryptocompare', 'isin', 'dti']) {
1332
+ const v = params[k];
1284
1333
  if (v) qs.set(k, String(v));
1285
1334
  }
1286
1335
  return withGuidance(
@@ -1309,7 +1358,7 @@ server.tool(
1309
1358
  // ─── Tool 30: getCapabilities ───
1310
1359
  server.tool(
1311
1360
  'getCapabilities',
1312
- 'Return server capabilities, workflow patterns, validation rules, and best-practice sequences. Use this to onboard agents quickly.',
1361
+ "Get the server's capabilities, supported workflow patterns, validation rules and recommended tool sequences. Use FIRST to onboard as an agent, or when asked 'what can this server do', 'how should I chain these tools', 'what are the parameter rules'. This describes the MCP server itself, not crypto data.",
1313
1362
  {},
1314
1363
  async () => {
1315
1364
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coinpaprika/mcp",
3
- "version": "1.1.1",
3
+ "version": "1.3.0",
4
4
  "description": "A Model Context Protocol server for CoinPaprika cryptocurrency market data — prices, tickers, exchanges, and more",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",