@quicknode/mcp 0.0.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/README.md +114 -0
- package/dist/clients/base_http_client/base_client.js +75 -0
- package/dist/clients/base_http_client/index.js +17 -0
- package/dist/clients/console_api_client/console_client.js +142 -0
- package/dist/clients/console_api_client/index.js +18 -0
- package/dist/clients/console_api_client/types.js +29 -0
- package/dist/common/generic_args.js +29 -0
- package/dist/common/utils.js +66 -0
- package/dist/index.js +34 -0
- package/dist/resource/endpoints.js +46 -0
- package/dist/resource/index.js +7 -0
- package/dist/tools/billing.js +48 -0
- package/dist/tools/chains.js +19 -0
- package/dist/tools/endpoints.js +623 -0
- package/dist/tools/index.js +13 -0
- package/dist/tools/usage.js +119 -0
- package/package.json +66 -0
- package/tsconfig.json +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# uickNode MCP Server
|
|
2
|
+
|
|
3
|
+
> **🚀 The official Model Context Protocol (MCP) server for QuickNode**
|
|
4
|
+
>
|
|
5
|
+
> Unleash the power of blockchain infrastructure from your AI workflows
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 🌟 What is QuickNode MCP?
|
|
10
|
+
|
|
11
|
+
QuickNode MCP Server brings the power of QuickNode's blockchain infrastructure directly to your AI assistant. With this MCP server, you can:
|
|
12
|
+
|
|
13
|
+
- **🔧 Set up and configure** QuickNode endpoints and infra across multiple networks
|
|
14
|
+
- **📊 Monitor endpoint usage** and billing information
|
|
15
|
+
- **⚡ Manage your QuickNode infrastructure** through natural language
|
|
16
|
+
- **🛠️ Unlock blockchain operations** by provisioning the infrastructure you need
|
|
17
|
+
|
|
18
|
+
Built by the team at [QuickNode](http://quicknode.com/), we're trailblazers in blockchain and web3 technology, tirelessly working to simplify blockchain infrastructure. Our combined passion, ingenuity, and dedication pave the way for seamless, high-performance API access across multiple platforms, shaping the future of digital interactions.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 🚀 Getting Started
|
|
23
|
+
|
|
24
|
+
Add to your config on your `claude_desktop_config.json`, Cursor's `mcp.json` or alike
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"quicknode-mcp": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["-y", "@quicknode/mcp"],
|
|
32
|
+
"env": {
|
|
33
|
+
"QUICKNODE_API_KEY": "<replace-with-qn-token>"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Replace `<qn-token>` with a QuickNode API token. Can be created on the [QuickNode dashboard](https://dashboard.quicknode.com/api-keys)
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 📋 Notes
|
|
45
|
+
|
|
46
|
+
This MCP server requires **Node.js 18.18.0 or higher**.
|
|
47
|
+
|
|
48
|
+
### Installing Node.js
|
|
49
|
+
|
|
50
|
+
If you don't have Node.js installed or need to upgrade:
|
|
51
|
+
|
|
52
|
+
**Download from nodejs.org**
|
|
53
|
+
|
|
54
|
+
- Visit [nodejs.org](https://nodejs.org/)
|
|
55
|
+
- Download and install the LTS version (recommended)
|
|
56
|
+
|
|
57
|
+
**Verify your installation:**
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
node --version # Should show v18.18.0 or higher
|
|
61
|
+
npm --version # Should show npm version
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 🛠️ Development Guide
|
|
67
|
+
|
|
68
|
+
### How to run locally
|
|
69
|
+
|
|
70
|
+
To run from local repo, put this server config on your `claude_desktop_config.json`, Cursor's `mcp.json` or alike
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"mcpServers": {
|
|
75
|
+
"quicknode-mcp": {
|
|
76
|
+
"command": "<absolute-path-to-repo>/qn-mcp/dist/index.js",
|
|
77
|
+
"env": {
|
|
78
|
+
"QUICKNODE_API_KEY": "<replace-with-qn-token>"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
To install dependencies
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
pnpm i
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
and kickstart the build with
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pnpm build
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
can also run with watch mode
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
pnpm watch
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Then restart Claude/reload Cursor config or similar, you should see resources/tools get picked up by the chat client.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 💬 Feedback & Support
|
|
108
|
+
|
|
109
|
+
We'd love to hear from you! If you have questions, suggestions, or run into any issues:
|
|
110
|
+
|
|
111
|
+
- 📧 **Email us:** [devex@quicknode.com](mailto:devex@quicknode.com)
|
|
112
|
+
- 🆘 **General support:** [QuickNode Support](https://support.quicknode.com/)
|
|
113
|
+
|
|
114
|
+
Your feedback helps us make QuickNode even better! 🚀
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Client = void 0;
|
|
4
|
+
class Client {
|
|
5
|
+
baseUrl;
|
|
6
|
+
token;
|
|
7
|
+
defaultHeaders;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.baseUrl = options.baseUrl.endsWith("/")
|
|
10
|
+
? options.baseUrl.slice(0, -1)
|
|
11
|
+
: options.baseUrl;
|
|
12
|
+
this.token = options.token;
|
|
13
|
+
this.defaultHeaders = {
|
|
14
|
+
"Content-Type": "application/json",
|
|
15
|
+
"x-api-key": this.token,
|
|
16
|
+
...options.headers,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
async request(method, path, options = {}) {
|
|
20
|
+
const url = new URL(path.startsWith("/") ? path.slice(1) : path, this.baseUrl);
|
|
21
|
+
// Add query parameters
|
|
22
|
+
if (options.params) {
|
|
23
|
+
Object.entries(options.params).forEach(([key, value]) => {
|
|
24
|
+
if (typeof value === "string") {
|
|
25
|
+
url.searchParams.append(key, value);
|
|
26
|
+
}
|
|
27
|
+
else if (value !== undefined) {
|
|
28
|
+
url.searchParams.append(key, value.toString());
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const headers = {
|
|
33
|
+
...this.defaultHeaders,
|
|
34
|
+
...options.headers,
|
|
35
|
+
};
|
|
36
|
+
const requestOptions = {
|
|
37
|
+
method,
|
|
38
|
+
headers,
|
|
39
|
+
};
|
|
40
|
+
if (options.body && method !== "GET" && method !== "HEAD") {
|
|
41
|
+
requestOptions.body = JSON.stringify(options.body);
|
|
42
|
+
}
|
|
43
|
+
const response = await fetch(url.toString(), requestOptions);
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
const errorText = await response.text();
|
|
46
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
47
|
+
}
|
|
48
|
+
// Return empty object for 204 No Content responses
|
|
49
|
+
if (response.status === 204) {
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
return (await response.json());
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
throw new Error(`Failed to parse response: ${error}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async get(path, options = {}) {
|
|
60
|
+
return this.request("GET", path, options);
|
|
61
|
+
}
|
|
62
|
+
async post(path, body, options = {}) {
|
|
63
|
+
return this.request("POST", path, { ...options, body });
|
|
64
|
+
}
|
|
65
|
+
async put(path, body, options = {}) {
|
|
66
|
+
return this.request("PUT", path, { ...options, body });
|
|
67
|
+
}
|
|
68
|
+
async patch(path, body, options = {}) {
|
|
69
|
+
return this.request("PATCH", path, { ...options, body });
|
|
70
|
+
}
|
|
71
|
+
async delete(path, options = {}) {
|
|
72
|
+
return this.request("DELETE", path, options);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.Client = Client;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./base_client"), exports);
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QuickNodeClient = void 0;
|
|
4
|
+
const base_client_1 = require("../base_http_client/base_client");
|
|
5
|
+
class QuickNodeClient extends base_client_1.Client {
|
|
6
|
+
constructor(options) {
|
|
7
|
+
super({
|
|
8
|
+
...options,
|
|
9
|
+
headers: {
|
|
10
|
+
...options.headers,
|
|
11
|
+
"x-qn-sdk": "qn-mcp",
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
async listEndpoints(limit = 10, offset = 0) {
|
|
16
|
+
const { data } = await this.get("/v0/endpoints", {
|
|
17
|
+
params: {
|
|
18
|
+
limit: limit.toString(),
|
|
19
|
+
offset: offset.toString(),
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
return data.map((endpoint) => ({
|
|
23
|
+
...endpoint,
|
|
24
|
+
name: endpointNameFromUrl(endpoint.http_url),
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
27
|
+
async getEndpoint(id) {
|
|
28
|
+
return this.get(`/v0/endpoints/${id}`);
|
|
29
|
+
}
|
|
30
|
+
async createEndpoint(body) {
|
|
31
|
+
return this.post("/v0/endpoints", body);
|
|
32
|
+
}
|
|
33
|
+
async deleteEndpoint(id) {
|
|
34
|
+
return this.delete(`/v0/endpoints/${id}`);
|
|
35
|
+
}
|
|
36
|
+
async getChains() {
|
|
37
|
+
return this.get("/v0/chains");
|
|
38
|
+
}
|
|
39
|
+
async getRpcUsage(queryParams) {
|
|
40
|
+
return this.get("/v0/usage/rpc", {
|
|
41
|
+
params: queryParams,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async getRpcUsageByEndpoint(queryParams) {
|
|
45
|
+
return this.get("/v0/usage/rpc/by-endpoint", {
|
|
46
|
+
params: queryParams,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async getRpcUsageByMethod(queryParams) {
|
|
50
|
+
return this.get("/v0/usage/rpc/by-method", {
|
|
51
|
+
params: queryParams,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async getRpcUsageByChain(queryParams) {
|
|
55
|
+
return this.get("/v0/usage/rpc/by-chain", {
|
|
56
|
+
params: queryParams,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async getRpcEndpointSecurityOptions(endpointId) {
|
|
60
|
+
return this.get(`v0/endpoints/${endpointId}/security_options`);
|
|
61
|
+
}
|
|
62
|
+
async getEndpointLogs(queryParams) {
|
|
63
|
+
return this.get(`/v0/endpoints/${queryParams.endpoint_id}/logs`, {
|
|
64
|
+
params: queryParams,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async getEndpointLogDetails(queryParams) {
|
|
68
|
+
return this.get(`/v0/endpoints/${queryParams.endpoint_id}/log_details`, {
|
|
69
|
+
params: queryParams,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async getBillingInvoices() {
|
|
73
|
+
return this.get("/v0/billing/invoices");
|
|
74
|
+
}
|
|
75
|
+
async getBillingPayments() {
|
|
76
|
+
return this.get("/v0/billing/payments");
|
|
77
|
+
}
|
|
78
|
+
async getEndpointMetrics(endpointId, queryParams) {
|
|
79
|
+
return this.get(`/v0/endpoints/${endpointId}/metrics`, {
|
|
80
|
+
params: queryParams,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async updateRateLimits(endpointId, body) {
|
|
84
|
+
return this.put(`/v0/endpoints/${endpointId}/rate-limits`, body);
|
|
85
|
+
}
|
|
86
|
+
async getMethodRateLimits(endpointId) {
|
|
87
|
+
return this.get(`/v0/endpoints/${endpointId}/method-rate-limits`);
|
|
88
|
+
}
|
|
89
|
+
async createMethodRateLimit(endpointId, body) {
|
|
90
|
+
return this.post(`/v0/endpoints/${endpointId}/method-rate-limits`, body);
|
|
91
|
+
}
|
|
92
|
+
async updateMethodRateLimit(endpointId, methodRateLimitId, body) {
|
|
93
|
+
return this.patch(`/v0/endpoints/${endpointId}/method-rate-limits/${methodRateLimitId}`, body);
|
|
94
|
+
}
|
|
95
|
+
async deleteMethodRateLimit(endpointId, methodRateLimitId) {
|
|
96
|
+
return this.delete(`/v0/endpoints/${endpointId}/method-rate-limits/${methodRateLimitId}`);
|
|
97
|
+
}
|
|
98
|
+
async getSecurityOptions(endpointId) {
|
|
99
|
+
return this.get(`/v0/endpoints/${endpointId}/security_options`);
|
|
100
|
+
}
|
|
101
|
+
async updateSecurityOptions(endpointId, body) {
|
|
102
|
+
return this.patch(`/v0/endpoints/${endpointId}/security_options`, body);
|
|
103
|
+
}
|
|
104
|
+
async createEndpointSecurityDomainMask(endpointId, body) {
|
|
105
|
+
return this.post(`/v0/endpoints/${endpointId}/security/domain_masks`, body);
|
|
106
|
+
}
|
|
107
|
+
async deleteEndpointSecurityDomainMask(endpointId, domainMaskId) {
|
|
108
|
+
return this.delete(`/v0/endpoints/${endpointId}/security/domain_masks/${domainMaskId}`);
|
|
109
|
+
}
|
|
110
|
+
async createEndpointSecurityIp(endpointId, body) {
|
|
111
|
+
return this.post(`/v0/endpoints/${endpointId}/security/ips`, body);
|
|
112
|
+
}
|
|
113
|
+
async deleteEndpointSecurityIp(endpointId, ipId) {
|
|
114
|
+
return this.delete(`/v0/endpoints/${endpointId}/security/ips/${ipId}`);
|
|
115
|
+
}
|
|
116
|
+
async createEndpointSecurityJwt(endpointId, body) {
|
|
117
|
+
return this.post(`/v0/endpoints/${endpointId}/security/jwts`, body);
|
|
118
|
+
}
|
|
119
|
+
async deleteEndpointSecurityJwt(endpointId, jwtId) {
|
|
120
|
+
return this.delete(`/v0/endpoints/${endpointId}/security/jwts/${jwtId}`);
|
|
121
|
+
}
|
|
122
|
+
async createEndpointSecurityReferrer(endpointId, body) {
|
|
123
|
+
return this.post(`/v0/endpoints/${endpointId}/security/referrers`, body);
|
|
124
|
+
}
|
|
125
|
+
async deleteEndpointSecurityReferrer(endpointId, referrerId) {
|
|
126
|
+
return this.delete(`/v0/endpoints/${endpointId}/security/referrers/${referrerId}`);
|
|
127
|
+
}
|
|
128
|
+
async createEndpointSecurityToken(endpointId, body) {
|
|
129
|
+
return this.post(`/v0/endpoints/${endpointId}/security/tokens`, body);
|
|
130
|
+
}
|
|
131
|
+
async deleteEndpointSecurityToken(endpointId, tokenId) {
|
|
132
|
+
return this.delete(`/v0/endpoints/${endpointId}/security/tokens/${tokenId}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
exports.QuickNodeClient = QuickNodeClient;
|
|
136
|
+
const endpointNameFromUrl = (url) => {
|
|
137
|
+
const match = url.match(/https:\/\/(.*)\.quiknode\.pro\//);
|
|
138
|
+
if (!match) {
|
|
139
|
+
throw new Error(`Invalid URL: ${url}`);
|
|
140
|
+
}
|
|
141
|
+
return match[1];
|
|
142
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./console_client"), exports);
|
|
18
|
+
__exportStar(require("./types"), exports);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RateLimitStatus = exports.RateLimitInterval = exports.MetricsType = exports.MetricsPeriod = void 0;
|
|
4
|
+
var MetricsPeriod;
|
|
5
|
+
(function (MetricsPeriod) {
|
|
6
|
+
MetricsPeriod["HOUR"] = "hour";
|
|
7
|
+
MetricsPeriod["DAY"] = "day";
|
|
8
|
+
MetricsPeriod["WEEK"] = "week";
|
|
9
|
+
MetricsPeriod["MONTH"] = "month";
|
|
10
|
+
})(MetricsPeriod || (exports.MetricsPeriod = MetricsPeriod = {}));
|
|
11
|
+
var MetricsType;
|
|
12
|
+
(function (MetricsType) {
|
|
13
|
+
MetricsType["METHOD_CALLS_OVER_TIME"] = "method_calls_over_time";
|
|
14
|
+
MetricsType["RESPONSE_STATUS_OVER_TIME"] = "response_status_over_time";
|
|
15
|
+
MetricsType["METHOD_CALL_BREAKDOWN"] = "method_call_breakdown";
|
|
16
|
+
MetricsType["RESPONSE_STATUS_BREAKDOWN"] = "response_status_breakdown";
|
|
17
|
+
MetricsType["METHOD_RESPONSE_TIME_MAX"] = "method_response_time_max";
|
|
18
|
+
})(MetricsType || (exports.MetricsType = MetricsType = {}));
|
|
19
|
+
var RateLimitInterval;
|
|
20
|
+
(function (RateLimitInterval) {
|
|
21
|
+
RateLimitInterval["SECOND"] = "second";
|
|
22
|
+
RateLimitInterval["MINUTE"] = "minute";
|
|
23
|
+
RateLimitInterval["HOUR"] = "hour";
|
|
24
|
+
})(RateLimitInterval || (exports.RateLimitInterval = RateLimitInterval = {}));
|
|
25
|
+
var RateLimitStatus;
|
|
26
|
+
(function (RateLimitStatus) {
|
|
27
|
+
RateLimitStatus["ENABLED"] = "enabled";
|
|
28
|
+
RateLimitStatus["DISABLED"] = "disabled";
|
|
29
|
+
})(RateLimitStatus || (exports.RateLimitStatus = RateLimitStatus = {}));
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Define generic args for the tools and resources to have unified descriptions
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.genericArgs = exports.endpointIdArgs = exports.timeRangeArgs = void 0;
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
const utils_1 = require("./utils");
|
|
7
|
+
exports.timeRangeArgs = {
|
|
8
|
+
start_time: zod_1.z
|
|
9
|
+
.string()
|
|
10
|
+
.describe("The start time of the time range (ISO 8601 format)")
|
|
11
|
+
.refine(utils_1.isValidIsoString, {
|
|
12
|
+
message: "start_time must be a valid ISO 8601 date string",
|
|
13
|
+
}),
|
|
14
|
+
end_time: zod_1.z
|
|
15
|
+
.string()
|
|
16
|
+
.describe("The end time of the time range (ISO 8601 format)")
|
|
17
|
+
.refine(utils_1.isValidIsoString, {
|
|
18
|
+
message: "end_time must be a valid ISO 8601 date string",
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
exports.endpointIdArgs = {
|
|
22
|
+
endpoint_id: zod_1.z
|
|
23
|
+
.string()
|
|
24
|
+
.describe("The unique identifier of the QuickNode endpoint"),
|
|
25
|
+
};
|
|
26
|
+
exports.genericArgs = {
|
|
27
|
+
timeRangeArgs: exports.timeRangeArgs,
|
|
28
|
+
endpointIdArgs: exports.endpointIdArgs,
|
|
29
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isoToUnixTimestamp = isoToUnixTimestamp;
|
|
4
|
+
exports.unixTimestampToIso = unixTimestampToIso;
|
|
5
|
+
exports.isValidIsoString = isValidIsoString;
|
|
6
|
+
exports.getCurrentUnixTimestamp = getCurrentUnixTimestamp;
|
|
7
|
+
/**
|
|
8
|
+
* Converts an ISO date string to a UNIX timestamp (seconds since epoch)
|
|
9
|
+
* @param isoString - The ISO date string to convert
|
|
10
|
+
* @returns The UNIX timestamp in seconds, or null if the input is invalid
|
|
11
|
+
*/
|
|
12
|
+
function isoToUnixTimestamp(isoString) {
|
|
13
|
+
try {
|
|
14
|
+
const date = new Date(isoString);
|
|
15
|
+
// Check if the date is valid
|
|
16
|
+
if (isNaN(date.getTime())) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
// Convert to UNIX timestamp (seconds since epoch)
|
|
20
|
+
return Math.floor(date.getTime() / 1000);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Converts a UNIX timestamp (seconds since epoch) to an ISO date string
|
|
28
|
+
* @param unixTimestamp - The UNIX timestamp in seconds
|
|
29
|
+
* @returns The ISO date string, or null if the input is invalid
|
|
30
|
+
*/
|
|
31
|
+
function unixTimestampToIso(unixTimestamp) {
|
|
32
|
+
try {
|
|
33
|
+
// Check if the timestamp is valid (positive number)
|
|
34
|
+
if (unixTimestamp < 0 || !Number.isFinite(unixTimestamp)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
// Convert to milliseconds and create Date object
|
|
38
|
+
const date = new Date(unixTimestamp * 1000);
|
|
39
|
+
// Return ISO string
|
|
40
|
+
return date.toISOString();
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Validates if a string is a valid ISO date string
|
|
48
|
+
* @param isoString - The string to validate
|
|
49
|
+
* @returns True if the string is a valid ISO date string, false otherwise
|
|
50
|
+
*/
|
|
51
|
+
function isValidIsoString(isoString) {
|
|
52
|
+
try {
|
|
53
|
+
const date = new Date(isoString);
|
|
54
|
+
return !isNaN(date.getTime());
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Gets the current UNIX timestamp in seconds
|
|
62
|
+
* @returns The current UNIX timestamp
|
|
63
|
+
*/
|
|
64
|
+
function getCurrentUnixTimestamp() {
|
|
65
|
+
return Math.floor(Date.now() / 1000);
|
|
66
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
5
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
|
+
const console_api_client_1 = require("./clients/console_api_client");
|
|
7
|
+
const resource_1 = require("./resource");
|
|
8
|
+
const tools_1 = require("./tools");
|
|
9
|
+
const QUICKNODE_API_KEY = process.env.QUICKNODE_API_KEY;
|
|
10
|
+
const QUICKNODE_API_URL = process.env.QUICKNODE_API_URL || "https://api.quicknode.com";
|
|
11
|
+
if (!QUICKNODE_API_KEY) {
|
|
12
|
+
console.error("QUICKNODE_API_KEY must be set");
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
const client = new console_api_client_1.QuickNodeClient({
|
|
16
|
+
token: QUICKNODE_API_KEY,
|
|
17
|
+
baseUrl: QUICKNODE_API_URL,
|
|
18
|
+
});
|
|
19
|
+
const server = new mcp_js_1.McpServer({
|
|
20
|
+
name: "QuickNode Web3 InfrastructureMCP Server",
|
|
21
|
+
description: "A MCP server for interacting with QuickNode, leading Web3 infrastructure provider, this server can be used to manage web3 infrastructure (i.e. rpc endpoints) under a user's QuickNode account",
|
|
22
|
+
version: "0.0.1",
|
|
23
|
+
});
|
|
24
|
+
(0, tools_1.setTools)(server, client);
|
|
25
|
+
(0, resource_1.setResources)(server, client);
|
|
26
|
+
async function runServer() {
|
|
27
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
28
|
+
await server.connect(transport);
|
|
29
|
+
console.error("Secure MCP Filesystem Server running on stdio");
|
|
30
|
+
}
|
|
31
|
+
runServer().catch((error) => {
|
|
32
|
+
console.error("Fatal error running server:", error);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setEndpointResources = setEndpointResources;
|
|
4
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
5
|
+
function setEndpointResources(server, client) {
|
|
6
|
+
server.resource("endpoint", new mcp_js_1.ResourceTemplate("endpoint://{name}", {
|
|
7
|
+
list: async (data) => {
|
|
8
|
+
const endpoints = await client.listEndpoints();
|
|
9
|
+
return {
|
|
10
|
+
resources: endpoints.map((endpoint) => ({
|
|
11
|
+
name: endpoint.name,
|
|
12
|
+
uri: `endpoint://${endpoint.name}`,
|
|
13
|
+
})),
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
complete: {
|
|
17
|
+
name: async (data) => {
|
|
18
|
+
const endpoints = await client.listEndpoints();
|
|
19
|
+
return endpoints
|
|
20
|
+
.map((endpoint) => endpoint.name)
|
|
21
|
+
.filter((name) => name.startsWith(data));
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
}), {
|
|
25
|
+
description: "A resource for listing and getting QuickNode endpoints information",
|
|
26
|
+
}, async (uri, { name }) => {
|
|
27
|
+
if (Array.isArray(name)) {
|
|
28
|
+
console.error("\n\n---name is array this is not supported yet\n", {
|
|
29
|
+
name,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const endpoints = await client.listEndpoints();
|
|
33
|
+
const endpoint = endpoints.find((endpoint) => endpoint.name === name);
|
|
34
|
+
if (!endpoint) {
|
|
35
|
+
throw new Error(`Endpoint not found: ${name}`);
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
contents: [
|
|
39
|
+
{
|
|
40
|
+
uri: uri.href,
|
|
41
|
+
text: JSON.stringify(endpoint, null, 2),
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setBillingTools = setBillingTools;
|
|
4
|
+
const utils_1 = require("../common/utils");
|
|
5
|
+
function setBillingTools(server, client) {
|
|
6
|
+
server.registerTool("get-billing-invoices", {
|
|
7
|
+
description: "Get all billing invoices for the user's QuickNode account",
|
|
8
|
+
}, async () => {
|
|
9
|
+
const invoices = await client.getBillingInvoices();
|
|
10
|
+
const data = {
|
|
11
|
+
invoices: invoices.data.invoices.map((invoice) => ({
|
|
12
|
+
...invoice,
|
|
13
|
+
period_end: (0, utils_1.unixTimestampToIso)(invoice.period_end),
|
|
14
|
+
period_start: (0, utils_1.unixTimestampToIso)(invoice.period_start),
|
|
15
|
+
created: (0, utils_1.unixTimestampToIso)(invoice.created),
|
|
16
|
+
})),
|
|
17
|
+
};
|
|
18
|
+
return {
|
|
19
|
+
structuredContent: { data },
|
|
20
|
+
content: [
|
|
21
|
+
{
|
|
22
|
+
type: "text",
|
|
23
|
+
text: JSON.stringify(data, null, 2),
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
server.registerTool("get-billing-payments", {
|
|
29
|
+
description: "Get all billing payments for the user's QuickNode account",
|
|
30
|
+
}, async () => {
|
|
31
|
+
const payments = await client.getBillingPayments();
|
|
32
|
+
const data = {
|
|
33
|
+
payments: payments.data.payments.map((payment) => ({
|
|
34
|
+
...payment,
|
|
35
|
+
created_at: (0, utils_1.unixTimestampToIso)(parseInt(payment.created_at)),
|
|
36
|
+
})),
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
structuredContent: { data },
|
|
40
|
+
content: [
|
|
41
|
+
{
|
|
42
|
+
type: "text",
|
|
43
|
+
text: JSON.stringify(data, null, 2),
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setChainTools = setChainTools;
|
|
4
|
+
function setChainTools(server, client) {
|
|
5
|
+
server.registerTool("get-chains", {
|
|
6
|
+
description: "Get all chains supported by QuickNode",
|
|
7
|
+
}, async () => {
|
|
8
|
+
const chains = await client.getChains();
|
|
9
|
+
return {
|
|
10
|
+
structuredContent: { data: chains.data },
|
|
11
|
+
content: [
|
|
12
|
+
{
|
|
13
|
+
type: "text",
|
|
14
|
+
text: JSON.stringify(chains.data, null, 2),
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
}
|