@dritan/mcp 0.8.0 → 0.10.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 +7 -5
- package/dist/index.js +117 -19
- package/package.json +1 -1
package/.env.example
CHANGED
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ npm run build && npm start
|
|
|
41
41
|
- `system_check_prereqs`
|
|
42
42
|
- `auth_status`
|
|
43
43
|
- `auth_set_api_key`
|
|
44
|
+
- `auth_clear_api_key`
|
|
44
45
|
- `wallet_create_local`
|
|
45
46
|
- `wallet_get_address`
|
|
46
47
|
- `wallet_get_balance`
|
|
@@ -82,12 +83,13 @@ npm run build && npm start
|
|
|
82
83
|
- Wallets default to the current working directory (`process.cwd()`).
|
|
83
84
|
- Private keys never leave local files; only public address/signature are returned.
|
|
84
85
|
- `swap_sign_and_broadcast` signs locally, then broadcasts via Dritan.
|
|
85
|
-
- `auth_set_api_key` activates a key for the running MCP process without restart.
|
|
86
|
+
- `auth_set_api_key` activates a key for the running MCP process without restart and persists it to a local auth store.
|
|
86
87
|
- `auth_set_api_key` and successful `x402_create_api_key` responses include a capability summary so agents can immediately guide users to next actions.
|
|
87
|
-
- Agent onboarding without `
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
-
|
|
88
|
+
- Agent onboarding without an active API key should use x402-first flow (`wallet_create_local` in current directory -> share wallet + backup file path -> user chooses SOL amount and funds agent wallet -> if no key exists use `x402_create_api_key_quote` -> `wallet_transfer_sol` -> `x402_create_api_key`).
|
|
89
|
+
- Free key at `https://dritan.dev` is fallback only if the user declines funding or x402 cannot proceed.
|
|
90
|
+
- `x402_create_api_key` auto-activates returned keys and persists them locally for restart recovery.
|
|
91
|
+
- Default auth store path is `.dritan-mcp/auth.json` under current working directory; override with `DRITAN_MCP_AUTH_FILE`.
|
|
92
|
+
- Use `auth_clear_api_key` to remove in-memory + persisted key state (and optionally clear process env key).
|
|
91
93
|
- `token_get_ohlcv_chart` returns a shareable chart URL plus a ready-to-send markdown image snippet.
|
|
92
94
|
- `token_get_ohlcv_chart` supports `chartType: "line-volume" | "candlestick"` (default is `candlestick`).
|
|
93
95
|
- `ths_get_top_wallets` returns a paginated leaderboard of THS-ranked wallets (`page`, `limit`) for smart-wallet discovery workflows.
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { mkdirSync, readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { mkdirSync, readFileSync, existsSync, writeFileSync, rmSync } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -9,6 +9,7 @@ import { DritanClient, MeteoraThsClient, } from "dritan-sdk";
|
|
|
9
9
|
import { Connection, Keypair, PublicKey, SystemProgram, Transaction, VersionedTransaction, clusterApiUrl, } from "@solana/web3.js";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
const DEFAULT_WALLET_DIR = process.cwd();
|
|
12
|
+
const DEFAULT_API_KEY_STORE_PATH = resolve(process.cwd(), ".dritan-mcp", "auth.json");
|
|
12
13
|
const LAMPORTS_PER_SOL = 1_000_000_000;
|
|
13
14
|
function normalizeApiKey(value) {
|
|
14
15
|
const trimmed = value?.trim();
|
|
@@ -21,8 +22,57 @@ function apiKeyPreview(apiKey) {
|
|
|
21
22
|
return `${apiKey.slice(0, 4)}...`;
|
|
22
23
|
return `${apiKey.slice(0, 8)}...${apiKey.slice(-4)}`;
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
function getApiKeyStorePath() {
|
|
26
|
+
const configured = process.env.DRITAN_MCP_AUTH_FILE?.trim();
|
|
27
|
+
return configured ? resolve(configured) : DEFAULT_API_KEY_STORE_PATH;
|
|
28
|
+
}
|
|
29
|
+
const API_KEY_STORE_PATH = getApiKeyStorePath();
|
|
30
|
+
function loadPersistedApiKey() {
|
|
31
|
+
if (!existsSync(API_KEY_STORE_PATH))
|
|
32
|
+
return null;
|
|
33
|
+
try {
|
|
34
|
+
const raw = readFileSync(API_KEY_STORE_PATH, "utf8");
|
|
35
|
+
const parsed = JSON.parse(raw);
|
|
36
|
+
return normalizeApiKey(parsed.apiKey);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.warn(`[dritan-mcp] Failed to read persisted API key from ${API_KEY_STORE_PATH}: ${error instanceof Error ? error.message : String(error)}`);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function persistApiKey(apiKey, source) {
|
|
44
|
+
const payload = {
|
|
45
|
+
apiKey,
|
|
46
|
+
source,
|
|
47
|
+
updatedAt: new Date().toISOString(),
|
|
48
|
+
};
|
|
49
|
+
try {
|
|
50
|
+
mkdirSync(dirname(API_KEY_STORE_PATH), { recursive: true, mode: 0o700 });
|
|
51
|
+
writeFileSync(API_KEY_STORE_PATH, JSON.stringify(payload, null, 2), { encoding: "utf8", mode: 0o600 });
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.warn(`[dritan-mcp] Failed to persist API key to ${API_KEY_STORE_PATH}: ${error instanceof Error ? error.message : String(error)}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function clearPersistedApiKey() {
|
|
58
|
+
if (!existsSync(API_KEY_STORE_PATH))
|
|
59
|
+
return false;
|
|
60
|
+
try {
|
|
61
|
+
rmSync(API_KEY_STORE_PATH, { force: true });
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.warn(`[dritan-mcp] Failed to remove persisted API key file ${API_KEY_STORE_PATH}: ${error instanceof Error ? error.message : String(error)}`);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const persistedStartupKey = loadPersistedApiKey();
|
|
70
|
+
const envStartupKey = normalizeApiKey(process.env.DRITAN_API_KEY);
|
|
71
|
+
let runtimeApiKey = persistedStartupKey ?? envStartupKey;
|
|
72
|
+
let runtimeApiKeySource = persistedStartupKey ? "persisted" : envStartupKey ? "env" : "none";
|
|
73
|
+
if (runtimeApiKey) {
|
|
74
|
+
process.env.DRITAN_API_KEY = runtimeApiKey;
|
|
75
|
+
}
|
|
26
76
|
function setRuntimeApiKey(apiKey, source) {
|
|
27
77
|
const normalized = normalizeApiKey(apiKey);
|
|
28
78
|
if (!normalized) {
|
|
@@ -30,12 +80,20 @@ function setRuntimeApiKey(apiKey, source) {
|
|
|
30
80
|
}
|
|
31
81
|
runtimeApiKey = normalized;
|
|
32
82
|
runtimeApiKeySource = source;
|
|
83
|
+
persistApiKey(normalized, source);
|
|
33
84
|
process.env.DRITAN_API_KEY = normalized;
|
|
34
85
|
return normalized;
|
|
35
86
|
}
|
|
36
87
|
function getActiveApiKey() {
|
|
37
88
|
if (runtimeApiKey)
|
|
38
89
|
return runtimeApiKey;
|
|
90
|
+
const fromPersisted = loadPersistedApiKey();
|
|
91
|
+
if (fromPersisted) {
|
|
92
|
+
runtimeApiKey = fromPersisted;
|
|
93
|
+
runtimeApiKeySource = "persisted";
|
|
94
|
+
process.env.DRITAN_API_KEY = fromPersisted;
|
|
95
|
+
return fromPersisted;
|
|
96
|
+
}
|
|
39
97
|
const fromEnv = normalizeApiKey(process.env.DRITAN_API_KEY);
|
|
40
98
|
if (fromEnv) {
|
|
41
99
|
runtimeApiKey = fromEnv;
|
|
@@ -66,13 +124,14 @@ const server = new Server({
|
|
|
66
124
|
tools: {},
|
|
67
125
|
},
|
|
68
126
|
instructions: [
|
|
69
|
-
"
|
|
70
|
-
"
|
|
127
|
+
"API-key onboarding policy when DRITAN_API_KEY is missing:",
|
|
128
|
+
"Default path is x402-first. Do not ask user for an API key first.",
|
|
129
|
+
"1) x402 pay-per-use key flow (default):",
|
|
71
130
|
" a) Create a local agent wallet with wallet_create_local (saved in current working directory by default).",
|
|
72
131
|
" b) Ask user how much SOL they want to allocate and provide both wallet address and backup file path.",
|
|
73
132
|
" c) If no active key exists, create quote -> pay from agent wallet -> claim key.",
|
|
74
|
-
"2)
|
|
75
|
-
|
|
133
|
+
"2) Fallback only: if user explicitly declines funding or x402 cannot proceed, then offer free key flow at https://dritan.dev.",
|
|
134
|
+
`After key is obtained, set it with auth_set_api_key (no restart needed); MCP persists it at ${API_KEY_STORE_PATH} for restart recovery.`,
|
|
76
135
|
"Suggested setup command:",
|
|
77
136
|
" claude mcp add dritan-mcp -e DRITAN_API_KEY=<your-key> -- npx @dritan/mcp@latest",
|
|
78
137
|
].join("\n"),
|
|
@@ -83,10 +142,11 @@ function getControlBaseUrl() {
|
|
|
83
142
|
function missingApiKeyError() {
|
|
84
143
|
return new Error([
|
|
85
144
|
"Missing DRITAN_API_KEY in environment.",
|
|
86
|
-
|
|
145
|
+
`No active key found in persisted auth store (${API_KEY_STORE_PATH}).`,
|
|
146
|
+
"Onboarding is x402-first by default: use wallet and x402 tools before asking user for an API key.",
|
|
87
147
|
"Paid flow order: create wallet in current directory -> tell user funding amount + backup file path -> if no key exists then create/claim x402 key.",
|
|
88
|
-
"
|
|
89
|
-
"You can activate a key immediately with auth_set_api_key without restarting MCP.",
|
|
148
|
+
"Fallback only if user declines funding or x402 is not possible: user can create a free key at https://dritan.dev and set DRITAN_API_KEY.",
|
|
149
|
+
"You can activate a key immediately with auth_set_api_key without restarting MCP; key is persisted locally for restart recovery.",
|
|
90
150
|
].join(" "));
|
|
91
151
|
}
|
|
92
152
|
const postAuthCapabilities = [
|
|
@@ -503,6 +563,9 @@ const walletCreateSchema = z.object({
|
|
|
503
563
|
const authSetApiKeySchema = z.object({
|
|
504
564
|
apiKey: z.string().min(8),
|
|
505
565
|
});
|
|
566
|
+
const authClearApiKeySchema = z.object({
|
|
567
|
+
clearEnv: z.boolean().optional(),
|
|
568
|
+
});
|
|
506
569
|
const walletPathSchema = z.object({
|
|
507
570
|
walletPath: z.string().min(1),
|
|
508
571
|
});
|
|
@@ -636,7 +699,7 @@ const tools = [
|
|
|
636
699
|
},
|
|
637
700
|
{
|
|
638
701
|
name: "auth_set_api_key",
|
|
639
|
-
description: "Set the active Dritan API key for this running MCP process
|
|
702
|
+
description: "Set the active Dritan API key for this running MCP process and persist it locally for restart recovery.",
|
|
640
703
|
inputSchema: {
|
|
641
704
|
type: "object",
|
|
642
705
|
required: ["apiKey"],
|
|
@@ -645,6 +708,16 @@ const tools = [
|
|
|
645
708
|
},
|
|
646
709
|
},
|
|
647
710
|
},
|
|
711
|
+
{
|
|
712
|
+
name: "auth_clear_api_key",
|
|
713
|
+
description: "Clear the active in-memory API key and delete the persisted auth file. Optionally clear DRITAN_API_KEY in this process env.",
|
|
714
|
+
inputSchema: {
|
|
715
|
+
type: "object",
|
|
716
|
+
properties: {
|
|
717
|
+
clearEnv: { type: "boolean", description: "Also remove DRITAN_API_KEY from this process environment." },
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
},
|
|
648
721
|
{
|
|
649
722
|
name: "wallet_create_local",
|
|
650
723
|
description: "Create a local Solana wallet using solana-keygen and return path + public address.",
|
|
@@ -1103,9 +1176,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1103
1176
|
const solanaCli = checkSolanaCli();
|
|
1104
1177
|
const activeApiKey = getActiveApiKey();
|
|
1105
1178
|
const apiKeySet = !!activeApiKey;
|
|
1179
|
+
const persistedApiKeyPresent = existsSync(API_KEY_STORE_PATH);
|
|
1106
1180
|
return ok({
|
|
1107
1181
|
ready: solanaCli.ok && apiKeySet,
|
|
1108
1182
|
readyForX402Onboarding: solanaCli.ok,
|
|
1183
|
+
apiKeyStorePath: API_KEY_STORE_PATH,
|
|
1184
|
+
persistedApiKeyPresent,
|
|
1109
1185
|
checks: [
|
|
1110
1186
|
solanaCli,
|
|
1111
1187
|
{
|
|
@@ -1115,11 +1191,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1115
1191
|
preview: apiKeyPreview(activeApiKey),
|
|
1116
1192
|
hint: apiKeySet
|
|
1117
1193
|
? "API key is configured."
|
|
1118
|
-
: "Missing
|
|
1194
|
+
: "Missing active API key. Start x402 flow first (wallet_create_local -> fund wallet -> quote/claim). Use free key only as fallback.",
|
|
1119
1195
|
},
|
|
1120
1196
|
],
|
|
1121
1197
|
nextAction: !apiKeySet
|
|
1122
|
-
? "
|
|
1198
|
+
? "Run x402-first onboarding now: wallet_create_local -> share wallet + backup file path -> ask funding amount -> x402_create_api_key_quote -> wallet_transfer_sol -> x402_create_api_key. Offer free key only if user declines or x402 cannot proceed."
|
|
1123
1199
|
: !solanaCli.ok
|
|
1124
1200
|
? "Install Solana CLI using installHint, then retry wallet_create_local."
|
|
1125
1201
|
: "Environment ready.",
|
|
@@ -1132,9 +1208,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1132
1208
|
source: runtimeApiKeySource,
|
|
1133
1209
|
preview: apiKeyPreview(activeApiKey),
|
|
1134
1210
|
controlBaseUrl: getControlBaseUrl(),
|
|
1211
|
+
apiKeyStorePath: API_KEY_STORE_PATH,
|
|
1212
|
+
persistedApiKeyPresent: existsSync(API_KEY_STORE_PATH),
|
|
1135
1213
|
onboardingOptions: [
|
|
1136
|
-
"
|
|
1137
|
-
"
|
|
1214
|
+
"Default (x402-first): create wallet in current directory -> share wallet + backup file path -> user funds wallet -> if no key exists, quote/transfer/claim key.",
|
|
1215
|
+
"Fallback only: if user declines funding or x402 cannot proceed, user can create key at https://dritan.dev and set it with auth_set_api_key.",
|
|
1138
1216
|
],
|
|
1139
1217
|
...(activeApiKey ? buildPostAuthGuidance() : {}),
|
|
1140
1218
|
});
|
|
@@ -1144,12 +1222,31 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1144
1222
|
const activated = setRuntimeApiKey(input.apiKey, "runtime");
|
|
1145
1223
|
return ok({
|
|
1146
1224
|
ok: true,
|
|
1147
|
-
message: "API key activated for this MCP session without restart.",
|
|
1225
|
+
message: "API key activated and persisted for this MCP session without restart.",
|
|
1148
1226
|
source: runtimeApiKeySource,
|
|
1149
1227
|
preview: apiKeyPreview(activated),
|
|
1228
|
+
apiKeyStorePath: API_KEY_STORE_PATH,
|
|
1150
1229
|
...buildPostAuthGuidance(),
|
|
1151
1230
|
});
|
|
1152
1231
|
}
|
|
1232
|
+
case "auth_clear_api_key": {
|
|
1233
|
+
const input = authClearApiKeySchema.parse(args);
|
|
1234
|
+
runtimeApiKey = null;
|
|
1235
|
+
runtimeApiKeySource = "none";
|
|
1236
|
+
const persistedApiKeyRemoved = clearPersistedApiKey();
|
|
1237
|
+
const envCleared = !!input.clearEnv;
|
|
1238
|
+
if (envCleared) {
|
|
1239
|
+
delete process.env.DRITAN_API_KEY;
|
|
1240
|
+
}
|
|
1241
|
+
return ok({
|
|
1242
|
+
ok: true,
|
|
1243
|
+
source: runtimeApiKeySource,
|
|
1244
|
+
persistedApiKeyRemoved,
|
|
1245
|
+
envCleared,
|
|
1246
|
+
apiKeyStorePath: API_KEY_STORE_PATH,
|
|
1247
|
+
message: "Active key cleared from memory and persisted store. If clearEnv=false and env still has DRITAN_API_KEY, restarting may load that env key.",
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1153
1250
|
case "dritan_health": {
|
|
1154
1251
|
return ok(await checkDritanHealth());
|
|
1155
1252
|
}
|
|
@@ -1231,8 +1328,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1231
1328
|
return ok({
|
|
1232
1329
|
...(pricing ?? {}),
|
|
1233
1330
|
onboardingOptions: [
|
|
1234
|
-
"
|
|
1235
|
-
"
|
|
1331
|
+
"Default (x402-first): create wallet in current directory -> share wallet + backup path -> user funds wallet -> if no key exists, quote/payment/claim.",
|
|
1332
|
+
"Fallback only: user gets API key at https://dritan.dev and provides it as DRITAN_API_KEY if x402 cannot proceed.",
|
|
1236
1333
|
],
|
|
1237
1334
|
});
|
|
1238
1335
|
}
|
|
@@ -1267,7 +1364,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1267
1364
|
activated: true,
|
|
1268
1365
|
source: runtimeApiKeySource,
|
|
1269
1366
|
preview: apiKeyPreview(activated),
|
|
1270
|
-
|
|
1367
|
+
apiKeyStorePath: API_KEY_STORE_PATH,
|
|
1368
|
+
message: "x402-created API key is active for this MCP session and persisted locally for restart recovery (no restart needed).",
|
|
1271
1369
|
};
|
|
1272
1370
|
Object.assign(payload, buildPostAuthGuidance());
|
|
1273
1371
|
}
|