dero-mcp-server 0.1.0 → 0.1.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.
@@ -0,0 +1,62 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, master]
6
+ pull_request:
7
+ branches: [main, master]
8
+
9
+ jobs:
10
+ build-and-test:
11
+ runs-on: ubuntu-latest
12
+
13
+ strategy:
14
+ matrix:
15
+ node-version: [18, 20, 22]
16
+
17
+ steps:
18
+ - name: Checkout
19
+ uses: actions/checkout@v4
20
+
21
+ - name: Setup Node.js ${{ matrix.node-version }}
22
+ uses: actions/setup-node@v4
23
+ with:
24
+ node-version: ${{ matrix.node-version }}
25
+ cache: 'npm'
26
+
27
+ - name: Install dependencies
28
+ run: npm ci
29
+
30
+ - name: Build
31
+ run: npm run build
32
+
33
+ - name: MCP smoke probes
34
+ run: npm run smoke:mcp
35
+ env:
36
+ DERO_DAEMON_URL: ${{ secrets.DERO_DAEMON_URL || '' }}
37
+
38
+ - name: Run flow tests
39
+ run: npm run test:flows
40
+ env:
41
+ # Uses default public RPC (82.65.143.182:10102)
42
+ # Override with repository secret if you have a dedicated test daemon
43
+ DERO_DAEMON_URL: ${{ secrets.DERO_DAEMON_URL || '' }}
44
+
45
+ typecheck:
46
+ runs-on: ubuntu-latest
47
+
48
+ steps:
49
+ - name: Checkout
50
+ uses: actions/checkout@v4
51
+
52
+ - name: Setup Node.js
53
+ uses: actions/setup-node@v4
54
+ with:
55
+ node-version: 20
56
+ cache: 'npm'
57
+
58
+ - name: Install dependencies
59
+ run: npm ci
60
+
61
+ - name: Typecheck
62
+ run: npx tsc --noEmit
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DHEBP
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,12 +1,17 @@
1
1
  # DERO MCP server
2
2
 
