@dritan/mcp 0.3.1 → 0.5.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/.env.example +1 -0
- package/README.md +10 -1
- package/dist/index.js +313 -11
- package/package.json +2 -2
package/.env.example
CHANGED
package/README.md
CHANGED
|
@@ -6,7 +6,8 @@ MCP server for personal agents to use `dritan-sdk` for market data and swap exec
|
|
|
6
6
|
|
|
7
7
|
- Node.js 20+
|
|
8
8
|
- `solana-keygen` available in `PATH`
|
|
9
|
-
- Dritan API key (`DRITAN_API_KEY`)
|
|
9
|
+
- Optional: Dritan API key (`DRITAN_API_KEY`) for market/swap tools.
|
|
10
|
+
- For paid onboarding without an existing key, use x402 tools.
|
|
10
11
|
|
|
11
12
|
## Setup
|
|
12
13
|
|
|
@@ -41,6 +42,10 @@ npm run build && npm start
|
|
|
41
42
|
- `wallet_create_local`
|
|
42
43
|
- `wallet_get_address`
|
|
43
44
|
- `wallet_get_balance`
|
|
45
|
+
- `wallet_transfer_sol`
|
|
46
|
+
- `x402_get_pricing`
|
|
47
|
+
- `x402_create_api_key_quote`
|
|
48
|
+
- `x402_create_api_key`
|
|
44
49
|
- `dritan_health`
|
|
45
50
|
- `market_get_snapshot`
|
|
46
51
|
- `token_search`
|
|
@@ -74,7 +79,11 @@ npm run build && npm start
|
|
|
74
79
|
- Wallets default to `~/.config/dritan-mcp/wallets`.
|
|
75
80
|
- Private keys never leave local files; only public address/signature are returned.
|
|
76
81
|
- `swap_sign_and_broadcast` signs locally, then broadcasts via Dritan.
|
|
82
|
+
- Agent onboarding without `DRITAN_API_KEY` should present two options:
|
|
83
|
+
- Option 1: paid x402 flow (`x402_get_pricing` -> `x402_create_api_key_quote` -> user funds agent wallet -> `wallet_transfer_sol` -> `x402_create_api_key`).
|
|
84
|
+
- Option 2: user gets a free key at `https://dritan.dev`.
|
|
77
85
|
- `token_get_ohlcv_chart` returns a shareable chart URL plus a ready-to-send markdown image snippet.
|
|
86
|
+
- `token_get_ohlcv_chart` supports `chartType: "line-volume" | "candlestick"` (default is `line-volume`).
|
|
78
87
|
- Ticker workflow for chart requests: `token_search` -> extract mint -> `token_get_ohlcv` or `token_get_ohlcv_chart`.
|
|
79
88
|
- If users ask for `$WIF` style symbols, always resolve mint with `token_search` first.
|
|
80
89
|
- If Solana CLI is missing, run `system_check_prereqs` and follow returned install steps.
|
package/dist/index.js
CHANGED
|
@@ -7,9 +7,10 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
7
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
8
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
9
9
|
import { DritanClient, MeteoraThsClient, } from "dritan-sdk";
|
|
10
|
-
import { Connection, Keypair, VersionedTransaction, clusterApiUrl } from "@solana/web3.js";
|
|
10
|
+
import { Connection, Keypair, PublicKey, SystemProgram, Transaction, VersionedTransaction, clusterApiUrl, } from "@solana/web3.js";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
const DEFAULT_WALLET_DIR = join(homedir(), ".config", "dritan-mcp", "wallets");
|
|
13
|
+
const LAMPORTS_PER_SOL = 1_000_000_000;
|
|
13
14
|
const STREAM_DEXES = [
|
|
14
15
|
"pumpamm",
|
|
15
16
|
"pumpfun",
|
|
@@ -31,20 +32,42 @@ const server = new Server({
|
|
|
31
32
|
tools: {},
|
|
32
33
|
},
|
|
33
34
|
instructions: [
|
|
34
|
-
"This server
|
|
35
|
-
"
|
|
35
|
+
"This server supports two API-key onboarding options when DRITAN_API_KEY is missing:",
|
|
36
|
+
"1) x402 pay-per-use key flow: create wallet, receive SOL, create quote, forward payment, claim key.",
|
|
37
|
+
"2) Free key flow: user creates a free API key at https://dritan.dev.",
|
|
38
|
+
"After key is obtained, set DRITAN_API_KEY and continue with market/swap tools.",
|
|
39
|
+
"Suggested setup command:",
|
|
36
40
|
" claude mcp add dritan-mcp -e DRITAN_API_KEY=<your-key> -- npx @dritan/mcp@latest",
|
|
37
|
-
"Without the key, only system_check_prereqs and wallet tools (create_local, get_address, get_balance) will work.",
|
|
38
41
|
].join("\n"),
|
|
39
42
|
});
|
|
43
|
+
function getControlBaseUrl() {
|
|
44
|
+
return process.env.DRITAN_CONTROL_BASE_URL ?? "https://api.dritan.dev";
|
|
45
|
+
}
|
|
46
|
+
function missingApiKeyError() {
|
|
47
|
+
return new Error([
|
|
48
|
+
"Missing DRITAN_API_KEY in environment.",
|
|
49
|
+
"Option 1 (paid): use x402 tools (x402_get_pricing, x402_create_api_key_quote, x402_create_api_key) and wallet tools.",
|
|
50
|
+
"Option 2 (free): create a free key at https://dritan.dev and set DRITAN_API_KEY.",
|
|
51
|
+
].join(" "));
|
|
52
|
+
}
|
|
40
53
|
function getDritanClient() {
|
|
41
54
|
const apiKey = process.env.DRITAN_API_KEY;
|
|
42
55
|
if (!apiKey) {
|
|
43
|
-
throw
|
|
56
|
+
throw missingApiKeyError();
|
|
44
57
|
}
|
|
45
58
|
return new DritanClient({
|
|
46
59
|
apiKey,
|
|
47
60
|
baseUrl: process.env.DRITAN_BASE_URL,
|
|
61
|
+
controlBaseUrl: getControlBaseUrl(),
|
|
62
|
+
wsBaseUrl: process.env.DRITAN_WS_BASE_URL,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function getX402Client() {
|
|
66
|
+
return new DritanClient({
|
|
67
|
+
// x402 endpoints are public; SDK constructor still needs a string.
|
|
68
|
+
apiKey: process.env.DRITAN_API_KEY ?? "x402_public_endpoints",
|
|
69
|
+
baseUrl: process.env.DRITAN_BASE_URL,
|
|
70
|
+
controlBaseUrl: getControlBaseUrl(),
|
|
48
71
|
wsBaseUrl: process.env.DRITAN_WS_BASE_URL,
|
|
49
72
|
});
|
|
50
73
|
}
|
|
@@ -61,7 +84,7 @@ async function searchTokens(client, query, options) {
|
|
|
61
84
|
// Backward-compatible fallback for environments where dritan-sdk hasn't been upgraded yet.
|
|
62
85
|
const apiKey = process.env.DRITAN_API_KEY;
|
|
63
86
|
if (!apiKey) {
|
|
64
|
-
throw
|
|
87
|
+
throw missingApiKeyError();
|
|
65
88
|
}
|
|
66
89
|
const baseUrl = process.env.DRITAN_BASE_URL ?? "https://us-east.dritan.dev";
|
|
67
90
|
const url = new URL("/token/search", baseUrl);
|
|
@@ -89,6 +112,65 @@ async function searchTokens(client, query, options) {
|
|
|
89
112
|
return { ok: true, raw: text };
|
|
90
113
|
}
|
|
91
114
|
}
|
|
115
|
+
async function x402GetPricing(client) {
|
|
116
|
+
const sdkMethod = client.getX402Pricing;
|
|
117
|
+
if (typeof sdkMethod === "function") {
|
|
118
|
+
return await sdkMethod.call(client);
|
|
119
|
+
}
|
|
120
|
+
const url = new URL("/v1/x402/pricing", getControlBaseUrl());
|
|
121
|
+
const response = await fetch(url.toString(), { method: "GET" });
|
|
122
|
+
const text = await response.text();
|
|
123
|
+
if (!response.ok)
|
|
124
|
+
throw new Error(`x402 pricing failed (${response.status}): ${text}`);
|
|
125
|
+
try {
|
|
126
|
+
return JSON.parse(text);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return { raw: text };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function x402CreateQuote(client, input) {
|
|
133
|
+
const sdkMethod = client.createX402ApiKeyQuote;
|
|
134
|
+
if (typeof sdkMethod === "function") {
|
|
135
|
+
return await sdkMethod.call(client, input);
|
|
136
|
+
}
|
|
137
|
+
const url = new URL("/v1/x402/api-keys/quote", getControlBaseUrl());
|
|
138
|
+
const response = await fetch(url.toString(), {
|
|
139
|
+
method: "POST",
|
|
140
|
+
headers: { "content-type": "application/json" },
|
|
141
|
+
body: JSON.stringify(input),
|
|
142
|
+
});
|
|
143
|
+
const text = await response.text();
|
|
144
|
+
if (!response.ok)
|
|
145
|
+
throw new Error(`x402 quote failed (${response.status}): ${text}`);
|
|
146
|
+
try {
|
|
147
|
+
return JSON.parse(text);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return { raw: text };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function x402CreateApiKey(client, input) {
|
|
154
|
+
const sdkMethod = client.createX402ApiKey;
|
|
155
|
+
if (typeof sdkMethod === "function") {
|
|
156
|
+
return await sdkMethod.call(client, input);
|
|
157
|
+
}
|
|
158
|
+
const url = new URL("/v1/x402/api-keys", getControlBaseUrl());
|
|
159
|
+
const response = await fetch(url.toString(), {
|
|
160
|
+
method: "POST",
|
|
161
|
+
headers: { "content-type": "application/json" },
|
|
162
|
+
body: JSON.stringify(input),
|
|
163
|
+
});
|
|
164
|
+
const text = await response.text();
|
|
165
|
+
if (!response.ok)
|
|
166
|
+
throw new Error(`x402 create key failed (${response.status}): ${text}`);
|
|
167
|
+
try {
|
|
168
|
+
return JSON.parse(text);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return { raw: text };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
92
174
|
async function checkDritanHealth() {
|
|
93
175
|
const baseUrl = process.env.DRITAN_BASE_URL ?? "https://us-east.dritan.dev";
|
|
94
176
|
const url = new URL("/health", baseUrl).toString();
|
|
@@ -117,7 +199,7 @@ function formatChartLabel(ts) {
|
|
|
117
199
|
return String(ts);
|
|
118
200
|
return date.toISOString().replace("T", " ").slice(0, 16);
|
|
119
201
|
}
|
|
120
|
-
function
|
|
202
|
+
function buildLineVolumeOhlcvChartUrl(mint, timeframe, bars, width, height) {
|
|
121
203
|
const labels = bars.map((bar) => formatChartLabel(bar.time));
|
|
122
204
|
const closeSeries = bars.map((bar) => Number(bar.close.toFixed(12)));
|
|
123
205
|
const volumeSeries = bars.map((bar) => Number(bar.volume.toFixed(12)));
|
|
@@ -166,6 +248,66 @@ function buildOhlcvChartUrl(mint, timeframe, bars, width, height) {
|
|
|
166
248
|
// so pinning `v=4` prevents runtime render errors like "Cannot read properties of undefined (reading 'options')".
|
|
167
249
|
return `https://quickchart.io/chart?w=${width}&h=${height}&f=png&v=4&c=${encoded}`;
|
|
168
250
|
}
|
|
251
|
+
function buildCandlestickOhlcvChartUrl(mint, timeframe, bars, width, height) {
|
|
252
|
+
const labels = bars.map((bar) => formatChartLabel(bar.time));
|
|
253
|
+
const candles = bars.map((bar, index) => ({
|
|
254
|
+
x: labels[index],
|
|
255
|
+
o: Number(bar.open.toFixed(12)),
|
|
256
|
+
h: Number(bar.high.toFixed(12)),
|
|
257
|
+
l: Number(bar.low.toFixed(12)),
|
|
258
|
+
c: Number(bar.close.toFixed(12)),
|
|
259
|
+
}));
|
|
260
|
+
const volumeSeries = bars.map((bar) => Number(bar.volume.toFixed(12)));
|
|
261
|
+
const volumeColors = bars.map((bar) => bar.close >= bar.open ? "rgba(16,185,129,0.25)" : "rgba(239,68,68,0.25)");
|
|
262
|
+
const config = {
|
|
263
|
+
type: "candlestick",
|
|
264
|
+
data: {
|
|
265
|
+
labels,
|
|
266
|
+
datasets: [
|
|
267
|
+
{
|
|
268
|
+
label: "OHLC",
|
|
269
|
+
data: candles,
|
|
270
|
+
color: {
|
|
271
|
+
up: "#10b981",
|
|
272
|
+
down: "#ef4444",
|
|
273
|
+
unchanged: "#94a3b8",
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
type: "bar",
|
|
278
|
+
label: "Volume",
|
|
279
|
+
data: volumeSeries,
|
|
280
|
+
backgroundColor: volumeColors,
|
|
281
|
+
borderWidth: 0,
|
|
282
|
+
yAxisID: "volume",
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
},
|
|
286
|
+
options: {
|
|
287
|
+
plugins: {
|
|
288
|
+
legend: { display: true },
|
|
289
|
+
title: {
|
|
290
|
+
display: true,
|
|
291
|
+
text: `${mint} ${timeframe.toUpperCase()} Candlestick`,
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
scales: {
|
|
295
|
+
x: { type: "category" },
|
|
296
|
+
y: { type: "linear", position: "left" },
|
|
297
|
+
volume: { type: "linear", position: "right", grid: { drawOnChartArea: false } },
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
const encoded = encodeURIComponent(JSON.stringify(config));
|
|
302
|
+
// Pin Chart.js v4 for stable scale behavior and financial chart rendering.
|
|
303
|
+
return `https://quickchart.io/chart?w=${width}&h=${height}&f=png&v=4&c=${encoded}`;
|
|
304
|
+
}
|
|
305
|
+
function buildOhlcvChartUrl(chartType, mint, timeframe, bars, width, height) {
|
|
306
|
+
if (chartType === "candlestick") {
|
|
307
|
+
return buildCandlestickOhlcvChartUrl(mint, timeframe, bars, width, height);
|
|
308
|
+
}
|
|
309
|
+
return buildLineVolumeOhlcvChartUrl(mint, timeframe, bars, width, height);
|
|
310
|
+
}
|
|
169
311
|
function getPlatformInstallHint(binary) {
|
|
170
312
|
switch (process.platform) {
|
|
171
313
|
case "darwin":
|
|
@@ -276,6 +418,31 @@ const walletPathSchema = z.object({
|
|
|
276
418
|
const walletBalanceSchema = walletPathSchema.extend({
|
|
277
419
|
rpcUrl: z.string().url().optional(),
|
|
278
420
|
});
|
|
421
|
+
const walletTransferSchema = z
|
|
422
|
+
.object({
|
|
423
|
+
walletPath: z.string().min(1),
|
|
424
|
+
toAddress: z.string().min(32),
|
|
425
|
+
lamports: z.number().int().positive().optional(),
|
|
426
|
+
sol: z.number().positive().optional(),
|
|
427
|
+
rpcUrl: z.string().url().optional(),
|
|
428
|
+
})
|
|
429
|
+
.refine((v) => v.lamports != null || v.sol != null, {
|
|
430
|
+
message: "Either lamports or sol is required",
|
|
431
|
+
path: ["lamports"],
|
|
432
|
+
});
|
|
433
|
+
const x402QuoteSchema = z.object({
|
|
434
|
+
durationMinutes: z.number().int().min(1).max(60 * 24 * 30),
|
|
435
|
+
name: z.string().min(1).max(120).optional(),
|
|
436
|
+
scopes: z.array(z.string().min(1).max(120)).max(64).optional(),
|
|
437
|
+
payerWallet: z.string().min(32).max(80).optional(),
|
|
438
|
+
});
|
|
439
|
+
const x402CreateApiKeySchema = z.object({
|
|
440
|
+
quoteId: z.string().min(1),
|
|
441
|
+
paymentTxSignature: z.string().min(40),
|
|
442
|
+
payerWallet: z.string().min(32).max(80).optional(),
|
|
443
|
+
name: z.string().min(1).max(120).optional(),
|
|
444
|
+
scopes: z.array(z.string().min(1).max(120)).max(64).optional(),
|
|
445
|
+
});
|
|
279
446
|
const marketSnapshotSchema = z.object({
|
|
280
447
|
mint: z.string().min(1),
|
|
281
448
|
mode: z.enum(["price", "metadata", "risk", "first-buyers", "aggregated"]).default("aggregated"),
|
|
@@ -300,6 +467,7 @@ const tokenOhlcvSchema = z.object({
|
|
|
300
467
|
timeTo: z.number().int().positive().optional(),
|
|
301
468
|
});
|
|
302
469
|
const tokenOhlcvChartSchema = tokenOhlcvSchema.extend({
|
|
470
|
+
chartType: z.enum(["line-volume", "candlestick"]).default("line-volume"),
|
|
303
471
|
includeActive: z.boolean().default(true),
|
|
304
472
|
maxPoints: z.number().int().min(10).max(500).default(120),
|
|
305
473
|
width: z.number().int().min(300).max(2000).default(1200),
|
|
@@ -397,6 +565,58 @@ const tools = [
|
|
|
397
565
|
},
|
|
398
566
|
},
|
|
399
567
|
},
|
|
568
|
+
{
|
|
569
|
+
name: "wallet_transfer_sol",
|
|
570
|
+
description: "Send SOL from a local wallet to a destination wallet (used in x402 paid key flow).",
|
|
571
|
+
inputSchema: {
|
|
572
|
+
type: "object",
|
|
573
|
+
required: ["walletPath", "toAddress"],
|
|
574
|
+
properties: {
|
|
575
|
+
walletPath: { type: "string" },
|
|
576
|
+
toAddress: { type: "string" },
|
|
577
|
+
lamports: { type: "number", description: "Integer lamports to transfer" },
|
|
578
|
+
sol: { type: "number", description: "SOL amount to transfer (used when lamports is not provided)" },
|
|
579
|
+
rpcUrl: { type: "string" },
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
name: "x402_get_pricing",
|
|
585
|
+
description: "Get x402 pricing and receiver wallet for paid time-limited API keys.",
|
|
586
|
+
inputSchema: {
|
|
587
|
+
type: "object",
|
|
588
|
+
properties: {},
|
|
589
|
+
},
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
name: "x402_create_api_key_quote",
|
|
593
|
+
description: "Create an x402 payment quote for a time-limited API key (returns quoteId, receiverWallet, and exact SOL amount).",
|
|
594
|
+
inputSchema: {
|
|
595
|
+
type: "object",
|
|
596
|
+
required: ["durationMinutes"],
|
|
597
|
+
properties: {
|
|
598
|
+
durationMinutes: { type: "number", description: "Minutes for key validity (1 to 43200)." },
|
|
599
|
+
name: { type: "string", description: "Optional key name." },
|
|
600
|
+
payerWallet: { type: "string", description: "Optional payer wallet to lock quote payer." },
|
|
601
|
+
scopes: { type: "array", items: { type: "string" }, description: "Optional scopes." },
|
|
602
|
+
},
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
name: "x402_create_api_key",
|
|
607
|
+
description: "Claim a paid x402 API key using quoteId and payment transaction signature.",
|
|
608
|
+
inputSchema: {
|
|
609
|
+
type: "object",
|
|
610
|
+
required: ["quoteId", "paymentTxSignature"],
|
|
611
|
+
properties: {
|
|
612
|
+
quoteId: { type: "string" },
|
|
613
|
+
paymentTxSignature: { type: "string" },
|
|
614
|
+
payerWallet: { type: "string" },
|
|
615
|
+
name: { type: "string" },
|
|
616
|
+
scopes: { type: "array", items: { type: "string" } },
|
|
617
|
+
},
|
|
618
|
+
},
|
|
619
|
+
},
|
|
400
620
|
{
|
|
401
621
|
name: "dritan_health",
|
|
402
622
|
description: "Check data plane health endpoint via Dritan SDK.",
|
|
@@ -515,7 +735,7 @@ const tools = [
|
|
|
515
735
|
},
|
|
516
736
|
{
|
|
517
737
|
name: "token_get_ohlcv_chart",
|
|
518
|
-
description: "Build a shareable chart URL from token OHLCV candles so agents can send an actual chart in chat (resolve ticker with token_search first).",
|
|
738
|
+
description: "Build a shareable chart URL from token OHLCV candles so agents can send an actual chart in chat (resolve ticker with token_search first). Supports line-volume or candlestick charts.",
|
|
519
739
|
inputSchema: {
|
|
520
740
|
type: "object",
|
|
521
741
|
required: ["mint", "timeframe"],
|
|
@@ -523,6 +743,11 @@ const tools = [
|
|
|
523
743
|
mint: { type: "string" },
|
|
524
744
|
timeframe: { type: "string", description: "e.g. 1m, 5m, 1h, 1d" },
|
|
525
745
|
timeTo: { type: "number" },
|
|
746
|
+
chartType: {
|
|
747
|
+
type: "string",
|
|
748
|
+
enum: ["line-volume", "candlestick"],
|
|
749
|
+
description: "Chart style. Default line-volume.",
|
|
750
|
+
},
|
|
526
751
|
includeActive: { type: "boolean" },
|
|
527
752
|
maxPoints: { type: "number" },
|
|
528
753
|
width: { type: "number" },
|
|
@@ -754,6 +979,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
754
979
|
const apiKeySet = !!process.env.DRITAN_API_KEY;
|
|
755
980
|
return ok({
|
|
756
981
|
ready: solanaCli.ok && apiKeySet,
|
|
982
|
+
readyForX402Onboarding: solanaCli.ok,
|
|
757
983
|
checks: [
|
|
758
984
|
solanaCli,
|
|
759
985
|
{
|
|
@@ -761,11 +987,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
761
987
|
name: "DRITAN_API_KEY",
|
|
762
988
|
hint: apiKeySet
|
|
763
989
|
? "API key is configured."
|
|
764
|
-
: "Missing DRITAN_API_KEY.
|
|
990
|
+
: "Missing DRITAN_API_KEY. You can either use x402 onboarding tools or get a free key at https://dritan.dev.",
|
|
765
991
|
},
|
|
766
992
|
],
|
|
767
993
|
nextAction: !apiKeySet
|
|
768
|
-
? "
|
|
994
|
+
? "Choose one: (1) x402 paid onboarding flow with wallet tools, or (2) get a free key at https://dritan.dev and set DRITAN_API_KEY."
|
|
769
995
|
: !solanaCli.ok
|
|
770
996
|
? "Install Solana CLI using installHint, then retry wallet_create_local."
|
|
771
997
|
: "Environment ready.",
|
|
@@ -799,6 +1025,81 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
799
1025
|
sol: lamports / 1_000_000_000,
|
|
800
1026
|
});
|
|
801
1027
|
}
|
|
1028
|
+
case "wallet_transfer_sol": {
|
|
1029
|
+
const input = walletTransferSchema.parse(args);
|
|
1030
|
+
const walletPath = resolve(input.walletPath);
|
|
1031
|
+
const keypair = loadKeypairFromPath(walletPath);
|
|
1032
|
+
const rpcUrl = getRpcUrl(input.rpcUrl);
|
|
1033
|
+
const conn = new Connection(rpcUrl, "confirmed");
|
|
1034
|
+
const lamportsRaw = input.lamports != null ? input.lamports : Math.round((input.sol ?? 0) * LAMPORTS_PER_SOL);
|
|
1035
|
+
if (!Number.isSafeInteger(lamportsRaw) || lamportsRaw <= 0) {
|
|
1036
|
+
throw new Error("Transfer amount must be a positive safe integer number of lamports.");
|
|
1037
|
+
}
|
|
1038
|
+
const toPubkey = new PublicKey(input.toAddress);
|
|
1039
|
+
const latest = await conn.getLatestBlockhash("confirmed");
|
|
1040
|
+
const tx = new Transaction({
|
|
1041
|
+
feePayer: keypair.publicKey,
|
|
1042
|
+
recentBlockhash: latest.blockhash,
|
|
1043
|
+
}).add(SystemProgram.transfer({
|
|
1044
|
+
fromPubkey: keypair.publicKey,
|
|
1045
|
+
toPubkey,
|
|
1046
|
+
lamports: lamportsRaw,
|
|
1047
|
+
}));
|
|
1048
|
+
tx.sign(keypair);
|
|
1049
|
+
const signature = await conn.sendRawTransaction(tx.serialize(), {
|
|
1050
|
+
skipPreflight: false,
|
|
1051
|
+
maxRetries: 3,
|
|
1052
|
+
});
|
|
1053
|
+
const confirmation = await conn.confirmTransaction({
|
|
1054
|
+
signature,
|
|
1055
|
+
blockhash: latest.blockhash,
|
|
1056
|
+
lastValidBlockHeight: latest.lastValidBlockHeight,
|
|
1057
|
+
}, "confirmed");
|
|
1058
|
+
if (confirmation.value.err) {
|
|
1059
|
+
throw new Error(`Transfer failed: ${JSON.stringify(confirmation.value.err)}`);
|
|
1060
|
+
}
|
|
1061
|
+
return ok({
|
|
1062
|
+
walletPath,
|
|
1063
|
+
fromAddress: keypair.publicKey.toBase58(),
|
|
1064
|
+
toAddress: toPubkey.toBase58(),
|
|
1065
|
+
rpcUrl,
|
|
1066
|
+
lamports: lamportsRaw,
|
|
1067
|
+
sol: lamportsRaw / LAMPORTS_PER_SOL,
|
|
1068
|
+
signature,
|
|
1069
|
+
explorerUrl: `https://solscan.io/tx/${signature}`,
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
case "x402_get_pricing": {
|
|
1073
|
+
const client = getX402Client();
|
|
1074
|
+
const pricing = await x402GetPricing(client);
|
|
1075
|
+
return ok({
|
|
1076
|
+
...(pricing ?? {}),
|
|
1077
|
+
onboardingOptions: [
|
|
1078
|
+
"Option 1 (paid x402): create wallet -> receive user SOL -> x402 quote -> forward payment -> claim key.",
|
|
1079
|
+
"Option 2 (free): user gets API key at https://dritan.dev and provides it as DRITAN_API_KEY.",
|
|
1080
|
+
],
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
case "x402_create_api_key_quote": {
|
|
1084
|
+
const input = x402QuoteSchema.parse(args);
|
|
1085
|
+
const client = getX402Client();
|
|
1086
|
+
const quote = await x402CreateQuote(client, input);
|
|
1087
|
+
return ok({
|
|
1088
|
+
...(quote ?? {}),
|
|
1089
|
+
nextSteps: [
|
|
1090
|
+
"If user picked paid flow, ensure agent has a local wallet (wallet_create_local + wallet_get_address).",
|
|
1091
|
+
"User funds the agent wallet.",
|
|
1092
|
+
"Transfer quoted SOL amount to receiver wallet using wallet_transfer_sol.",
|
|
1093
|
+
"Claim key with x402_create_api_key using returned tx signature.",
|
|
1094
|
+
],
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
case "x402_create_api_key": {
|
|
1098
|
+
const input = x402CreateApiKeySchema.parse(args);
|
|
1099
|
+
const client = getX402Client();
|
|
1100
|
+
const created = await x402CreateApiKey(client, input);
|
|
1101
|
+
return ok(created);
|
|
1102
|
+
}
|
|
802
1103
|
case "market_get_snapshot": {
|
|
803
1104
|
const input = marketSnapshotSchema.parse(args);
|
|
804
1105
|
const client = getDritanClient();
|
|
@@ -868,10 +1169,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
868
1169
|
if (trimmedBars.length === 0) {
|
|
869
1170
|
throw new Error(`No OHLCV data available for ${input.mint} (${input.timeframe})`);
|
|
870
1171
|
}
|
|
871
|
-
const chartUrl = buildOhlcvChartUrl(input.mint, input.timeframe, trimmedBars, input.width, input.height);
|
|
1172
|
+
const chartUrl = buildOhlcvChartUrl(input.chartType, input.mint, input.timeframe, trimmedBars, input.width, input.height);
|
|
872
1173
|
return ok({
|
|
873
1174
|
mint: input.mint,
|
|
874
1175
|
timeframe: input.timeframe,
|
|
1176
|
+
chartType: input.chartType,
|
|
875
1177
|
points: trimmedBars.length,
|
|
876
1178
|
chartUrl,
|
|
877
1179
|
markdown: ``,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dritan/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "MCP server for Dritan SDK market data and local Solana wallet swap execution",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@modelcontextprotocol/sdk": "^1.17.4",
|
|
36
36
|
"@solana/web3.js": "^1.98.4",
|
|
37
|
-
"dritan-sdk": "^0.
|
|
37
|
+
"dritan-sdk": "^0.9.0",
|
|
38
38
|
"zod": "^3.24.1"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|