dero-mcp-server 0.1.2 → 0.4.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.
Files changed (77) hide show
  1. package/POSITIONING.md +94 -0
  2. package/README.md +132 -31
  3. package/SKILL.md +264 -0
  4. package/data/docs-index.json +276 -264
  5. package/dist/bn254.d.ts +74 -0
  6. package/dist/bn254.d.ts.map +1 -0
  7. package/dist/bn254.js +205 -0
  8. package/dist/bn254.js.map +1 -0
  9. package/dist/citations.d.ts +140 -0
  10. package/dist/citations.d.ts.map +1 -0
  11. package/dist/citations.js +322 -0
  12. package/dist/citations.js.map +1 -0
  13. package/dist/composites/_shared.d.ts +119 -0
  14. package/dist/composites/_shared.d.ts.map +1 -0
  15. package/dist/composites/_shared.js +152 -0
  16. package/dist/composites/_shared.js.map +1 -0
  17. package/dist/composites/audit-chain-artifact-claim.d.ts +128 -0
  18. package/dist/composites/audit-chain-artifact-claim.d.ts.map +1 -0
  19. package/dist/composites/audit-chain-artifact-claim.js +305 -0
  20. package/dist/composites/audit-chain-artifact-claim.js.map +1 -0
  21. package/dist/composites/diagnose-chain-health.d.ts +64 -0
  22. package/dist/composites/diagnose-chain-health.d.ts.map +1 -0
  23. package/dist/composites/diagnose-chain-health.js +144 -0
  24. package/dist/composites/diagnose-chain-health.js.map +1 -0
  25. package/dist/composites/estimate-deploy-cost.d.ts +83 -0
  26. package/dist/composites/estimate-deploy-cost.d.ts.map +1 -0
  27. package/dist/composites/estimate-deploy-cost.js +116 -0
  28. package/dist/composites/estimate-deploy-cost.js.map +1 -0
  29. package/dist/composites/explain-smart-contract.d.ts +64 -0
  30. package/dist/composites/explain-smart-contract.d.ts.map +1 -0
  31. package/dist/composites/explain-smart-contract.js +149 -0
  32. package/dist/composites/explain-smart-contract.js.map +1 -0
  33. package/dist/composites/forge-demo-proof.d.ts +81 -0
  34. package/dist/composites/forge-demo-proof.d.ts.map +1 -0
  35. package/dist/composites/forge-demo-proof.js +204 -0
  36. package/dist/composites/forge-demo-proof.js.map +1 -0
  37. package/dist/composites/recommend-docs-path.d.ts +97 -0
  38. package/dist/composites/recommend-docs-path.d.ts.map +1 -0
  39. package/dist/composites/recommend-docs-path.js +149 -0
  40. package/dist/composites/recommend-docs-path.js.map +1 -0
  41. package/dist/composites/trace-transaction-with-context.d.ts +107 -0
  42. package/dist/composites/trace-transaction-with-context.d.ts.map +1 -0
  43. package/dist/composites/trace-transaction-with-context.js +217 -0
  44. package/dist/composites/trace-transaction-with-context.js.map +1 -0
  45. package/dist/daemon-base.d.ts +28 -0
  46. package/dist/daemon-base.d.ts.map +1 -0
  47. package/dist/daemon-base.js +62 -0
  48. package/dist/daemon-base.js.map +1 -0
  49. package/dist/dero-curve.d.ts +79 -0
  50. package/dist/dero-curve.d.ts.map +1 -0
  51. package/dist/dero-curve.js +79 -0
  52. package/dist/dero-curve.js.map +1 -0
  53. package/dist/docs-parse.d.ts.map +1 -1
  54. package/dist/docs-parse.js +18 -2
  55. package/dist/docs-parse.js.map +1 -1
  56. package/dist/http-server.d.ts +37 -0
  57. package/dist/http-server.d.ts.map +1 -0
  58. package/dist/http-server.js +132 -0
  59. package/dist/http-server.js.map +1 -0
  60. package/dist/index.js +18 -11
  61. package/dist/index.js.map +1 -1
  62. package/dist/proof-decode.d.ts +125 -0
  63. package/dist/proof-decode.d.ts.map +1 -0
  64. package/dist/proof-decode.js +619 -0
  65. package/dist/proof-decode.js.map +1 -0
  66. package/dist/server.d.ts.map +1 -1
  67. package/dist/server.js +414 -114
  68. package/dist/server.js.map +1 -1
  69. package/dist/tool-descriptions.d.ts +53 -0
  70. package/dist/tool-descriptions.d.ts.map +1 -0
  71. package/dist/tool-descriptions.js +285 -0
  72. package/dist/tool-descriptions.js.map +1 -0
  73. package/dist/tx-parse.d.ts +63 -0
  74. package/dist/tx-parse.d.ts.map +1 -0
  75. package/dist/tx-parse.js +183 -0
  76. package/dist/tx-parse.js.map +1 -0
  77. package/package.json +27 -2
package/dist/server.js CHANGED
@@ -2,6 +2,16 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { z } from 'zod';
3
3
  import { deroJsonRpc, jsonRpcEndpoint } from './rpc.js';
4
4
  import { DERO_DOC_PRODUCTS, getDeroDocPage, listDeroDocs, searchDeroDocs, } from './docs.js';