3
+ [![CI](https://github.com/DHEBP/dero-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/DHEBP/dero-mcp-server/actions/workflows/ci.yml)
4
+ [![dero-mcp-server MCP server](https://glama.ai/mcp/servers/DHEBP/dero-mcp-server/badges/card.svg)](https://glama.ai/mcp/servers/DHEBP/dero-mcp-server)
5
+
3
6
  [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
7
 
5
8
  ## What it does
6
9
 
7
10
  - Connects to `{DERO_DAEMON_URL}/json_rpc` (default `http://82.65.143.182:10102`).
8
11
  - Registers one MCP tool per common daemon method (`DERO.GetInfo`, `DERO.GetHeight`, `DERO.GetSC`, etc.).
12
+ - Exposes MCP resources and prompts for consistent investigation workflows.
9
13
  - Returns JSON results as MCP text content.
14
+ - Returns structured tool errors with `_meta.error` (`code`, `hint`, `retryable`) to help agents self-correct.
10
15
 
11
16
  **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
17
 
@@ -68,6 +73,61 @@ In **Cursor Settings → MCP**, add a server that runs the same `command` / `arg
68
73
  |----------|---------|-------------|
69
74
  | `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
75
 
76
+ ## Testing
77
+
78
+ ```bash
79
+ # Check daemon connectivity
80
+ npm run doctor
81
+
82
+ # MCP surface contract checks (tools/resources/prompts + error probe)
83
+ npm run smoke:mcp
84
+
85
+ # Run flow tests (10 RPC checks)
86
+ npm run test:flows
87
+
88
+ # Typecheck
89
+ npm run typecheck
90
+ ```
91
+
92
+ Flow tests run against the default public RPC. Set `DERO_DAEMON_URL` to test against your own daemon.
93
+
94
+ CI runs on every push and PR — see `.github/workflows/ci.yml`.
95
+
96
+ ## MCP Surface
97
+
98
+ - **Tools (17):** daemon read and analysis methods (`dero_get_info`, `dero_get_sc`, `dero_get_transaction`, etc.)
99
+ - **Resources (3):** `dero://mcp/server-info`, `dero://mcp/safety-boundary`, `dero://mcp/example-flows`
100
+ - **Prompts (3):** `network_health_check`, `inspect_smart_contract`, `trace_transaction`
101
+
102
+ ## Error Contract
103
+
104
+ When a tool call fails, the server returns a structured error payload in tool content:
105
+
106
+ ```json
107
+ {
108
+ "ok": false,
109
+ "tool": "dero_get_sc",
110
+ "_meta": {
111
+ "error": {
112
+ "code": "RPC_UNREACHABLE",
113
+ "hint": "Confirm daemon is running and reachable, then rerun `npm run doctor`.",
114
+ "retryable": true,
115
+ "raw": "fetch failed"
116
+ }
117
+ }
118
+ }
119
+ ```
120
+
121
+ Common `code` values:
122
+
123
+ - `INVALID_INPUT`
124
+ - `RPC_INVALID_PARAMS`
125
+ - `RPC_METHOD_NOT_FOUND`
126
+ - `RPC_HTTP_ERROR`
127
+ - `RPC_UNREACHABLE`
128
+ - `RPC_INVALID_RESPONSE`
129
+ - `TOOL_EXECUTION_ERROR`
130
+
71
131
  ## Roadmap
72
132
 
73
133
  - Optional wallet-RPC tools behind `DERO_ENABLE_WALLET_RPC=1` + separate URL.
@@ -1 +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"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAqKnE,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,CAsdpE"}
package/dist/server.js CHANGED
@@ -6,6 +6,42 @@ const scRpcArgSchema = z.object({
6
6
  datatype: z.enum(['S', 'U', 'H']),
7
7
  value: z.union([z.string(), z.number()]),
8
8
  });
9
+ const hex64Schema = z
10
+ .string()
11
+ .regex(/^[0-9a-fA-F]{64}$/, 'Expected 64-character hex string');
12
+ const deroAddressSchema = z
13
+ .string()
14
+ .regex(/^(dero1|deto1)[0-9a-z]+$/i, 'Expected DERO address starting with dero1 or deto1');
15
+ const NAME_REGISTRY_SCID = '0000000000000000000000000000000000000000000000000000000000000001';
16
+ const DERO_TOOL_NAMES = [
17
+ 'dero_daemon_ping',
18
+ 'dero_daemon_echo',
19
+ 'dero_get_info',
20
+ 'dero_get_height',
21
+ 'dero_get_block_count',
22
+ 'dero_get_last_block_header',
23
+ 'dero_get_block',
24
+ 'dero_get_block_header_by_topo_height',
25
+ 'dero_get_block_header_by_hash',
26
+ 'dero_get_tx_pool',
27
+ 'dero_get_random_address',
28
+ 'dero_get_transaction',
29
+ 'dero_get_encrypted_balance',
30
+ 'dero_get_sc',
31
+ 'dero_get_gas_estimate',
32
+ 'dero_name_to_address',
33
+ 'dero_get_block_template',
34
+ ];
35
+ const DERO_RESOURCE_URIS = [
36
+ 'dero://mcp/server-info',
37
+ 'dero://mcp/safety-boundary',
38
+ 'dero://mcp/example-flows',
39
+ ];
40
+ const DERO_PROMPT_NAMES = [
41
+ 'network_health_check',
42
+ 'inspect_smart_contract',
43
+ 'trace_transaction',
44
+ ];
9
45
  function toolText(data) {
10
46
  return {
11
47
  content: [
@@ -16,6 +52,87 @@ function toolText(data) {
16
52
  ],
17
53
  };
18
54
  }
55
+ function classifyToolError(error) {
56
+ const message = error instanceof Error ? error.message : String(error);
57
+ if (message.includes('Provide either hash or height')) {
58
+ return {
59
+ code: 'INVALID_INPUT',
60
+ hint: 'Pass exactly one of "hash" or "height".',
61
+ retryable: false,
62
+ };
63
+ }
64
+ if (message.includes('RPC error -32601')) {
65
+ return {
66
+ code: 'RPC_METHOD_NOT_FOUND',
67
+ hint: 'Your daemon may be outdated or not a Stargate endpoint. Verify DERO_DAEMON_URL.',
68
+ retryable: false,
69
+ };
70
+ }
71
+ if (message.includes('RPC error -32602')) {
72
+ return {
73
+ code: 'RPC_INVALID_PARAMS',
74
+ hint: 'Verify argument names and types for this tool.',
75
+ retryable: false,
76
+ };
77
+ }
78
+ const httpMatch = message.match(/HTTP (\d{3})/);
79
+ if (httpMatch) {
80
+ const status = Number(httpMatch[1]);
81
+ return {
82
+ code: 'RPC_HTTP_ERROR',
83
+ hint: status >= 500
84
+ ? 'Daemon is reachable but errored; retry after checking node health.'
85
+ : 'Check DERO_DAEMON_URL and ensure /json_rpc is accessible.',
86
+ retryable: status >= 500,
87
+ };
88
+ }
89
+ if (message.toLowerCase().includes('fetch failed') ||
90
+ message.toLowerCase().includes('network') ||
91
+ message.toLowerCase().includes('econnrefused') ||
92
+ message.toLowerCase().includes('aborted')) {
93
+ return {
94
+ code: 'RPC_UNREACHABLE',
95
+ hint: 'Confirm daemon is running and reachable, then rerun `npm run doctor`.',
96
+ retryable: true,
97
+ };
98
+ }
99
+ if (message.includes('Invalid JSON from node')) {
100
+ return {
101
+ code: 'RPC_INVALID_RESPONSE',
102
+ hint: 'Daemon returned malformed JSON. Check reverse proxies or node health.',
103
+ retryable: true,
104
+ };
105
+ }
106
+ return {
107
+ code: 'TOOL_EXECUTION_ERROR',
108
+ hint: 'Retry once, then inspect daemon logs and tool input values.',
109
+ retryable: false,
110
+ };
111
+ }
112
+ function toolError(tool, error) {
113
+ const structured = classifyToolError(error);
114
+ const raw = error instanceof Error ? error.message : String(error);
115
+ return toolText({
116
+ ok: false,
117
+ tool,
118
+ _meta: {
119
+ error: {
120
+ ...structured,
121
+ raw,
122
+ },
123
+ },
124
+ });
125
+ }
126
+ function withStructuredErrors(tool, handler) {
127
+ return async (args) => {
128
+ try {
129
+ return toolText(await handler(args));
130
+ }
131
+ catch (error) {
132
+ return toolError(tool, error);
133
+ }
134
+ };
135
+ }
19
136
  export function createDeroMcpServer(daemonBaseUrl) {
20
137
  const endpoint = jsonRpcEndpoint(daemonBaseUrl);
21
138
  const rpc = async (method, params) => deroJsonRpc(endpoint, method, params);
@@ -25,30 +142,29 @@ export function createDeroMcpServer(daemonBaseUrl) {
25
142
  });
26
143
  server.registerTool('dero_daemon_ping', {
27
144
  description: 'DERO daemon connectivity check. Calls DERO.Ping. No parameters.',
28
- }, async () => toolText(await rpc('DERO.Ping')));
145
+ }, withStructuredErrors('dero_daemon_ping', async () => rpc('DERO.Ping')));
29
146
  server.registerTool('dero_daemon_echo', {
30
147
  description: 'Echo strings through the daemon (DERO.Echo).',
31
148
  inputSchema: {
32
149
  words: z.array(z.string()).describe('Strings to echo back'),
33
150
  },
34
- }, async ({ words }) => toolText(await rpc('DERO.Echo', words)));
151
+ }, withStructuredErrors('dero_daemon_echo', async ({ words }) => rpc('DERO.Echo', words)));
35
152
  server.registerTool('dero_get_info', {
36
153
  description: 'Get daemon / chain info: height, difficulty, version, mempool size, etc. (DERO.GetInfo).',
37
- }, async () => toolText(await rpc('DERO.GetInfo')));
154
+ }, withStructuredErrors('dero_get_info', async () => rpc('DERO.GetInfo')));
38
155
  server.registerTool('dero_get_height', {
39
156
  description: 'Get top block height and stable/topo heights (DERO.GetHeight).',
40
- }, async () => toolText(await rpc('DERO.GetHeight')));
157
+ }, withStructuredErrors('dero_get_height', async () => rpc('DERO.GetHeight')));
41
158
  server.registerTool('dero_get_block_count', {
42
159
  description: 'Total block count (DERO.GetBlockCount).',
43
- }, async () => toolText(await rpc('DERO.GetBlockCount')));
160
+ }, withStructuredErrors('dero_get_block_count', async () => rpc('DERO.GetBlockCount')));
44
161
  server.registerTool('dero_get_last_block_header', {
45
162
  description: 'Header of the tip block (DERO.GetLastBlockHeader).',
46
- }, async () => toolText(await rpc('DERO.GetLastBlockHeader')));
163
+ }, withStructuredErrors('dero_get_last_block_header', async () => rpc('DERO.GetLastBlockHeader')));
47
164
  server.registerTool('dero_get_block', {
48
165
  description: 'Fetch a full block by height or hash (DERO.GetBlock). Provide one of hash or height.',
49
166
  inputSchema: {
50
- hash: z
51
- .string()
167
+ hash: hex64Schema
52
168
  .optional()
53
169
  .describe('64-char hex block hash'),
54
170
  height: z
@@ -58,7 +174,7 @@ export function createDeroMcpServer(daemonBaseUrl) {
58
174
  .optional()
59
175
  .describe('Block height'),
60
176
  },
61
- }, async (args) => {
177
+ }, withStructuredErrors('dero_get_block', async (args) => {
62
178
  if (!args.hash && args.height === undefined) {
63
179
  throw new Error('Provide either hash or height');
64
180
  }
@@ -67,8 +183,8 @@ export function createDeroMcpServer(daemonBaseUrl) {
67
183
  params.hash = args.hash;
68
184
  if (args.height !== undefined)
69
185
  params.height = args.height;
70
- return toolText(await rpc('DERO.GetBlock', params));
71
- });
186
+ return rpc('DERO.GetBlock', params);
187
+ }));
72
188
  server.registerTool('dero_get_block_header_by_topo_height', {
73
189
  description: 'Block header by topological height (DERO.GetBlockHeaderByTopoHeight).',
74
190
  inputSchema: {
@@ -78,30 +194,29 @@ export function createDeroMcpServer(daemonBaseUrl) {
78
194
  .nonnegative()
79
195
  .describe('Topological height'),
80
196
  },
81
- }, async ({ topoheight }) => toolText(await rpc('DERO.GetBlockHeaderByTopoHeight', { topoheight })));
197
+ }, withStructuredErrors('dero_get_block_header_by_topo_height', async ({ topoheight }) => rpc('DERO.GetBlockHeaderByTopoHeight', { topoheight })));
82
198
  server.registerTool('dero_get_block_header_by_hash', {
83
199
  description: 'Block header by hash (DERO.GetBlockHeaderByHash).',
84
200
  inputSchema: {
85
- hash: z.string().describe('Block top hash (hex)'),
201
+ hash: hex64Schema.describe('Block top hash (hex)'),
86
202
  },
87
- }, async ({ hash }) => toolText(await rpc('DERO.GetBlockHeaderByHash', { hash })));
203
+ }, withStructuredErrors('dero_get_block_header_by_hash', async ({ hash }) => rpc('DERO.GetBlockHeaderByHash', { hash })));
88
204
  server.registerTool('dero_get_tx_pool', {
89
205
  description: 'Pending mempool transaction hashes (DERO.GetTxPool).',
90
- }, async () => toolText(await rpc('DERO.GetTxPool')));
206
+ }, withStructuredErrors('dero_get_tx_pool', async () => rpc('DERO.GetTxPool')));
91
207
  server.registerTool('dero_get_random_address', {
92
208
  description: 'Random registered addresses from chain (for ring construction); optional asset scid (DERO.GetRandomAddress).',
93
209
  inputSchema: {
94
- scid: z
95
- .string()
210
+ scid: hex64Schema
96
211
  .optional()
97
212
  .describe('Optional asset smart-contract id (hex)'),
98
213
  },
99
- }, async (args) => toolText(await rpc('DERO.GetRandomAddress', args.scid != null ? { scid: args.scid } : undefined)));
214
+ }, withStructuredErrors('dero_get_random_address', async (args) => rpc('DERO.GetRandomAddress', args.scid != null ? { scid: args.scid } : undefined)));
100
215
  server.registerTool('dero_get_transaction', {
101
216
  description: 'Fetch transactions by tx hashes (DERO.GetTransaction).',
102
217
  inputSchema: {
103
218
  txs_hashes: z
104
- .array(z.string())
219
+ .array(hex64Schema)
105
220
  .min(1)
106
221
  .describe('List of transaction hashes (hex)'),
107
222
  decode_as_json: z
@@ -110,32 +225,32 @@ export function createDeroMcpServer(daemonBaseUrl) {
110
225
  .optional()
111
226
  .describe('Optional: decode each tx as JSON when non-zero'),
112
227
  },
113
- }, async ({ txs_hashes, decode_as_json }) => {
228
+ }, withStructuredErrors('dero_get_transaction', async ({ txs_hashes, decode_as_json }) => {
114
229
  const params = { txs_hashes };
115
230
  if (decode_as_json !== undefined)
116
231
  params.decode_as_json = decode_as_json;
117
- return toolText(await rpc('DERO.GetTransaction', params));
118
- });
232
+ return rpc('DERO.GetTransaction', params);
233
+ }));
119
234
  server.registerTool('dero_get_encrypted_balance', {
120
235
  description: 'Encrypted balance blob for an address at a topo height (DERO.GetEncryptedBalance). Not cleartext balance.',
121
236
  inputSchema: {
122
- address: z.string().describe('DERO address (deto1…)'),
237
+ address: deroAddressSchema.describe('DERO address (dero1… or deto1…)'),
123
238
  topoheight: z
124
239
  .number()
125
240
  .int()
126
241
  .describe('Use -1 for latest chain tip'),
127
- scid: z.string().optional().describe('Asset SCID hex; omit for native DERO'),
242
+ scid: hex64Schema.optional().describe('Asset SCID hex; omit for native DERO'),
128
243
  },
129
- }, async ({ address, topoheight, scid }) => {
244
+ }, withStructuredErrors('dero_get_encrypted_balance', async ({ address, topoheight, scid }) => {
130
245
  const params = { address, topoheight };
131
246
  if (scid)
132
247
  params.scid = scid;
133
- return toolText(await rpc('DERO.GetEncryptedBalance', params));
134
- });
248
+ return rpc('DERO.GetEncryptedBalance', params);
249
+ }));
135
250
  server.registerTool('dero_get_sc', {
136
251
  description: 'Read smart contract code and/or variables by SCID (DERO.GetSC).',
137
252
  inputSchema: {
138
- scid: z.string().describe('64-char hex Smart Contract ID'),
253
+ scid: hex64Schema.describe('64-char hex Smart Contract ID'),
139
254
  code: z
140
255
  .boolean()
141
256
  .optional()
@@ -150,7 +265,7 @@ export function createDeroMcpServer(daemonBaseUrl) {
150
265
  .optional()
151
266
  .describe('Topo height; omit or use -1 for latest'),
152
267
  },
153
- }, async ({ scid, code, variables, topoheight }) => {
268
+ }, withStructuredErrors('dero_get_sc', async ({ scid, code, variables, topoheight }) => {
154
269
  const params = {
155
270
  scid,
156
271
  code: code ?? true,
@@ -158,8 +273,8 @@ export function createDeroMcpServer(daemonBaseUrl) {
158
273
  };
159
274
  if (topoheight !== undefined)
160
275
  params.topoheight = topoheight;
161
- return toolText(await rpc('DERO.GetSC', params));
162
- });
276
+ return rpc('DERO.GetSC', params);
277
+ }));
163
278
  server.registerTool('dero_get_gas_estimate', {
164
279
  description: 'Estimate gas (compute + storage) for transfers, deploy, or SC call (DERO.GetGasEstimate).',
165
280
  inputSchema: {
@@ -177,7 +292,7 @@ export function createDeroMcpServer(daemonBaseUrl) {
177
292
  .optional()
178
293
  .describe('Signer address used for estimation'),
179
294
  },
180
- }, async (args) => {
295
+ }, withStructuredErrors('dero_get_gas_estimate', async (args) => {
181
296
  const params = {};
182
297
  if (args.transfers)
183
298
  params.transfers = args.transfers;
@@ -187,36 +302,174 @@ export function createDeroMcpServer(daemonBaseUrl) {
187
302
  params.sc_rpc = args.sc_rpc;
188
303
  if (args.signer)
189
304
  params.signer = args.signer;
190
- return toolText(await rpc('DERO.GetGasEstimate', params));
191
- });
305
+ return rpc('DERO.GetGasEstimate', params);
306
+ }));
192
307
  server.registerTool('dero_name_to_address', {
193
308
  description: 'Resolve a DERO on-chain name to address (DERO.NameToAddress).',
194
309
  inputSchema: {
195
- name: z.string().describe('Registered name'),
310
+ name: z.string().min(1).describe('Registered name'),
196
311
  topoheight: z
197
312
  .number()
198
313
  .int()
199
314
  .describe('Use -1 for latest'),
200
315
  },
201
- }, async ({ name, topoheight }) => toolText(await rpc('DERO.NameToAddress', { name, topoheight })));
316
+ }, withStructuredErrors('dero_name_to_address', async ({ name, topoheight }) => rpc('DERO.NameToAddress', { name, topoheight })));
202
317
  server.registerTool('dero_get_block_template', {
203
318
  description: 'Mining: get block template for a miner address (DERO.GetBlockTemplate).',
204
319
  inputSchema: {
205
- wallet_address: z.string().describe('Miner payout DERO address'),
320
+ wallet_address: deroAddressSchema.describe('Miner payout DERO address'),
206
321
  block: z
207
322
  .boolean()
208
323
  .optional()
209
324
  .describe('Include block blob'),
210
325
  miner: z.string().optional().describe('Optional miner id / label'),
211
326
  },
212
- }, async ({ wallet_address, block, miner }) => {
327
+ }, withStructuredErrors('dero_get_block_template', async ({ wallet_address, block, miner }) => {
213
328
  const params = { wallet_address };
214
329
  if (block !== undefined)
215
330
  params.block = block;
216
331
  if (miner)
217
332
  params.miner = miner;
218
- return toolText(await rpc('DERO.GetBlockTemplate', params));
219
- });
333
+ return rpc('DERO.GetBlockTemplate', params);
334
+ }));
335
+ server.registerResource('dero_mcp_server_info', 'dero://mcp/server-info', {
336
+ description: 'Server metadata, tool list, resource list, and prompt names.',
337
+ mimeType: 'application/json',
338
+ }, async (uri) => ({
339
+ contents: [
340
+ {
341
+ uri: uri.toString(),
342
+ mimeType: 'application/json',
343
+ text: JSON.stringify({
344
+ name: 'dero-daemon-mcp',
345
+ version: '0.1.0',
346
+ mode: 'read-only',
347
+ endpoint: endpoint,
348
+ tools: DERO_TOOL_NAMES,
349
+ resources: DERO_RESOURCE_URIS,
350
+ prompts: DERO_PROMPT_NAMES,
351
+ }, null, 2),
352
+ },
353
+ ],
354
+ }));
355
+ server.registerResource('dero_mcp_safety_boundary', 'dero://mcp/safety-boundary', {
356
+ description: 'Explicit read-only safety boundaries and escalation guidance for write actions.',
357
+ mimeType: 'application/json',
358
+ }, async (uri) => ({
359
+ contents: [
360
+ {
361
+ uri: uri.toString(),
362
+ mimeType: 'application/json',
363
+ text: JSON.stringify({
364
+ read_only: true,
365
+ excluded_methods: [
366
+ 'transfer',
367
+ 'scinvoke',
368
+ 'DERO.SendRawTransaction',
369
+ 'DERO.SubmitBlock',
370
+ ],
371
+ reasoning: 'These methods can move funds or mutate chain state.',
372
+ write_path: [
373
+ 'Use wallet RPC tooling (curl/XSWD/Engram) for writes.',
374
+ 'Use dero-mcp-server for live chain reads and analysis.',
375
+ ],
376
+ }, null, 2),
377
+ },
378
+ ],
379
+ }));
380
+ server.registerResource('dero_mcp_example_flows', 'dero://mcp/example-flows', {
381
+ description: 'Compact agent flow recipes for common DERO investigations.',
382
+ mimeType: 'text/markdown',
383
+ }, async (uri) => ({
384
+ contents: [
385
+ {
386
+ uri: uri.toString(),
387
+ mimeType: 'text/markdown',
388
+ text: [
389
+ '# DERO MCP Example Flows',
390
+ '',
391
+ '- Network health: `dero_daemon_ping` -> `dero_get_info` -> `dero_get_height`',
392
+ `- Inspect SC state: \`dero_get_sc\` with SCID (name registry: \`${NAME_REGISTRY_SCID}\`)`,
393
+ '- Trace transaction: `dero_get_transaction` with `decode_as_json: 1`',
394
+ '- Read-only boundary: no wallet writes or raw tx submission',
395
+ ].join('\n'),
396
+ },
397
+ ],
398
+ }));
399
+ server.registerPrompt('network_health_check', {
400
+ description: 'Guide the model through a DERO daemon sync and health check sequence.',
401
+ argsSchema: {
402
+ reference_topoheight: z
403
+ .number()
404
+ .int()
405
+ .positive()
406
+ .optional(),
407
+ },
408
+ }, async ({ reference_topoheight }) => ({
409
+ description: 'Prompt for sync health investigation.',
410
+ messages: [
411
+ {
412
+ role: 'user',
413
+ content: {
414
+ type: 'text',
415
+ text: [
416
+ 'Check DERO daemon health using MCP tools.',
417
+ '1) Call dero_daemon_ping.',
418
+ '2) Call dero_get_info and dero_get_height.',
419
+ '3) Report topoheight, stableheight, version, and network.',
420
+ reference_topoheight
421
+ ? `4) Compare topoheight against reference_topoheight=${reference_topoheight}.`
422
+ : '4) If no reference topoheight is provided, state that external comparison is still needed for final sync confidence.',
423
+ ].join('\n'),
424
+ },
425
+ },
426
+ ],
427
+ }));
428
+ server.registerPrompt('inspect_smart_contract', {
429
+ description: 'Inspect contract code/variables and explain likely state model.',
430
+ argsSchema: {
431
+ scid: hex64Schema,
432
+ },
433
+ }, async ({ scid }) => ({
434
+ description: 'Prompt for smart contract inspection.',
435
+ messages: [
436
+ {
437
+ role: 'user',
438
+ content: {
439
+ type: 'text',
440
+ text: [
441
+ `Inspect DERO smart contract ${scid}.`,
442
+ '1) Call dero_get_sc with variables=true and code=true.',
443
+ '2) Summarize key stringkeys and balances.',
444
+ '3) Explain likely data model and any assumptions.',
445
+ '4) Include topoheight context from response.',
446
+ ].join('\n'),
447
+ },
448
+ },
449
+ ],
450
+ }));
451
+ server.registerPrompt('trace_transaction', {
452
+ description: 'Trace one transaction and summarize confirmation + SC activity.',
453
+ argsSchema: {
454
+ tx_hash: hex64Schema,
455
+ },
456
+ }, async ({ tx_hash }) => ({
457
+ description: 'Prompt for transaction tracing.',
458
+ messages: [
459
+ {
460
+ role: 'user',
461
+ content: {
462
+ type: 'text',
463
+ text: [
464
+ `Trace DERO transaction ${tx_hash}.`,
465
+ '1) Call dero_get_transaction with txs_hashes=[tx_hash] and decode_as_json=1.',
466
+ '2) Summarize confirmation status, block height, transfers, and SC invokes.',
467
+ '3) If not confirmed, mention mempool status uncertainty and next check timing.',
468
+ ].join('\n'),
469
+ },
470
+ },
471
+ ],
472
+ }));
220
473
  return server;
221
474
  }
222
475
  //# sourceMappingURL=server.js.map