@dritan/mcp 0.1.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 ADDED
@@ -0,0 +1,4 @@
1
+ DRITAN_API_KEY=
2
+ DRITAN_BASE_URL=https://us-east.dritan.dev
3
+ DRITAN_WS_BASE_URL=wss://us-east.dritan.dev
4
+ SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
package/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # dritan-mcp
2
+
3
+ MCP server for personal agents to use `dritan-sdk` for market data and swap execution, with local Solana wallet signing.
4
+
5
+ ## Requirements
6
+
7
+ - Node.js 20+
8
+ - `solana-keygen` available in `PATH`
9
+ - Dritan API key (`DRITAN_API_KEY`)
10
+
11
+ ## Setup
12
+
13
+ ```bash
14
+ npm install
15
+ cp .env.example .env
16
+ ```
17
+
18
+ ## Install As MCP (npx)
19
+
20
+ ```bash
21
+ npx @dritan/mcp@latest
22
+ ```
23
+
24
+ Codex example:
25
+
26
+ ```bash
27
+ codex mcp add dritan npx \"@dritan/mcp@latest\"
28
+ ```
29
+
30
+ ## Run
31
+
32
+ ```bash
33
+ npm run dev
34
+ # or
35
+ npm run build && npm start
36
+ ```
37
+
38
+ ## Tools
39
+
40
+ - `system_check_prereqs`
41
+ - `wallet_create_local`
42
+ - `wallet_get_address`
43
+ - `wallet_get_balance`
44
+ - `market_get_snapshot`
45
+ - `market_stream_sample`
46
+ - `swap_build`
47
+ - `swap_sign_and_broadcast`
48
+ - `swap_build_sign_and_broadcast`
49
+
50
+ ## Notes
51
+
52
+ - Wallets default to `~/.config/dritan-mcp/wallets`.
53
+ - Private keys never leave local files; only public address/signature are returned.
54
+ - `swap_sign_and_broadcast` signs locally, then broadcasts via Dritan.
55
+ - If Solana CLI is missing, run `system_check_prereqs` and follow returned install steps.
package/dist/index.js ADDED
@@ -0,0 +1,457 @@
1
+ #!/usr/bin/env node
2
+ import { mkdirSync, readFileSync, existsSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { spawnSync } from "node:child_process";
6
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
7
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
9
+ import { DritanClient } from "dritan-sdk";
10
+ import { Connection, Keypair, VersionedTransaction, clusterApiUrl } from "@solana/web3.js";
11
+ import { z } from "zod";
12
+ const DEFAULT_WALLET_DIR = join(homedir(), ".config", "dritan-mcp", "wallets");
13
+ const server = new Server({
14
+ name: "dritan-mcp",
15
+ version: "0.1.0",
16
+ }, {
17
+ capabilities: {
18
+ tools: {},
19
+ },
20
+ });
21
+ function getDritanClient() {
22
+ const apiKey = process.env.DRITAN_API_KEY;
23
+ if (!apiKey) {
24
+ throw new Error("Missing DRITAN_API_KEY in environment");
25
+ }
26
+ return new DritanClient({
27
+ apiKey,
28
+ baseUrl: process.env.DRITAN_BASE_URL,
29
+ wsBaseUrl: process.env.DRITAN_WS_BASE_URL,
30
+ });
31
+ }
32
+ function getPlatformInstallHint(binary) {
33
+ switch (process.platform) {
34
+ case "darwin":
35
+ return {
36
+ platform: "macOS",
37
+ install: [
38
+ "sh -c \"$(curl -sSfL https://release.anza.xyz/stable/install)\"",
39
+ "export PATH=\"$HOME/.local/share/solana/install/active_release/bin:$PATH\"",
40
+ "solana-keygen --version",
41
+ ],
42
+ };
43
+ case "linux":
44
+ return {
45
+ platform: "Linux",
46
+ install: [
47
+ "sh -c \"$(curl -sSfL https://release.anza.xyz/stable/install)\"",
48
+ "export PATH=\"$HOME/.local/share/solana/install/active_release/bin:$PATH\"",
49
+ "solana-keygen --version",
50
+ ],
51
+ };
52
+ case "win32":
53
+ return {
54
+ platform: "Windows",
55
+ install: [
56
+ "Install WSL2 (recommended) and run Linux install inside WSL.",
57
+ "Or follow Solana/Anza Windows instructions, then ensure `solana-keygen` is in PATH.",
58
+ ],
59
+ };
60
+ default:
61
+ return {
62
+ platform: process.platform,
63
+ install: ["Install Solana CLI and ensure `solana-keygen` is available in PATH."],
64
+ };
65
+ }
66
+ }
67
+ function checkSolanaCli() {
68
+ const installHint = getPlatformInstallHint("solana-keygen");
69
+ const cmd = spawnSync("solana-keygen", ["--version"], {
70
+ encoding: "utf8",
71
+ stdio: ["ignore", "pipe", "pipe"],
72
+ });
73
+ if (cmd.error || cmd.status !== 0) {
74
+ return {
75
+ ok: false,
76
+ binary: "solana-keygen",
77
+ version: null,
78
+ installHint,
79
+ };
80
+ }
81
+ return {
82
+ ok: true,
83
+ binary: "solana-keygen",
84
+ version: (cmd.stdout || "").trim() || null,
85
+ installHint,
86
+ };
87
+ }
88
+ function ensureWalletDir(walletDir) {
89
+ mkdirSync(walletDir, { recursive: true });
90
+ }
91
+ function toWalletPath(name, walletDir) {
92
+ const dir = resolve(walletDir ?? DEFAULT_WALLET_DIR);
93
+ ensureWalletDir(dir);
94
+ const safeName = name.replace(/[^a-zA-Z0-9._-]/g, "-");
95
+ return resolve(join(dir, `${safeName}.json`));
96
+ }
97
+ function createLocalWalletFile(walletPath) {
98
+ const dir = dirname(walletPath);
99
+ ensureWalletDir(dir);
100
+ if (existsSync(walletPath)) {
101
+ throw new Error(`Wallet already exists: ${walletPath}`);
102
+ }
103
+ const cmd = spawnSync("solana-keygen", ["new", "--no-bip39-passphrase", "-o", walletPath], {
104
+ encoding: "utf8",
105
+ stdio: ["ignore", "pipe", "pipe"],
106
+ });
107
+ if (cmd.error) {
108
+ const hint = getPlatformInstallHint("solana-keygen");
109
+ throw new Error(`SOLANA_CLI_MISSING: failed to run solana-keygen (${cmd.error.message}). Install steps (${hint.platform}): ${hint.install.join(" && ")}`);
110
+ }
111
+ if (cmd.status !== 0) {
112
+ throw new Error(`solana-keygen failed (${cmd.status}): ${cmd.stderr || cmd.stdout}`);
113
+ }
114
+ const keypair = loadKeypairFromPath(walletPath);
115
+ return { walletPath, address: keypair.publicKey.toBase58() };
116
+ }
117
+ function loadKeypairFromPath(walletPath) {
118
+ const bytes = JSON.parse(readFileSync(walletPath, "utf8"));
119
+ if (!Array.isArray(bytes) || bytes.some((v) => typeof v !== "number")) {
120
+ throw new Error(`Invalid wallet file format: ${walletPath}`);
121
+ }
122
+ return Keypair.fromSecretKey(Uint8Array.from(bytes));
123
+ }
124
+ function getRpcUrl(rpcUrl) {
125
+ return rpcUrl || process.env.SOLANA_RPC_URL || clusterApiUrl("mainnet-beta");
126
+ }
127
+ function ok(data) {
128
+ return {
129
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
130
+ };
131
+ }
132
+ const walletCreateSchema = z.object({
133
+ name: z.string().min(1).default("agent-wallet"),
134
+ walletDir: z.string().min(1).optional(),
135
+ });
136
+ const walletPathSchema = z.object({
137
+ walletPath: z.string().min(1),
138
+ });
139
+ const walletBalanceSchema = walletPathSchema.extend({
140
+ rpcUrl: z.string().url().optional(),
141
+ });
142
+ const marketSnapshotSchema = z.object({
143
+ mint: z.string().min(1),
144
+ mode: z.enum(["price", "metadata", "risk", "first-buyers", "aggregated"]).default("aggregated"),
145
+ });
146
+ const marketStreamSampleSchema = z.object({
147
+ dex: z.enum(["pumpamm", "pumpfun", "launchlab", "dlmm", "damm2", "damm1", "dbc"]),
148
+ durationMs: z.number().int().min(500).max(60_000).default(10_000),
149
+ maxEvents: z.number().int().min(1).max(250).default(20),
150
+ });
151
+ const swapBuildSchema = z.object({
152
+ walletPath: z.string().min(1).optional(),
153
+ userPublicKey: z.string().min(1).optional(),
154
+ inputMint: z.string().min(1),
155
+ outputMint: z.string().min(1),
156
+ amount: z.union([z.number().int().positive(), z.string().min(1)]),
157
+ slippageBps: z.number().int().min(1).max(5000).optional(),
158
+ swapType: z.string().min(1).optional(),
159
+ feeWallet: z.string().min(1).optional(),
160
+ feeBps: z.number().int().min(0).max(10_000).optional(),
161
+ feePercent: z.number().min(0).max(100).optional(),
162
+ });
163
+ const swapSignBroadcastSchema = z.object({
164
+ walletPath: z.string().min(1),
165
+ transactionBase64: z.string().min(1),
166
+ });
167
+ const tools = [
168
+ {
169
+ name: "system_check_prereqs",
170
+ description: "Check whether required local binaries are installed (currently solana-keygen) and return install commands.",
171
+ inputSchema: {
172
+ type: "object",
173
+ properties: {},
174
+ },
175
+ },
176
+ {
177
+ name: "wallet_create_local",
178
+ description: "Create a local Solana wallet using solana-keygen and return path + public address.",
179
+ inputSchema: {
180
+ type: "object",
181
+ properties: {
182
+ name: { type: "string", description: "Wallet name, used as <name>.json" },
183
+ walletDir: { type: "string", description: "Optional wallet directory path" },
184
+ },
185
+ },
186
+ },
187
+ {
188
+ name: "wallet_get_address",
189
+ description: "Get the public address for a local Solana wallet file.",
190
+ inputSchema: {
191
+ type: "object",
192
+ required: ["walletPath"],
193
+ properties: {
194
+ walletPath: { type: "string" },
195
+ },
196
+ },
197
+ },
198
+ {
199
+ name: "wallet_get_balance",
200
+ description: "Get SOL balance for a local wallet address from a Solana RPC endpoint.",
201
+ inputSchema: {
202
+ type: "object",
203
+ required: ["walletPath"],
204
+ properties: {
205
+ walletPath: { type: "string" },
206
+ rpcUrl: { type: "string" },
207
+ },
208
+ },
209
+ },
210
+ {
211
+ name: "market_get_snapshot",
212
+ description: "Fetch token market snapshot via Dritan SDK (price/metadata/risk/first-buyers/aggregated).",
213
+ inputSchema: {
214
+ type: "object",
215
+ required: ["mint"],
216
+ properties: {
217
+ mint: { type: "string" },
218
+ mode: {
219
+ type: "string",
220
+ enum: ["price", "metadata", "risk", "first-buyers", "aggregated"],
221
+ },
222
+ },
223
+ },
224
+ },
225
+ {
226
+ name: "market_stream_sample",
227
+ description: "Open a DEX websocket stream and collect events for a short duration.",
228
+ inputSchema: {
229
+ type: "object",
230
+ required: ["dex"],
231
+ properties: {
232
+ dex: {
233
+ type: "string",
234
+ enum: ["pumpamm", "pumpfun", "launchlab", "dlmm", "damm2", "damm1", "dbc"],
235
+ },
236
+ durationMs: { type: "number" },
237
+ maxEvents: { type: "number" },
238
+ },
239
+ },
240
+ },
241
+ {
242
+ name: "swap_build",
243
+ description: "Build an unsigned swap transaction with Dritan.",
244
+ inputSchema: {
245
+ type: "object",
246
+ required: ["inputMint", "outputMint", "amount"],
247
+ properties: {
248
+ walletPath: { type: "string" },
249
+ userPublicKey: { type: "string" },
250
+ inputMint: { type: "string" },
251
+ outputMint: { type: "string" },
252
+ amount: { anyOf: [{ type: "number" }, { type: "string" }] },
253
+ slippageBps: { type: "number" },
254
+ swapType: { type: "string" },
255
+ feeWallet: { type: "string" },
256
+ feeBps: { type: "number" },
257
+ feePercent: { type: "number" },
258
+ },
259
+ },
260
+ },
261
+ {
262
+ name: "swap_sign_and_broadcast",
263
+ description: "Sign a base64 swap transaction with a local wallet and broadcast through Dritan.",
264
+ inputSchema: {
265
+ type: "object",
266
+ required: ["walletPath", "transactionBase64"],
267
+ properties: {
268
+ walletPath: { type: "string" },
269
+ transactionBase64: { type: "string" },
270
+ },
271
+ },
272
+ },
273
+ {
274
+ name: "swap_build_sign_and_broadcast",
275
+ description: "Convenience tool: build swap, sign locally with wallet, and broadcast in one call.",
276
+ inputSchema: {
277
+ type: "object",
278
+ required: ["walletPath", "inputMint", "outputMint", "amount"],
279
+ properties: {
280
+ walletPath: { type: "string" },
281
+ inputMint: { type: "string" },
282
+ outputMint: { type: "string" },
283
+ amount: { anyOf: [{ type: "number" }, { type: "string" }] },
284
+ slippageBps: { type: "number" },
285
+ swapType: { type: "string" },
286
+ feeWallet: { type: "string" },
287
+ feeBps: { type: "number" },
288
+ feePercent: { type: "number" },
289
+ },
290
+ },
291
+ },
292
+ ];
293
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
294
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
295
+ try {
296
+ const args = request.params.arguments ?? {};
297
+ switch (request.params.name) {
298
+ case "system_check_prereqs": {
299
+ const solanaCli = checkSolanaCli();
300
+ return ok({
301
+ ready: solanaCli.ok,
302
+ checks: [solanaCli],
303
+ nextAction: solanaCli.ok
304
+ ? "Environment ready."
305
+ : "Install Solana CLI using installHint, then retry wallet_create_local.",
306
+ });
307
+ }
308
+ case "wallet_create_local": {
309
+ const input = walletCreateSchema.parse(args);
310
+ const walletPath = toWalletPath(input.name, input.walletDir);
311
+ const created = createLocalWalletFile(walletPath);
312
+ return ok(created);
313
+ }
314
+ case "wallet_get_address": {
315
+ const input = walletPathSchema.parse(args);
316
+ const keypair = loadKeypairFromPath(resolve(input.walletPath));
317
+ return ok({ walletPath: resolve(input.walletPath), address: keypair.publicKey.toBase58() });
318
+ }
319
+ case "wallet_get_balance": {
320
+ const input = walletBalanceSchema.parse(args);
321
+ const keypair = loadKeypairFromPath(resolve(input.walletPath));
322
+ const rpcUrl = getRpcUrl(input.rpcUrl);
323
+ const conn = new Connection(rpcUrl, "confirmed");
324
+ const lamports = await conn.getBalance(keypair.publicKey, "confirmed");
325
+ return ok({
326
+ walletPath: resolve(input.walletPath),
327
+ address: keypair.publicKey.toBase58(),
328
+ rpcUrl,
329
+ lamports,
330
+ sol: lamports / 1_000_000_000,
331
+ });
332
+ }
333
+ case "market_get_snapshot": {
334
+ const input = marketSnapshotSchema.parse(args);
335
+ const client = getDritanClient();
336
+ if (input.mode === "price")
337
+ return ok(await client.getTokenPrice(input.mint));
338
+ if (input.mode === "metadata")
339
+ return ok(await client.getTokenMetadata(input.mint));
340
+ if (input.mode === "risk")
341
+ return ok(await client.getTokenRisk(input.mint));
342
+ if (input.mode === "first-buyers")
343
+ return ok(await client.getFirstBuyers(input.mint));
344
+ return ok(await client.getTokenAggregated(input.mint));
345
+ }
346
+ case "market_stream_sample": {
347
+ const input = marketStreamSampleSchema.parse(args);
348
+ const client = getDritanClient();
349
+ const events = [];
350
+ let opened = false;
351
+ let closed = false;
352
+ const done = new Promise((resolvePromise) => {
353
+ const handle = client.streamDex(input.dex, {
354
+ onOpen: () => {
355
+ opened = true;
356
+ },
357
+ onClose: () => {
358
+ closed = true;
359
+ resolvePromise();
360
+ },
361
+ onMessage: (event) => {
362
+ events.push(event);
363
+ if (events.length >= input.maxEvents) {
364
+ handle.close();
365
+ }
366
+ },
367
+ });
368
+ setTimeout(() => {
369
+ if (!closed) {
370
+ handle.close();
371
+ }
372
+ }, input.durationMs);
373
+ });
374
+ await done;
375
+ return ok({ dex: input.dex, opened, closed, eventsCaptured: events.length, sample: events });
376
+ }
377
+ case "swap_build": {
378
+ const input = swapBuildSchema.parse(args);
379
+ const client = getDritanClient();
380
+ const resolvedUserPublicKey = input.userPublicKey
381
+ ? input.userPublicKey
382
+ : input.walletPath
383
+ ? loadKeypairFromPath(resolve(input.walletPath)).publicKey.toBase58()
384
+ : null;
385
+ if (!resolvedUserPublicKey) {
386
+ throw new Error("Provide either userPublicKey or walletPath");
387
+ }
388
+ const body = {
389
+ userPublicKey: resolvedUserPublicKey,
390
+ inputMint: input.inputMint,
391
+ outputMint: input.outputMint,
392
+ amount: input.amount,
393
+ slippageBps: input.slippageBps,
394
+ swapType: input.swapType,
395
+ feeWallet: input.feeWallet,
396
+ feeBps: input.feeBps,
397
+ feePercent: input.feePercent,
398
+ };
399
+ return ok(await client.buildSwap(body));
400
+ }
401
+ case "swap_sign_and_broadcast": {
402
+ const input = swapSignBroadcastSchema.parse(args);
403
+ const keypair = loadKeypairFromPath(resolve(input.walletPath));
404
+ const tx = VersionedTransaction.deserialize(Buffer.from(input.transactionBase64, "base64"));
405
+ tx.sign([keypair]);
406
+ const signedTransactionBase64 = Buffer.from(tx.serialize()).toString("base64");
407
+ const client = getDritanClient();
408
+ const result = await client.broadcastSwap(signedTransactionBase64);
409
+ return ok({
410
+ signature: result.signature,
411
+ signer: keypair.publicKey.toBase58(),
412
+ });
413
+ }
414
+ case "swap_build_sign_and_broadcast": {
415
+ const input = swapBuildSchema.extend({ walletPath: z.string().min(1) }).parse(args);
416
+ const walletPath = resolve(input.walletPath);
417
+ const keypair = loadKeypairFromPath(walletPath);
418
+ const client = getDritanClient();
419
+ const built = await client.buildSwap({
420
+ userPublicKey: keypair.publicKey.toBase58(),
421
+ inputMint: input.inputMint,
422
+ outputMint: input.outputMint,
423
+ amount: input.amount,
424
+ slippageBps: input.slippageBps,
425
+ swapType: input.swapType,
426
+ feeWallet: input.feeWallet,
427
+ feeBps: input.feeBps,
428
+ feePercent: input.feePercent,
429
+ });
430
+ const tx = VersionedTransaction.deserialize(Buffer.from(built.transactionBase64, "base64"));
431
+ tx.sign([keypair]);
432
+ const signedTransactionBase64 = Buffer.from(tx.serialize()).toString("base64");
433
+ const sent = await client.broadcastSwap(signedTransactionBase64);
434
+ return ok({
435
+ signer: keypair.publicKey.toBase58(),
436
+ fees: built.fees,
437
+ quote: built.quote,
438
+ signature: sent.signature,
439
+ });
440
+ }
441
+ default:
442
+ throw new Error(`Unknown tool: ${request.params.name}`);
443
+ }
444
+ }
445
+ catch (error) {
446
+ const message = error instanceof Error ? error.message : String(error);
447
+ return {
448
+ isError: true,
449
+ content: [{ type: "text", text: message }],
450
+ };
451
+ }
452
+ });
453
+ async function main() {
454
+ const transport = new StdioServerTransport();
455
+ await server.connect(transport);
456
+ }
457
+ void main();
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@dritan/mcp",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "description": "MCP server for Dritan SDK market data and local Solana wallet swap execution",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dritan/dritan"
10
+ },
11
+ "homepage": "https://dritan.dev",
12
+ "bugs": {
13
+ "url": "https://dritan.dev"
14
+ },
15
+ "main": "dist/index.js",
16
+ "bin": {
17
+ "dritan-mcp": "dist/index.js"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md",
22
+ ".env.example"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsc -p tsconfig.json",
26
+ "dev": "tsx src/index.ts",
27
+ "start": "node dist/index.js",
28
+ "lint": "tsc -p tsconfig.json --noEmit"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "dependencies": {
34
+ "@modelcontextprotocol/sdk": "^1.17.4",
35
+ "@solana/web3.js": "^1.98.4",
36
+ "dritan-sdk": "^0.1.1",
37
+ "zod": "^3.24.1"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^22.14.0",
41
+ "tsx": "^4.19.2",
42
+ "typescript": "^5.8.2"
43
+ }
44
+ }