mcp-crypto-price 3.3.0 → 3.4.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/README.md +46 -35
- package/dist/config/index.js +2 -2
- package/dist/homepage.js +1 -1
- package/dist/http.js +17 -5
- package/dist/index.js +91 -43
- package/dist/mcp-crypto-price-3.4.0.tgz +0 -0
- package/dist/services/__tests__/coincap.test.js +15 -15
- package/dist/services/__tests__/formatters.test.js +12 -12
- package/dist/services/coincap.js +65 -5
- package/dist/services/formatters.js +137 -8
- package/dist/services/schemas.js +66 -0
- package/dist/tools/__tests__/exchanges.test.js +92 -0
- package/dist/tools/__tests__/historical.test.js +11 -4
- package/dist/tools/__tests__/market.test.js +20 -8
- package/dist/tools/__tests__/price.test.js +11 -4
- package/dist/tools/__tests__/rates.test.js +100 -0
- package/dist/tools/__tests__/technical-analysis.test.js +81 -0
- package/dist/tools/__tests__/top-assets.test.js +22 -8
- package/dist/tools/exchanges.js +61 -0
- package/dist/tools/historical.js +42 -12
- package/dist/tools/index.js +3 -0
- package/dist/tools/market.js +22 -8
- package/dist/tools/price.js +19 -7
- package/dist/tools/rates.js +51 -0
- package/dist/tools/technical-analysis.js +52 -0
- package/dist/tools/top-assets.js +17 -8
- package/package.json +20 -7
- package/dist/mcp-crypto-price-3.3.0.tgz +0 -0
package/README.md
CHANGED
|
@@ -7,11 +7,15 @@
|
|
|
7
7
|
|
|
8
8
|
A Model Context Protocol (MCP) server that provides comprehensive cryptocurrency analysis using the CoinCap API. This server offers real-time price data, market analysis, and historical trends through an easy-to-use interface. Supports both STDIO and Streamable HTTP transports.
|
|
9
9
|
|
|
10
|
+
## Requirements
|
|
11
|
+
|
|
12
|
+
- **Node.js 22.14+**
|
|
13
|
+
- **CoinCap API key** via `COINCAP_API_KEY`
|
|
14
|
+
|
|
10
15
|
## What's New
|
|
11
16
|
|
|
12
17
|
- **BREAKING**: CoinCap v2 API removed. Now uses v3 API exclusively. A `COINCAP_API_KEY` is required (free tier available at [pro.coincap.io/dashboard](https://pro.coincap.io/dashboard))
|
|
13
18
|
- Streamable HTTP transport added (while keeping STDIO compatibility)
|
|
14
|
-
- Release workflow signs commits via SSH for Verified releases
|
|
15
19
|
- Smithery CLI scripts to build and run the HTTP server
|
|
16
20
|
|
|
17
21
|
## Usage
|
|
@@ -51,6 +55,19 @@ If your MCP client requires launching via `cmd.exe` on Windows:
|
|
|
51
55
|
}
|
|
52
56
|
```
|
|
53
57
|
|
|
58
|
+
### Development scripts
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm run build # Compile TypeScript → dist/
|
|
62
|
+
npm run format # Format source files with Prettier
|
|
63
|
+
npm run lint # Check for lint errors (ESLint + typescript-eslint)
|
|
64
|
+
npm run lint:fix # Auto-fix lint errors
|
|
65
|
+
npm run types:check # TypeScript type-check without emitting files
|
|
66
|
+
npm test # Run all tests
|
|
67
|
+
npm run test:coverage # Run tests with coverage report
|
|
68
|
+
npm run inspector # Open MCP inspector for interactive debugging
|
|
69
|
+
```
|
|
70
|
+
|
|
54
71
|
### Run as Streamable HTTP server
|
|
55
72
|
|
|
56
73
|
You can run the server over HTTP for environments that support MCP over HTTP streaming.
|
|
@@ -90,7 +107,7 @@ http://localhost:3000/mcp?COINCAP_API_KEY=YOUR_API_KEY_HERE
|
|
|
90
107
|
For remote deployments:
|
|
91
108
|
|
|
92
109
|
```
|
|
93
|
-
https://
|
|
110
|
+
https://mcp-crypto-price.codemonkeyinnovations.com/mcp?COINCAP_API_KEY=YOUR_API_KEY_HERE
|
|
94
111
|
```
|
|
95
112
|
|
|
96
113
|
## Required: CoinCap API Key
|
|
@@ -112,39 +129,6 @@ If you do use the Smithery CLI, authenticate with `smithery auth login` or by se
|
|
|
112
129
|
|
|
113
130
|
Launch Claude Desktop to start using the crypto analysis tools.
|
|
114
131
|
|
|
115
|
-
## Verified commits & SSH signing
|
|
116
|
-
|
|
117
|
-
This repository requires Verified (cryptographically signed) commits. CI also includes a job (`Verify commit signatures`) that fails PRs with unsigned commits.
|
|
118
|
-
|
|
119
|
-
### Create an SSH signing key (once)
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
# Generate a new ed25519 SSH key (no passphrase makes CI easier)
|
|
123
|
-
ssh-keygen -t ed25519 -C "CI signing key for mcp-crypto-price" -f ~/.ssh/id_ed25519 -N ''
|
|
124
|
-
|
|
125
|
-
# Your keys will be at:
|
|
126
|
-
# Private: ~/.ssh/id_ed25519
|
|
127
|
-
# Public : ~/.ssh/id_ed25519.pub
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Enable SSH signing locally (optional but recommended)
|
|
131
|
-
|
|
132
|
-
```bash
|
|
133
|
-
git config --global gpg.format ssh
|
|
134
|
-
git config --global user.signingkey ~/.ssh/id_ed25519.pub
|
|
135
|
-
git config --global commit.gpgsign true
|
|
136
|
-
|
|
137
|
-
# Example signed commit
|
|
138
|
-
git commit -S -m 'feat: add something'
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### Configure GitHub to verify your signatures
|
|
142
|
-
|
|
143
|
-
1. Add your public key as an SSH Signing Key in your GitHub account:
|
|
144
|
-
- GitHub → Settings → SSH and GPG keys → New SSH key
|
|
145
|
-
- Key type: Signing Key (SSH)
|
|
146
|
-
- Paste contents of `~/.ssh/id_ed25519.pub`
|
|
147
|
-
|
|
148
132
|
## Tools
|
|
149
133
|
|
|
150
134
|
#### get-crypto-price
|
|
@@ -181,6 +165,30 @@ Lists top cryptocurrencies ranked by market cap, including:
|
|
|
181
165
|
- Market cap and rank
|
|
182
166
|
- Configurable result count (1–50, default 10)
|
|
183
167
|
|
|
168
|
+
#### get-technical-analysis
|
|
169
|
+
|
|
170
|
+
Returns the latest technical indicators for any cryptocurrency:
|
|
171
|
+
- SMA (Simple Moving Average) with period
|
|
172
|
+
- EMA (Exponential Moving Average) with period
|
|
173
|
+
- RSI (Relative Strength Index) with Overbought/Oversold/Neutral signal
|
|
174
|
+
- MACD with signal line, histogram, and Bullish/Bearish label
|
|
175
|
+
- VWAP (Volume Weighted Average Price, 24h)
|
|
176
|
+
|
|
177
|
+
#### get-rates
|
|
178
|
+
|
|
179
|
+
Returns USD-based conversion rates for fiat currencies and cryptocurrencies:
|
|
180
|
+
- All fiat currency rates (USD base)
|
|
181
|
+
- Top 10 cryptocurrency rates
|
|
182
|
+
- Optional `slug` parameter (e.g. `euro`, `bitcoin`) for a single rate lookup
|
|
183
|
+
|
|
184
|
+
#### get-exchanges
|
|
185
|
+
|
|
186
|
+
Lists top cryptocurrency exchanges ranked by 24h volume:
|
|
187
|
+
- Exchange name, rank, and 24h volume in USD
|
|
188
|
+
- Number of trading pairs and market share percentage
|
|
189
|
+
- Optional `exchangeId` parameter (e.g. `binance`) for single exchange details
|
|
190
|
+
- Optional `limit` parameter (1–50, default 10)
|
|
191
|
+
|
|
184
192
|
## Sample Prompts
|
|
185
193
|
|
|
186
194
|
- "What's the current price of Bitcoin?"
|
|
@@ -188,6 +196,9 @@ Lists top cryptocurrencies ranked by market cap, including:
|
|
|
188
196
|
- "Give me the 7-day price history for DOGE"
|
|
189
197
|
- "What are the top exchanges trading BTC?"
|
|
190
198
|
- "Show me the price trends for SOL with 1-hour intervals"
|
|
199
|
+
- "What are the technical indicators for ETH right now?"
|
|
200
|
+
- "What's the current EUR to USD exchange rate?"
|
|
201
|
+
- "Which crypto exchanges have the highest 24h volume?"
|
|
191
202
|
|
|
192
203
|
## Project Inspiration
|
|
193
204
|
|
package/dist/config/index.js
CHANGED
|
@@ -11,9 +11,9 @@ function readVersion() {
|
|
|
11
11
|
return '0.0.0';
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
-
export const COINCAP_API_BASE =
|
|
14
|
+
export const COINCAP_API_BASE = 'https://rest.coincap.io/v3';
|
|
15
15
|
export const SERVER_CONFIG = {
|
|
16
|
-
name:
|
|
16
|
+
name: 'mcp-crypto-price',
|
|
17
17
|
version: readVersion(),
|
|
18
18
|
};
|
|
19
19
|
export const CACHE_TTL = 60000; // default fallback (ms)
|
package/dist/homepage.js
CHANGED
|
@@ -294,7 +294,7 @@ export function renderHomepage() {
|
|
|
294
294
|
</div>
|
|
295
295
|
<div class="metric">
|
|
296
296
|
<strong>Remote endpoint</strong>
|
|
297
|
-
<code>https
|
|
297
|
+
<code>https://mcp-crypto-price.codemonkeyinnovations.com/mcp</code>
|
|
298
298
|
</div>
|
|
299
299
|
</div>
|
|
300
300
|
</article>
|
package/dist/http.js
CHANGED
|
@@ -7,7 +7,9 @@ import { renderHomepage } from './homepage.js';
|
|
|
7
7
|
const PORT = parseInt(process.env.PORT ?? '3000', 10);
|
|
8
8
|
async function handleMcp(req, res, searchParams) {
|
|
9
9
|
const coincapApiKey = searchParams.get('COINCAP_API_KEY') ?? process.env.COINCAP_API_KEY;
|
|
10
|
-
const transport = new StreamableHTTPServerTransport({
|
|
10
|
+
const transport = new StreamableHTTPServerTransport({
|
|
11
|
+
sessionIdGenerator: undefined,
|
|
12
|
+
});
|
|
11
13
|
const server = createServer({ config: { COINCAP_API_KEY: coincapApiKey } });
|
|
12
14
|
await server.connect(transport);
|
|
13
15
|
if (req.method === 'POST') {
|
|
@@ -34,7 +36,10 @@ const serverCard = {
|
|
|
34
36
|
inputSchema: {
|
|
35
37
|
type: 'object',
|
|
36
38
|
properties: {
|
|
37
|
-
symbol: {
|
|
39
|
+
symbol: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
description: 'Cryptocurrency symbol or name (e.g. BTC or Bitcoin)',
|
|
42
|
+
},
|
|
38
43
|
},
|
|
39
44
|
required: ['symbol'],
|
|
40
45
|
},
|
|
@@ -52,7 +57,10 @@ const serverCard = {
|
|
|
52
57
|
inputSchema: {
|
|
53
58
|
type: 'object',
|
|
54
59
|
properties: {
|
|
55
|
-
symbol: {
|
|
60
|
+
symbol: {
|
|
61
|
+
type: 'string',
|
|
62
|
+
description: 'Cryptocurrency symbol or name (e.g. BTC or Bitcoin)',
|
|
63
|
+
},
|
|
56
64
|
},
|
|
57
65
|
required: ['symbol'],
|
|
58
66
|
},
|
|
@@ -70,7 +78,10 @@ const serverCard = {
|
|
|
70
78
|
inputSchema: {
|
|
71
79
|
type: 'object',
|
|
72
80
|
properties: {
|
|
73
|
-
symbol: {
|
|
81
|
+
symbol: {
|
|
82
|
+
type: 'string',
|
|
83
|
+
description: 'Cryptocurrency symbol or name (e.g. BTC or Bitcoin)',
|
|
84
|
+
},
|
|
74
85
|
interval: {
|
|
75
86
|
type: 'string',
|
|
76
87
|
enum: ['m5', 'm15', 'm30', 'h1', 'h2', 'h6', 'h12', 'd1'],
|
|
@@ -138,7 +149,8 @@ const httpServer = http.createServer(async (req, res) => {
|
|
|
138
149
|
const parsed = new URL(req.url ?? '/', `http://localhost`);
|
|
139
150
|
const pathname = parsed.pathname;
|
|
140
151
|
// MCP server card for discovery (required by Smithery)
|
|
141
|
-
if (pathname === '/.well-known/mcp/server-card.json' &&
|
|
152
|
+
if (pathname === '/.well-known/mcp/server-card.json' &&
|
|
153
|
+
req.method === 'GET') {
|
|
142
154
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
143
155
|
res.end(JSON.stringify(serverCard));
|
|
144
156
|
return;
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { McpServer } from
|
|
3
|
-
import { StdioServerTransport } from
|
|
4
|
-
import { z } from
|
|
5
|
-
import path from
|
|
6
|
-
import { fileURLToPath } from
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { SERVER_CONFIG } from './config/index.js';
|
|
8
|
-
import { handleGetPrice, handleGetMarketAnalysis, handleGetHistoricalAnalysis, handleGetTopAssets, GetPriceArgumentsSchema, GetMarketAnalysisSchema, GetHistoricalAnalysisSchema, GetTopAssetsSchema, } from './tools/index.js';
|
|
8
|
+
import { handleGetPrice, handleGetMarketAnalysis, handleGetHistoricalAnalysis, handleGetTopAssets, handleGetTechnicalAnalysis, handleGetRates, handleGetExchanges, GetPriceArgumentsSchema, GetMarketAnalysisSchema, GetHistoricalAnalysisSchema, GetTopAssetsSchema, GetTechnicalAnalysisSchema, GetRatesSchema, GetExchangesSchema, } from './tools/index.js';
|
|
9
9
|
export const configSchema = z.object({
|
|
10
10
|
COINCAP_API_KEY: z
|
|
11
11
|
.string()
|
|
12
12
|
.optional()
|
|
13
|
-
.describe(
|
|
13
|
+
.describe('API key for CoinCap v3 API. Free tier available at https://pro.coincap.io/dashboard'),
|
|
14
14
|
CACHE_TTL_SECONDS: z
|
|
15
15
|
.number()
|
|
16
16
|
.int()
|
|
@@ -18,7 +18,7 @@ export const configSchema = z.object({
|
|
|
18
18
|
.max(3600)
|
|
19
19
|
.default(60)
|
|
20
20
|
.optional()
|
|
21
|
-
.describe(
|
|
21
|
+
.describe('How long to cache API responses in seconds (default: 60). Lower values return fresher data; higher values reduce API usage.'),
|
|
22
22
|
});
|
|
23
23
|
export function createServer({ config, }) {
|
|
24
24
|
if (config?.COINCAP_API_KEY && !process.env.COINCAP_API_KEY) {
|
|
@@ -32,17 +32,17 @@ export function createServer({ config, }) {
|
|
|
32
32
|
version: SERVER_CONFIG.version,
|
|
33
33
|
icons: [
|
|
34
34
|
{
|
|
35
|
-
src:
|
|
36
|
-
mimeType:
|
|
35
|
+
src: 'https://raw.githubusercontent.com/truss44/mcp-crypto-price/main/logo.png',
|
|
36
|
+
mimeType: 'image/png',
|
|
37
37
|
},
|
|
38
38
|
],
|
|
39
39
|
});
|
|
40
|
-
server.registerTool(
|
|
41
|
-
title:
|
|
42
|
-
description:
|
|
40
|
+
server.registerTool('get-crypto-price', {
|
|
41
|
+
title: 'Get Crypto Price',
|
|
42
|
+
description: 'Get real-time price, 24-hour change percentage, trading volume, and market cap for any cryptocurrency by symbol or name.',
|
|
43
43
|
inputSchema: GetPriceArgumentsSchema.shape,
|
|
44
44
|
annotations: {
|
|
45
|
-
title:
|
|
45
|
+
title: 'Get Crypto Price',
|
|
46
46
|
readOnlyHint: true,
|
|
47
47
|
destructiveHint: false,
|
|
48
48
|
idempotentHint: true,
|
|
@@ -52,12 +52,12 @@ export function createServer({ config, }) {
|
|
|
52
52
|
const result = await handleGetPrice(args);
|
|
53
53
|
return result;
|
|
54
54
|
});
|
|
55
|
-
server.registerTool(
|
|
56
|
-
title:
|
|
57
|
-
description:
|
|
55
|
+
server.registerTool('get-market-analysis', {
|
|
56
|
+
title: 'Get Market Analysis',
|
|
57
|
+
description: 'Get detailed market analysis for a cryptocurrency including the top 5 exchanges by volume, price per exchange, and volume distribution percentages.',
|
|
58
58
|
inputSchema: GetMarketAnalysisSchema.shape,
|
|
59
59
|
annotations: {
|
|
60
|
-
title:
|
|
60
|
+
title: 'Get Market Analysis',
|
|
61
61
|
readOnlyHint: true,
|
|
62
62
|
destructiveHint: false,
|
|
63
63
|
idempotentHint: true,
|
|
@@ -67,12 +67,12 @@ export function createServer({ config, }) {
|
|
|
67
67
|
const result = await handleGetMarketAnalysis(args);
|
|
68
68
|
return result;
|
|
69
69
|
});
|
|
70
|
-
server.registerTool(
|
|
71
|
-
title:
|
|
72
|
-
description:
|
|
70
|
+
server.registerTool('get-historical-analysis', {
|
|
71
|
+
title: 'Get Historical Analysis',
|
|
72
|
+
description: 'Get historical price data for a cryptocurrency with trend analysis including period high/low, price change percentage, and volatility metrics over a customizable timeframe.',
|
|
73
73
|
inputSchema: GetHistoricalAnalysisSchema.shape,
|
|
74
74
|
annotations: {
|
|
75
|
-
title:
|
|
75
|
+
title: 'Get Historical Analysis',
|
|
76
76
|
readOnlyHint: true,
|
|
77
77
|
destructiveHint: false,
|
|
78
78
|
idempotentHint: true,
|
|
@@ -82,12 +82,12 @@ export function createServer({ config, }) {
|
|
|
82
82
|
const result = await handleGetHistoricalAnalysis(args);
|
|
83
83
|
return result;
|
|
84
84
|
});
|
|
85
|
-
server.registerTool(
|
|
86
|
-
title:
|
|
87
|
-
description:
|
|
85
|
+
server.registerTool('get-top-assets', {
|
|
86
|
+
title: 'Get Top Assets',
|
|
87
|
+
description: 'Get the top cryptocurrencies ranked by market cap, with real-time price, 24-hour change percentage, and market cap for each asset.',
|
|
88
88
|
inputSchema: GetTopAssetsSchema.shape,
|
|
89
89
|
annotations: {
|
|
90
|
-
title:
|
|
90
|
+
title: 'Get Top Assets',
|
|
91
91
|
readOnlyHint: true,
|
|
92
92
|
destructiveHint: false,
|
|
93
93
|
idempotentHint: true,
|
|
@@ -97,18 +97,65 @@ export function createServer({ config, }) {
|
|
|
97
97
|
const result = await handleGetTopAssets(args);
|
|
98
98
|
return result;
|
|
99
99
|
});
|
|
100
|
-
server.
|
|
101
|
-
title:
|
|
102
|
-
description:
|
|
100
|
+
server.registerTool('get-technical-analysis', {
|
|
101
|
+
title: 'Get Technical Analysis',
|
|
102
|
+
description: 'Get the latest technical indicators for a cryptocurrency including SMA, EMA, RSI, MACD, and VWAP to assess momentum, trend direction, and overbought/oversold conditions.',
|
|
103
|
+
inputSchema: GetTechnicalAnalysisSchema.shape,
|
|
104
|
+
annotations: {
|
|
105
|
+
title: 'Get Technical Analysis',
|
|
106
|
+
readOnlyHint: true,
|
|
107
|
+
destructiveHint: false,
|
|
108
|
+
idempotentHint: true,
|
|
109
|
+
openWorldHint: true,
|
|
110
|
+
},
|
|
111
|
+
}, async (args, _extra) => {
|
|
112
|
+
const result = await handleGetTechnicalAnalysis(args);
|
|
113
|
+
return result;
|
|
114
|
+
});
|
|
115
|
+
server.registerTool('get-rates', {
|
|
116
|
+
title: 'Get Currency Rates',
|
|
117
|
+
description: "Get USD-based conversion rates for fiat currencies and cryptocurrencies. Optionally pass a slug (e.g. 'euro', 'us-dollar', 'bitcoin') to look up a single rate.",
|
|
118
|
+
inputSchema: GetRatesSchema.shape,
|
|
119
|
+
annotations: {
|
|
120
|
+
title: 'Get Currency Rates',
|
|
121
|
+
readOnlyHint: true,
|
|
122
|
+
destructiveHint: false,
|
|
123
|
+
idempotentHint: true,
|
|
124
|
+
openWorldHint: true,
|
|
125
|
+
},
|
|
126
|
+
}, async (args, _extra) => {
|
|
127
|
+
const result = await handleGetRates(args);
|
|
128
|
+
return result;
|
|
129
|
+
});
|
|
130
|
+
server.registerTool('get-exchanges', {
|
|
131
|
+
title: 'Get Exchanges',
|
|
132
|
+
description: "Get top cryptocurrency exchanges ranked by 24h volume. Optionally pass an exchangeId (e.g. 'binance', 'coinbase') to get details for a specific exchange including volume, trading pairs, and market share.",
|
|
133
|
+
inputSchema: GetExchangesSchema.shape,
|
|
134
|
+
annotations: {
|
|
135
|
+
title: 'Get Exchanges',
|
|
136
|
+
readOnlyHint: true,
|
|
137
|
+
destructiveHint: false,
|
|
138
|
+
idempotentHint: true,
|
|
139
|
+
openWorldHint: true,
|
|
140
|
+
},
|
|
141
|
+
}, async (args, _extra) => {
|
|
142
|
+
const result = await handleGetExchanges(args);
|
|
143
|
+
return result;
|
|
144
|
+
});
|
|
145
|
+
server.registerPrompt('analyze-crypto', {
|
|
146
|
+
title: 'Analyze Cryptocurrency',
|
|
147
|
+
description: 'Generate a comprehensive analysis of a cryptocurrency covering price, market, and historical trends',
|
|
103
148
|
argsSchema: {
|
|
104
|
-
symbol: z
|
|
149
|
+
symbol: z
|
|
150
|
+
.string()
|
|
151
|
+
.describe('Cryptocurrency symbol or name (e.g. BTC, ETH, Bitcoin)'),
|
|
105
152
|
},
|
|
106
153
|
}, ({ symbol }) => ({
|
|
107
154
|
messages: [
|
|
108
155
|
{
|
|
109
|
-
role:
|
|
156
|
+
role: 'user',
|
|
110
157
|
content: {
|
|
111
|
-
type:
|
|
158
|
+
type: 'text',
|
|
112
159
|
text: `Please provide a comprehensive analysis of ${symbol}. Use the available tools to:
|
|
113
160
|
1. Get the current price and 24-hour stats
|
|
114
161
|
2. Analyze the top exchanges and trading volume distribution
|
|
@@ -122,15 +169,15 @@ Summarize the findings including price performance, market liquidity, and any no
|
|
|
122
169
|
// Register a no-op resource so the server advertises resources capability
|
|
123
170
|
// during the MCP initialize handshake. Without this, clients may receive
|
|
124
171
|
// -32601 "Method not found" errors for resources/list.
|
|
125
|
-
server.registerResource(
|
|
126
|
-
title:
|
|
127
|
-
description:
|
|
128
|
-
mimeType:
|
|
172
|
+
server.registerResource('server-info', 'info://server', {
|
|
173
|
+
title: 'Server Info',
|
|
174
|
+
description: 'Basic server information',
|
|
175
|
+
mimeType: 'application/json',
|
|
129
176
|
}, async (uri) => ({
|
|
130
177
|
contents: [
|
|
131
178
|
{
|
|
132
179
|
uri: uri.href,
|
|
133
|
-
mimeType:
|
|
180
|
+
mimeType: 'application/json',
|
|
134
181
|
text: JSON.stringify({
|
|
135
182
|
name: SERVER_CONFIG.name,
|
|
136
183
|
version: SERVER_CONFIG.version,
|
|
@@ -152,28 +199,29 @@ async function main() {
|
|
|
152
199
|
});
|
|
153
200
|
const transport = new StdioServerTransport();
|
|
154
201
|
await server.connect(transport);
|
|
155
|
-
console.error(
|
|
202
|
+
console.error('Crypto Price MCP Server running on stdio');
|
|
156
203
|
}
|
|
157
204
|
// Start stdio transport when:
|
|
158
205
|
// 1. Explicitly requested via MCP_TRANSPORT=stdio, OR
|
|
159
206
|
// 2. Run directly from CLI (not imported as a module)
|
|
160
207
|
let thisFilePath;
|
|
161
208
|
try {
|
|
209
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
162
210
|
const importMetaUrl = import.meta?.url;
|
|
163
|
-
if (typeof importMetaUrl ===
|
|
211
|
+
if (typeof importMetaUrl === 'string') {
|
|
164
212
|
thisFilePath = fileURLToPath(importMetaUrl);
|
|
165
213
|
}
|
|
166
214
|
}
|
|
167
215
|
catch {
|
|
168
216
|
// no-op
|
|
169
217
|
}
|
|
170
|
-
const isEntrypoint = typeof thisFilePath ===
|
|
171
|
-
typeof process.argv[1] ===
|
|
218
|
+
const isEntrypoint = typeof thisFilePath === 'string' &&
|
|
219
|
+
typeof process.argv[1] === 'string' &&
|
|
172
220
|
path.resolve(process.argv[1]) === path.resolve(thisFilePath);
|
|
173
221
|
const isDirectRun = isEntrypoint || process.argv[1]?.includes('mcp-crypto-price');
|
|
174
|
-
if (process.env.MCP_TRANSPORT ===
|
|
222
|
+
if (process.env.MCP_TRANSPORT === 'stdio' || isDirectRun) {
|
|
175
223
|
main().catch((error) => {
|
|
176
|
-
console.error(
|
|
224
|
+
console.error('Fatal error in main():', error);
|
|
177
225
|
process.exit(1);
|
|
178
226
|
});
|
|
179
227
|
}
|
|
Binary file
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jest } from '@jest/globals';
|
|
2
|
-
import { getAssets, getMarkets, getHistoricalData, clearCache, MissingApiKeyError } from '../coincap.js';
|
|
2
|
+
import { getAssets, getMarkets, getHistoricalData, clearCache, MissingApiKeyError, } from '../coincap.js';
|
|
3
3
|
// Mock global fetch
|
|
4
4
|
const mockFetch = jest.fn();
|
|
5
5
|
global.fetch = mockFetch;
|
|
@@ -40,17 +40,17 @@ describe('CoinCap Service', () => {
|
|
|
40
40
|
supply: '19000000',
|
|
41
41
|
maxSupply: '21000000',
|
|
42
42
|
vwap24Hr: '49500.00',
|
|
43
|
-
}
|
|
44
|
-
]
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
45
|
};
|
|
46
46
|
mockFetch.mockImplementationOnce(() => Promise.resolve({
|
|
47
47
|
ok: true,
|
|
48
|
-
json: () => Promise.resolve(mockResponse)
|
|
48
|
+
json: () => Promise.resolve(mockResponse),
|
|
49
49
|
}));
|
|
50
50
|
const result = await getAssets();
|
|
51
51
|
expect(result).toEqual(mockResponse);
|
|
52
52
|
expect(mockFetch).toHaveBeenCalledWith('https://rest.coincap.io/v3/assets', expect.objectContaining({
|
|
53
|
-
headers: { Authorization: 'Bearer test-api-key' }
|
|
53
|
+
headers: { Authorization: 'Bearer test-api-key' },
|
|
54
54
|
}));
|
|
55
55
|
});
|
|
56
56
|
it('should handle fetch errors', async () => {
|
|
@@ -63,7 +63,7 @@ describe('CoinCap Service', () => {
|
|
|
63
63
|
mockFetch.mockImplementationOnce(() => Promise.resolve({
|
|
64
64
|
ok: false,
|
|
65
65
|
status: 500,
|
|
66
|
-
statusText: 'Internal Server Error'
|
|
66
|
+
statusText: 'Internal Server Error',
|
|
67
67
|
}));
|
|
68
68
|
const result = await getAssets();
|
|
69
69
|
expect(result).toBeNull();
|
|
@@ -81,17 +81,17 @@ describe('CoinCap Service', () => {
|
|
|
81
81
|
priceUsd: '50000.00',
|
|
82
82
|
volumeUsd24Hr: '5000000000',
|
|
83
83
|
volumePercent: '25.00',
|
|
84
|
-
}
|
|
85
|
-
]
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
86
|
};
|
|
87
87
|
mockFetch.mockImplementationOnce(() => Promise.resolve({
|
|
88
88
|
ok: true,
|
|
89
|
-
json: () => Promise.resolve(mockResponse)
|
|
89
|
+
json: () => Promise.resolve(mockResponse),
|
|
90
90
|
}));
|
|
91
91
|
const result = await getMarkets('bitcoin');
|
|
92
92
|
expect(result).toEqual(mockResponse);
|
|
93
93
|
expect(mockFetch).toHaveBeenCalledWith('https://rest.coincap.io/v3/assets/bitcoin/markets', expect.objectContaining({
|
|
94
|
-
headers: { Authorization: 'Bearer test-api-key' }
|
|
94
|
+
headers: { Authorization: 'Bearer test-api-key' },
|
|
95
95
|
}));
|
|
96
96
|
});
|
|
97
97
|
it('should handle fetch errors for markets', async () => {
|
|
@@ -108,18 +108,18 @@ describe('CoinCap Service', () => {
|
|
|
108
108
|
{
|
|
109
109
|
time: 1609459200000,
|
|
110
110
|
priceUsd: '45000.00',
|
|
111
|
-
date: '2021-01-01'
|
|
112
|
-
}
|
|
113
|
-
]
|
|
111
|
+
date: '2021-01-01',
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
114
|
};
|
|
115
115
|
mockFetch.mockImplementationOnce(() => Promise.resolve({
|
|
116
116
|
ok: true,
|
|
117
|
-
json: () => Promise.resolve(mockResponse)
|
|
117
|
+
json: () => Promise.resolve(mockResponse),
|
|
118
118
|
}));
|
|
119
119
|
const result = await getHistoricalData('bitcoin', 'h1', 1609459200000, 1609545600000);
|
|
120
120
|
expect(result).toEqual(mockResponse);
|
|
121
121
|
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining('https://rest.coincap.io/v3/assets/bitcoin/history'), expect.objectContaining({
|
|
122
|
-
headers: { Authorization: 'Bearer test-api-key' }
|
|
122
|
+
headers: { Authorization: 'Bearer test-api-key' },
|
|
123
123
|
}));
|
|
124
124
|
});
|
|
125
125
|
it('should handle fetch errors for historical data', async () => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatPriceInfo, formatMarketAnalysis, formatHistoricalAnalysis } from '../formatters.js';
|
|
1
|
+
import { formatPriceInfo, formatMarketAnalysis, formatHistoricalAnalysis, } from '../formatters.js';
|
|
2
2
|
describe('Formatters', () => {
|
|
3
3
|
describe('formatPriceInfo', () => {
|
|
4
4
|
it('should format price info correctly', () => {
|
|
@@ -13,7 +13,7 @@ describe('Formatters', () => {
|
|
|
13
13
|
marketCapUsd: '1000000000000',
|
|
14
14
|
supply: '19000000',
|
|
15
15
|
maxSupply: '21000000',
|
|
16
|
-
vwap24Hr: '49500.00'
|
|
16
|
+
vwap24Hr: '49500.00',
|
|
17
17
|
};
|
|
18
18
|
const formatted = formatPriceInfo(asset);
|
|
19
19
|
expect(formatted).toContain('Bitcoin (BTC)');
|
|
@@ -35,7 +35,7 @@ describe('Formatters', () => {
|
|
|
35
35
|
marketCapUsd: '7000000000',
|
|
36
36
|
supply: '589000000000000',
|
|
37
37
|
maxSupply: '',
|
|
38
|
-
vwap24Hr: '0.00001200'
|
|
38
|
+
vwap24Hr: '0.00001200',
|
|
39
39
|
};
|
|
40
40
|
const formatted = formatPriceInfo(asset);
|
|
41
41
|
expect(formatted).toContain('Price: $0.00001234');
|
|
@@ -55,7 +55,7 @@ describe('Formatters', () => {
|
|
|
55
55
|
marketCapUsd: '1000000000000',
|
|
56
56
|
supply: '19000000',
|
|
57
57
|
maxSupply: '21000000',
|
|
58
|
-
vwap24Hr: '49500.00'
|
|
58
|
+
vwap24Hr: '49500.00',
|
|
59
59
|
};
|
|
60
60
|
const markets = [
|
|
61
61
|
{
|
|
@@ -64,7 +64,7 @@ describe('Formatters', () => {
|
|
|
64
64
|
quoteSymbol: 'USD',
|
|
65
65
|
priceUsd: '50100.00',
|
|
66
66
|
volumeUsd24Hr: '10000000000',
|
|
67
|
-
volumePercent: '33.33'
|
|
67
|
+
volumePercent: '33.33',
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
exchangeId: 'coinbase',
|
|
@@ -72,8 +72,8 @@ describe('Formatters', () => {
|
|
|
72
72
|
quoteSymbol: 'USD',
|
|
73
73
|
priceUsd: '50000.00',
|
|
74
74
|
volumeUsd24Hr: '8000000000',
|
|
75
|
-
volumePercent: '26.67'
|
|
76
|
-
}
|
|
75
|
+
volumePercent: '26.67',
|
|
76
|
+
},
|
|
77
77
|
];
|
|
78
78
|
const formatted = formatMarketAnalysis(asset, markets);
|
|
79
79
|
expect(formatted).toContain('Market Analysis for Bitcoin (BTC)');
|
|
@@ -97,27 +97,27 @@ describe('Formatters', () => {
|
|
|
97
97
|
marketCapUsd: '1000000000000',
|
|
98
98
|
supply: '19000000',
|
|
99
99
|
maxSupply: '21000000',
|
|
100
|
-
vwap24Hr: '49500.00'
|
|
100
|
+
vwap24Hr: '49500.00',
|
|
101
101
|
};
|
|
102
102
|
const history = [
|
|
103
103
|
{
|
|
104
104
|
time: 1609459200000,
|
|
105
105
|
priceUsd: '45000.00',
|
|
106
106
|
circulatingSupply: '18500000',
|
|
107
|
-
date: '2021-01-01'
|
|
107
|
+
date: '2021-01-01',
|
|
108
108
|
},
|
|
109
109
|
{
|
|
110
110
|
time: 1609545600000,
|
|
111
111
|
priceUsd: '48000.00',
|
|
112
112
|
circulatingSupply: '18500000',
|
|
113
|
-
date: '2021-01-02'
|
|
113
|
+
date: '2021-01-02',
|
|
114
114
|
},
|
|
115
115
|
{
|
|
116
116
|
time: 1609632000000,
|
|
117
117
|
priceUsd: '50000.00',
|
|
118
118
|
circulatingSupply: '18500000',
|
|
119
|
-
date: '2021-01-03'
|
|
120
|
-
}
|
|
119
|
+
date: '2021-01-03',
|
|
120
|
+
},
|
|
121
121
|
];
|
|
122
122
|
const formatted = formatHistoricalAnalysis(asset, history);
|
|
123
123
|
expect(formatted).toContain('Historical Analysis for Bitcoin (BTC)');
|