@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 CHANGED
@@ -1,5 +1,6 @@
1
1
  DRITAN_API_KEY=
2
2
  DRITAN_BASE_URL=https://us-east.dritan.dev
3
+ DRITAN_CONTROL_BASE_URL=https://api.dritan.dev
3
4
  DRITAN_WS_BASE_URL=wss://us-east.dritan.dev
4
5
  SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
5
6
  METEORA_THS_BASE_URL=https://ths.dritan.dev
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 requires a DRITAN_API_KEY environment variable to use market data and swap tools.",
35
- "Get your API key at https://dritan.dev and configure it:",
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 new Error("Missing DRITAN_API_KEY in environment");
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 new Error("Missing DRITAN_API_KEY in environment");
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. Get your key at https://dritan.dev and set it as an environment variable.",
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
- ? "Set DRITAN_API_KEY environment variable. Get your key at https://dritan.dev"
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.4.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.6.0",
37
+ "dritan-sdk": "^0.9.0",
38
38
  "zod": "^3.24.1"
39
39
  },
40
40
  "devDependencies": {