@coinpaprika/mcp 1.1.1 → 1.2.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/dist/index.js +60 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -32,6 +32,21 @@ const ErrorCodes = {
|
|
|
32
32
|
// Coin ID validation regex
|
|
33
33
|
const COIN_ID_REGEX = /^[a-z0-9]+-[a-z0-9-]+$/;
|
|
34
34
|
|
|
35
|
+
// Canonical-slug guidance shared by every tool that takes a coin id. Agents
|
|
36
|
+
// kept passing ticker symbols ("BTC") or guessing slugs, then retry-looping on
|
|
37
|
+
// "resource not found" (devrel#5). The slug is NOT derivable from the symbol,
|
|
38
|
+
// so steer them to search / resolveId first.
|
|
39
|
+
const COIN_ID_DESCRIPTION =
|
|
40
|
+
"Canonical CoinPaprika coin slug in 'symbol-name' format, e.g. 'btc-bitcoin', " +
|
|
41
|
+
"'eth-ethereum', 'ada-cardano'. Do NOT pass ticker symbols ('BTC', 'AAVE') " +
|
|
42
|
+
"or guess the slug — it is not derivable from the symbol (e.g. AAVE resolves " +
|
|
43
|
+
"to 'aave-new', which you could not guess). Call search or resolveId first to " +
|
|
44
|
+
"resolve a symbol or name to its canonical id.";
|
|
45
|
+
|
|
46
|
+
// Known misspellings, surfaced in getCapabilities so MCP catalogs / tool
|
|
47
|
+
// routers / agent frameworks can match fuzzy references back to us (devrel#6).
|
|
48
|
+
const SERVER_ALIASES = ["coinparika", "coinpapika", "coinaprika", "coin-paprika", "coin paprika"];
|
|
49
|
+
|
|
35
50
|
// Structured error response builder
|
|
36
51
|
function buildErrorResponse(code, message, retryable, suggestion, correctedExample, metadata) {
|
|
37
52
|
const error = {
|
|
@@ -97,7 +112,7 @@ function parseAPIError(status, statusText, endpoint) {
|
|
|
97
112
|
ErrorCodes.CP404_NOT_FOUND,
|
|
98
113
|
'Resource not found',
|
|
99
114
|
false,
|
|
100
|
-
'
|
|
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.',
|
|
101
116
|
undefined,
|
|
102
117
|
{ endpoint }
|
|
103
118
|
);
|
|
@@ -260,6 +275,7 @@ async function withGuidance(exec, hint) {
|
|
|
260
275
|
function buildCapabilitiesDocument() {
|
|
261
276
|
return {
|
|
262
277
|
service: "coinpaprika",
|
|
278
|
+
aliases: SERVER_ALIASES,
|
|
263
279
|
version: SERVER_VERSION,
|
|
264
280
|
description: "Cryptocurrency market data for 8000+ coins",
|
|
265
281
|
server: {
|
|
@@ -721,6 +737,34 @@ const server = new McpServer({
|
|
|
721
737
|
description: 'MCP server for accessing CoinPaprika cryptocurrency market data',
|
|
722
738
|
});
|
|
723
739
|
|
|
740
|
+
// devrel#5 parity: validate coin/currency id format BEFORE the handler runs,
|
|
741
|
+
// returning the structured CP400_INVALID_COIN_ID guidance (matches the hosted
|
|
742
|
+
// MCP). Wrapping server.tool centralizes this for every id-bearing tool
|
|
743
|
+
// (coinId / baseCurrencyId / quoteCurrencyId) instead of repeating it in each
|
|
744
|
+
// handler — so a ticker symbol like "BTC" gets actionable guidance to call
|
|
745
|
+
// search/resolveId instead of a bare upstream 404.
|
|
746
|
+
const _registerTool = server.tool.bind(server);
|
|
747
|
+
server.tool = (...regArgs) => {
|
|
748
|
+
const handler = regArgs[regArgs.length - 1];
|
|
749
|
+
const schema = regArgs.length >= 4 && regArgs[2] && typeof regArgs[2] === 'object' ? regArgs[2] : null;
|
|
750
|
+
if (typeof handler === 'function' && schema) {
|
|
751
|
+
const idFields = ['coinId', 'baseCurrencyId', 'quoteCurrencyId'].filter((f) => f in schema);
|
|
752
|
+
if (idFields.length > 0) {
|
|
753
|
+
regArgs[regArgs.length - 1] = async (args, ...rest) => {
|
|
754
|
+
for (const f of idFields) {
|
|
755
|
+
const v = args && args[f];
|
|
756
|
+
if (typeof v === 'string') {
|
|
757
|
+
const err = validateCoinId(v);
|
|
758
|
+
if (err) return formatMcpError(err);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return handler(args, ...rest);
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return _registerTool(...regArgs);
|
|
766
|
+
};
|
|
767
|
+
|
|
724
768
|
// ─── Tool 1: status ───
|
|
725
769
|
server.tool(
|
|
726
770
|
'status',
|
|
@@ -801,7 +845,7 @@ server.tool(
|
|
|
801
845
|
'getCoinOHLCVLatest',
|
|
802
846
|
'OHLCV last full day',
|
|
803
847
|
{
|
|
804
|
-
coinId: z.string().describe(
|
|
848
|
+
coinId: z.string().describe(COIN_ID_DESCRIPTION),
|
|
805
849
|
quote: z.string().optional().default('usd').describe("Quote currency (default: usd)")
|
|
806
850
|
},
|
|
807
851
|
async ({ coinId, quote = 'usd' }) => {
|
|
@@ -819,7 +863,7 @@ server.tool(
|
|
|
819
863
|
'getCoinOHLCVToday',
|
|
820
864
|
'OHLCV today',
|
|
821
865
|
{
|
|
822
|
-
coinId: z.string().describe(
|
|
866
|
+
coinId: z.string().describe(COIN_ID_DESCRIPTION),
|
|
823
867
|
quote: z.string().optional().default('usd').describe("Quote currency (default: usd)")
|
|
824
868
|
},
|
|
825
869
|
async ({ coinId, quote = 'usd' }) => {
|
|
@@ -837,7 +881,7 @@ server.tool(
|
|
|
837
881
|
'getCoinOHLCVHistorical',
|
|
838
882
|
'OHLCV historical (plan-dependent)',
|
|
839
883
|
{
|
|
840
|
-
coinId: z.string().describe(
|
|
884
|
+
coinId: z.string().describe(COIN_ID_DESCRIPTION),
|
|
841
885
|
start: z.string().describe("Start date (ISO 8601 or yyyy-mm-dd)"),
|
|
842
886
|
end: z.string().optional().describe("End date (ISO 8601 or yyyy-mm-dd)"),
|
|
843
887
|
limit: z.number().optional().default(50).describe("Number of data points (default: 50, max: 250)"),
|
|
@@ -858,7 +902,7 @@ server.tool(
|
|
|
858
902
|
server.tool(
|
|
859
903
|
'getCoinById',
|
|
860
904
|
'Coin details (descriptive info)',
|
|
861
|
-
{ coinId: z.string().describe(
|
|
905
|
+
{ coinId: z.string().describe(COIN_ID_DESCRIPTION) },
|
|
862
906
|
async ({ coinId }) => {
|
|
863
907
|
try {
|
|
864
908
|
const response = await fetchFromAPI(`/coins/${encodeURIComponent(coinId)}`);
|
|
@@ -874,7 +918,7 @@ server.tool(
|
|
|
874
918
|
'getCoinEvents',
|
|
875
919
|
'Events for a coin',
|
|
876
920
|
{
|
|
877
|
-
coinId: z.string().describe(
|
|
921
|
+
coinId: z.string().describe(COIN_ID_DESCRIPTION),
|
|
878
922
|
limit: z.number().optional().default(50).describe("Number of events to return (default: 50, max: 250)")
|
|
879
923
|
},
|
|
880
924
|
async ({ coinId, limit = 50 }) => {
|
|
@@ -895,7 +939,7 @@ server.tool(
|
|
|
895
939
|
'getCoinExchanges',
|
|
896
940
|
'Exchanges for a coin',
|
|
897
941
|
{
|
|
898
|
-
coinId: z.string().describe(
|
|
942
|
+
coinId: z.string().describe(COIN_ID_DESCRIPTION),
|
|
899
943
|
limit: z.number().optional().default(50).describe("Number of exchanges to return (default: 50, max: 250)")
|
|
900
944
|
},
|
|
901
945
|
async ({ coinId, limit = 50 }) => {
|
|
@@ -916,7 +960,7 @@ server.tool(
|
|
|
916
960
|
'getCoinMarkets',
|
|
917
961
|
'Markets for a coin',
|
|
918
962
|
{
|
|
919
|
-
coinId: z.string().describe(
|
|
963
|
+
coinId: z.string().describe(COIN_ID_DESCRIPTION),
|
|
920
964
|
quotes: z.string().optional().describe("Comma-separated quote currencies"),
|
|
921
965
|
limit: z.number().optional().default(50).describe("Number of markets to return (default: 50, max: 250)")
|
|
922
966
|
},
|
|
@@ -939,7 +983,7 @@ server.tool(
|
|
|
939
983
|
'getTickersById',
|
|
940
984
|
'Ticker for a specific coin',
|
|
941
985
|
{
|
|
942
|
-
coinId: z.string().describe(
|
|
986
|
+
coinId: z.string().describe(COIN_ID_DESCRIPTION),
|
|
943
987
|
quotes: z.string().optional().describe("Comma-separated quote currencies")
|
|
944
988
|
},
|
|
945
989
|
async ({ coinId, quotes }) => {
|
|
@@ -958,7 +1002,7 @@ server.tool(
|
|
|
958
1002
|
'getTickersHistoricalById',
|
|
959
1003
|
'Historical ticks for a coin (plan dependent)',
|
|
960
1004
|
{
|
|
961
|
-
coinId: z.string().describe(
|
|
1005
|
+
coinId: z.string().describe(COIN_ID_DESCRIPTION),
|
|
962
1006
|
start: z.string().describe("Start date (ISO 8601 or yyyy-mm-dd)"),
|
|
963
1007
|
end: z.string().optional().describe("End date"),
|
|
964
1008
|
limit: z.number().optional().default(50).describe("Number of data points (default: 50, max: 250)"),
|
|
@@ -1228,8 +1272,8 @@ server.tool(
|
|
|
1228
1272
|
'priceConverter',
|
|
1229
1273
|
'Convert base -> quote',
|
|
1230
1274
|
{
|
|
1231
|
-
baseCurrencyId: z.string().describe("
|
|
1232
|
-
quoteCurrencyId: z.string().describe("
|
|
1275
|
+
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."),
|
|
1276
|
+
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
1277
|
amount: z.number().optional().default(0).describe("Amount to convert (default: 0)")
|
|
1234
1278
|
},
|
|
1235
1279
|
async ({ baseCurrencyId, quoteCurrencyId, amount = 0 }) => {
|
|
@@ -1280,7 +1324,10 @@ server.tool(
|
|
|
1280
1324
|
},
|
|
1281
1325
|
async (params) => {
|
|
1282
1326
|
const qs = new URLSearchParams();
|
|
1283
|
-
|
|
1327
|
+
// Allowlist the upstream-recognized mapping params only — never forward
|
|
1328
|
+
// MCP-only or unexpected fields, which the upstream rejects with a 400.
|
|
1329
|
+
for (const k of ['coinpaprika', 'coinmarketcap', 'coingecko', 'cryptocompare', 'isin', 'dti']) {
|
|
1330
|
+
const v = params[k];
|
|
1284
1331
|
if (v) qs.set(k, String(v));
|
|
1285
1332
|
}
|
|
1286
1333
|
return withGuidance(
|
package/package.json
CHANGED