dero-mcp-server 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/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # DERO MCP server
2
+
3
+ [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that exposes **read-only and analysis** calls against a DERO Stargate **daemon** JSON-RPC endpoint. Use it from **Claude Desktop**, **Cursor**, or any MCP client that launches a local process over stdio.
4
+
5
+ ## What it does
6
+
7
+ - Connects to `{DERO_DAEMON_URL}/json_rpc` (default `http://82.65.143.182:10102`).
8
+ - Registers one MCP tool per common daemon method (`DERO.GetInfo`, `DERO.GetHeight`, `DERO.GetSC`, etc.).
9
+ - Returns JSON results as MCP text content.
10
+
11
+ **Not included (by design in v0.1):** wallet RPC (`transfer`, `scinvoke`), `DERO.SendRawTransaction`, `DERO.SubmitBlock`. Those can move funds or consensus data; add them only with explicit user consent and a locked-down setup.
12
+
13
+ ## Requirements
14
+
15
+ - Node.js **18+**
16
+ - A reachable DERO daemon with RPC enabled (local node or your own remote URL).
17
+
18
+ ## Install & build
19
+
20
+ ```bash
21
+ cd dero-mcp-server
22
+ npm install
23
+ npm run build
24
+ ```
25
+
26
+ Run (same default RPC as below if `DERO_DAEMON_URL` is unset):
27
+
28
+ ```bash
29
+ node dist/index.js
30
+ ```
31
+
32
+ Or set an explicit URL (e.g. your local daemon):
33
+
34
+ ```bash
35
+ DERO_DAEMON_URL=http://127.0.0.1:10102 node dist/index.js
36
+ ```
37
+
38
+ The baked-in default is a **third-party** public RPC (`82.65.143.182:10102`) — prefer your own node when you run one.
39
+
40
+ Strip a trailing `/json_rpc` if you paste a full JSON-RPC URL — this server appends `/json_rpc`.
41
+
42
+ ## Claude Desktop
43
+
44
+ Add to `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`):
45
+
46
+ ```json
47
+ {
48
+ "mcpServers": {
49
+ "dero-daemon": {
50
+ "command": "node",
51
+ "args": ["/ABSOLUTE/PATH/TO/dero-mcp-server/dist/index.js"]
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ Optional: add `"env": { "DERO_DAEMON_URL": "http://127.0.0.1:10102" }` if you use a **local** daemon instead of the default public RPC.
58
+
59
+ Restart Claude Desktop.
60
+
61
+ ## Cursor
62
+
63
+ In **Cursor Settings → MCP**, add a server that runs the same `command` / `args` / `env` as above.
64
+
65
+ ## Environment
66
+
67
+ | Variable | Default | Description |
68
+ |----------|---------|-------------|
69
+ | `DERO_DAEMON_URL` | `http://82.65.143.182:10102` | Daemon **base** URL (no `/json_rpc` required). Set to `http://127.0.0.1:10102` for a local daemon. |
70
+
71
+ ## Roadmap
72
+
73
+ - Optional wallet-RPC tools behind `DERO_ENABLE_WALLET_RPC=1` + separate URL.
74
+ - Streamable HTTP transport for hosted MCP.
75
+ - Stricter typing / OpenAPI-derived tool schemas.
76
+
77
+ ## License
78
+
79
+ MIT
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { createDeroMcpServer } from './server.js';
4
+ /** Default public mainnet JSON-RPC (override with DERO_DAEMON_URL). */
5
+ const DEFAULT_DAEMON_BASE = 'http://82.65.143.182:10102';
6
+ function daemonUrlFromEnv() {
7
+ const fromEnv = process.env.DERO_DAEMON_URL?.trim();
8
+ if (fromEnv)
9
+ return fromEnv.replace(/\/json_rpc\/?$/, '');
10
+ return DEFAULT_DAEMON_BASE;
11
+ }
12
+ async function main() {
13
+ const base = daemonUrlFromEnv();
14
+ const server = createDeroMcpServer(base);
15
+ const transport = new StdioServerTransport();
16
+ process.stderr.write(`[dero-mcp-server] DERO_DAEMON_URL base: ${base} (JSON-RPC at ${base.replace(/\/$/, '')}/json_rpc)\n`);
17
+ await server.connect(transport);
18
+ }
19
+ main().catch((err) => {
20
+ process.stderr.write(`[dero-mcp-server] fatal: ${err instanceof Error ? err.message : String(err)}\n`);
21
+ process.exit(1);
22
+ });
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEjD,uEAAuE;AACvE,MAAM,mBAAmB,GAAG,4BAA4B,CAAA;AAExD,SAAS,gBAAgB;IACvB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,EAAE,CAAA;IACnD,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAA;IACzD,OAAO,mBAAmB,CAAA;AAC5B,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,gBAAgB,EAAE,CAAA;IAC/B,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAA;IAExC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAA;IAE5C,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,2CAA2C,IAAI,iBAAiB,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,cAAc,CACtG,CAAA;IAED,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;AACjC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACtG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA"}
package/dist/rpc.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ export type JsonRpcResponse<T = unknown> = {
2
+ jsonrpc: '2.0';
3
+ id: string | number;
4
+ result?: T;
5
+ error?: {
6
+ code: number;
7
+ message: string;
8
+ data?: unknown;
9
+ };
10
+ };
11
+ /**
12
+ * POST JSON-RPC 2.0 to a DERO daemon or wallet endpoint (…/json_rpc).
13
+ */
14
+ export declare function deroJsonRpc<T = unknown>(jsonRpcUrl: string, method: string, params?: unknown, options?: {
15
+ timeoutMs?: number;
16
+ }): Promise<T>;
17
+ export declare function jsonRpcEndpoint(baseUrl: string): string;
18
+ //# sourceMappingURL=rpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.d.ts","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,eAAe,CAAC,CAAC,GAAG,OAAO,IAAI;IACzC,OAAO,EAAE,KAAK,CAAA;IACd,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,CAAC,CAAA;IACV,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAC1D,CAAA;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,CAAC,GAAG,OAAO,EAC3C,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/B,OAAO,CAAC,CAAC,CAAC,CAqCZ;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGvD"}
package/dist/rpc.js ADDED
@@ -0,0 +1,47 @@
1
+ const DEFAULT_TIMEOUT_MS = 45_000;
2
+ /**
3
+ * POST JSON-RPC 2.0 to a DERO daemon or wallet endpoint (…/json_rpc).
4
+ */
5
+ export async function deroJsonRpc(jsonRpcUrl, method, params, options) {
6
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
7
+ const body = {
8
+ jsonrpc: '2.0',
9
+ id: 'dero-mcp',
10
+ method,
11
+ };
12
+ if (params !== undefined)
13
+ body.params = params;
14
+ const controller = new AbortController();
15
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
16
+ try {
17
+ const res = await fetch(jsonRpcUrl, {
18
+ method: 'POST',
19
+ headers: { 'content-type': 'application/json' },
20
+ body: JSON.stringify(body),
21
+ signal: controller.signal,
22
+ });
23
+ const text = await res.text();
24
+ if (!res.ok) {
25
+ throw new Error(`HTTP ${res.status}: ${text.slice(0, 500)}`);
26
+ }
27
+ let json;
28
+ try {
29
+ json = JSON.parse(text);
30
+ }
31
+ catch {
32
+ throw new Error(`Invalid JSON from node: ${text.slice(0, 200)}`);
33
+ }
34
+ if (json.error) {
35
+ throw new Error(`RPC error ${json.error.code}: ${json.error.message}${json.error.data != null ? ` ${JSON.stringify(json.error.data)}` : ''}`);
36
+ }
37
+ return json.result;
38
+ }
39
+ finally {
40
+ clearTimeout(timer);
41
+ }
42
+ }
43
+ export function jsonRpcEndpoint(baseUrl) {
44
+ const trimmed = baseUrl.replace(/\/$/, '');
45
+ return trimmed.endsWith('/json_rpc') ? trimmed : `${trimmed}/json_rpc`;
46
+ }
47
+ //# sourceMappingURL=rpc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.js","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"AAAA,MAAM,kBAAkB,GAAG,MAAM,CAAA;AASjC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,UAAkB,EAClB,MAAc,EACd,MAAgB,EAChB,OAAgC;IAEhC,MAAM,SAAS,GAAG,OAAO,EAAE,SAAS,IAAI,kBAAkB,CAAA;IAC1D,MAAM,IAAI,GAA4B;QACpC,OAAO,EAAE,KAAK;QACd,EAAE,EAAE,UAAU;QACd,MAAM;KACP,CAAA;IACD,IAAI,MAAM,KAAK,SAAS;QAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;IAE9C,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;IACxC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAA;IAC7D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,UAAU,EAAE;YAClC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;YAC1B,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAA;QACF,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;QAC7B,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAA;QAC9D,CAAC;QACD,IAAI,IAAwB,CAAA;QAC5B,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAuB,CAAA;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,2BAA2B,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAA;QAClE,CAAC;QACD,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACb,aAAa,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAC7H,CAAA;QACH,CAAC;QACD,OAAO,IAAI,CAAC,MAAW,CAAA;IACzB,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAA;IACrB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;IAC1C,OAAO,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,WAAW,CAAA;AACxE,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function createDeroMcpServer(daemonBaseUrl: string): McpServer;
3
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAqBnE,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,CAuSpE"}
package/dist/server.js ADDED
@@ -0,0 +1,222 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { deroJsonRpc, jsonRpcEndpoint } from './rpc.js';
4
+ const scRpcArgSchema = z.object({
5
+ name: z.string(),
6
+ datatype: z.enum(['S', 'U', 'H']),
7
+ value: z.union([z.string(), z.number()]),
8
+ });
9
+ function toolText(data) {
10
+ return {
11
+ content: [
12
+ {
13
+ type: 'text',
14
+ text: typeof data === 'string' ? data : JSON.stringify(data, null, 2),
15
+ },
16
+ ],
17
+ };
18
+ }
19
+ export function createDeroMcpServer(daemonBaseUrl) {
20
+ const endpoint = jsonRpcEndpoint(daemonBaseUrl);
21
+ const rpc = async (method, params) => deroJsonRpc(endpoint, method, params);
22
+ const server = new McpServer({
23
+ name: 'dero-daemon-mcp',
24
+ version: '0.1.0',
25
+ });
26
+ server.registerTool('dero_daemon_ping', {
27
+ description: 'DERO daemon connectivity check. Calls DERO.Ping. No parameters.',
28
+ }, async () => toolText(await rpc('DERO.Ping')));
29
+ server.registerTool('dero_daemon_echo', {
30
+ description: 'Echo strings through the daemon (DERO.Echo).',
31
+ inputSchema: {
32
+ words: z.array(z.string()).describe('Strings to echo back'),
33
+ },
34
+ }, async ({ words }) => toolText(await rpc('DERO.Echo', words)));
35
+ server.registerTool('dero_get_info', {
36
+ description: 'Get daemon / chain info: height, difficulty, version, mempool size, etc. (DERO.GetInfo).',
37
+ }, async () => toolText(await rpc('DERO.GetInfo')));
38
+ server.registerTool('dero_get_height', {
39
+ description: 'Get top block height and stable/topo heights (DERO.GetHeight).',
40
+ }, async () => toolText(await rpc('DERO.GetHeight')));
41
+ server.registerTool('dero_get_block_count', {
42
+ description: 'Total block count (DERO.GetBlockCount).',
43
+ }, async () => toolText(await rpc('DERO.GetBlockCount')));
44
+ server.registerTool('dero_get_last_block_header', {
45
+ description: 'Header of the tip block (DERO.GetLastBlockHeader).',
46
+ }, async () => toolText(await rpc('DERO.GetLastBlockHeader')));
47
+ server.registerTool('dero_get_block', {
48
+ description: 'Fetch a full block by height or hash (DERO.GetBlock). Provide one of hash or height.',
49
+ inputSchema: {
50
+ hash: z
51
+ .string()
52
+ .optional()
53
+ .describe('64-char hex block hash'),
54
+ height: z
55
+ .number()
56
+ .int()
57
+ .nonnegative()
58
+ .optional()
59
+ .describe('Block height'),
60
+ },
61
+ }, async (args) => {
62
+ if (!args.hash && args.height === undefined) {
63
+ throw new Error('Provide either hash or height');
64
+ }
65
+ const params = {};
66
+ if (args.hash)
67
+ params.hash = args.hash;
68
+ if (args.height !== undefined)
69
+ params.height = args.height;
70
+ return toolText(await rpc('DERO.GetBlock', params));
71
+ });
72
+ server.registerTool('dero_get_block_header_by_topo_height', {
73
+ description: 'Block header by topological height (DERO.GetBlockHeaderByTopoHeight).',
74
+ inputSchema: {
75
+ topoheight: z
76
+ .number()
77
+ .int()
78
+ .nonnegative()
79
+ .describe('Topological height'),
80
+ },
81
+ }, async ({ topoheight }) => toolText(await rpc('DERO.GetBlockHeaderByTopoHeight', { topoheight })));
82
+ server.registerTool('dero_get_block_header_by_hash', {
83
+ description: 'Block header by hash (DERO.GetBlockHeaderByHash).',
84
+ inputSchema: {
85
+ hash: z.string().describe('Block top hash (hex)'),
86
+ },
87
+ }, async ({ hash }) => toolText(await rpc('DERO.GetBlockHeaderByHash', { hash })));
88
+ server.registerTool('dero_get_tx_pool', {
89
+ description: 'Pending mempool transaction hashes (DERO.GetTxPool).',
90
+ }, async () => toolText(await rpc('DERO.GetTxPool')));
91
+ server.registerTool('dero_get_random_address', {
92
+ description: 'Random registered addresses from chain (for ring construction); optional asset scid (DERO.GetRandomAddress).',
93
+ inputSchema: {
94
+ scid: z
95
+ .string()
96
+ .optional()
97
+ .describe('Optional asset smart-contract id (hex)'),
98
+ },
99
+ }, async (args) => toolText(await rpc('DERO.GetRandomAddress', args.scid != null ? { scid: args.scid } : undefined)));
100
+ server.registerTool('dero_get_transaction', {
101
+ description: 'Fetch transactions by tx hashes (DERO.GetTransaction).',
102
+ inputSchema: {
103
+ txs_hashes: z
104
+ .array(z.string())
105
+ .min(1)
106
+ .describe('List of transaction hashes (hex)'),
107
+ decode_as_json: z
108
+ .number()
109
+ .int()
110
+ .optional()
111
+ .describe('Optional: decode each tx as JSON when non-zero'),
112
+ },
113
+ }, async ({ txs_hashes, decode_as_json }) => {
114
+ const params = { txs_hashes };
115
+ if (decode_as_json !== undefined)
116
+ params.decode_as_json = decode_as_json;
117
+ return toolText(await rpc('DERO.GetTransaction', params));
118
+ });
119
+ server.registerTool('dero_get_encrypted_balance', {
120
+ description: 'Encrypted balance blob for an address at a topo height (DERO.GetEncryptedBalance). Not cleartext balance.',
121
+ inputSchema: {
122
+ address: z.string().describe('DERO address (deto1…)'),
123
+ topoheight: z
124
+ .number()
125
+ .int()
126
+ .describe('Use -1 for latest chain tip'),
127
+ scid: z.string().optional().describe('Asset SCID hex; omit for native DERO'),
128
+ },
129
+ }, async ({ address, topoheight, scid }) => {
130
+ const params = { address, topoheight };
131
+ if (scid)
132
+ params.scid = scid;
133
+ return toolText(await rpc('DERO.GetEncryptedBalance', params));
134
+ });
135
+ server.registerTool('dero_get_sc', {
136
+ description: 'Read smart contract code and/or variables by SCID (DERO.GetSC).',
137
+ inputSchema: {
138
+ scid: z.string().describe('64-char hex Smart Contract ID'),
139
+ code: z
140
+ .boolean()
141
+ .optional()
142
+ .describe('Include contract source (default true)'),
143
+ variables: z
144
+ .boolean()
145
+ .optional()
146
+ .describe('Include stored variables (default true)'),
147
+ topoheight: z
148
+ .number()
149
+ .int()
150
+ .optional()
151
+ .describe('Topo height; omit or use -1 for latest'),
152
+ },
153
+ }, async ({ scid, code, variables, topoheight }) => {
154
+ const params = {
155
+ scid,
156
+ code: code ?? true,
157
+ variables: variables ?? true,
158
+ };
159
+ if (topoheight !== undefined)
160
+ params.topoheight = topoheight;
161
+ return toolText(await rpc('DERO.GetSC', params));
162
+ });
163
+ server.registerTool('dero_get_gas_estimate', {
164
+ description: 'Estimate gas (compute + storage) for transfers, deploy, or SC call (DERO.GetGasEstimate).',
165
+ inputSchema: {
166
+ transfers: z
167
+ .array(z.record(z.unknown()))
168
+ .optional()
169
+ .describe('Optional transfer list'),
170
+ sc: z.string().optional().describe('SC source to deploy'),
171
+ sc_rpc: z
172
+ .array(scRpcArgSchema)
173
+ .optional()
174
+ .describe('SC invocation arguments (entrypoint, SC_ID, etc.)'),
175
+ signer: z
176
+ .string()
177
+ .optional()
178
+ .describe('Signer address used for estimation'),
179
+ },
180
+ }, async (args) => {
181
+ const params = {};
182
+ if (args.transfers)
183
+ params.transfers = args.transfers;
184
+ if (args.sc)
185
+ params.sc = args.sc;
186
+ if (args.sc_rpc)
187
+ params.sc_rpc = args.sc_rpc;
188
+ if (args.signer)
189
+ params.signer = args.signer;
190
+ return toolText(await rpc('DERO.GetGasEstimate', params));
191
+ });
192
+ server.registerTool('dero_name_to_address', {
193
+ description: 'Resolve a DERO on-chain name to address (DERO.NameToAddress).',
194
+ inputSchema: {
195
+ name: z.string().describe('Registered name'),
196
+ topoheight: z
197
+ .number()
198
+ .int()
199
+ .describe('Use -1 for latest'),
200
+ },
201
+ }, async ({ name, topoheight }) => toolText(await rpc('DERO.NameToAddress', { name, topoheight })));
202
+ server.registerTool('dero_get_block_template', {
203
+ description: 'Mining: get block template for a miner address (DERO.GetBlockTemplate).',
204
+ inputSchema: {
205
+ wallet_address: z.string().describe('Miner payout DERO address'),
206
+ block: z
207
+ .boolean()
208
+ .optional()
209
+ .describe('Include block blob'),
210
+ miner: z.string().optional().describe('Optional miner id / label'),
211
+ },
212
+ }, async ({ wallet_address, block, miner }) => {
213
+ const params = { wallet_address };
214
+ if (block !== undefined)
215
+ params.block = block;
216
+ if (miner)
217
+ params.miner = miner;
218
+ return toolText(await rpc('DERO.GetBlockTemplate', params));
219
+ });
220
+ return server;
221
+ }
222
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "dero-mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "Model Context Protocol (MCP) server exposing DERO Stargate daemon JSON-RPC to Claude and other MCP clients",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "engines": {
8
+ "node": ">=18"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "bin": {
12
+ "dero-mcp-server": "./dist/index.js"
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "start": "node dist/index.js",
17
+ "dev": "node --import tsx src/index.ts",
18
+ "prepack": "npm run build"
19
+ },
20
+ "dependencies": {
21
+ "@modelcontextprotocol/sdk": "^1.29.0",
22
+ "zod": "^3.25.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^22.15.0",
26
+ "tsx": "^4.20.0",
27
+ "typescript": "^5.8.0"
28
+ },
29
+ "keywords": [
30
+ "dero",
31
+ "mcp",
32
+ "model-context-protocol",
33
+ "blockchain",
34
+ "json-rpc"
35
+ ],
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/DHEBP/dero-mcp-server.git"
39
+ },
40
+ "homepage": "https://derod.org",
41
+ "bugs": {
42
+ "url": "https://github.com/DHEBP/dero-mcp-server/issues"
43
+ }
44
+ }
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
3
+ import { createDeroMcpServer } from './server.js'
4
+
5
+ /** Default public mainnet JSON-RPC (override with DERO_DAEMON_URL). */
6
+ const DEFAULT_DAEMON_BASE = 'http://82.65.143.182:10102'
7
+
8
+ function daemonUrlFromEnv(): string {
9
+ const fromEnv = process.env.DERO_DAEMON_URL?.trim()
10
+ if (fromEnv) return fromEnv.replace(/\/json_rpc\/?$/, '')
11
+ return DEFAULT_DAEMON_BASE
12
+ }
13
+
14
+ async function main() {
15
+ const base = daemonUrlFromEnv()
16
+ const server = createDeroMcpServer(base)
17
+
18
+ const transport = new StdioServerTransport()
19
+
20
+ process.stderr.write(
21
+ `[dero-mcp-server] DERO_DAEMON_URL base: ${base} (JSON-RPC at ${base.replace(/\/$/, '')}/json_rpc)\n`,
22
+ )
23
+
24
+ await server.connect(transport)
25
+ }
26
+
27
+ main().catch((err) => {
28
+ process.stderr.write(`[dero-mcp-server] fatal: ${err instanceof Error ? err.message : String(err)}\n`)
29
+ process.exit(1)
30
+ })
package/src/rpc.ts ADDED
@@ -0,0 +1,60 @@
1
+ const DEFAULT_TIMEOUT_MS = 45_000
2
+
3
+ export type JsonRpcResponse<T = unknown> = {
4
+ jsonrpc: '2.0'
5
+ id: string | number
6
+ result?: T
7
+ error?: { code: number; message: string; data?: unknown }
8
+ }
9
+
10
+ /**
11
+ * POST JSON-RPC 2.0 to a DERO daemon or wallet endpoint (…/json_rpc).
12
+ */
13
+ export async function deroJsonRpc<T = unknown>(
14
+ jsonRpcUrl: string,
15
+ method: string,
16
+ params?: unknown,
17
+ options?: { timeoutMs?: number },
18
+ ): Promise<T> {
19
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS
20
+ const body: Record<string, unknown> = {
21
+ jsonrpc: '2.0',
22
+ id: 'dero-mcp',
23
+ method,
24
+ }
25
+ if (params !== undefined) body.params = params
26
+
27
+ const controller = new AbortController()
28
+ const timer = setTimeout(() => controller.abort(), timeoutMs)
29
+ try {
30
+ const res = await fetch(jsonRpcUrl, {
31
+ method: 'POST',
32
+ headers: { 'content-type': 'application/json' },
33
+ body: JSON.stringify(body),
34
+ signal: controller.signal,
35
+ })
36
+ const text = await res.text()
37
+ if (!res.ok) {
38
+ throw new Error(`HTTP ${res.status}: ${text.slice(0, 500)}`)
39
+ }
40
+ let json: JsonRpcResponse<T>
41
+ try {
42
+ json = JSON.parse(text) as JsonRpcResponse<T>
43
+ } catch {
44
+ throw new Error(`Invalid JSON from node: ${text.slice(0, 200)}`)
45
+ }
46
+ if (json.error) {
47
+ throw new Error(
48
+ `RPC error ${json.error.code}: ${json.error.message}${json.error.data != null ? ` ${JSON.stringify(json.error.data)}` : ''}`,
49
+ )
50
+ }
51
+ return json.result as T
52
+ } finally {
53
+ clearTimeout(timer)
54
+ }
55
+ }
56
+
57
+ export function jsonRpcEndpoint(baseUrl: string): string {
58
+ const trimmed = baseUrl.replace(/\/$/, '')
59
+ return trimmed.endsWith('/json_rpc') ? trimmed : `${trimmed}/json_rpc`
60
+ }
package/src/server.ts ADDED
@@ -0,0 +1,317 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
+ import { z } from 'zod'
3
+ import { deroJsonRpc, jsonRpcEndpoint } from './rpc.js'
4
+
5
+ const scRpcArgSchema = z.object({
6
+ name: z.string(),
7
+ datatype: z.enum(['S', 'U', 'H']),
8
+ value: z.union([z.string(), z.number()]),
9
+ })
10
+
11
+ function toolText(data: unknown) {
12
+ return {
13
+ content: [
14
+ {
15
+ type: 'text' as const,
16
+ text: typeof data === 'string' ? data : JSON.stringify(data, null, 2),
17
+ },
18
+ ],
19
+ }
20
+ }
21
+
22
+ export function createDeroMcpServer(daemonBaseUrl: string): McpServer {
23
+ const endpoint = jsonRpcEndpoint(daemonBaseUrl)
24
+ const rpc = async <T>(method: string, params?: unknown) =>
25
+ deroJsonRpc<T>(endpoint, method, params)
26
+
27
+ const server = new McpServer({
28
+ name: 'dero-daemon-mcp',
29
+ version: '0.1.0',
30
+ })
31
+
32
+ server.registerTool(
33
+ 'dero_daemon_ping',
34
+ {
35
+ description:
36
+ 'DERO daemon connectivity check. Calls DERO.Ping. No parameters.',
37
+ },
38
+ async () => toolText(await rpc<string>('DERO.Ping')),
39
+ )
40
+
41
+ server.registerTool(
42
+ 'dero_daemon_echo',
43
+ {
44
+ description: 'Echo strings through the daemon (DERO.Echo).',
45
+ inputSchema: {
46
+ words: z.array(z.string()).describe('Strings to echo back'),
47
+ },
48
+ },
49
+ async ({ words }) => toolText(await rpc<string>('DERO.Echo', words)),
50
+ )
51
+
52
+ server.registerTool(
53
+ 'dero_get_info',
54
+ {
55
+ description:
56
+ 'Get daemon / chain info: height, difficulty, version, mempool size, etc. (DERO.GetInfo).',
57
+ },
58
+ async () => toolText(await rpc('DERO.GetInfo')),
59
+ )
60
+
61
+ server.registerTool(
62
+ 'dero_get_height',
63
+ {
64
+ description: 'Get top block height and stable/topo heights (DERO.GetHeight).',
65
+ },
66
+ async () => toolText(await rpc('DERO.GetHeight')),
67
+ )
68
+
69
+ server.registerTool(
70
+ 'dero_get_block_count',
71
+ {
72
+ description: 'Total block count (DERO.GetBlockCount).',
73
+ },
74
+ async () => toolText(await rpc('DERO.GetBlockCount')),
75
+ )
76
+
77
+ server.registerTool(
78
+ 'dero_get_last_block_header',
79
+ {
80
+ description: 'Header of the tip block (DERO.GetLastBlockHeader).',
81
+ },
82
+ async () => toolText(await rpc('DERO.GetLastBlockHeader')),
83
+ )
84
+
85
+ server.registerTool(
86
+ 'dero_get_block',
87
+ {
88
+ description: 'Fetch a full block by height or hash (DERO.GetBlock). Provide one of hash or height.',
89
+ inputSchema: {
90
+ hash: z
91
+ .string()
92
+ .optional()
93
+ .describe('64-char hex block hash'),
94
+ height: z
95
+ .number()
96
+ .int()
97
+ .nonnegative()
98
+ .optional()
99
+ .describe('Block height'),
100
+ },
101
+ },
102
+ async (args) => {
103
+ if (!args.hash && args.height === undefined) {
104
+ throw new Error('Provide either hash or height')
105
+ }
106
+ const params: Record<string, unknown> = {}
107
+ if (args.hash) params.hash = args.hash
108
+ if (args.height !== undefined) params.height = args.height
109
+ return toolText(await rpc('DERO.GetBlock', params))
110
+ },
111
+ )
112
+
113
+ server.registerTool(
114
+ 'dero_get_block_header_by_topo_height',
115
+ {
116
+ description: 'Block header by topological height (DERO.GetBlockHeaderByTopoHeight).',
117
+ inputSchema: {
118
+ topoheight: z
119
+ .number()
120
+ .int()
121
+ .nonnegative()
122
+ .describe('Topological height'),
123
+ },
124
+ },
125
+ async ({ topoheight }) =>
126
+ toolText(await rpc('DERO.GetBlockHeaderByTopoHeight', { topoheight })),
127
+ )
128
+
129
+ server.registerTool(
130
+ 'dero_get_block_header_by_hash',
131
+ {
132
+ description: 'Block header by hash (DERO.GetBlockHeaderByHash).',
133
+ inputSchema: {
134
+ hash: z.string().describe('Block top hash (hex)'),
135
+ },
136
+ },
137
+ async ({ hash }) =>
138
+ toolText(await rpc('DERO.GetBlockHeaderByHash', { hash })),
139
+ )
140
+
141
+ server.registerTool(
142
+ 'dero_get_tx_pool',
143
+ {
144
+ description: 'Pending mempool transaction hashes (DERO.GetTxPool).',
145
+ },
146
+ async () => toolText(await rpc('DERO.GetTxPool')),
147
+ )
148
+
149
+ server.registerTool(
150
+ 'dero_get_random_address',
151
+ {
152
+ description:
153
+ 'Random registered addresses from chain (for ring construction); optional asset scid (DERO.GetRandomAddress).',
154
+ inputSchema: {
155
+ scid: z
156
+ .string()
157
+ .optional()
158
+ .describe('Optional asset smart-contract id (hex)'),
159
+ },
160
+ },
161
+ async (args) =>
162
+ toolText(
163
+ await rpc(
164
+ 'DERO.GetRandomAddress',
165
+ args.scid != null ? { scid: args.scid } : undefined,
166
+ ),
167
+ ),
168
+ )
169
+
170
+ server.registerTool(
171
+ 'dero_get_transaction',
172
+ {
173
+ description: 'Fetch transactions by tx hashes (DERO.GetTransaction).',
174
+ inputSchema: {
175
+ txs_hashes: z
176
+ .array(z.string())
177
+ .min(1)
178
+ .describe('List of transaction hashes (hex)'),
179
+ decode_as_json: z
180
+ .number()
181
+ .int()
182
+ .optional()
183
+ .describe('Optional: decode each tx as JSON when non-zero'),
184
+ },
185
+ },
186
+ async ({ txs_hashes, decode_as_json }) => {
187
+ const params: Record<string, unknown> = { txs_hashes }
188
+ if (decode_as_json !== undefined) params.decode_as_json = decode_as_json
189
+ return toolText(await rpc('DERO.GetTransaction', params))
190
+ },
191
+ )
192
+
193
+ server.registerTool(
194
+ 'dero_get_encrypted_balance',
195
+ {
196
+ description:
197
+ 'Encrypted balance blob for an address at a topo height (DERO.GetEncryptedBalance). Not cleartext balance.',
198
+ inputSchema: {
199
+ address: z.string().describe('DERO address (deto1…)'),
200
+ topoheight: z
201
+ .number()
202
+ .int()
203
+ .describe('Use -1 for latest chain tip'),
204
+ scid: z.string().optional().describe('Asset SCID hex; omit for native DERO'),
205
+ },
206
+ },
207
+ async ({ address, topoheight, scid }) => {
208
+ const params: Record<string, unknown> = { address, topoheight }
209
+ if (scid) params.scid = scid
210
+ return toolText(await rpc('DERO.GetEncryptedBalance', params))
211
+ },
212
+ )
213
+
214
+ server.registerTool(
215
+ 'dero_get_sc',
216
+ {
217
+ description:
218
+ 'Read smart contract code and/or variables by SCID (DERO.GetSC).',
219
+ inputSchema: {
220
+ scid: z.string().describe('64-char hex Smart Contract ID'),
221
+ code: z
222
+ .boolean()
223
+ .optional()
224
+ .describe('Include contract source (default true)'),
225
+ variables: z
226
+ .boolean()
227
+ .optional()
228
+ .describe('Include stored variables (default true)'),
229
+ topoheight: z
230
+ .number()
231
+ .int()
232
+ .optional()
233
+ .describe('Topo height; omit or use -1 for latest'),
234
+ },
235
+ },
236
+ async ({ scid, code, variables, topoheight }) => {
237
+ const params: Record<string, unknown> = {
238
+ scid,
239
+ code: code ?? true,
240
+ variables: variables ?? true,
241
+ }
242
+ if (topoheight !== undefined) params.topoheight = topoheight
243
+ return toolText(await rpc('DERO.GetSC', params))
244
+ },
245
+ )
246
+
247
+ server.registerTool(
248
+ 'dero_get_gas_estimate',
249
+ {
250
+ description:
251
+ 'Estimate gas (compute + storage) for transfers, deploy, or SC call (DERO.GetGasEstimate).',
252
+ inputSchema: {
253
+ transfers: z
254
+ .array(z.record(z.unknown()))
255
+ .optional()
256
+ .describe('Optional transfer list'),
257
+ sc: z.string().optional().describe('SC source to deploy'),
258
+ sc_rpc: z
259
+ .array(scRpcArgSchema)
260
+ .optional()
261
+ .describe('SC invocation arguments (entrypoint, SC_ID, etc.)'),
262
+ signer: z
263
+ .string()
264
+ .optional()
265
+ .describe('Signer address used for estimation'),
266
+ },
267
+ },
268
+ async (args) => {
269
+ const params: Record<string, unknown> = {}
270
+ if (args.transfers) params.transfers = args.transfers
271
+ if (args.sc) params.sc = args.sc
272
+ if (args.sc_rpc) params.sc_rpc = args.sc_rpc
273
+ if (args.signer) params.signer = args.signer
274
+ return toolText(await rpc('DERO.GetGasEstimate', params))
275
+ },
276
+ )
277
+
278
+ server.registerTool(
279
+ 'dero_name_to_address',
280
+ {
281
+ description: 'Resolve a DERO on-chain name to address (DERO.NameToAddress).',
282
+ inputSchema: {
283
+ name: z.string().describe('Registered name'),
284
+ topoheight: z
285
+ .number()
286
+ .int()
287
+ .describe('Use -1 for latest'),
288
+ },
289
+ },
290
+ async ({ name, topoheight }) =>
291
+ toolText(await rpc('DERO.NameToAddress', { name, topoheight })),
292
+ )
293
+
294
+ server.registerTool(
295
+ 'dero_get_block_template',
296
+ {
297
+ description:
298
+ 'Mining: get block template for a miner address (DERO.GetBlockTemplate).',
299
+ inputSchema: {
300
+ wallet_address: z.string().describe('Miner payout DERO address'),
301
+ block: z
302
+ .boolean()
303
+ .optional()
304
+ .describe('Include block blob'),
305
+ miner: z.string().optional().describe('Optional miner id / label'),
306
+ },
307
+ },
308
+ async ({ wallet_address, block, miner }) => {
309
+ const params: Record<string, unknown> = { wallet_address }
310
+ if (block !== undefined) params.block = block
311
+ if (miner) params.miner = miner
312
+ return toolText(await rpc('DERO.GetBlockTemplate', params))
313
+ },
314
+ )
315
+
316
+ return server
317
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "declaration": true,
11
+ "declarationMap": true,
12
+ "sourceMap": true,
13
+ "esModuleInterop": true
14
+ },
15
+ "include": ["src/**/*.ts"]
16
+ }