@dritan/mcp 0.4.0 → 0.5.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/.env.example +1 -0
- package/README.md +9 -1
- package/dist/index.js +244 -9
- 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,6 +79,9 @@ 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.
|
|
78
86
|
- `token_get_ohlcv_chart` supports `chartType: "line-volume" | "candlestick"` (default is `line-volume`).
|
|
79
87
|
- Ticker workflow for chart requests: `token_search` -> extract mint -> `token_get_ohlcv` or `token_get_ohlcv_chart`.
|
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
|
}
|
|
@@ -56,12 +79,12 @@ function getThsClient() {
|
|
|
56
79
|
async function searchTokens(client, query, options) {
|
|
57
80
|
const sdkSearch = client.searchTokens;
|
|
58
81
|
if (typeof sdkSearch === "function") {
|
|
59
|
-
return await sdkSearch(query, options);
|
|
82
|
+
return await sdkSearch.call(client, query, options);
|
|
60
83
|
}
|
|
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();
|
|
@@ -336,6 +418,31 @@ const walletPathSchema = z.object({
|
|
|
336
418
|
const walletBalanceSchema = walletPathSchema.extend({
|
|
337
419
|
rpcUrl: z.string().url().optional(),
|
|
338
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
|
+
});
|
|
339
446
|
const marketSnapshotSchema = z.object({
|
|
340
447
|
mint: z.string().min(1),
|
|
341
448
|
mode: z.enum(["price", "metadata", "risk", "first-buyers", "aggregated"]).default("aggregated"),
|
|
@@ -458,6 +565,58 @@ const tools = [
|
|
|
458
565
|
},
|
|
459
566
|
},
|
|
460
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
|
+
},
|
|
461
620
|
{
|
|
462
621
|
name: "dritan_health",
|
|
463
622
|
description: "Check data plane health endpoint via Dritan SDK.",
|
|
@@ -820,6 +979,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
820
979
|
const apiKeySet = !!process.env.DRITAN_API_KEY;
|
|
821
980
|
return ok({
|
|
822
981
|
ready: solanaCli.ok && apiKeySet,
|
|
982
|
+
readyForX402Onboarding: solanaCli.ok,
|
|
823
983
|
checks: [
|
|
824
984
|
solanaCli,
|
|
825
985
|
{
|
|
@@ -827,11 +987,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
827
987
|
name: "DRITAN_API_KEY",
|
|
828
988
|
hint: apiKeySet
|
|
829
989
|
? "API key is configured."
|
|
830
|
-
: "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.",
|
|
831
991
|
},
|
|
832
992
|
],
|
|
833
993
|
nextAction: !apiKeySet
|
|
834
|
-
? "
|
|
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."
|
|
835
995
|
: !solanaCli.ok
|
|
836
996
|
? "Install Solana CLI using installHint, then retry wallet_create_local."
|
|
837
997
|
: "Environment ready.",
|
|
@@ -865,6 +1025,81 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
865
1025
|
sol: lamports / 1_000_000_000,
|
|
866
1026
|
});
|
|
867
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
|
+
}
|
|
868
1103
|
case "market_get_snapshot": {
|
|
869
1104
|
const input = marketSnapshotSchema.parse(args);
|
|
870
1105
|
const client = getDritanClient();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dritan/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
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": {
|