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.
- package/.github/workflows/ci.yml +62 -0
- package/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +293 -40
- package/dist/server.js.map +1 -1
- package/docs/example-agent-flows.md +236 -0
- package/docs/mcp-agent-ready-evidence.md +108 -0
- package/glama.json +6 -0
- package/package.json +7 -2
- package/scripts/doctor.sh +85 -0
- package/scripts/flow-test.ts +257 -0
- package/scripts/mcp-smoke-probes.ts +168 -0
- package/server.json +23 -0
- package/src/server.ts +367 -48
|
@@ -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
|
+
[](https://github.com/DHEBP/dero-mcp-server/actions/workflows/ci.yml)
|
|
4
|
+
[](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.
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;
|
|
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 () =>
|
|
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 }) =>
|
|
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 () =>
|
|
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 () =>
|
|
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 () =>
|
|
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 () =>
|
|
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:
|
|
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
|
|
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 }) =>
|
|
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:
|
|
201
|
+
hash: hex64Schema.describe('Block top hash (hex)'),
|
|
86
202
|
},
|
|
87
|
-
}, async ({ 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 () =>
|
|
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:
|
|
95
|
-
.string()
|
|
210
|
+
scid: hex64Schema
|
|
96
211
|
.optional()
|
|
97
212
|
.describe('Optional asset smart-contract id (hex)'),
|
|
98
213
|
},
|
|
99
|
-
}, async (args) =>
|
|
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(
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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 }) =>
|
|
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:
|
|
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
|
|
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
|