dero-mcp-server 0.1.0 → 0.1.2
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/LICENSE +21 -0
- package/README.md +138 -5
- package/data/docs-index.json +5694 -0
- package/dist/docs-parse.d.ts +30 -0
- package/dist/docs-parse.d.ts.map +1 -0
- package/dist/docs-parse.js +147 -0
- package/dist/docs-parse.js.map +1 -0
- package/dist/docs.d.ts +101 -0
- package/dist/docs.d.ts.map +1 -0
- package/dist/docs.js +172 -0
- package/dist/docs.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +389 -41
- package/dist/server.js.map +1 -1
- package/package.json +16 -3
- package/src/index.ts +0 -30
- package/src/rpc.ts +0 -60
- package/src/server.ts +0 -317
- package/tsconfig.json +0 -16
package/src/index.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,317 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
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
|
-
}
|