cryptoquant-mcp 0.2.0 → 1.0.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 +84 -118
- package/dist/api-key.js +76 -0
- package/dist/api-key.js.map +1 -0
- package/dist/config.js +24 -18
- package/dist/config.js.map +1 -1
- package/dist/index.js +33 -40
- package/dist/index.js.map +1 -1
- package/dist/proxy.js +133 -0
- package/dist/proxy.js.map +1 -0
- package/dist/utils.js +6 -64
- package/dist/utils.js.map +1 -1
- package/package.json +16 -11
- package/dist/auth/storage.d.ts +0 -11
- package/dist/auth/storage.d.ts.map +0 -1
- package/dist/auth/storage.js +0 -53
- package/dist/auth/storage.js.map +0 -1
- package/dist/cache/storage.d.ts +0 -47
- package/dist/cache/storage.d.ts.map +0 -1
- package/dist/cache/storage.js +0 -140
- package/dist/cache/storage.js.map +0 -1
- package/dist/cache/summary.d.ts +0 -16
- package/dist/cache/summary.d.ts.map +0 -1
- package/dist/cache/summary.js +0 -85
- package/dist/cache/summary.js.map +0 -1
- package/dist/cache/types.d.ts +0 -76
- package/dist/cache/types.d.ts.map +0 -1
- package/dist/cache/types.js +0 -6
- package/dist/cache/types.js.map +0 -1
- package/dist/config.d.ts +0 -18
- package/dist/config.d.ts.map +0 -1
- package/dist/data/metrics.toon +0 -293
- package/dist/discovery.d.ts +0 -70
- package/dist/discovery.d.ts.map +0 -1
- package/dist/discovery.js +0 -171
- package/dist/discovery.js.map +0 -1
- package/dist/index.d.ts +0 -18
- package/dist/index.d.ts.map +0 -1
- package/dist/permissions.d.ts +0 -36
- package/dist/permissions.d.ts.map +0 -1
- package/dist/permissions.js +0 -159
- package/dist/permissions.js.map +0 -1
- package/dist/plan-limits.d.ts +0 -71
- package/dist/plan-limits.d.ts.map +0 -1
- package/dist/plan-limits.js +0 -416
- package/dist/plan-limits.js.map +0 -1
- package/dist/tools/auth.d.ts +0 -3
- package/dist/tools/auth.d.ts.map +0 -1
- package/dist/tools/auth.js +0 -196
- package/dist/tools/auth.js.map +0 -1
- package/dist/tools/core.d.ts +0 -3
- package/dist/tools/core.d.ts.map +0 -1
- package/dist/tools/core.js +0 -546
- package/dist/tools/core.js.map +0 -1
- package/dist/utils.d.ts +0 -45
- package/dist/utils.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -1,107 +1,74 @@
|
|
|
1
|
-
# CryptoQuant MCP
|
|
1
|
+
# CryptoQuant MCP
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
</p>
|
|
3
|
+
[](https://www.npmjs.com/package/cryptoquant-mcp)
|
|
4
|
+
[](LICENSE)
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
<a href="#installation">Installation</a> •
|
|
9
|
-
<a href="#mcp-tools">MCP Tools</a> •
|
|
10
|
-
<a href="#authentication">Authentication</a>
|
|
11
|
-
</p>
|
|
6
|
+
CryptoQuant on-chain, market, and derivatives data, plus CryptoQuant Research and QuickTake insights, for AI agents through the [Model Context Protocol](https://modelcontextprotocol.io).
|
|
12
7
|
|
|
13
|
-
|
|
8
|
+
This repository contains:
|
|
14
9
|
|
|
15
|
-
|
|
10
|
+
- **Remote server setup** for `https://mcp.cryptoquant.com/mcp` (recommended; you sign in with OAuth)
|
|
11
|
+
- **An Agent Plugins / Cursor plugin** (`plugin.json`, `mcp.json`, `skills/`) that connects to the remote server and teaches the agent which metric answers which question
|
|
12
|
+
- **A local stdio proxy** (`npx cryptoquant-mcp`) for clients that cannot connect to remote MCP servers. It forwards every message to the remote server unchanged.
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
<!-- TODO: demo GIF (to be recorded by the CryptoQuant team) -->
|
|
18
15
|
|
|
19
|
-
|
|
20
|
-
- **Real-time metrics**: MVRV, SOPR, Exchange Flows, Funding Rates
|
|
21
|
-
- **Market insights**: AI-powered interpretation of on-chain data
|
|
22
|
-
- **Whale tracking**: Monitor large holder movements
|
|
16
|
+
## Remote server (recommended)
|
|
23
17
|
|
|
24
|
-
|
|
18
|
+
Server URL: `https://mcp.cryptoquant.com/mcp` (Streamable HTTP, OAuth)
|
|
25
19
|
|
|
26
|
-
|
|
20
|
+
No API key is needed in your config. The first time you connect, your client opens a CryptoQuant sign-in page. More setup details: [CryptoQuant user guide](https://userguide.cryptoquant.com/api/mcp-server-beta/remote-mcp-recommended).
|
|
27
21
|
|
|
28
|
-
###
|
|
22
|
+
### Claude (claude.ai and Claude Desktop)
|
|
29
23
|
|
|
30
|
-
**
|
|
24
|
+
Open **Settings → Connectors → Add custom connector** and paste the server URL.
|
|
31
25
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
### ChatGPT
|
|
27
|
+
|
|
28
|
+
Add a custom connector with the server URL (developer mode). Availability depends on your ChatGPT plan.
|
|
29
|
+
|
|
30
|
+
### Cursor
|
|
31
|
+
|
|
32
|
+
Install the **CryptoQuant** plugin from the Cursor marketplace (once listed). It adds the remote server and the `cryptoquant-onchain` skill.
|
|
33
|
+
|
|
34
|
+
Or add the server yourself in `.cursor/mcp.json` (project) or `~/.cursor/mcp.json` (global):
|
|
38
35
|
|
|
39
36
|
```json
|
|
40
37
|
{
|
|
41
38
|
"mcpServers": {
|
|
42
39
|
"cryptoquant": {
|
|
43
|
-
"
|
|
44
|
-
"args": ["-y", "cryptoquant-mcp"]
|
|
40
|
+
"url": "https://mcp.cryptoquant.com/mcp"
|
|
45
41
|
}
|
|
46
42
|
}
|
|
47
43
|
}
|
|
48
44
|
```
|
|
49
45
|
|
|
50
|
-
|
|
46
|
+
### VS Code
|
|
51
47
|
|
|
52
|
-
|
|
48
|
+
`.vscode/mcp.json`:
|
|
53
49
|
|
|
54
|
-
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"servers": {
|
|
53
|
+
"cryptoquant": {
|
|
54
|
+
"type": "http",
|
|
55
|
+
"url": "https://mcp.cryptoquant.com/mcp"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
55
60
|
|
|
56
|
-
|
|
61
|
+
### Claude Code
|
|
57
62
|
|
|
58
63
|
```bash
|
|
59
|
-
|
|
60
|
-
cd cryptoquant-mcp
|
|
61
|
-
npm install && npm run build
|
|
64
|
+
claude mcp add --transport http cryptoquant https://mcp.cryptoquant.com/mcp
|
|
62
65
|
```
|
|
63
66
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
## MCP Tools
|
|
67
|
-
|
|
68
|
-
The MCP server provides these tools for API access:
|
|
69
|
-
|
|
70
|
-
| Tool | Description |
|
|
71
|
-
|------|-------------|
|
|
72
|
-
| `initialize` | Start session with API key, returns plan info |
|
|
73
|
-
| `discover_endpoints` | Browse 245+ available endpoints |
|
|
74
|
-
| `get_endpoint_info` | Get endpoint parameter details |
|
|
75
|
-
| `query_data` | Query raw API data |
|
|
76
|
-
| `describe_metric` | Get metric descriptions and thresholds |
|
|
77
|
-
| `list_assets` | List supported assets |
|
|
78
|
-
| `reset_session` | Clear session (logout) |
|
|
79
|
-
|
|
80
|
-
### Supported Assets
|
|
81
|
-
|
|
82
|
-
BTC, ETH, ALT, Stablecoin, ERC20, TRX, XRP
|
|
83
|
-
|
|
84
|
-
---
|
|
67
|
+
Then run `/mcp` in Claude Code to sign in.
|
|
85
68
|
|
|
86
|
-
##
|
|
69
|
+
## Local install (npx proxy)
|
|
87
70
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
| Query | Intent | Metric |
|
|
91
|
-
|-------|--------|--------|
|
|
92
|
-
| "비트코인 가격 전망이 어때?" | VALUATION | MVRV |
|
|
93
|
-
| "Is BTC overvalued?" | VALUATION | MVRV |
|
|
94
|
-
| "고래들 움직임 보여줘" | WHALE_ACTIVITY | whale-ratio |
|
|
95
|
-
| "What's the funding rate?" | LEVERAGE | funding-rates |
|
|
96
|
-
| "익절/손절 상황?" | PROFIT_BEHAVIOR | SOPR |
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
## Authentication
|
|
101
|
-
|
|
102
|
-
### Option A. Environment Variable (Recommended)
|
|
103
|
-
|
|
104
|
-
Add your API key to the MCP config:
|
|
71
|
+
For clients that only support local (stdio) servers. Requires Node.js 18+ and a [CryptoQuant API key](https://cryptoquant.com/account/api).
|
|
105
72
|
|
|
106
73
|
```json
|
|
107
74
|
{
|
|
@@ -117,67 +84,66 @@ Add your API key to the MCP config:
|
|
|
117
84
|
}
|
|
118
85
|
```
|
|
119
86
|
|
|
120
|
-
|
|
87
|
+
| App | Config file |
|
|
88
|
+
| --- | --- |
|
|
89
|
+
| Claude Desktop (macOS) | `~/Library/Application Support/Claude/claude_desktop_config.json` |
|
|
90
|
+
| Claude Desktop (Windows) | `%APPDATA%\Claude\claude_desktop_config.json` |
|
|
91
|
+
| Cursor | `.cursor/mcp.json` or `~/.cursor/mcp.json` |
|
|
92
|
+
| Claude Code | project `.mcp.json` |
|
|
121
93
|
|
|
122
|
-
|
|
94
|
+
| Variable | Required | Description |
|
|
95
|
+
| --- | --- | --- |
|
|
96
|
+
| `CRYPTOQUANT_API_KEY` | Yes | Your API key, sent to the remote server as a bearer token. If it is not set, the key saved by 0.x in `~/.cryptoquant/credentials` is used. |
|
|
97
|
+
| `CRYPTOQUANT_MCP_URL` | No | Overrides the server URL (default `https://mcp.cryptoquant.com/mcp`). Must be `https`; plain `http` is accepted only for loopback addresses (`localhost`, `127.0.0.1`, `[::1]`). |
|
|
123
98
|
|
|
124
|
-
|
|
125
|
-
initialize(api_key="your-api-key")
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
The key will be saved to `~/.cryptoquant/credentials` for future sessions.
|
|
129
|
-
|
|
130
|
-
**Get your API key**: [https://cryptoquant.com/settings/api](https://cryptoquant.com/settings/api)
|
|
99
|
+
Without a key, the proxy prints setup instructions to stderr and exits.
|
|
131
100
|
|
|
132
|
-
|
|
101
|
+
## Tools
|
|
133
102
|
|
|
134
|
-
|
|
135
|
-
# Switch accounts
|
|
136
|
-
reset_session(clear_stored=true)
|
|
137
|
-
initialize(api_key="new-api-key")
|
|
138
|
-
```
|
|
103
|
+
The tools come from the remote server. This list is for reference and may change.
|
|
139
104
|
|
|
140
|
-
|
|
105
|
+
| Tool | Description |
|
|
106
|
+
| --- | --- |
|
|
107
|
+
| `discover_endpoints` | Find data endpoints by asset and category |
|
|
108
|
+
| `get_endpoint_info` | Parameters of an endpoint (window, exchange, token, ...) |
|
|
109
|
+
| `query_data` | Query on-chain, market, and derivatives data |
|
|
110
|
+
| `describe_metric` | Definition, thresholds, and interpretation of a metric |
|
|
111
|
+
| `list_assets` | Supported assets |
|
|
112
|
+
| `recent_research` | Latest CryptoQuant Research articles |
|
|
113
|
+
| `recent_quicktake` | Latest QuickTake articles from CryptoQuant Verified Authors |
|
|
114
|
+
| `query_research` | Search Research articles by topic |
|
|
115
|
+
| `query_quicktake` | Search QuickTake articles by topic |
|
|
141
116
|
|
|
142
|
-
##
|
|
117
|
+
## Assets, dates, and rate limits
|
|
143
118
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
| API Access | [CryptoQuant API key](https://cryptoquant.com/settings/api) |
|
|
119
|
+
- **Assets:** BTC, ETH, XRP, TRX, stablecoins, ERC-20 tokens, and other altcoins. `list_assets` returns the current list.
|
|
120
|
+
- **Dates:** `yyyyMMdd` for `window=day` (e.g. `20260115`), `yyyyMMddTHHmmss` for other windows (e.g. `20260115T000000`).
|
|
121
|
+
- **Rate limits and history:** depend on your CryptoQuant plan. The server tells the agent your plan's limits when it connects. See [plans](https://cryptoquant.com/pricing).
|
|
148
122
|
|
|
149
|
-
|
|
123
|
+
## Migrating from 0.x
|
|
150
124
|
|
|
151
|
-
|
|
125
|
+
1.0 turns the npm package into a proxy to the remote server, so the local and remote servers now offer the same tools.
|
|
152
126
|
|
|
153
|
-
|
|
127
|
+
- The `initialize` and `reset_session` tools are removed. Set `CRYPTOQUANT_API_KEY` in your MCP config instead. A key you saved in 0.x with `initialize(api_key=...)` keeps working; it is read from `~/.cryptoquant/credentials`.
|
|
128
|
+
- QuickTake and Research tools are now available locally.
|
|
129
|
+
- The package no longer exports a JavaScript library API.
|
|
130
|
+
- `CRYPTOQUANT_API_URL` (the 0.x API base URL override) is no longer used; use `CRYPTOQUANT_MCP_URL` to point at another MCP server.
|
|
131
|
+
- `npx -y cryptoquant-mcp` always fetches the latest version, so existing configs switch to 1.0 on the next start. Prompts or workflows that call `initialize` need to be updated.
|
|
154
132
|
|
|
155
|
-
|
|
133
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
156
134
|
|
|
157
135
|
## Development
|
|
158
136
|
|
|
159
|
-
### Setup
|
|
160
|
-
|
|
161
|
-
After cloning the repository, install dependencies to set up git hooks:
|
|
162
|
-
|
|
163
137
|
```bash
|
|
138
|
+
git clone https://github.com/CryptoQuantOfficial/cryptoquant-mcp.git
|
|
139
|
+
cd cryptoquant-mcp
|
|
164
140
|
npm install
|
|
141
|
+
npm test
|
|
142
|
+
npm run build
|
|
165
143
|
```
|
|
166
144
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
### Pre-commit Hooks
|
|
170
|
-
|
|
171
|
-
When committing changes to `src/`, ESLint runs automatically:
|
|
145
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
172
146
|
|
|
173
|
-
|
|
174
|
-
- Commit is blocked if lint errors are found
|
|
175
|
-
- Fix errors before committing: `npm run lint`
|
|
176
|
-
|
|
177
|
-
---
|
|
147
|
+
## License
|
|
178
148
|
|
|
179
|
-
|
|
180
|
-
<a href="https://cryptoquant.com">CryptoQuant</a> •
|
|
181
|
-
<a href="https://docs.cryptoquant.com">Docs</a> •
|
|
182
|
-
<a href="https://github.com/CryptoQuantOfficial/cryptoquant-mcp/issues">Issues</a>
|
|
183
|
-
</p>
|
|
149
|
+
MIT. See [LICENSE](LICENSE).
|
package/dist/api-key.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { DEFAULT_MCP_URL } from "./config.js";
|
|
5
|
+
export const API_KEY_URL = "https://cryptoquant.com/account/api";
|
|
6
|
+
export function credentialsPath(home = homedir()) {
|
|
7
|
+
return join(home, ".cryptoquant", "credentials");
|
|
8
|
+
}
|
|
9
|
+
function clean(value) {
|
|
10
|
+
if (typeof value !== "string")
|
|
11
|
+
return null;
|
|
12
|
+
const trimmed = value.trim();
|
|
13
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
14
|
+
}
|
|
15
|
+
/** Reads the key that 0.x saved via initialize(api_key=...). Never writes. */
|
|
16
|
+
function readStoredKey(path) {
|
|
17
|
+
try {
|
|
18
|
+
const data = JSON.parse(readFileSync(path, "utf-8"));
|
|
19
|
+
if (data === null || typeof data !== "object" || Array.isArray(data))
|
|
20
|
+
return null;
|
|
21
|
+
return clean(data.api_key);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function resolveApiKey({ env = process.env, home = homedir(), } = {}) {
|
|
28
|
+
const fromEnv = clean(env.CRYPTOQUANT_API_KEY);
|
|
29
|
+
if (fromEnv)
|
|
30
|
+
return { key: fromEnv, source: "env" };
|
|
31
|
+
const fromFile = readStoredKey(credentialsPath(home));
|
|
32
|
+
if (fromFile)
|
|
33
|
+
return { key: fromFile, source: "credentials" };
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
/** Printable ASCII only: anything else cannot be sent in an Authorization header. */
|
|
37
|
+
export function isValidApiKey(key) {
|
|
38
|
+
return /^[\x21-\x7E]+$/.test(key);
|
|
39
|
+
}
|
|
40
|
+
export function keySourceLabel(source) {
|
|
41
|
+
return source === "env" ? "CRYPTOQUANT_API_KEY" : "~/.cryptoquant/credentials";
|
|
42
|
+
}
|
|
43
|
+
/** Never includes the key or any part of it. */
|
|
44
|
+
export function invalidKeyMessage(source) {
|
|
45
|
+
return [
|
|
46
|
+
`[cryptoquant-mcp] The API key from ${keySourceLabel(source)} contains invalid characters`,
|
|
47
|
+
"(only printable ASCII without spaces is allowed). Check for stray quotes, line breaks, or spaces.",
|
|
48
|
+
"",
|
|
49
|
+
`Get an API key: ${API_KEY_URL}`,
|
|
50
|
+
"",
|
|
51
|
+
].join("\n");
|
|
52
|
+
}
|
|
53
|
+
export function missingKeyMessage() {
|
|
54
|
+
return [
|
|
55
|
+
"[cryptoquant-mcp] No CryptoQuant API key found.",
|
|
56
|
+
"",
|
|
57
|
+
"Set CRYPTOQUANT_API_KEY in your MCP client config:",
|
|
58
|
+
"",
|
|
59
|
+
" {",
|
|
60
|
+
' "mcpServers": {',
|
|
61
|
+
' "cryptoquant": {',
|
|
62
|
+
' "command": "npx",',
|
|
63
|
+
' "args": ["-y", "cryptoquant-mcp"],',
|
|
64
|
+
' "env": { "CRYPTOQUANT_API_KEY": "<your-api-key>" }',
|
|
65
|
+
" }",
|
|
66
|
+
" }",
|
|
67
|
+
" }",
|
|
68
|
+
"",
|
|
69
|
+
`Get an API key: ${API_KEY_URL}`,
|
|
70
|
+
"",
|
|
71
|
+
"If your client supports remote MCP servers, connect to the remote server instead",
|
|
72
|
+
`and sign in with OAuth (no API key in your config): ${DEFAULT_MCP_URL}`,
|
|
73
|
+
"",
|
|
74
|
+
].join("\n");
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=api-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key.js","sourceRoot":"","sources":["../src/api-key.ts"],"names":[],"mappings":"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"}
|
package/dist/config.js
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*/
|
|
8
|
-
const DEFAULT_API_URL = "https://api.cryptoquant.com/v1";
|
|
9
|
-
/**
|
|
10
|
-
* Get the configured API URL.
|
|
11
|
-
* Returns the base URL with /v1 suffix (e.g., "https://api.cryptoquant.com/v1")
|
|
12
|
-
*/
|
|
13
|
-
export function getApiUrl() {
|
|
14
|
-
return process.env.CRYPTOQUANT_API_URL || DEFAULT_API_URL;
|
|
1
|
+
export const DEFAULT_MCP_URL = "https://mcp.cryptoquant.com/mcp";
|
|
2
|
+
export class ConfigError extends Error {
|
|
3
|
+
name = "ConfigError";
|
|
4
|
+
}
|
|
5
|
+
function isLoopback(hostname) {
|
|
6
|
+
return hostname === "localhost" || hostname === "[::1]" || /^127(\.\d{1,3}){3}$/.test(hostname);
|
|
15
7
|
}
|
|
16
8
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
9
|
+
* CRYPTOQUANT_MCP_URL overrides the server (stage testing). The API key travels
|
|
10
|
+
* in a header, so plain http is allowed only when it cannot leave the machine.
|
|
19
11
|
*/
|
|
20
|
-
export function
|
|
21
|
-
|
|
12
|
+
export function resolveServerUrl(env = process.env) {
|
|
13
|
+
const raw = env.CRYPTOQUANT_MCP_URL?.trim();
|
|
14
|
+
if (!raw)
|
|
15
|
+
return new URL(DEFAULT_MCP_URL);
|
|
16
|
+
let url;
|
|
17
|
+
try {
|
|
18
|
+
url = new URL(raw);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw new ConfigError("CRYPTOQUANT_MCP_URL is not a valid URL.");
|
|
22
|
+
}
|
|
23
|
+
if (url.protocol === "https:")
|
|
24
|
+
return url;
|
|
25
|
+
if (url.protocol === "http:" && isLoopback(url.hostname))
|
|
26
|
+
return url;
|
|
27
|
+
throw new ConfigError("CRYPTOQUANT_MCP_URL must use https (plain http is allowed only for localhost, 127.0.0.0/8, and [::1]).");
|
|
22
28
|
}
|
|
23
29
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,iCAAiC,CAAC;AAEjE,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,IAAI,GAAG,aAAa,CAAC;CAC/B;AAED,SAAS,UAAU,CAAC,QAAgB;IAClC,OAAO,QAAQ,KAAK,WAAW,IAAI,QAAQ,KAAK,OAAO,IAAI,qBAAqB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AAClG,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACnE,MAAM,GAAG,GAAG,GAAG,CAAC,mBAAmB,EAAE,IAAI,EAAE,CAAC;IAC5C,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,GAAG,CAAC,eAAe,CAAC,CAAC;IAE1C,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,WAAW,CAAC,yCAAyC,CAAC,CAAC;IACnE,CAAC;IAED,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC;IAC1C,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,GAAG,CAAC;IACrE,MAAM,IAAI,WAAW,CACnB,wGAAwG,CACzG,CAAC;AACJ,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,47 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { registerCoreTools } from "./tools/core.js";
|
|
2
|
+
import { invalidKeyMessage, isValidApiKey, keySourceLabel, missingKeyMessage, resolveApiKey } from "./api-key.js";
|
|
3
|
+
import { ConfigError, resolveServerUrl } from "./config.js";
|
|
4
|
+
import { startProxy } from "./proxy.js";
|
|
6
5
|
import { logger } from "./utils.js";
|
|
7
|
-
export { fetchDiscoveryEndpoints, getEndpointCatalog, isDiscoveryLoaded, searchEndpoints, getEndpointByPath, getParameterOptions, getDiscoverySummary, getAssetCategoryMap, resetDiscovery, } from "./discovery.js";
|
|
8
|
-
export { initializePermissions, getPermissionState, setGuestMode, resetPermissions, clearDiscoveryCache, getDiscoveryCachePath, getCachedDiscovery, } from "./permissions.js";
|
|
9
|
-
export { fetchPlanLimits, getPlanLimitsState, getPlanLimit, getEndpointResultLimit, getEndpointWindowLimits, hasEndpointAccess, parseDurationToDate, getEarliestAllowedDate, validateDateRange, getRequiredPlan, getApiRateLimit, getStaticEndpoints, resetPlanLimits, loadPlanLimitsFromCache, getAccessibleEndpointsSummary, detectPlanFromLimits, } from "./plan-limits.js";
|
|
10
|
-
export { CACHE_VERSION, CACHE_TTL_DAYS } from "./cache/types.js";
|
|
11
|
-
export { getCacheFilePath, isCacheValid, readCache, writeCache, invalidateCache, clearAllCaches, getCacheAgeDays, getCacheStatus, getCachePath, } from "./cache/storage.js";
|
|
12
|
-
export { generateSummary, extractRawResponse } from "./cache/summary.js";
|
|
13
|
-
export { getStoredApiKey, saveApiKey, updateValidatedAt, clearCredentials, getCredentialsPath, } from "./auth/storage.js";
|
|
14
|
-
// =============================================================================
|
|
15
|
-
// Utility Exports
|
|
16
|
-
// =============================================================================
|
|
17
|
-
export { jsonResponse, errorResponse, getPlanNote, capitalizeFirst, logger, } from "./utils.js";
|
|
18
|
-
// =============================================================================
|
|
19
|
-
// Config Exports
|
|
20
|
-
// =============================================================================
|
|
21
|
-
export { getApiUrl, getApiBaseUrl } from "./config.js";
|
|
22
|
-
// =============================================================================
|
|
23
|
-
// Tool Registration Exports
|
|
24
|
-
// =============================================================================
|
|
25
|
-
export { registerAuthTools } from "./tools/auth.js";
|
|
26
|
-
export { registerCoreTools } from "./tools/core.js";
|
|
27
|
-
// =============================================================================
|
|
28
|
-
// MCP Server Startup
|
|
29
|
-
// =============================================================================
|
|
30
6
|
async function main() {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
7
|
+
let url;
|
|
8
|
+
try {
|
|
9
|
+
url = resolveServerUrl();
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (error instanceof ConfigError) {
|
|
13
|
+
process.stderr.write(`[cryptoquant-mcp] ${error.message}\n`);
|
|
14
|
+
return 1;
|
|
15
|
+
}
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
const resolved = resolveApiKey();
|
|
19
|
+
if (!resolved) {
|
|
20
|
+
process.stderr.write(missingKeyMessage());
|
|
21
|
+
return 1;
|
|
22
|
+
}
|
|
23
|
+
if (!isValidApiKey(resolved.key)) {
|
|
24
|
+
process.stderr.write(invalidKeyMessage(resolved.source));
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
logger.info(`CryptoQuant MCP proxy → ${url.origin}${url.pathname} (API key from ${keySourceLabel(resolved.source)})`);
|
|
28
|
+
const proxy = await startProxy({ url, apiKey: resolved.key });
|
|
29
|
+
await proxy.closed;
|
|
30
|
+
return 0;
|
|
42
31
|
}
|
|
43
|
-
main().
|
|
44
|
-
|
|
32
|
+
main().then((code) => {
|
|
33
|
+
// Let stdout/stderr drain instead of exiting mid-write; force exit if a handle lingers.
|
|
34
|
+
process.exitCode = code;
|
|
35
|
+
setTimeout(() => process.exit(code), 1_000).unref();
|
|
36
|
+
}, (error) => {
|
|
37
|
+
logger.error("Fatal:", error instanceof Error ? error.message : String(error));
|
|
45
38
|
process.exit(1);
|
|
46
39
|
});
|
|
47
40
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,cAAc,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClH,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,KAAK,UAAU,IAAI;IACjB,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,gBAAgB,EAAE,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;YACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qBAAqB,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;YAC7D,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IAED,MAAM,QAAQ,GAAG,aAAa,EAAE,CAAC;IACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAC;QAC1C,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QACzD,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,2BAA2B,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,kBAAkB,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAEtH,MAAM,KAAK,GAAG,MAAM,UAAU,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC;IAC9D,MAAM,KAAK,CAAC,MAAM,CAAC;IACnB,OAAO,CAAC,CAAC;AACX,CAAC;AAED,IAAI,EAAE,CAAC,IAAI,CACT,CAAC,IAAI,EAAE,EAAE;IACP,wFAAwF;IACxF,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IACxB,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC;AACtD,CAAC,EACD,CAAC,KAAc,EAAE,EAAE;IACjB,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAC/E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CACF,CAAC"}
|
package/dist/proxy.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { StreamableHTTPClientTransport, StreamableHTTPError } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { API_KEY_URL } from "./api-key.js";
|
|
4
|
+
import { logger } from "./utils.js";
|
|
5
|
+
export const API_KEY_REJECTED = -32001;
|
|
6
|
+
export const UPSTREAM_ERROR = -32603;
|
|
7
|
+
export const DEFAULT_REQUEST_TIMEOUT_MS = 120_000;
|
|
8
|
+
function isRequest(message) {
|
|
9
|
+
return "method" in message && "id" in message && message.id !== undefined;
|
|
10
|
+
}
|
|
11
|
+
function isResponse(message) {
|
|
12
|
+
return "id" in message && !("method" in message) && ("result" in message || "error" in message);
|
|
13
|
+
}
|
|
14
|
+
function label(message) {
|
|
15
|
+
return "method" in message ? message.method : "response";
|
|
16
|
+
}
|
|
17
|
+
function summarize(error, redact) {
|
|
18
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
19
|
+
const cause = error instanceof Error && error.cause instanceof Error ? ` (${error.cause.message})` : "";
|
|
20
|
+
const text = redact(`${message}${cause}`);
|
|
21
|
+
return text.length > 300 ? `${text.slice(0, 297)}...` : text;
|
|
22
|
+
}
|
|
23
|
+
function toRpcError(error, redact) {
|
|
24
|
+
if (error instanceof StreamableHTTPError && error.code === 401) {
|
|
25
|
+
return {
|
|
26
|
+
code: API_KEY_REJECTED,
|
|
27
|
+
message: `CryptoQuant rejected the API key. Check CRYPTOQUANT_API_KEY, or create a key at ${API_KEY_URL}`,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return { code: UPSTREAM_ERROR, message: `CryptoQuant MCP server request failed: ${summarize(error, redact)}` };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Relays JSON-RPC between a stdio MCP client and the remote Streamable HTTP server.
|
|
34
|
+
* Messages are forwarded as is; the proxy only answers on the server's behalf when
|
|
35
|
+
* a request cannot reach it (401, network, 5xx, timeout).
|
|
36
|
+
*/
|
|
37
|
+
export async function startProxy(options) {
|
|
38
|
+
const stdin = options.stdin ?? process.stdin;
|
|
39
|
+
const stdout = options.stdout ?? process.stdout;
|
|
40
|
+
const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
41
|
+
const local = new StdioServerTransport(stdin, stdout);
|
|
42
|
+
const remote = new StreamableHTTPClientTransport(options.url, {
|
|
43
|
+
requestInit: { headers: { Authorization: `Bearer ${options.apiKey}` } },
|
|
44
|
+
});
|
|
45
|
+
// The one place that scrubs the key from anything that can reach stdout or stderr.
|
|
46
|
+
const redact = (text) => (options.apiKey ? text.split(options.apiKey).join("***") : text);
|
|
47
|
+
const pending = new Map();
|
|
48
|
+
let closing = false;
|
|
49
|
+
let markClosed;
|
|
50
|
+
const closed = new Promise((resolve) => {
|
|
51
|
+
markClosed = resolve;
|
|
52
|
+
});
|
|
53
|
+
const reply = (message) => {
|
|
54
|
+
if (closing)
|
|
55
|
+
return;
|
|
56
|
+
local.send(message).catch((error) => logger.error("Failed to write to stdout:", summarize(error, redact)));
|
|
57
|
+
};
|
|
58
|
+
const settle = (id) => {
|
|
59
|
+
const entry = pending.get(id);
|
|
60
|
+
if (!entry)
|
|
61
|
+
return undefined;
|
|
62
|
+
clearTimeout(entry.timer);
|
|
63
|
+
pending.delete(id);
|
|
64
|
+
return entry;
|
|
65
|
+
};
|
|
66
|
+
const fail = (id, error) => {
|
|
67
|
+
if (!settle(id))
|
|
68
|
+
return; // already answered or timed out
|
|
69
|
+
reply({ jsonrpc: "2.0", id, error });
|
|
70
|
+
};
|
|
71
|
+
remote.onmessage = (message) => {
|
|
72
|
+
if (isResponse(message)) {
|
|
73
|
+
const entry = settle(message.id);
|
|
74
|
+
if (!entry) {
|
|
75
|
+
logger.warn(`Dropping a late or unknown response (id ${String(message.id)})`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (entry.method === "initialize" && "result" in message) {
|
|
79
|
+
const version = message.result.protocolVersion;
|
|
80
|
+
if (typeof version === "string")
|
|
81
|
+
remote.setProtocolVersion(version);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
reply(message);
|
|
85
|
+
};
|
|
86
|
+
// The SDK reports a failed send() here as well as by rejecting it, so this is the single
|
|
87
|
+
// warn for request failures; the send() catch below only builds the JSON-RPC error.
|
|
88
|
+
remote.onerror = (error) => {
|
|
89
|
+
if (closing || error.name === "AbortError")
|
|
90
|
+
return;
|
|
91
|
+
logger.warn("Remote transport error:", summarize(error, redact));
|
|
92
|
+
};
|
|
93
|
+
local.onmessage = (message) => {
|
|
94
|
+
if (isRequest(message)) {
|
|
95
|
+
const id = message.id;
|
|
96
|
+
settle(id); // a client reusing a pending id must not leave the old timer running
|
|
97
|
+
const timer = setTimeout(() => fail(id, { code: UPSTREAM_ERROR, message: `CryptoQuant MCP server did not respond within ${timeoutMs / 1000}s` }), timeoutMs);
|
|
98
|
+
pending.set(id, { method: message.method, timer });
|
|
99
|
+
}
|
|
100
|
+
if ("method" in message && message.method === "notifications/cancelled" && "params" in message) {
|
|
101
|
+
const requestId = message.params?.requestId;
|
|
102
|
+
if (typeof requestId === "string" || typeof requestId === "number")
|
|
103
|
+
settle(requestId);
|
|
104
|
+
}
|
|
105
|
+
remote.send(message).catch((error) => {
|
|
106
|
+
if (isRequest(message)) {
|
|
107
|
+
fail(message.id, toRpcError(error, redact));
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
logger.warn(`Failed to forward ${label(message)}:`, summarize(error, redact));
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
local.onerror = (error) => logger.warn("Invalid message from client:", summarize(error, redact));
|
|
115
|
+
const shutdown = async () => {
|
|
116
|
+
if (closing)
|
|
117
|
+
return closed;
|
|
118
|
+
closing = true;
|
|
119
|
+
for (const entry of pending.values())
|
|
120
|
+
clearTimeout(entry.timer);
|
|
121
|
+
pending.clear();
|
|
122
|
+
await remote.close().catch(() => undefined);
|
|
123
|
+
await local.close().catch(() => undefined);
|
|
124
|
+
markClosed();
|
|
125
|
+
return closed;
|
|
126
|
+
};
|
|
127
|
+
stdin.once("end", () => void shutdown());
|
|
128
|
+
stdin.once("close", () => void shutdown());
|
|
129
|
+
await remote.start();
|
|
130
|
+
await local.start();
|
|
131
|
+
return { closed, close: shutdown };
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
|