@coinpaprika/mcp 1.3.3 → 1.4.1
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 +25 -0
- package/README.md +27 -25
- package/dist/index.js +35 -33
- package/dist/safe-path.js +28 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the CoinPaprika MCP Server will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [1.4.1] - 2026-10-21
|
|
8
|
+
|
|
9
|
+
### Security
|
|
10
|
+
|
|
11
|
+
- **An id could change which API path a tool called.** Ids go into the request path through `encodeURIComponent`, which leaves `.` and `..` as they are, so an id of `..` moved the request, with your API key on it, to a different path. Every request now has its path checked before it is sent: a path that changes when parsed, or a segment that decodes to `.` or `..` or holds `/`, `\` or `%`, is refused with "Invalid identifier". No real coin, exchange, tag, person, platform or contract id is affected.
|
|
12
|
+
|
|
13
|
+
## [1.4.0] - 2026-08-19
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **The free-tier quota was stated as "10,000 requests per day". It is 20,000 calls per month.** Verified against the live plan table at https://coinpaprika.com/api/pricing/. The wrong figure appeared in the 429 error message, in `getCapabilities.rate_limits`, in the `CP429_RATE_LIMIT` error-code text and in the README.
|
|
18
|
+
- **`meta.rate_limit` claimed a real remaining balance it could not know.** It counted only calls made by this process since start and compared them against the invented 10,000 daily ceiling, so `remaining` and `percentage_used` were wrong for anyone who had used the API elsewhere. Replaced with `meta.quota`, which states the monthly free-tier allowance, the count for this process, and says plainly that it is process-local.
|
|
19
|
+
- README coin and exchange counts: "8,000+ coins" and "200+ exchanges" are now "12,000+ cryptocurrencies" and "350+ exchanges".
|
|
20
|
+
- README Smithery link and install command pointed at `@coinpaprika/coinpaprika-mcp`, which 404s. The live listing is `coinpaprika/coinpaprika`.
|
|
21
|
+
- README said the hosted server has "same 30 tools". A live `tools/list` against `mcp.coinpaprika.com` returns 31: the 30 here plus `submitFeedback`.
|
|
22
|
+
|
|
23
|
+
### Removed
|
|
24
|
+
|
|
25
|
+
- **`meta.rate_limit`.** It reported `limit`, `remaining` and `percentage_used` against a
|
|
26
|
+
ceiling that does not exist, counting only this process's own calls. Read `meta.quota`
|
|
27
|
+
instead: it states the monthly free-tier allowance and says plainly that its count is
|
|
28
|
+
process-local rather than your real balance.
|
|
29
|
+
|
|
5
30
|
## [1.3.3] - 2026-07-29
|
|
6
31
|
|
|
7
32
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# CoinPaprika MCP Server
|
|
2
2
|
|
|
3
|
+
[](https://smithery.ai/servers/coinpaprika/coinpaprika)
|
|
4
|
+
|
|
3
5
|
A Model Context Protocol (MCP) server that provides on-demand access to CoinPaprika's cryptocurrency market data API. Built specifically for AI assistants like Claude to programmatically fetch real-time prices, tickers, exchange data, OHLCV candles, and more with zero configuration.
|
|
4
6
|
|
|
5
7
|
## TL;DR
|
|
@@ -15,16 +17,16 @@ coinpaprika-mcp
|
|
|
15
17
|
npx @coinpaprika/mcp@latest
|
|
16
18
|
```
|
|
17
19
|
|
|
18
|
-
CoinPaprika MCP connects Claude to live crypto market data for
|
|
20
|
+
CoinPaprika MCP connects Claude to live crypto market data for 12,000+ cryptocurrencies. The free tier needs no API key. [Installation](#installation) | [Configuration](#claude-desktop-integration) | [API Reference](https://api.coinpaprika.com)
|
|
19
21
|
|
|
20
|
-
> **Prefer zero setup?** Use the hosted MCP server at [mcp.coinpaprika.com](https://mcp.coinpaprika.com)
|
|
22
|
+
> **Prefer zero setup?** Use the hosted MCP server at [mcp.coinpaprika.com](https://mcp.coinpaprika.com): no installation, no API key to start, 31 tools (the 30 here plus `submitFeedback`). See [Hosted server](#hosted-server-no-installation) for transport endpoints.
|
|
21
23
|
|
|
22
24
|
## What Can You Build?
|
|
23
25
|
|
|
24
26
|
- **Market Dashboards**: Real-time market overview with global stats, top coins, and volume trends
|
|
25
|
-
- **Coin Analysis Tools**: Deep-dive into any cryptocurrency
|
|
27
|
+
- **Coin Analysis Tools**: Deep-dive into any cryptocurrency: price, team, events, exchanges, and markets
|
|
26
28
|
- **Price Trackers**: Track prices across multiple quote currencies with historical OHLCV data
|
|
27
|
-
- **Exchange Comparisons**: Compare trading pairs and volumes across
|
|
29
|
+
- **Exchange Comparisons**: Compare trading pairs and volumes across 350+ exchanges
|
|
28
30
|
- **Portfolio Valuations**: Convert an amount between currencies ('0.5 BTC in USD') and track price changes over time
|
|
29
31
|
- **Contract Lookup**: Find tokens by their smart contract address across multiple platforms
|
|
30
32
|
|
|
@@ -32,10 +34,10 @@ CoinPaprika MCP connects Claude to live crypto market data for 8,000+ coins. No
|
|
|
32
34
|
|
|
33
35
|
### Installing via Smithery
|
|
34
36
|
|
|
35
|
-
To install CoinPaprika MCP for Claude Desktop automatically via [Smithery](https://smithery.ai/
|
|
37
|
+
To install CoinPaprika MCP for Claude Desktop automatically via [Smithery](https://smithery.ai/servers/coinpaprika/coinpaprika):
|
|
36
38
|
|
|
37
39
|
```bash
|
|
38
|
-
npx -y
|
|
40
|
+
npx -y smithery mcp add coinpaprika/coinpaprika
|
|
39
41
|
```
|
|
40
42
|
|
|
41
43
|
### Manual Installation
|
|
@@ -91,7 +93,7 @@ claude mcp add coinpaprika -- npx @coinpaprika/mcp@latest
|
|
|
91
93
|
COINPAPRIKA_API_KEY=your-key claude mcp add coinpaprika -- npx @coinpaprika/mcp@latest
|
|
92
94
|
```
|
|
93
95
|
|
|
94
|
-
### Hosted
|
|
96
|
+
### Hosted server (no installation)
|
|
95
97
|
|
|
96
98
|
If you prefer zero setup, point any MCP-compatible client directly at the hosted server at [mcp.coinpaprika.com](https://mcp.coinpaprika.com). The landing page provides setup instructions and documentation. The following transport endpoints are available:
|
|
97
99
|
|
|
@@ -101,7 +103,7 @@ If you prefer zero setup, point any MCP-compatible client directly at the hosted
|
|
|
101
103
|
| SSE | `https://mcp.coinpaprika.com/sse` | Legacy SSE transport |
|
|
102
104
|
| JSON-RPC | `https://mcp.coinpaprika.com/json-rpc` | Direct JSON-RPC |
|
|
103
105
|
|
|
104
|
-
> **Note**: These are MCP protocol endpoints
|
|
106
|
+
> **Note**: These are MCP protocol endpoints. They won't display anything in a browser. Visit [mcp.coinpaprika.com](https://mcp.coinpaprika.com) for the landing page.
|
|
105
107
|
|
|
106
108
|
```json
|
|
107
109
|
{
|
|
@@ -128,7 +130,7 @@ If you prefer zero setup, point any MCP-compatible client directly at the hosted
|
|
|
128
130
|
|
|
129
131
|
| Tool | Description | Required Parameters |
|
|
130
132
|
|------|-------------|---------------------|
|
|
131
|
-
| `getCoins` | List all coins (id, name, symbol, rank); no prices, use getTickers for prices |
|
|
133
|
+
| `getCoins` | List all coins (id, name, symbol, rank); no prices, use getTickers for prices | none |
|
|
132
134
|
| `getCoinById` | Coin project details (description, team, links); no price, use getTickersById | `coinId` |
|
|
133
135
|
| `getCoinEvents` | Upcoming events for a coin | `coinId` |
|
|
134
136
|
| `getCoinExchanges` | Exchanges listing a coin | `coinId` |
|
|
@@ -138,7 +140,7 @@ If you prefer zero setup, point any MCP-compatible client directly at the hosted
|
|
|
138
140
|
|
|
139
141
|
| Tool | Description | Required Parameters |
|
|
140
142
|
|------|-------------|---------------------|
|
|
141
|
-
| `getTickers` | Current prices + market caps for top coins by market cap ('top 5 coins', market snapshot) |
|
|
143
|
+
| `getTickers` | Current prices + market caps for top coins by market cap ('top 5 coins', market snapshot) | none |
|
|
142
144
|
| `getTickersById` | Current price, market cap & 24h change of one coin ('BTC price', 'how much is ETH') | `coinId` |
|
|
143
145
|
| `getCoinOHLCVLatest` | Latest full-day OHLC candle (not the live price) | `coinId` |
|
|
144
146
|
| `getCoinOHLCVToday` | Today's in-progress OHLC candle | `coinId` |
|
|
@@ -148,7 +150,7 @@ If you prefer zero setup, point any MCP-compatible client directly at the hosted
|
|
|
148
150
|
|
|
149
151
|
| Tool | Description | Required Parameters |
|
|
150
152
|
|------|-------------|---------------------|
|
|
151
|
-
| `getExchanges` | List all exchanges |
|
|
153
|
+
| `getExchanges` | List all exchanges | none |
|
|
152
154
|
| `getExchangeByID` | Exchange details | `exchangeId` |
|
|
153
155
|
| `getExchangeMarkets` | Markets on a specific exchange | `exchangeId` |
|
|
154
156
|
|
|
@@ -156,7 +158,7 @@ If you prefer zero setup, point any MCP-compatible client directly at the hosted
|
|
|
156
158
|
|
|
157
159
|
| Tool | Description | Required Parameters |
|
|
158
160
|
|------|-------------|---------------------|
|
|
159
|
-
| `getTags` | List all tags/categories |
|
|
161
|
+
| `getTags` | List all tags/categories | none |
|
|
160
162
|
| `getTagById` | Tag details | `tagId` |
|
|
161
163
|
| `getPeopleById` | Person/team member details | `personId` |
|
|
162
164
|
|
|
@@ -164,7 +166,7 @@ If you prefer zero setup, point any MCP-compatible client directly at the hosted
|
|
|
164
166
|
|
|
165
167
|
| Tool | Description | Required Parameters |
|
|
166
168
|
|------|-------------|---------------------|
|
|
167
|
-
| `getPlatforms` | List contract platforms |
|
|
169
|
+
| `getPlatforms` | List contract platforms | none |
|
|
168
170
|
| `getContracts` | Contracts on a platform | `platformId` |
|
|
169
171
|
| `getTickerByContract` | Token price by contract address (on-chain/DeFi tokens) | `platformId`, `contractAddress` |
|
|
170
172
|
|
|
@@ -241,32 +243,32 @@ const resolved = await resolveId({ type: "coin", query: "cardano" });
|
|
|
241
243
|
## Sample Prompts for Claude
|
|
242
244
|
|
|
243
245
|
- "What's the current price of Bitcoin and Ethereum? Show me 24h change."
|
|
244
|
-
- "Give me a comprehensive analysis of Cardano
|
|
246
|
+
- "Give me a comprehensive analysis of Cardano: price, team, events, and top exchanges."
|
|
245
247
|
- "Compare the top 5 crypto exchanges by trading volume."
|
|
246
248
|
- "Convert 10 ETH to USD and show the current exchange rate."
|
|
247
249
|
- "Find all coins tagged as 'defi' and show their market caps."
|
|
248
250
|
- "What exchanges list Solana and what trading pairs are available?"
|
|
249
251
|
- "Show me the OHLCV data for Bitcoin today."
|
|
250
252
|
- "Search for all coins related to 'layer-2' and rank them by market cap."
|
|
251
|
-
- "Look up the team behind Ethereum
|
|
253
|
+
- "Look up the team behind Ethereum. Who are the key people?"
|
|
252
254
|
- "Get the ticker for USDT by its Ethereum contract address."
|
|
253
255
|
|
|
254
256
|
## Rate Limits & Performance
|
|
255
257
|
|
|
256
|
-
- **Free Tier Limits**:
|
|
258
|
+
- **Free Tier Limits**: 20,000 calls per month, no API key needed. Paid plans start at $99/month for 400,000 calls: https://coinpaprika.com/api/pricing/
|
|
257
259
|
- **Response Time**: 100-500ms for most endpoints
|
|
258
|
-
- **Data Coverage**:
|
|
260
|
+
- **Data Coverage**: 12,000+ cryptocurrencies, 350+ exchanges
|
|
259
261
|
- **Error Handling**: Structured errors with codes, suggestions, and retry guidance
|
|
260
|
-
- **
|
|
262
|
+
- **Quota Info**: Running this package yourself, every response carries a `meta.quota` block with the monthly free-tier allowance and the number of calls this server process has made since it started. That count is process-local, not your account balance. The hosted server at `mcp.coinpaprika.com` returns no `meta` block at all, so do not write client code that depends on one
|
|
261
263
|
|
|
262
264
|
## Troubleshooting
|
|
263
265
|
|
|
264
266
|
**Common Issues:**
|
|
265
267
|
|
|
266
|
-
- **Rate limiting**: If receiving `CP429_RATE_LIMIT` errors,
|
|
268
|
+
- **Rate limiting**: If receiving `CP429_RATE_LIMIT` errors, back off and retry. The free-tier quota is monthly, not daily, so a sustained 429 means the monthly allowance is spent
|
|
267
269
|
- **Invalid coin ID**: Coin IDs use `symbol-name` format (e.g., `btc-bitcoin`, not `bitcoin` or `BTC`). Use `search` or `resolveId` to find correct IDs
|
|
268
270
|
- **Paid-tier errors**: `CP402_INSUFFICIENT_PLAN` or `CP403_FORBIDDEN` mean the endpoint requires a paid plan. Set `COINPAPRIKA_API_KEY` environment variable
|
|
269
|
-
- **Timeout errors**: Large data requests may take longer
|
|
271
|
+
- **Timeout errors**: Large data requests may take longer, so reduce the `limit` parameter
|
|
270
272
|
- **Network errors**: Check network connectivity, the service requires internet access
|
|
271
273
|
|
|
272
274
|
## Development
|
|
@@ -296,10 +298,10 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
296
298
|
|
|
297
299
|
## Additional Resources
|
|
298
300
|
|
|
299
|
-
- [CoinPaprika API Documentation](https://
|
|
301
|
+
- [CoinPaprika API Documentation](https://docs.coinpaprika.com)
|
|
300
302
|
- [API Pricing & Plans](https://coinpaprika.com/api/pricing/)
|
|
301
|
-
- [Hosted MCP Server](https://mcp.coinpaprika.com)
|
|
302
|
-
- [DexPaprika MCP](https://www.npmjs.com/package/dexpaprika-mcp)
|
|
303
|
+
- [Hosted MCP Server](https://mcp.coinpaprika.com), zero-setup option
|
|
304
|
+
- [DexPaprika MCP](https://www.npmjs.com/package/dexpaprika-mcp), DEX and DeFi data companion package
|
|
303
305
|
- [Model Context Protocol Specification](https://modelcontextprotocol.io)
|
|
304
|
-
- [CoinPaprika](https://coinpaprika.com)
|
|
305
|
-
- [DexPaprika](https://dexpaprika.com)
|
|
306
|
+
- [CoinPaprika](https://coinpaprika.com), comprehensive cryptocurrency market data
|
|
307
|
+
- [DexPaprika](https://dexpaprika.com), onchain DEX analytics
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { createRequire } from 'module';
|
|
4
4
|
import fetch from 'node-fetch';
|
|
5
|
+
import { assertPathUnchanged } from './safe-path.js';
|
|
5
6
|
import { z } from 'zod';
|
|
6
7
|
|
|
7
8
|
// Server version, read from package.json so the banner, capabilities
|
|
@@ -41,7 +42,7 @@ const COIN_ID_REGEX = /^[a-z0-9]+-[a-z0-9-]+$/;
|
|
|
41
42
|
const COIN_ID_DESCRIPTION =
|
|
42
43
|
"Canonical CoinPaprika coin slug in 'symbol-name' format, e.g. 'btc-bitcoin', " +
|
|
43
44
|
"'eth-ethereum', 'ada-cardano'. Do NOT pass ticker symbols ('BTC', 'AAVE') " +
|
|
44
|
-
"or guess the slug
|
|
45
|
+
"or guess the slug; it is not derivable from the symbol (e.g. AAVE resolves " +
|
|
45
46
|
"to 'aave-new', which you could not guess). Call search or resolveId first to " +
|
|
46
47
|
"resolve a symbol or name to its canonical id.";
|
|
47
48
|
|
|
@@ -93,18 +94,15 @@ function parseAPIError(status, statusText, endpoint) {
|
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
if (status === 429) {
|
|
96
|
-
const resetTime = new Date();
|
|
97
|
-
resetTime.setUTCHours(24, 0, 0, 0);
|
|
98
97
|
return buildErrorResponse(
|
|
99
98
|
ErrorCodes.CP429_RATE_LIMIT,
|
|
100
|
-
'
|
|
99
|
+
'Rate limit exceeded',
|
|
101
100
|
true,
|
|
102
|
-
'
|
|
101
|
+
'Back off and retry. The free-tier quota is 20,000 calls per month, not a daily allowance, so a sustained 429 means the monthly allowance is spent. See https://coinpaprika.com/api/pricing/',
|
|
103
102
|
undefined,
|
|
104
103
|
{
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
retry_after_seconds: Math.floor((resetTime.getTime() - Date.now()) / 1000)
|
|
104
|
+
free_tier_calls_per_month: 20000,
|
|
105
|
+
pricing_url: 'https://coinpaprika.com/api/pricing/'
|
|
108
106
|
}
|
|
109
107
|
);
|
|
110
108
|
}
|
|
@@ -114,7 +112,7 @@ function parseAPIError(status, statusText, endpoint) {
|
|
|
114
112
|
ErrorCodes.CP404_NOT_FOUND,
|
|
115
113
|
'Resource not found',
|
|
116
114
|
false,
|
|
117
|
-
'No resource with that id exists
|
|
115
|
+
'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.',
|
|
118
116
|
undefined,
|
|
119
117
|
{ endpoint }
|
|
120
118
|
);
|
|
@@ -171,9 +169,13 @@ function validateCoinId(coinId) {
|
|
|
171
169
|
return null;
|
|
172
170
|
}
|
|
173
171
|
|
|
174
|
-
//
|
|
172
|
+
// Quota tracking. The CoinPaprika free tier is a MONTHLY allowance (20,000
|
|
173
|
+
// calls), not a daily one. This counter only sees calls made by this process
|
|
174
|
+
// since it started, so `remaining` is a local upper bound, not the account's
|
|
175
|
+
// real balance. Use the keyInfo tool with a paid key for the authoritative
|
|
176
|
+
// figure.
|
|
175
177
|
let requestCount = 0;
|
|
176
|
-
const
|
|
178
|
+
const FREE_TIER_CALLS_PER_MONTH = 20000;
|
|
177
179
|
|
|
178
180
|
// Build request headers (with optional API key)
|
|
179
181
|
function buildHeaders() {
|
|
@@ -194,7 +196,9 @@ function clamp(n, min, max) {
|
|
|
194
196
|
// Helper function to fetch data from CoinPaprika API with structured error handling
|
|
195
197
|
async function fetchFromAPI(endpoint) {
|
|
196
198
|
const startTime = Date.now();
|
|
197
|
-
const
|
|
199
|
+
const url = `${API_BASE_URL}${endpoint}`;
|
|
200
|
+
assertPathUnchanged(url); // before the key leaves the process
|
|
201
|
+
const response = await fetch(url, { headers: buildHeaders() });
|
|
198
202
|
|
|
199
203
|
if (!response.ok) {
|
|
200
204
|
const structuredError = parseAPIError(response.status, response.statusText, endpoint);
|
|
@@ -205,18 +209,14 @@ async function fetchFromAPI(endpoint) {
|
|
|
205
209
|
requestCount++;
|
|
206
210
|
const responseTime = Date.now() - startTime;
|
|
207
211
|
|
|
208
|
-
const resetTime = new Date();
|
|
209
|
-
resetTime.setUTCHours(24, 0, 0, 0);
|
|
210
|
-
|
|
211
212
|
return {
|
|
212
213
|
data,
|
|
213
214
|
meta: {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
reset_at: resetTime.toISOString()
|
|
215
|
+
quota: {
|
|
216
|
+
free_tier_calls_per_month: FREE_TIER_CALLS_PER_MONTH,
|
|
217
|
+
used_this_process: requestCount,
|
|
218
|
+
scope: 'this process since start, not the account balance',
|
|
219
|
+
pricing_url: 'https://coinpaprika.com/api/pricing/'
|
|
220
220
|
},
|
|
221
221
|
response_time_ms: responseTime,
|
|
222
222
|
cached: false,
|
|
@@ -284,7 +284,7 @@ function buildCapabilitiesDocument() {
|
|
|
284
284
|
name: "CoinPaprika MCP Server",
|
|
285
285
|
version: SERVER_VERSION,
|
|
286
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.",
|
|
287
|
-
documentation_url: "https://
|
|
287
|
+
documentation_url: "https://docs.coinpaprika.com",
|
|
288
288
|
},
|
|
289
289
|
tools: [
|
|
290
290
|
{
|
|
@@ -619,8 +619,10 @@ function buildCapabilitiesDocument() {
|
|
|
619
619
|
},
|
|
620
620
|
|
|
621
621
|
rate_limits: {
|
|
622
|
-
|
|
623
|
-
|
|
622
|
+
free_tier_calls_per_month: 20000,
|
|
623
|
+
note: "Free tier allowance. Paid plans start at 400,000 calls per month; see https://coinpaprika.com/api/pricing/",
|
|
624
|
+
documentation_url: "https://docs.coinpaprika.com",
|
|
625
|
+
pricing_url: "https://coinpaprika.com/api/pricing/"
|
|
624
626
|
},
|
|
625
627
|
|
|
626
628
|
error_codes: {
|
|
@@ -628,9 +630,9 @@ function buildCapabilitiesDocument() {
|
|
|
628
630
|
CP400_MISSING_REQUIRED: "Required parameter is missing",
|
|
629
631
|
CP400_BAD_REQUEST: "Invalid request parameters",
|
|
630
632
|
CP402_INSUFFICIENT_PLAN: "This endpoint requires a paid plan",
|
|
631
|
-
CP403_FORBIDDEN: "Access forbidden
|
|
633
|
+
CP403_FORBIDDEN: "Access forbidden. Check API key or plan",
|
|
632
634
|
CP404_NOT_FOUND: "Resource not found. Use search to find correct IDs",
|
|
633
|
-
CP429_RATE_LIMIT: "
|
|
635
|
+
CP429_RATE_LIMIT: "Rate limit exceeded. The free-tier quota is monthly, not daily",
|
|
634
636
|
CP500_SERVER_ERROR: "Internal server error. Please try again later"
|
|
635
637
|
},
|
|
636
638
|
|
|
@@ -718,14 +720,14 @@ function buildCapabilitiesDocument() {
|
|
|
718
720
|
],
|
|
719
721
|
error_handling: [
|
|
720
722
|
"Always validate coin ID format before API calls",
|
|
721
|
-
"Handle 402/403 errors gracefully
|
|
723
|
+
"Handle 402/403 errors gracefully; they indicate paid-plan requirements",
|
|
722
724
|
"Retry with backoff for 429 rate limit errors",
|
|
723
725
|
"Use resolveId to find correct IDs when 404 errors occur",
|
|
724
726
|
],
|
|
725
727
|
},
|
|
726
728
|
|
|
727
729
|
meta: {
|
|
728
|
-
documentation: "https://
|
|
730
|
+
documentation: "https://docs.coinpaprika.com",
|
|
729
731
|
pricing: "https://coinpaprika.com/api/pricing/",
|
|
730
732
|
support: "https://github.com/coinpaprika/coinpaprika-mcp"
|
|
731
733
|
}
|
|
@@ -743,7 +745,7 @@ const server = new McpServer({
|
|
|
743
745
|
// returning the structured CP400_INVALID_COIN_ID guidance (matches the hosted
|
|
744
746
|
// MCP). Wrapping server.tool centralizes this for every id-bearing tool
|
|
745
747
|
// (coinId / baseCurrencyId / quoteCurrencyId) instead of repeating it in each
|
|
746
|
-
// handler
|
|
748
|
+
// handler, so a ticker symbol like "BTC" gets actionable guidance to call
|
|
747
749
|
// search/resolveId instead of a bare upstream 404.
|
|
748
750
|
const _registerTool = server.tool.bind(server);
|
|
749
751
|
server.tool = (...regArgs) => {
|
|
@@ -780,7 +782,7 @@ server.tool(
|
|
|
780
782
|
api_key_configured: !!API_KEY,
|
|
781
783
|
mode: API_KEY ? 'authenticated' : 'free-tier',
|
|
782
784
|
paid_features: API_KEY
|
|
783
|
-
? 'API key configured
|
|
785
|
+
? 'API key configured, paid-tier endpoints available'
|
|
784
786
|
: 'No API key configured. For Pro/Business features, set COINPAPRIKA_API_KEY environment variable. Get a key at https://coinpaprika.com/api/pricing/',
|
|
785
787
|
};
|
|
786
788
|
return formatMcpResponse(statusData);
|
|
@@ -1326,7 +1328,7 @@ server.tool(
|
|
|
1326
1328
|
},
|
|
1327
1329
|
async (params) => {
|
|
1328
1330
|
const qs = new URLSearchParams();
|
|
1329
|
-
// Allowlist the upstream-recognized mapping params only
|
|
1331
|
+
// Allowlist the upstream-recognized mapping params only, never forward
|
|
1330
1332
|
// MCP-only or unexpected fields, which the upstream rejects with a 400.
|
|
1331
1333
|
for (const k of ['coinpaprika', 'coinmarketcap', 'coingecko', 'cryptocompare', 'isin', 'dti']) {
|
|
1332
1334
|
const v = params[k];
|
|
@@ -1377,9 +1379,9 @@ async function main() {
|
|
|
1377
1379
|
await server.connect(transport);
|
|
1378
1380
|
console.error(`CoinPaprika MCP server v${SERVER_VERSION} is running...`);
|
|
1379
1381
|
if (API_KEY) {
|
|
1380
|
-
console.error('API key configured
|
|
1382
|
+
console.error('API key configured, paid-tier endpoints available.');
|
|
1381
1383
|
} else {
|
|
1382
|
-
console.error('No API key configured
|
|
1384
|
+
console.error('No API key configured, running in free-tier mode. Set COINPAPRIKA_API_KEY for paid features.');
|
|
1383
1385
|
}
|
|
1384
1386
|
} catch (error) {
|
|
1385
1387
|
console.error('Failed to start server:', error);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Ids reach the upstream path through encodeURIComponent, which encodes "/", "?" and "#" but
|
|
2
|
+
// leaves "." and "..". An id of ".." would make the request, with your API key on it, go to a
|
|
3
|
+
// different path than the tool intended, because URL parsing resolves dot segments. And an id
|
|
4
|
+
// of "../key/info" encodes to "..%2Fkey%2Finfo", which URL parsing leaves alone but a server
|
|
5
|
+
// that decodes before routing might not. So refuse any URL whose path changes when parsed, and
|
|
6
|
+
// any path segment that, decoded once, is "." or ".." or holds "/", "\" or "%". No real coin,
|
|
7
|
+
// exchange, tag, person, platform or contract id contains any of these.
|
|
8
|
+
export function assertPathUnchanged(url) {
|
|
9
|
+
const afterScheme = url.indexOf('://');
|
|
10
|
+
const pathStart = afterScheme === -1 ? -1 : url.indexOf('/', afterScheme + 3);
|
|
11
|
+
if (pathStart === -1) return;
|
|
12
|
+
const end = url.slice(pathStart).search(/[?#]/);
|
|
13
|
+
const rawPath = end === -1 ? url.slice(pathStart) : url.slice(pathStart, pathStart + end);
|
|
14
|
+
if (new URL(url).pathname !== rawPath) {
|
|
15
|
+
throw new Error('Invalid identifier: an id may not be "." or "..".');
|
|
16
|
+
}
|
|
17
|
+
for (const seg of rawPath.split('/').slice(1)) {
|
|
18
|
+
let decoded;
|
|
19
|
+
try {
|
|
20
|
+
decoded = decodeURIComponent(seg);
|
|
21
|
+
} catch {
|
|
22
|
+
throw new Error('Invalid identifier.');
|
|
23
|
+
}
|
|
24
|
+
if (decoded === '.' || decoded === '..' || /[\\/%]/.test(decoded)) {
|
|
25
|
+
throw new Error('Invalid identifier.');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coinpaprika/mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A Model Context Protocol server for CoinPaprika cryptocurrency market data
|
|
3
|
+
"version": "1.4.1",
|
|
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",
|
|
7
7
|
"bin": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"build": "mkdir -p dist && cp -r src/* dist/ && chmod +x dist/bin.js",
|
|
18
18
|
"prepublishOnly": "npm run build",
|
|
19
19
|
"start": "node src/index.js",
|
|
20
|
-
"test": "node test/smoke.js"
|
|
20
|
+
"test": "node test/path_safety.js && node test/smoke.js"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
|
23
23
|
"mcp",
|