5
+ import { DERO_TOOL_NAMES, TOOL_DESCRIPTIONS } from './tool-descriptions.js';
6
+ import { enrichWithFlaggedArtifacts, relatedDocsFor } from './citations.js';
7
+ import { decodeDeroBech32, interpretValueTransfer } from './proof-decode.js';
8
+ import { auditChainArtifactClaim, auditChainArtifactClaimInputSchema, } from './composites/audit-chain-artifact-claim.js';
9
+ import { forgeDemoProof, forgeDemoProofInputSchema, } from './composites/forge-demo-proof.js';
10
+ import { diagnoseChainHealth, diagnoseChainHealthInputSchema, } from './composites/diagnose-chain-health.js';
11
+ import { explainSmartContract, explainSmartContractInputSchema, } from './composites/explain-smart-contract.js';
12
+ import { recommendDocsPath, recommendDocsPathInputSchema, } from './composites/recommend-docs-path.js';
13
+ import { estimateDeployCost, estimateDeployCostInputSchema, } from './composites/estimate-deploy-cost.js';
14
+ import { traceTransactionWithContext, traceTransactionWithContextInputSchema, } from './composites/trace-transaction-with-context.js';
5
15
  const scRpcArgSchema = z.object({
6
16
  name: z.string(),
7
17
  datatype: z.enum(['S', 'U', 'H']),
@@ -14,37 +24,18 @@ const deroAddressSchema = z
14
24
  .string()
15
25
  .regex(/^(dero1|deto1)[0-9a-z]+$/i, 'Expected DERO address starting with dero1 or deto1');
16
26
  const NAME_REGISTRY_SCID = '0000000000000000000000000000000000000000000000000000000000000001';
17
- const DERO_TOOL_NAMES = [
18
- 'dero_daemon_ping',
19
- 'dero_daemon_echo',
20
- 'dero_get_info',
21
- 'dero_get_height',
22
- 'dero_get_block_count',
23
- 'dero_get_last_block_header',
24
- 'dero_get_block',
25
- 'dero_get_block_header_by_topo_height',
26
- 'dero_get_block_header_by_hash',
27
- 'dero_get_tx_pool',
28
- 'dero_get_random_address',
29
- 'dero_get_transaction',
30
- 'dero_get_encrypted_balance',
31
- 'dero_get_sc',
32
- 'dero_get_gas_estimate',
33
- 'dero_name_to_address',
34
- 'dero_get_block_template',
35
- 'dero_docs_search',
36
- 'dero_docs_get_page',
37
- 'dero_docs_list',
38
- ];
39
27
  const DERO_RESOURCE_URIS = [
40
28
  'dero://mcp/server-info',
41
29
  'dero://mcp/safety-boundary',
42
30
  'dero://mcp/example-flows',
31
+ 'dero://mcp/composites',
43
32
  ];
44
33
  const DERO_PROMPT_NAMES = [
45
34
  'network_health_check',
46
35
  'inspect_smart_contract',
47
36
  'trace_transaction',
37
+ 'find_dero_docs_for_intent',
38
+ 'estimate_deploy_for_contract',
48
39
  ];
49
40
  const deroDocProductSchema = z.enum(DERO_DOC_PRODUCTS);
50
41
  function toolText(data) {
@@ -59,6 +50,16 @@ function toolText(data) {
59
50
  }
60
51
  function classifyToolError(error) {
61
52
  const message = error instanceof Error ? error.message : String(error);
53
+ // Generic `INVALID_INPUT: <reason>` prefix used by composites that validate
54
+ // their own argument shape (audit_chain_artifact_claim, dero_forge_demo_proof).
55
+ // The composite's own `<reason>` is preserved verbatim in `_meta.error.raw`.
56
+ if (message.startsWith('INVALID_INPUT:')) {
57
+ return {
58
+ code: 'INVALID_INPUT',
59
+ hint: message.slice('INVALID_INPUT:'.length).trim() || 'Re-check the tool input shape against the tool description.',
60
+ retryable: false,
61
+ };
62
+ }
62
63
  if (message.includes('Provide either hash or height')) {
63
64
  return {
64
65
  code: 'INVALID_INPUT',
@@ -66,6 +67,13 @@ function classifyToolError(error) {
66
67
  retryable: false,
67
68
  };
68
69
  }
70
+ if (message.startsWith('INVALID_BECH32:')) {
71
+ return {
72
+ code: 'INVALID_BECH32',
73
+ hint: 'Confirm the string starts with one of dero/deto/deroi/detoi/deroproof, includes the "1" separator, and is uniformly cased (BIP-0173 forbids mixed case). See integrity/payload-vs-transaction-proofs for proof anatomy.',
74
+ retryable: false,
75
+ };
76
+ }
69
77
  if (message.includes('DERO docs unavailable') ||
70
78
  message.includes('bundled docs index is missing')) {
71
79
  return {
@@ -95,6 +103,20 @@ function classifyToolError(error) {
95
103
  retryable: false,
96
104
  };
97
105
  }
106
+ if (message.includes('No DERO docs matched intent')) {
107
+ return {
108
+ code: 'NO_DOCS_MATCH',
109
+ hint: 'Rephrase the intent (drop verbs, use product nouns like "TELA app" or "DVM contract"), then retry. You can also pass product_hint to bias the search.',
110
+ retryable: false,
111
+ };
112
+ }
113
+ if (message.includes('DERO transaction not found')) {
114
+ return {
115
+ code: 'TX_NOT_FOUND',
116
+ hint: 'The daemon has no record of that tx hash on this chain. Verify the hash is correct (64 hex chars), check whether you queried the right network (mainnet vs testnet), and if the tx is freshly broadcast wait a few seconds for mempool propagation and retry.',
117
+ retryable: true,
118
+ };
119
+ }
98
120
  if (message.includes('RPC error -32601')) {
99
121
  return {
100
122
  code: 'RPC_METHOD_NOT_FOUND',
@@ -109,6 +131,13 @@ function classifyToolError(error) {
109
131
  retryable: false,
110
132
  };
111
133
  }
134
+ if (message.includes('RPC error -32098')) {
135
+ return {
136
+ code: 'INVALID_INPUT',
137
+ hint: 'The DVM compiler rejected the contract source. Inspect _meta.error.raw for the exact compile error (often points at a line, symbol, or missing keyword). Common causes: missing `End Function`, missing return type (`Uint64`/`String`), unbalanced parens, or sending a function body instead of a full contract.',
138
+ retryable: false,
139
+ };
140
+ }
112
141
  const httpMatch = message.match(/HTTP (\d{3})/);
113
142
  if (httpMatch) {
114
143
  const status = Number(httpMatch[1]);
@@ -167,36 +196,69 @@ function withStructuredErrors(tool, handler) {
167
196
  }
168
197
  };
169
198
  }
199
+ /**
200
+ * MCP tool annotation hint block applied to every tool in this server.
201
+ *
202
+ * - `readOnlyHint: true` lets MCP hosts (Cursor, Claude Desktop, OpenCode)
203
+ * auto-approve calls without per-invocation confirmation.
204
+ * - `destructiveHint: false` makes the read-only promise explicit so hosts
205
+ * render a safe-call badge.
206
+ * - `idempotentHint: false` because chain state advances between calls —
207
+ * identical inputs may return different blocks/heights/tx pools.
208
+ * - `openWorldHint: false` because we hit a configured daemon endpoint only,
209
+ * not arbitrary external services.
210
+ *
211
+ * Any future wallet/write tools MUST use a different annotation block
212
+ * (`readOnlyHint: false`, `destructiveHint: true`) and remain require-approval.
213
+ */
214
+ const READ_ONLY_ANNOTATIONS = {
215
+ readOnlyHint: true,
216
+ destructiveHint: false,
217
+ idempotentHint: false,
218
+ openWorldHint: false,
219
+ };
220
+ /**
221
+ * Helper that tags a tool config with the read-only annotation block.
222
+ * Use for every primitive in this v0.1 server. Composites built on these
223
+ * primitives are also read-only and should use this same helper.
224
+ */
225
+ function readOnly(config) {
226
+ return { ...config, annotations: READ_ONLY_ANNOTATIONS };
227
+ }
170
228
  export function createDeroMcpServer(daemonBaseUrl) {
171
229
  const endpoint = jsonRpcEndpoint(daemonBaseUrl);
172
230
  const rpc = async (method, params) => deroJsonRpc(endpoint, method, params);
173
231
  const server = new McpServer({
174
232
  name: 'dero-daemon-mcp',
175
- version: '0.1.2',
233
+ version: '0.4.0',
176
234
  });
177
- server.registerTool('dero_daemon_ping', {
178
- description: 'DERO daemon connectivity check. Calls DERO.Ping. No parameters.',
179
- }, withStructuredErrors('dero_daemon_ping', async () => rpc('DERO.Ping')));
180
- server.registerTool('dero_daemon_echo', {
181
- description: 'Echo strings through the daemon (DERO.Echo).',
235
+ server.registerTool('dero_daemon_ping', readOnly({
236
+ description: TOOL_DESCRIPTIONS.dero_daemon_ping,
237
+ }), withStructuredErrors('dero_daemon_ping', async () => rpc('DERO.Ping')));
238
+ server.registerTool('dero_daemon_echo', readOnly({
239
+ description: TOOL_DESCRIPTIONS.dero_daemon_echo,
182
240
  inputSchema: {
183
241
  words: z.array(z.string()).describe('Strings to echo back'),
184
242
  },
185
- }, withStructuredErrors('dero_daemon_echo', async ({ words }) => rpc('DERO.Echo', words)));
186
- server.registerTool('dero_get_info', {
187
- description: 'Get daemon / chain info: height, difficulty, version, mempool size, etc. (DERO.GetInfo).',
188
- }, withStructuredErrors('dero_get_info', async () => rpc('DERO.GetInfo')));
189
- server.registerTool('dero_get_height', {
190
- description: 'Get top block height and stable/topo heights (DERO.GetHeight).',
191
- }, withStructuredErrors('dero_get_height', async () => rpc('DERO.GetHeight')));
192
- server.registerTool('dero_get_block_count', {
193
- description: 'Total block count (DERO.GetBlockCount).',
194
- }, withStructuredErrors('dero_get_block_count', async () => rpc('DERO.GetBlockCount')));
195
- server.registerTool('dero_get_last_block_header', {
196
- description: 'Header of the tip block (DERO.GetLastBlockHeader).',
197
- }, withStructuredErrors('dero_get_last_block_header', async () => rpc('DERO.GetLastBlockHeader')));
198
- server.registerTool('dero_get_block', {
199
- description: 'Fetch a full block by height or hash (DERO.GetBlock). Provide one of hash or height.',
243
+ }), withStructuredErrors('dero_daemon_echo', async ({ words }) => rpc('DERO.Echo', words)));
244
+ server.registerTool('dero_get_info', readOnly({
245
+ description: TOOL_DESCRIPTIONS.dero_get_info,
246
+ }), withStructuredErrors('dero_get_info', async () => {
247
+ const result = (await rpc('DERO.GetInfo')) ?? {};
248
+ const related_docs = relatedDocsFor('dero_get_info');
249
+ return { ...result, ...(related_docs ? { related_docs } : {}) };
250
+ }));
251
+ server.registerTool('dero_get_height', readOnly({
252
+ description: TOOL_DESCRIPTIONS.dero_get_height,
253
+ }), withStructuredErrors('dero_get_height', async () => rpc('DERO.GetHeight')));
254
+ server.registerTool('dero_get_block_count', readOnly({
255
+ description: TOOL_DESCRIPTIONS.dero_get_block_count,
256
+ }), withStructuredErrors('dero_get_block_count', async () => rpc('DERO.GetBlockCount')));
257
+ server.registerTool('dero_get_last_block_header', readOnly({
258
+ description: TOOL_DESCRIPTIONS.dero_get_last_block_header,
259
+ }), withStructuredErrors('dero_get_last_block_header', async () => rpc('DERO.GetLastBlockHeader')));
260
+ server.registerTool('dero_get_block', readOnly({
261
+ description: TOOL_DESCRIPTIONS.dero_get_block,
200
262
  inputSchema: {
201
263
  hash: hex64Schema
202
264
  .optional()
@@ -208,7 +270,7 @@ export function createDeroMcpServer(daemonBaseUrl) {
208
270
  .optional()
209
271
  .describe('Block height'),
210
272
  },
211
- }, withStructuredErrors('dero_get_block', async (args) => {
273
+ }), withStructuredErrors('dero_get_block', async (args) => {
212
274
  if (!args.hash && args.height === undefined) {
213
275
  throw new Error('Provide either hash or height');
214
276
  }
@@ -217,10 +279,12 @@ export function createDeroMcpServer(daemonBaseUrl) {
217
279
  params.hash = args.hash;
218
280
  if (args.height !== undefined)
219
281
  params.height = args.height;
220
- return rpc('DERO.GetBlock', params);
282
+ const result = (await rpc('DERO.GetBlock', params)) ?? {};
283
+ const enrichment = enrichWithFlaggedArtifacts({ block_hash: args.hash }, relatedDocsFor('dero_get_block'));
284
+ return { ...result, ...(enrichment ?? {}) };
221
285
  }));
222
- server.registerTool('dero_get_block_header_by_topo_height', {
223
- description: 'Block header by topological height (DERO.GetBlockHeaderByTopoHeight).',
286
+ server.registerTool('dero_get_block_header_by_topo_height', readOnly({
287
+ description: TOOL_DESCRIPTIONS.dero_get_block_header_by_topo_height,
224
288
  inputSchema: {
225
289
  topoheight: z
226
290
  .number()
@@ -228,26 +292,34 @@ export function createDeroMcpServer(daemonBaseUrl) {
228
292
  .nonnegative()
229
293
  .describe('Topological height'),
230
294
  },
231
- }, withStructuredErrors('dero_get_block_header_by_topo_height', async ({ topoheight }) => rpc('DERO.GetBlockHeaderByTopoHeight', { topoheight })));
232
- server.registerTool('dero_get_block_header_by_hash', {
233
- description: 'Block header by hash (DERO.GetBlockHeaderByHash).',
295
+ }), withStructuredErrors('dero_get_block_header_by_topo_height', async ({ topoheight }) => {
296
+ const result = (await rpc('DERO.GetBlockHeaderByTopoHeight', { topoheight })) ?? {};
297
+ const enrichment = enrichWithFlaggedArtifacts({ topoheight }, relatedDocsFor('dero_get_block_header_by_topo_height'));
298
+ return { ...result, ...(enrichment ?? {}) };
299
+ }));
300
+ server.registerTool('dero_get_block_header_by_hash', readOnly({
301
+ description: TOOL_DESCRIPTIONS.dero_get_block_header_by_hash,
234
302
  inputSchema: {
235
303
  hash: hex64Schema.describe('Block top hash (hex)'),
236
304
  },
237
- }, withStructuredErrors('dero_get_block_header_by_hash', async ({ hash }) => rpc('DERO.GetBlockHeaderByHash', { hash })));
238
- server.registerTool('dero_get_tx_pool', {
239
- description: 'Pending mempool transaction hashes (DERO.GetTxPool).',
240
- }, withStructuredErrors('dero_get_tx_pool', async () => rpc('DERO.GetTxPool')));
241
- server.registerTool('dero_get_random_address', {
242
- description: 'Random registered addresses from chain (for ring construction); optional asset scid (DERO.GetRandomAddress).',
305
+ }), withStructuredErrors('dero_get_block_header_by_hash', async ({ hash }) => {
306
+ const result = (await rpc('DERO.GetBlockHeaderByHash', { hash })) ?? {};
307
+ const enrichment = enrichWithFlaggedArtifacts({ block_hash: hash }, relatedDocsFor('dero_get_block_header_by_hash'));
308
+ return { ...result, ...(enrichment ?? {}) };
309
+ }));
310
+ server.registerTool('dero_get_tx_pool', readOnly({
311
+ description: TOOL_DESCRIPTIONS.dero_get_tx_pool,
312
+ }), withStructuredErrors('dero_get_tx_pool', async () => rpc('DERO.GetTxPool')));
313
+ server.registerTool('dero_get_random_address', readOnly({
314
+ description: TOOL_DESCRIPTIONS.dero_get_random_address,
243
315
  inputSchema: {
244
316
  scid: hex64Schema
245
317
  .optional()
246
318
  .describe('Optional asset smart-contract id (hex)'),
247
319
  },
248
- }, withStructuredErrors('dero_get_random_address', async (args) => rpc('DERO.GetRandomAddress', args.scid != null ? { scid: args.scid } : undefined)));
249
- server.registerTool('dero_get_transaction', {
250
- description: 'Fetch transactions by tx hashes (DERO.GetTransaction).',
320
+ }), withStructuredErrors('dero_get_random_address', async (args) => rpc('DERO.GetRandomAddress', args.scid != null ? { scid: args.scid } : undefined)));
321
+ server.registerTool('dero_get_transaction', readOnly({
322
+ description: TOOL_DESCRIPTIONS.dero_get_transaction,
251
323
  inputSchema: {
252
324
  txs_hashes: z
253
325
  .array(hex64Schema)
@@ -259,14 +331,16 @@ export function createDeroMcpServer(daemonBaseUrl) {
259
331
  .optional()
260
332
  .describe('Optional: decode each tx as JSON when non-zero'),
261
333
  },
262
- }, withStructuredErrors('dero_get_transaction', async ({ txs_hashes, decode_as_json }) => {
334
+ }), withStructuredErrors('dero_get_transaction', async ({ txs_hashes, decode_as_json }) => {
263
335
  const params = { txs_hashes };
264
336
  if (decode_as_json !== undefined)
265
337
  params.decode_as_json = decode_as_json;
266
- return rpc('DERO.GetTransaction', params);
338
+ const result = (await rpc('DERO.GetTransaction', params)) ?? {};
339
+ const enrichment = enrichWithFlaggedArtifacts({ tx_hashes: txs_hashes }, relatedDocsFor('dero_get_transaction'));
340
+ return { ...result, ...(enrichment ?? {}) };
267
341
  }));
268
- server.registerTool('dero_get_encrypted_balance', {
269
- description: 'Encrypted balance blob for an address at a topo height (DERO.GetEncryptedBalance). Not cleartext balance.',
342
+ server.registerTool('dero_get_encrypted_balance', readOnly({
343
+ description: TOOL_DESCRIPTIONS.dero_get_encrypted_balance,
270
344
  inputSchema: {
271
345
  address: deroAddressSchema.describe('DERO address (dero1… or deto1…)'),
272
346
  topoheight: z
@@ -275,14 +349,14 @@ export function createDeroMcpServer(daemonBaseUrl) {
275
349
  .describe('Use -1 for latest chain tip'),
276
350
  scid: hex64Schema.optional().describe('Asset SCID hex; omit for native DERO'),
277
351
  },
278
- }, withStructuredErrors('dero_get_encrypted_balance', async ({ address, topoheight, scid }) => {
352
+ }), withStructuredErrors('dero_get_encrypted_balance', async ({ address, topoheight, scid }) => {
279
353
  const params = { address, topoheight };
280
354
  if (scid)
281
355
  params.scid = scid;
282
356
  return rpc('DERO.GetEncryptedBalance', params);
283
357
  }));
284
- server.registerTool('dero_get_sc', {
285
- description: 'Read smart contract code and/or variables by SCID (DERO.GetSC).',
358
+ server.registerTool('dero_get_sc', readOnly({
359
+ description: TOOL_DESCRIPTIONS.dero_get_sc,
286
360
  inputSchema: {
287
361
  scid: hex64Schema.describe('64-char hex Smart Contract ID'),
288
362
  code: z
@@ -299,7 +373,7 @@ export function createDeroMcpServer(daemonBaseUrl) {
299
373
  .optional()
300
374
  .describe('Topo height; omit or use -1 for latest'),
301
375
  },
302
- }, withStructuredErrors('dero_get_sc', async ({ scid, code, variables, topoheight }) => {
376
+ }), withStructuredErrors('dero_get_sc', async ({ scid, code, variables, topoheight }) => {
303
377
  const params = {
304
378
  scid,
305
379
  code: code ?? true,
@@ -307,10 +381,12 @@ export function createDeroMcpServer(daemonBaseUrl) {
307
381
  };
308
382
  if (topoheight !== undefined)
309
383
  params.topoheight = topoheight;
310
- return rpc('DERO.GetSC', params);
384
+ const result = (await rpc('DERO.GetSC', params)) ?? {};
385
+ const related_docs = relatedDocsFor('dero_get_sc');
386
+ return { ...result, ...(related_docs ? { related_docs } : {}) };
311
387
  }));
312
- server.registerTool('dero_get_gas_estimate', {
313
- description: 'Estimate gas (compute + storage) for transfers, deploy, or SC call (DERO.GetGasEstimate).',
388
+ server.registerTool('dero_get_gas_estimate', readOnly({
389
+ description: TOOL_DESCRIPTIONS.dero_get_gas_estimate,
314
390
  inputSchema: {
315
391
  transfers: z
316
392
  .array(z.record(z.unknown()))
@@ -326,7 +402,7 @@ export function createDeroMcpServer(daemonBaseUrl) {
326
402
  .optional()
327
403
  .describe('Signer address used for estimation'),
328
404
  },
329
- }, withStructuredErrors('dero_get_gas_estimate', async (args) => {
405
+ }), withStructuredErrors('dero_get_gas_estimate', async (args) => {
330
406
  const params = {};
331
407
  if (args.transfers)
332
408
  params.transfers = args.transfers;
@@ -336,10 +412,12 @@ export function createDeroMcpServer(daemonBaseUrl) {
336
412
  params.sc_rpc = args.sc_rpc;
337
413
  if (args.signer)
338
414
  params.signer = args.signer;
339
- return rpc('DERO.GetGasEstimate', params);
415
+ const result = (await rpc('DERO.GetGasEstimate', params)) ?? {};
416
+ const related_docs = relatedDocsFor('dero_get_gas_estimate');
417
+ return { ...result, ...(related_docs ? { related_docs } : {}) };
340
418
  }));
341
- server.registerTool('dero_name_to_address', {
342
- description: 'Resolve a DERO on-chain name to address (DERO.NameToAddress).',
419
+ server.registerTool('dero_name_to_address', readOnly({
420
+ description: TOOL_DESCRIPTIONS.dero_name_to_address,
343
421
  inputSchema: {
344
422
  name: z.string().min(1).describe('Registered name'),
345
423
  topoheight: z
@@ -347,9 +425,9 @@ export function createDeroMcpServer(daemonBaseUrl) {
347
425
  .int()
348
426
  .describe('Use -1 for latest'),
349
427
  },
350
- }, withStructuredErrors('dero_name_to_address', async ({ name, topoheight }) => rpc('DERO.NameToAddress', { name, topoheight })));
351
- server.registerTool('dero_get_block_template', {
352
- description: 'Mining: get block template for a miner address (DERO.GetBlockTemplate).',
428
+ }), withStructuredErrors('dero_name_to_address', async ({ name, topoheight }) => rpc('DERO.NameToAddress', { name, topoheight })));
429
+ server.registerTool('dero_get_block_template', readOnly({
430
+ description: TOOL_DESCRIPTIONS.dero_get_block_template,
353
431
  inputSchema: {
354
432
  wallet_address: deroAddressSchema.describe('Miner payout DERO address'),
355
433
  block: z
@@ -358,7 +436,7 @@ export function createDeroMcpServer(daemonBaseUrl) {
358
436
  .describe('Include block blob'),
359
437
  miner: z.string().optional().describe('Optional miner id / label'),
360
438
  },
361
- }, withStructuredErrors('dero_get_block_template', async ({ wallet_address, block, miner }) => {
439
+ }), withStructuredErrors('dero_get_block_template', async ({ wallet_address, block, miner }) => {
362
440
  const params = { wallet_address };
363
441
  if (block !== undefined)
364
442
  params.block = block;
@@ -366,8 +444,48 @@ export function createDeroMcpServer(daemonBaseUrl) {
366
444
  params.miner = miner;
367
445
  return rpc('DERO.GetBlockTemplate', params);
368
446
  }));
369
- server.registerTool('dero_docs_search', {
370
- description: 'Search bundled DERO documentation (derod/tela/hologram/deropay). Ships with npm package; optional DERO_DOCS_ROOT overrides for local dev.',
447
+ server.registerTool('dero_decode_proof_string', readOnly({
448
+ description: TOOL_DESCRIPTIONS.dero_decode_proof_string,
449
+ inputSchema: {
450
+ proof_string: z
451
+ .string()
452
+ .min(8)
453
+ .describe('Full bech32 string with HRP, e.g. "deroproof1qyy…" or "dero1abc…". Whitespace is trimmed.'),
454
+ },
455
+ }), withStructuredErrors('dero_decode_proof_string', async ({ proof_string }) => {
456
+ let decoded;
457
+ try {
458
+ decoded = decodeDeroBech32(proof_string);
459
+ }
460
+ catch (error) {
461
+ const message = error instanceof Error ? error.message : String(error);
462
+ throw new Error(`INVALID_BECH32: ${message}`);
463
+ }
464
+ const decodedJson = {
465
+ hrp: decoded.hrp,
466
+ mainnet: decoded.mainnet,
467
+ is_proof: decoded.is_proof,
468
+ public_key_hex: decoded.public_key_hex,
469
+ arguments: decoded.arguments,
470
+ };
471
+ const value_interpretation = decoded.value_transfer_uint64 !== undefined
472
+ ? interpretValueTransfer(decoded.value_transfer_uint64)
473
+ : undefined;
474
+ // Try flagged-artifact enrichment first; fall back to the generic
475
+ // per-tool related_docs when the input is not a known adversarial string.
476
+ const enrichment = enrichWithFlaggedArtifacts({ proof_string }, relatedDocsFor('dero_decode_proof_string'));
477
+ const baseline = enrichment
478
+ ? enrichment
479
+ : { related_docs: relatedDocsFor('dero_decode_proof_string') ?? [] };
480
+ return {
481
+ decoded: decodedJson,
482
+ ...(value_interpretation ? { value_interpretation } : {}),
483
+ ...(baseline.related_docs?.length ? { related_docs: baseline.related_docs } : {}),
484
+ ...(enrichment ? { context_note: enrichment.context_note } : {}),
485
+ };
486
+ }));
487
+ server.registerTool('dero_docs_search', readOnly({
488
+ description: TOOL_DESCRIPTIONS.dero_docs_search,
371
489
  inputSchema: {
372
490
  query: z
373
491
  .string()
@@ -388,9 +506,9 @@ export function createDeroMcpServer(daemonBaseUrl) {
388
506
  .optional()
389
507
  .describe('Max matches (default 8, max 25)'),
390
508
  },
391
- }, withStructuredErrors('dero_docs_search', async ({ query, product, section, limit }) => searchDeroDocs({ query, product, section, limit })));
392
- server.registerTool('dero_docs_get_page', {
393
- description: 'Get one docs page by slug (optionally scoped by product). Returns headings and normalized plain-text content.',
509
+ }), withStructuredErrors('dero_docs_search', async ({ query, product, section, limit }) => searchDeroDocs({ query, product, section, limit })));
510
+ server.registerTool('dero_docs_get_page', readOnly({
511
+ description: TOOL_DESCRIPTIONS.dero_docs_get_page,
394
512
  inputSchema: {
395
513
  slug: z
396
514
  .string()
@@ -400,9 +518,9 @@ export function createDeroMcpServer(daemonBaseUrl) {
400
518
  .optional()
401
519
  .describe('Optional product scope to disambiguate duplicate slugs'),
402
520
  },
403
- }, withStructuredErrors('dero_docs_get_page', async ({ slug, product }) => getDeroDocPage({ slug, product })));
404
- server.registerTool('dero_docs_list', {
405
- description: 'List indexed docs pages across derod/tela/hologram/deropay with slugs and canonical URLs.',
521
+ }), withStructuredErrors('dero_docs_get_page', async ({ slug, product }) => getDeroDocPage({ slug, product })));
522
+ server.registerTool('dero_docs_list', readOnly({
523
+ description: TOOL_DESCRIPTIONS.dero_docs_list,
406
524
  inputSchema: {
407
525
  product: deroDocProductSchema
408
526
  .optional()
@@ -415,7 +533,7 @@ export function createDeroMcpServer(daemonBaseUrl) {
415
533
  .optional()
416
534
  .describe('Max pages returned (default 120, max 500)'),
417
535
  },
418
- }, withStructuredErrors('dero_docs_list', async ({ product, limit }) => {
536
+ }), withStructuredErrors('dero_docs_list', async ({ product, limit }) => {
419
537
  const docsIndex = await listDeroDocs(product);
420
538
  const capped = Math.max(1, Math.min(limit ?? 120, 500));
421
539
  return {
@@ -424,6 +542,38 @@ export function createDeroMcpServer(daemonBaseUrl) {
424
542
  pages: docsIndex.pages.slice(0, capped),
425
543
  };
426
544
  }));
545
+ // ---------- Composite tools (Phase C) ----------
546
+ // Composites chain read-only primitives and bundled docs into
547
+ // intent-shaped responses. Each composite has a maintainer design contract
548
+ // (input schema, internal chain, response shape, failure modes, flow test ID).
549
+ server.registerTool('diagnose_chain_health', readOnly({
550
+ description: TOOL_DESCRIPTIONS.diagnose_chain_health,
551
+ inputSchema: diagnoseChainHealthInputSchema,
552
+ }), withStructuredErrors('diagnose_chain_health', async (args) => diagnoseChainHealth(rpc, args ?? {})));
553
+ server.registerTool('explain_smart_contract', readOnly({
554
+ description: TOOL_DESCRIPTIONS.explain_smart_contract,
555
+ inputSchema: explainSmartContractInputSchema,
556
+ }), withStructuredErrors('explain_smart_contract', async (args) => explainSmartContract(rpc, args)));
557
+ server.registerTool('recommend_docs_path', readOnly({
558
+ description: TOOL_DESCRIPTIONS.recommend_docs_path,
559
+ inputSchema: recommendDocsPathInputSchema,
560
+ }), withStructuredErrors('recommend_docs_path', async (args) => recommendDocsPath(args)));
561
+ server.registerTool('estimate_deploy_cost', readOnly({
562
+ description: TOOL_DESCRIPTIONS.estimate_deploy_cost,
563
+ inputSchema: estimateDeployCostInputSchema,
564
+ }), withStructuredErrors('estimate_deploy_cost', async (args) => estimateDeployCost(rpc, args)));
565
+ server.registerTool('trace_transaction_with_context', readOnly({
566
+ description: TOOL_DESCRIPTIONS.trace_transaction_with_context,
567
+ inputSchema: traceTransactionWithContextInputSchema,
568
+ }), withStructuredErrors('trace_transaction_with_context', async (args) => traceTransactionWithContext(rpc, args)));
569
+ server.registerTool('audit_chain_artifact_claim', readOnly({
570
+ description: TOOL_DESCRIPTIONS.audit_chain_artifact_claim,
571
+ inputSchema: auditChainArtifactClaimInputSchema,
572
+ }), withStructuredErrors('audit_chain_artifact_claim', async (args) => auditChainArtifactClaim(rpc, args ?? {})));
573
+ server.registerTool('dero_forge_demo_proof', readOnly({
574
+ description: TOOL_DESCRIPTIONS.dero_forge_demo_proof,
575
+ inputSchema: forgeDemoProofInputSchema,
576
+ }), withStructuredErrors('dero_forge_demo_proof', async (args) => forgeDemoProof(rpc, args ?? {})));
427
577
  server.registerResource('dero_mcp_server_info', 'dero://mcp/server-info', {
428
578
  description: 'Server metadata, tool list, resource list, and prompt names.',
429
579
  mimeType: 'application/json',
@@ -434,7 +584,7 @@ export function createDeroMcpServer(daemonBaseUrl) {
434
584
  mimeType: 'application/json',
435
585
  text: JSON.stringify({
436
586
  name: 'dero-daemon-mcp',
437
- version: '0.1.2',
587
+ version: '0.4.0',
438
588
  mode: 'read-only',
439
589
  endpoint: endpoint,
440
590
  docs_products: DERO_DOC_PRODUCTS,
@@ -473,7 +623,7 @@ export function createDeroMcpServer(daemonBaseUrl) {
473
623
  ],
474
624
  }));
475
625
  server.registerResource('dero_mcp_example_flows', 'dero://mcp/example-flows', {
476
- description: 'Compact agent flow recipes for common DERO investigations.',
626
+ description: 'Compact agent flow recipes for common DERO investigations. Composites are listed FIRST; primitives are the fallback path.',
477
627
  mimeType: 'text/markdown',
478
628
  }, async (uri) => ({
479
629
  contents: [
@@ -483,16 +633,97 @@ export function createDeroMcpServer(daemonBaseUrl) {
483
633
  text: [
484
634
  '# DERO MCP Example Flows',
485
635
  '',
486
- '- Network health: `dero_daemon_ping` -> `dero_get_info` -> `dero_get_height`',
487
- `- Inspect SC state: \`dero_get_sc\` with SCID (name registry: \`${NAME_REGISTRY_SCID}\`)`,
488
- '- Trace transaction: `dero_get_transaction` with `decode_as_json: 1`',
489
- '- Read-only boundary: no wallet writes or raw tx submission',
636
+ 'Prefer composites each is one call replacing a primitive chain, and each returns a narrative + curated docs citations.',
637
+ '',
638
+ '## Composites (preferred)',
639
+ '',
640
+ '- **Network health**: call `diagnose_chain_health` (no args). Returns narrative + signals + citations in one shot.',
641
+ `- **Inspect a contract**: call \`explain_smart_contract\` with the SCID. For example, the name registry: \`${NAME_REGISTRY_SCID}\`.`,
642
+ '- **Trace a transaction**: call `trace_transaction_with_context` with the tx_hash. Handles SC install surface extraction inline.',
643
+ '- **Find the right docs**: call `recommend_docs_path` with a natural-language intent (e.g. "deploy a TELA app"). Optional `product_hint` biases the score 1.5x toward that product.',
644
+ '- **Pre-flight a deploy**: call `estimate_deploy_cost` with the DVM-BASIC source. Returns gas estimate + plain-text breakdown + parsed surface.',
645
+ '',
646
+ '## Primitive fallback paths (only when a composite is unavailable or returns _meta.error)',
647
+ '',
648
+ '- Network: `dero_daemon_ping` → `dero_get_info` → `dero_get_height` → `dero_get_tx_pool`',
649
+ '- Contract: `dero_get_sc` (code=true, variables=true) then optionally `dero_docs_get_page`',
650
+ '- Transaction: `dero_get_transaction` (decode_as_json=1) — does NOT decode SC invocation args',
651
+ '- Docs: `dero_docs_search` (then `dero_docs_get_page` for full text)',
652
+ '- Deploy estimate: `dero_get_gas_estimate`',
653
+ '',
654
+ '## Structured error codes (`_meta.error.code`) the agent should react to',
655
+ '',
656
+ '- `NO_DOCS_MATCH` (recommend_docs_path): rephrase the intent, retry. Not a hard failure.',
657
+ '- `INVALID_INPUT` (estimate_deploy_cost): the daemon\'s raw -32098 compile message is in `_meta.error.raw`; surface it to the user.',
658
+ '- `TX_NOT_FOUND` (trace_transaction_with_context): the daemon returned an empty record. Retryable=true (mempool propagation), but only after verifying the hash and network.',
659
+ '',
660
+ '## Read-only boundary',
661
+ '',
662
+ 'No wallet writes. No raw tx submission. No contract invocation. See `dero://mcp/safety-boundary` and `dero://mcp/composites` for the full posture.',
490
663
  ].join('\n'),
491
664
  },
492
665
  ],
493
666
  }));
667
+ server.registerResource('dero_mcp_composites', 'dero://mcp/composites', {
668
+ description: 'Catalog of the 5 composite tools — what each replaces, when to call it, what it returns, and which structured _meta.error codes it can emit. Read this when picking between a composite and a primitive.',
669
+ mimeType: 'application/json',
670
+ }, async (uri) => ({
671
+ contents: [
672
+ {
673
+ uri: uri.toString(),
674
+ mimeType: 'application/json',
675
+ text: JSON.stringify({
676
+ version: 1,
677
+ note: 'Composites fuse one or more daemon-read primitives with bundled-docs lookups and emit a single narrative + curated related_docs. Always prefer the composite when its intent matches the user request.',
678
+ composites: [
679
+ {
680
+ name: 'diagnose_chain_health',
681
+ replaces: ['dero_daemon_ping', 'dero_get_info', 'dero_get_height', 'dero_get_tx_pool'],
682
+ when_to_call: 'User asks "is the chain healthy", "are we synced", "what is the network state", or any general daemon-status question.',
683
+ inputs: { include_tx_pool: 'optional boolean, default true' },
684
+ output_highlights: ['status (healthy | degraded | unreachable)', 'signals (e.g. healthy, stale-tip, lagging)', 'tip metadata', 'narrative', 'related_docs'],
685
+ error_codes: ['RPC_UNREACHABLE'],
686
+ },
687
+ {
688
+ name: 'explain_smart_contract',
689
+ replaces: ['dero_get_sc + manual parsing + dero_docs_search'],
690
+ when_to_call: 'User wants to UNDERSTAND a contract (functions, state shape, what DVM concept to read about). NOT for raw variable inspection — use dero_get_sc for that.',
691
+ inputs: { scid: '64-char hex SCID', topoheight: 'optional number' },
692
+ output_highlights: ['kind (token | registry | minimal | generic)', 'surface (functions, stringkeys, uint64keys, balances)', 'narrative', '1-4 curated DVM docs citations re-ranked for the contract pattern'],
693
+ error_codes: ['RPC_UNREACHABLE', 'RPC_INVALID_PARAMS'],
694
+ },
695
+ {
696
+ name: 'recommend_docs_path',
697
+ replaces: ['4x parallel dero_docs_search calls + manual ranking'],
698
+ when_to_call: 'User has a natural-language intent ("deploy a TELA app", "estimate gas") and needs to know which doc page to read. Bias-not-filter on product_hint.',
699
+ inputs: { intent: 'short natural-language string', product_hint: 'optional derod | tela | hologram | deropay', limit_per_product: 'optional number, default 2' },
700
+ output_highlights: ['recommended[] with score/boosted_score/rationale', 'summary_by_product', 'related_docs'],
701
+ error_codes: ['NO_DOCS_MATCH'],
702
+ },
703
+ {
704
+ name: 'estimate_deploy_cost',
705
+ replaces: ['dero_get_gas_estimate + manual surface extraction + manual interpretation of gascompute/gasstorage'],
706
+ when_to_call: 'User wants to deploy a contract and needs to know what it will cost. Read-only; nothing is submitted.',
707
+ inputs: { sc: 'DVM-BASIC source string', include_breakdown: 'optional boolean, default true' },
708
+ output_highlights: ['estimate (gascompute, gasstorage, total, status)', 'breakdown (compute_note, storage_note) | null', 'surface (functions, stringkeys, uint64keys)'],
709
+ error_codes: ['INVALID_INPUT (wraps daemon -32098 DVM compile errors; raw message in _meta.error.raw)', 'RPC_UNREACHABLE'],
710
+ },
711
+ {
712
+ name: 'trace_transaction_with_context',
713
+ replaces: ['dero_get_transaction + (for SC installs) dero_get_sc + manual classification'],
714
+ when_to_call: 'User asks "what is this tx", "is this confirmed", "what contract did this deploy", "what does this tx do".',
715
+ inputs: { tx_hash: '64-char hex', decode: 'optional boolean, default true', include_sc_context: 'optional boolean, default true' },
716
+ output_highlights: ['confirmation (status, block_height, valid_block, in_pool)', 'kind (sc_install | transfer_or_invocation | coinbase | unknown)', 'ring (groups, first_group_size)', 'sc_install (scid + parsed surface) | null', 'raw_tx_hex_length', 'narrative', 'related_docs'],
717
+ scope_note: 'SC invocation arg decoding is NOT performed (would require the binary tx codec). SC INSTALL surface extraction IS performed inline because the source is embedded in the tx record.',
718
+ error_codes: ['TX_NOT_FOUND (retryable=true; daemon returns empty record on unknown hashes)', 'RPC_UNREACHABLE'],
719
+ },
720
+ ],
721
+ }, null, 2),
722
+ },
723
+ ],
724
+ }));
494
725
  server.registerPrompt('network_health_check', {
495
- description: 'Guide the model through a DERO daemon sync and health check sequence.',
726
+ description: 'Guide the model through a DERO daemon sync and health check using the diagnose_chain_health composite.',
496
727
  argsSchema: {
497
728
  reference_topoheight: z
498
729
  .number()
@@ -501,65 +732,134 @@ export function createDeroMcpServer(daemonBaseUrl) {
501
732
  .optional(),
502
733
  },
503
734
  }, async ({ reference_topoheight }) => ({
504
- description: 'Prompt for sync health investigation.',
735
+ description: 'Prompt for sync health investigation (composite-first).',
505
736
  messages: [
506
737
  {
507
738
  role: 'user',
508
739
  content: {
509
740
  type: 'text',
510
741
  text: [
511
- 'Check DERO daemon health using MCP tools.',
512
- '1) Call dero_daemon_ping.',
513
- '2) Call dero_get_info and dero_get_height.',
514
- '3) Report topoheight, stableheight, version, and network.',
742
+ 'Check DERO daemon health using the MCP composite tools (one call replaces the old four-step chain).',
743
+ '',
744
+ '1) Call diagnose_chain_health with no arguments (or include_tx_pool=true if you specifically want mempool counts).',
745
+ '2) Read the returned narrative aloud; it already summarizes ping latency, topoheight, stableheight, version, network, and mempool state.',
746
+ '3) Inspect signals (e.g. "healthy", "stale-tip", "lagging") and surface any that are not "healthy".',
747
+ '4) Quote the related_docs citations so the user knows where to read further.',
515
748
  reference_topoheight
516
- ? `4) Compare topoheight against reference_topoheight=${reference_topoheight}.`
517
- : '4) If no reference topoheight is provided, state that external comparison is still needed for final sync confidence.',
749
+ ? `5) Compare the returned topoheight against reference_topoheight=${reference_topoheight} and report the delta.`
750
+ : '5) If no reference topoheight was provided, state that external comparison is still needed for final sync confidence.',
751
+ '',
752
+ 'Fallback: only chain primitives manually (dero_daemon_ping → dero_get_info → dero_get_height → dero_get_tx_pool) if diagnose_chain_health is unavailable or returns _meta.error.',
518
753
  ].join('\n'),
519
754
  },
520
755
  },
521
756
  ],
522
757
  }));
523
758
  server.registerPrompt('inspect_smart_contract', {
524
- description: 'Inspect contract code/variables and explain likely state model.',
759
+ description: 'Inspect a DERO contract via the explain_smart_contract composite (function surface + classification + curated DVM docs).',
525
760
  argsSchema: {
526
761
  scid: hex64Schema,
527
762
  },
528
763
  }, async ({ scid }) => ({
529
- description: 'Prompt for smart contract inspection.',
764
+ description: 'Prompt for smart contract inspection (composite-first).',
530
765
  messages: [
531
766
  {
532
767
  role: 'user',
533
768
  content: {
534
769
  type: 'text',
535
770
  text: [
536
- `Inspect DERO smart contract ${scid}.`,
537
- '1) Call dero_get_sc with variables=true and code=true.',
538
- '2) Summarize key stringkeys and balances.',
539
- '3) Explain likely data model and any assumptions.',
540
- '4) Include topoheight context from response.',
771
+ `Investigate DERO smart contract ${scid} using the MCP composite tools.`,
772
+ '',
773
+ `1) Call explain_smart_contract with scid="${scid}". This single call returns the parsed function surface, a contract kind classification (token | registry | minimal | generic), a plain-language narrative, and 1-4 DVM docs citations ranked for the contract pattern.`,
774
+ '2) Quote the narrative as-is — it already explains the likely data model, state keys, and where to read next.',
775
+ '3) If the user wants raw state (variable values, balances), THEN call dero_get_sc with variables=true and code=true as a follow-up; explain why you needed the second call.',
776
+ '4) If you want documentation for a DVM concept the contract uses, call dero_docs_get_page with the slug from one of the related_docs entries.',
777
+ '',
778
+ 'Fallback: call dero_get_sc manually only if explain_smart_contract is unavailable or returns _meta.error.',
541
779
  ].join('\n'),
542
780
  },
543
781
  },
544
782
  ],
545
783
  }));
546
784
  server.registerPrompt('trace_transaction', {
547
- description: 'Trace one transaction and summarize confirmation + SC activity.',
785
+ description: 'Trace one transaction via the trace_transaction_with_context composite (confirmation + kind classification + SC install surface).',
548
786
  argsSchema: {
549
787
  tx_hash: hex64Schema,
550
788
  },
551
789
  }, async ({ tx_hash }) => ({
552
- description: 'Prompt for transaction tracing.',
790
+ description: 'Prompt for transaction tracing (composite-first).',
791
+ messages: [
792
+ {
793
+ role: 'user',
794
+ content: {
795
+ type: 'text',
796
+ text: [
797
+ `Trace DERO transaction ${tx_hash} using the MCP composite tools.`,
798
+ '',
799
+ `1) Call trace_transaction_with_context with tx_hash="${tx_hash}". The response gives you confirmation status (confirmed | mempool | unknown), block height + valid_block, kind classification (sc_install | transfer_or_invocation | coinbase | unknown), ring stats, and — if the tx is a contract install — the parsed function surface inline (no second call needed).`,
800
+ '2) Read the returned narrative aloud. Quote the related_docs citations.',
801
+ '3) If confirmation is "mempool", explicitly note that the result is provisional and tell the user when to retry.',
802
+ '4) If _meta.error.code is TX_NOT_FOUND, do NOT retry blindly — verify the hash, confirm the network (mainnet vs testnet), and only retry if the tx was just broadcast.',
803
+ '5) Note: SC invocation arg decoding is NOT performed by the composite (would require the binary tx codec). If the tx is a non-install SC call and the user needs the entrypoint + args, surface that limitation and suggest a wallet-side decoder.',
804
+ '',
805
+ 'Fallback: call dero_get_transaction manually only if trace_transaction_with_context is unavailable or returns an unhandled _meta.error.',
806
+ ].join('\n'),
807
+ },
808
+ },
809
+ ],
810
+ }));
811
+ server.registerPrompt('find_dero_docs_for_intent', {
812
+ description: 'Find the right DERO documentation page(s) for a natural-language intent via the recommend_docs_path composite.',
813
+ argsSchema: {
814
+ intent: z.string().min(3, 'Provide a short intent like "deploy a TELA app" or "estimate gas"'),
815
+ product_hint: z.enum(DERO_DOC_PRODUCTS).optional(),
816
+ },
817
+ }, async ({ intent, product_hint }) => ({
818
+ description: 'Prompt for routing an agent intent to the right DERO docs.',
819
+ messages: [
820
+ {
821
+ role: 'user',
822
+ content: {
823
+ type: 'text',
824
+ text: [
825
+ `Find the best DERO documentation page(s) for the intent: "${intent}".`,
826
+ '',
827
+ `1) Call recommend_docs_path with intent="${intent}"${product_hint ? `, product_hint="${product_hint}" (this biases the score 1.5x toward that product; it does NOT filter the other three out)` : ' (no product_hint — all four DERO products will be searched in parallel)'}.`,
828
+ '2) Read the top 2-3 recommendations to the user with their rationale strings; quote the canonical URLs.',
829
+ '3) If the user wants the full content of any page, call dero_docs_get_page with the slug + product from the recommendation.',
830
+ '4) If _meta.error.code is NO_DOCS_MATCH, rephrase the intent (drop verbs, use product nouns like "TELA app" or "DVM contract") and call again. Do NOT just give up.',
831
+ '',
832
+ 'Prefer this composite over chaining dero_docs_search yourself across four products.',
833
+ ].join('\n'),
834
+ },
835
+ },
836
+ ],
837
+ }));
838
+ server.registerPrompt('estimate_deploy_for_contract', {
839
+ description: 'Run gas pre-flight for a DVM-BASIC contract source via the estimate_deploy_cost composite (numeric estimate + plain-text breakdown + parsed surface).',
840
+ argsSchema: {
841
+ sc_source: z.string().min(20, 'Provide DVM-BASIC contract source (at minimum: a Function/End Function block)'),
842
+ include_breakdown: z.boolean().optional(),
843
+ },
844
+ }, async ({ sc_source, include_breakdown }) => ({
845
+ description: 'Prompt for DVM deploy pre-flight (composite-first).',
553
846
  messages: [
554
847
  {
555
848
  role: 'user',
556
849
  content: {
557
850
  type: 'text',
558
851
  text: [
559
- `Trace DERO transaction ${tx_hash}.`,
560
- '1) Call dero_get_transaction with txs_hashes=[tx_hash] and decode_as_json=1.',
561
- '2) Summarize confirmation status, block height, transfers, and SC invokes.',
562
- '3) If not confirmed, mention mempool status uncertainty and next check timing.',
852
+ 'Run a deploy pre-flight (gas estimate) for the DVM-BASIC source the user supplied. This is read-only; nothing is submitted to chain.',
853
+ '',
854
+ `1) Call estimate_deploy_cost with the contract source as sc${include_breakdown === false ? ' and include_breakdown=false (caller does NOT want the plain-text gas notes)' : ' (include_breakdown defaults to true)'}.`,
855
+ '2) Quote estimate.gascompute, estimate.gasstorage, estimate.total, and the daemon\'s status string.',
856
+ '3) If include_breakdown is true, read the breakdown.compute_note and breakdown.storage_note as plain-language explanations.',
857
+ '4) Quote the parsed function surface (functions[].name) so the user can sanity-check the contract.',
858
+ '5) If _meta.error.code is INVALID_INPUT, read the hint and the raw -32098 compile message from _meta.error.raw verbatim — that tells the user what to fix in the source.',
859
+ '',
860
+ `Source (${sc_source.length} chars) starts with: ${sc_source.slice(0, 120)}${sc_source.length > 120 ? '...' : ''}`,
861
+ '',
862
+ 'Fallback: call dero_get_gas_estimate manually only if estimate_deploy_cost is unavailable.',
563
863
  ].join('\n'),
564
864
  },
565
865
  },