@scriptmasterlabs/mcp-x402 2.0.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 (304) hide show
  1. package/.env.example +35 -0
  2. package/.github/workflows/ci.yml +59 -0
  3. package/.github/workflows/keepalive.yml +31 -0
  4. package/.well-known/agentcard.json +34 -0
  5. package/CONTRIBUTING.md +76 -0
  6. package/Dockerfile +19 -0
  7. package/LICENSE +21 -0
  8. package/README.md +304 -0
  9. package/agents.json +67 -0
  10. package/dist/lib/chains/base.d.ts +10 -0
  11. package/dist/lib/chains/base.d.ts.map +1 -0
  12. package/dist/lib/chains/base.js +73 -0
  13. package/dist/lib/chains/base.js.map +1 -0
  14. package/dist/lib/chains/solana.d.ts +10 -0
  15. package/dist/lib/chains/solana.d.ts.map +1 -0
  16. package/dist/lib/chains/solana.js +49 -0
  17. package/dist/lib/chains/solana.js.map +1 -0
  18. package/dist/lib/chains/xrpl.d.ts +10 -0
  19. package/dist/lib/chains/xrpl.d.ts.map +1 -0
  20. package/dist/lib/chains/xrpl.js +55 -0
  21. package/dist/lib/chains/xrpl.js.map +1 -0
  22. package/dist/lib/credit/bureau.d.ts +10 -0
  23. package/dist/lib/credit/bureau.d.ts.map +1 -0
  24. package/dist/lib/credit/bureau.js +58 -0
  25. package/dist/lib/credit/bureau.js.map +1 -0
  26. package/dist/lib/sml-api/agentcard.d.ts +17 -0
  27. package/dist/lib/sml-api/agentcard.d.ts.map +1 -0
  28. package/dist/lib/sml-api/agentcard.js +30 -0
  29. package/dist/lib/sml-api/agentcard.js.map +1 -0
  30. package/dist/lib/sml-api/backtest.d.ts +22 -0
  31. package/dist/lib/sml-api/backtest.d.ts.map +1 -0
  32. package/dist/lib/sml-api/backtest.js +28 -0
  33. package/dist/lib/sml-api/backtest.js.map +1 -0
  34. package/dist/lib/sml-api/brokers.d.ts +40 -0
  35. package/dist/lib/sml-api/brokers.d.ts.map +1 -0
  36. package/dist/lib/sml-api/brokers.js +128 -0
  37. package/dist/lib/sml-api/brokers.js.map +1 -0
  38. package/dist/lib/sml-api/copytrader.d.ts +11 -0
  39. package/dist/lib/sml-api/copytrader.d.ts.map +1 -0
  40. package/dist/lib/sml-api/copytrader.js +30 -0
  41. package/dist/lib/sml-api/copytrader.js.map +1 -0
  42. package/dist/lib/sml-api/crawl.d.ts +20 -0
  43. package/dist/lib/sml-api/crawl.d.ts.map +1 -0
  44. package/dist/lib/sml-api/crawl.js +32 -0
  45. package/dist/lib/sml-api/crawl.js.map +1 -0
  46. package/dist/lib/sml-api/echo.d.ts +10 -0
  47. package/dist/lib/sml-api/echo.d.ts.map +1 -0
  48. package/dist/lib/sml-api/echo.js +23 -0
  49. package/dist/lib/sml-api/echo.js.map +1 -0
  50. package/dist/lib/sml-api/forge.d.ts +11 -0
  51. package/dist/lib/sml-api/forge.d.ts.map +1 -0
  52. package/dist/lib/sml-api/forge.js +29 -0
  53. package/dist/lib/sml-api/forge.js.map +1 -0
  54. package/dist/lib/sml-api/ftd.d.ts +18 -0
  55. package/dist/lib/sml-api/ftd.d.ts.map +1 -0
  56. package/dist/lib/sml-api/ftd.js +43 -0
  57. package/dist/lib/sml-api/ftd.js.map +1 -0
  58. package/dist/lib/sml-api/ghost.d.ts +13 -0
  59. package/dist/lib/sml-api/ghost.d.ts.map +1 -0
  60. package/dist/lib/sml-api/ghost.js +29 -0
  61. package/dist/lib/sml-api/ghost.js.map +1 -0
  62. package/dist/lib/sml-api/launchpad.d.ts +20 -0
  63. package/dist/lib/sml-api/launchpad.d.ts.map +1 -0
  64. package/dist/lib/sml-api/launchpad.js +31 -0
  65. package/dist/lib/sml-api/launchpad.js.map +1 -0
  66. package/dist/lib/sml-api/leviathan.d.ts +22 -0
  67. package/dist/lib/sml-api/leviathan.d.ts.map +1 -0
  68. package/dist/lib/sml-api/leviathan.js +33 -0
  69. package/dist/lib/sml-api/leviathan.js.map +1 -0
  70. package/dist/lib/sml-api/nexus.d.ts +18 -0
  71. package/dist/lib/sml-api/nexus.d.ts.map +1 -0
  72. package/dist/lib/sml-api/nexus.js +40 -0
  73. package/dist/lib/sml-api/nexus.js.map +1 -0
  74. package/dist/lib/sml-api/proof402.d.ts +6 -0
  75. package/dist/lib/sml-api/proof402.d.ts.map +1 -0
  76. package/dist/lib/sml-api/proof402.js +30 -0
  77. package/dist/lib/sml-api/proof402.js.map +1 -0
  78. package/dist/lib/sml-api/rails.d.ts +12 -0
  79. package/dist/lib/sml-api/rails.d.ts.map +1 -0
  80. package/dist/lib/sml-api/rails.js +29 -0
  81. package/dist/lib/sml-api/rails.js.map +1 -0
  82. package/dist/lib/sml-api/shadow.d.ts +15 -0
  83. package/dist/lib/sml-api/shadow.d.ts.map +1 -0
  84. package/dist/lib/sml-api/shadow.js +27 -0
  85. package/dist/lib/sml-api/shadow.js.map +1 -0
  86. package/dist/lib/sml-api/squeezeos.d.ts +21 -0
  87. package/dist/lib/sml-api/squeezeos.d.ts.map +1 -0
  88. package/dist/lib/sml-api/squeezeos.js +97 -0
  89. package/dist/lib/sml-api/squeezeos.js.map +1 -0
  90. package/dist/lib/sml-api/xdeo.d.ts +13 -0
  91. package/dist/lib/sml-api/xdeo.d.ts.map +1 -0
  92. package/dist/lib/sml-api/xdeo.js +34 -0
  93. package/dist/lib/sml-api/xdeo.js.map +1 -0
  94. package/dist/lib/sml-api/xmit.d.ts +13 -0
  95. package/dist/lib/sml-api/xmit.d.ts.map +1 -0
  96. package/dist/lib/sml-api/xmit.js +34 -0
  97. package/dist/lib/sml-api/xmit.js.map +1 -0
  98. package/dist/server/health.d.ts +16 -0
  99. package/dist/server/health.d.ts.map +1 -0
  100. package/dist/server/health.js +39 -0
  101. package/dist/server/health.js.map +1 -0
  102. package/dist/server/index.d.ts +3 -0
  103. package/dist/server/index.d.ts.map +1 -0
  104. package/dist/server/index.js +193 -0
  105. package/dist/server/index.js.map +1 -0
  106. package/dist/server/payments/ap2.d.ts +17 -0
  107. package/dist/server/payments/ap2.d.ts.map +1 -0
  108. package/dist/server/payments/ap2.js +75 -0
  109. package/dist/server/payments/ap2.js.map +1 -0
  110. package/dist/server/payments/receipt.d.ts +28 -0
  111. package/dist/server/payments/receipt.d.ts.map +1 -0
  112. package/dist/server/payments/receipt.js +60 -0
  113. package/dist/server/payments/receipt.js.map +1 -0
  114. package/dist/server/payments/router.d.ts +23 -0
  115. package/dist/server/payments/router.d.ts.map +1 -0
  116. package/dist/server/payments/router.js +69 -0
  117. package/dist/server/payments/router.js.map +1 -0
  118. package/dist/server/payments/wallet.d.ts +18 -0
  119. package/dist/server/payments/wallet.d.ts.map +1 -0
  120. package/dist/server/payments/wallet.js +107 -0
  121. package/dist/server/payments/wallet.js.map +1 -0
  122. package/dist/server/payments/x402.d.ts +29 -0
  123. package/dist/server/payments/x402.d.ts.map +1 -0
  124. package/dist/server/payments/x402.js +122 -0
  125. package/dist/server/payments/x402.js.map +1 -0
  126. package/dist/server/registry/catalog.d.ts +12 -0
  127. package/dist/server/registry/catalog.d.ts.map +1 -0
  128. package/dist/server/registry/catalog.js +55 -0
  129. package/dist/server/registry/catalog.js.map +1 -0
  130. package/dist/server/registry/discovery.d.ts +16 -0
  131. package/dist/server/registry/discovery.d.ts.map +1 -0
  132. package/dist/server/registry/discovery.js +33 -0
  133. package/dist/server/registry/discovery.js.map +1 -0
  134. package/dist/server/registry/pricing.d.ts +10 -0
  135. package/dist/server/registry/pricing.d.ts.map +1 -0
  136. package/dist/server/registry/pricing.js +66 -0
  137. package/dist/server/registry/pricing.js.map +1 -0
  138. package/dist/server/security/acl.d.ts +28 -0
  139. package/dist/server/security/acl.d.ts.map +1 -0
  140. package/dist/server/security/acl.js +36 -0
  141. package/dist/server/security/acl.js.map +1 -0
  142. package/dist/server/security/audit.d.ts +15 -0
  143. package/dist/server/security/audit.d.ts.map +1 -0
  144. package/dist/server/security/audit.js +77 -0
  145. package/dist/server/security/audit.js.map +1 -0
  146. package/dist/server/security/rate-limit.d.ts +12 -0
  147. package/dist/server/security/rate-limit.d.ts.map +1 -0
  148. package/dist/server/security/rate-limit.js +72 -0
  149. package/dist/server/security/rate-limit.js.map +1 -0
  150. package/dist/server/security/sandbox.d.ts +7 -0
  151. package/dist/server/security/sandbox.d.ts.map +1 -0
  152. package/dist/server/security/sandbox.js +42 -0
  153. package/dist/server/security/sandbox.js.map +1 -0
  154. package/dist/server/tools/agentcard.d.ts +3 -0
  155. package/dist/server/tools/agentcard.d.ts.map +1 -0
  156. package/dist/server/tools/agentcard.js +118 -0
  157. package/dist/server/tools/agentcard.js.map +1 -0
  158. package/dist/server/tools/backtest.d.ts +3 -0
  159. package/dist/server/tools/backtest.d.ts.map +1 -0
  160. package/dist/server/tools/backtest.js +112 -0
  161. package/dist/server/tools/backtest.js.map +1 -0
  162. package/dist/server/tools/brokers.d.ts +3 -0
  163. package/dist/server/tools/brokers.d.ts.map +1 -0
  164. package/dist/server/tools/brokers.js +223 -0
  165. package/dist/server/tools/brokers.js.map +1 -0
  166. package/dist/server/tools/copytrader.d.ts +3 -0
  167. package/dist/server/tools/copytrader.d.ts.map +1 -0
  168. package/dist/server/tools/copytrader.js +90 -0
  169. package/dist/server/tools/copytrader.js.map +1 -0
  170. package/dist/server/tools/crawl.d.ts +3 -0
  171. package/dist/server/tools/crawl.d.ts.map +1 -0
  172. package/dist/server/tools/crawl.js +60 -0
  173. package/dist/server/tools/crawl.js.map +1 -0
  174. package/dist/server/tools/discovery.d.ts +3 -0
  175. package/dist/server/tools/discovery.d.ts.map +1 -0
  176. package/dist/server/tools/discovery.js +188 -0
  177. package/dist/server/tools/discovery.js.map +1 -0
  178. package/dist/server/tools/echo.d.ts +3 -0
  179. package/dist/server/tools/echo.d.ts.map +1 -0
  180. package/dist/server/tools/echo.js +48 -0
  181. package/dist/server/tools/echo.js.map +1 -0
  182. package/dist/server/tools/forge.d.ts +3 -0
  183. package/dist/server/tools/forge.d.ts.map +1 -0
  184. package/dist/server/tools/forge.js +77 -0
  185. package/dist/server/tools/forge.js.map +1 -0
  186. package/dist/server/tools/ftd.d.ts +3 -0
  187. package/dist/server/tools/ftd.d.ts.map +1 -0
  188. package/dist/server/tools/ftd.js +70 -0
  189. package/dist/server/tools/ftd.js.map +1 -0
  190. package/dist/server/tools/ghost.d.ts +3 -0
  191. package/dist/server/tools/ghost.d.ts.map +1 -0
  192. package/dist/server/tools/ghost.js +83 -0
  193. package/dist/server/tools/ghost.js.map +1 -0
  194. package/dist/server/tools/index.d.ts +3 -0
  195. package/dist/server/tools/index.d.ts.map +1 -0
  196. package/dist/server/tools/index.js +44 -0
  197. package/dist/server/tools/index.js.map +1 -0
  198. package/dist/server/tools/launchpad.d.ts +3 -0
  199. package/dist/server/tools/launchpad.d.ts.map +1 -0
  200. package/dist/server/tools/launchpad.js +151 -0
  201. package/dist/server/tools/launchpad.js.map +1 -0
  202. package/dist/server/tools/leviathan.d.ts +3 -0
  203. package/dist/server/tools/leviathan.d.ts.map +1 -0
  204. package/dist/server/tools/leviathan.js +73 -0
  205. package/dist/server/tools/leviathan.js.map +1 -0
  206. package/dist/server/tools/nexus.d.ts +3 -0
  207. package/dist/server/tools/nexus.d.ts.map +1 -0
  208. package/dist/server/tools/nexus.js +65 -0
  209. package/dist/server/tools/nexus.js.map +1 -0
  210. package/dist/server/tools/proof402.d.ts +3 -0
  211. package/dist/server/tools/proof402.d.ts.map +1 -0
  212. package/dist/server/tools/proof402.js +74 -0
  213. package/dist/server/tools/proof402.js.map +1 -0
  214. package/dist/server/tools/rails.d.ts +3 -0
  215. package/dist/server/tools/rails.d.ts.map +1 -0
  216. package/dist/server/tools/rails.js +82 -0
  217. package/dist/server/tools/rails.js.map +1 -0
  218. package/dist/server/tools/shadow.d.ts +3 -0
  219. package/dist/server/tools/shadow.d.ts.map +1 -0
  220. package/dist/server/tools/shadow.js +114 -0
  221. package/dist/server/tools/shadow.js.map +1 -0
  222. package/dist/server/tools/squeezeos.d.ts +3 -0
  223. package/dist/server/tools/squeezeos.d.ts.map +1 -0
  224. package/dist/server/tools/squeezeos.js +231 -0
  225. package/dist/server/tools/squeezeos.js.map +1 -0
  226. package/dist/server/tools/xdeo.d.ts +3 -0
  227. package/dist/server/tools/xdeo.d.ts.map +1 -0
  228. package/dist/server/tools/xdeo.js +58 -0
  229. package/dist/server/tools/xdeo.js.map +1 -0
  230. package/dist/server/tools/xmit.d.ts +3 -0
  231. package/dist/server/tools/xmit.d.ts.map +1 -0
  232. package/dist/server/tools/xmit.js +59 -0
  233. package/dist/server/tools/xmit.js.map +1 -0
  234. package/docker-compose.yml +50 -0
  235. package/llms.txt +70 -0
  236. package/package.json +77 -0
  237. package/render.yaml +39 -0
  238. package/sdk/mcp-x402-sdk/package.json +18 -0
  239. package/sdk/mcp-x402-sdk/src/index.ts +118 -0
  240. package/sdk/mcp-x402-sdk/tsconfig.json +14 -0
  241. package/server.json +60 -0
  242. package/services/backtest_service.py +176 -0
  243. package/src/lib/chains/base.ts +77 -0
  244. package/src/lib/chains/solana.ts +59 -0
  245. package/src/lib/chains/xrpl.ts +63 -0
  246. package/src/lib/credit/bureau.ts +65 -0
  247. package/src/lib/sml-api/agentcard.ts +40 -0
  248. package/src/lib/sml-api/backtest.ts +47 -0
  249. package/src/lib/sml-api/brokers.ts +160 -0
  250. package/src/lib/sml-api/copytrader.ts +33 -0
  251. package/src/lib/sml-api/crawl.ts +44 -0
  252. package/src/lib/sml-api/echo.ts +28 -0
  253. package/src/lib/sml-api/forge.ts +33 -0
  254. package/src/lib/sml-api/ftd.ts +53 -0
  255. package/src/lib/sml-api/ghost.ts +35 -0
  256. package/src/lib/sml-api/launchpad.ts +43 -0
  257. package/src/lib/sml-api/leviathan.ts +49 -0
  258. package/src/lib/sml-api/nexus.ts +50 -0
  259. package/src/lib/sml-api/proof402.ts +27 -0
  260. package/src/lib/sml-api/rails.ts +34 -0
  261. package/src/lib/sml-api/shadow.ts +35 -0
  262. package/src/lib/sml-api/squeezeos.ts +95 -0
  263. package/src/lib/sml-api/xdeo.ts +40 -0
  264. package/src/lib/sml-api/xmit.ts +40 -0
  265. package/src/server/health.ts +52 -0
  266. package/src/server/index.ts +206 -0
  267. package/src/server/payments/ap2.ts +99 -0
  268. package/src/server/payments/receipt.ts +85 -0
  269. package/src/server/payments/router.ts +110 -0
  270. package/src/server/payments/wallet.ts +123 -0
  271. package/src/server/payments/x402.ts +162 -0
  272. package/src/server/registry/catalog.ts +61 -0
  273. package/src/server/registry/discovery.ts +39 -0
  274. package/src/server/registry/pricing.ts +76 -0
  275. package/src/server/security/acl.ts +42 -0
  276. package/src/server/security/audit.ts +94 -0
  277. package/src/server/security/rate-limit.ts +84 -0
  278. package/src/server/security/sandbox.ts +40 -0
  279. package/src/server/tools/agentcard.ts +134 -0
  280. package/src/server/tools/backtest.ts +119 -0
  281. package/src/server/tools/brokers.ts +250 -0
  282. package/src/server/tools/copytrader.ts +104 -0
  283. package/src/server/tools/crawl.ts +70 -0
  284. package/src/server/tools/discovery.ts +202 -0
  285. package/src/server/tools/echo.ts +58 -0
  286. package/src/server/tools/forge.ts +87 -0
  287. package/src/server/tools/ftd.ts +88 -0
  288. package/src/server/tools/ghost.ts +93 -0
  289. package/src/server/tools/index.ts +42 -0
  290. package/src/server/tools/launchpad.ts +173 -0
  291. package/src/server/tools/leviathan.ts +81 -0
  292. package/src/server/tools/nexus.ts +76 -0
  293. package/src/server/tools/proof402.ts +87 -0
  294. package/src/server/tools/rails.ts +92 -0
  295. package/src/server/tools/shadow.ts +128 -0
  296. package/src/server/tools/squeezeos.ts +312 -0
  297. package/src/server/tools/xdeo.ts +67 -0
  298. package/src/server/tools/xmit.ts +68 -0
  299. package/tests/integration/e2e.test.ts +51 -0
  300. package/tests/unit/payments.test.ts +49 -0
  301. package/tests/unit/security.test.ts +92 -0
  302. package/tests/unit/tools.test.ts +42 -0
  303. package/tsconfig.json +21 -0
  304. package/vitest.config.ts +20 -0
@@ -0,0 +1,134 @@
1
+ import { z } from 'zod';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { executeX402Payment } from '../payments/x402.js';
4
+ import { RateLimiter } from '../security/rate-limit.js';
5
+ import { Sandbox } from '../security/sandbox.js';
6
+ import { AuditLogger } from '../security/audit.js';
7
+ import { PriceRegistry } from '../registry/pricing.js';
8
+ import { AgentCardAPI } from '../../lib/sml-api/agentcard.js';
9
+
10
+ const LookupSchema = z.object({
11
+ identifier: z.string().min(1),
12
+ });
13
+
14
+ const VerifySchema = z.object({
15
+ wallet_address: z.string().min(10),
16
+ message: z.string().min(1),
17
+ signature: z.string().min(1),
18
+ });
19
+
20
+ const MintSchema = z.object({
21
+ wallet_address: z.string().min(10),
22
+ name: z.string().min(1).max(64),
23
+ did: z.string().optional(),
24
+ metadata: z.record(z.unknown()).optional(),
25
+ payment_wallet: z.string().optional(),
26
+ });
27
+
28
+ export function registerAgentCard(server: McpServer): void {
29
+ const audit = AuditLogger.getInstance();
30
+
31
+ // ── FREE: agentcard_lookup ─────────────────────────────────────────────────
32
+ server.tool(
33
+ 'agentcard_lookup',
34
+ {
35
+ identifier: z.string().describe('Agent wallet address or DID to look up.'),
36
+ },
37
+ async (rawArgs) => {
38
+ const { identifier } = Sandbox.validate(LookupSchema, rawArgs);
39
+ if (!RateLimiter.getInstance().checkTool('agentcard_lookup')) {
40
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'rate_limit_exceeded', retry_after: 60 }) }], isError: true };
41
+ }
42
+ try {
43
+ const data = await AgentCardAPI.lookup(identifier);
44
+ audit.info('agentcard_lookup', { identifier });
45
+ return { content: [{ type: 'text', text: JSON.stringify(data) }] };
46
+ } catch (err) {
47
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'api_error', message: String(err) }) }], isError: true };
48
+ }
49
+ },
50
+ );
51
+
52
+ // ── FREE: agentcard_verify ─────────────────────────────────────────────────
53
+ server.tool(
54
+ 'agentcard_verify',
55
+ {
56
+ wallet_address: z.string().describe('Agent wallet address that signed the message.'),
57
+ message: z.string().describe('Original message that was signed.'),
58
+ signature: z.string().describe('Ed25519 signature (hex or base64).'),
59
+ },
60
+ async (rawArgs) => {
61
+ const args = Sandbox.validate(VerifySchema, rawArgs);
62
+ if (!RateLimiter.getInstance().checkTool('agentcard_verify')) {
63
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'rate_limit_exceeded', retry_after: 60 }) }], isError: true };
64
+ }
65
+ try {
66
+ const data = await AgentCardAPI.verify({
67
+ walletAddress: args.wallet_address,
68
+ message: args.message,
69
+ signature: args.signature,
70
+ });
71
+ audit.info('agentcard_verify', { wallet_address: args.wallet_address });
72
+ return { content: [{ type: 'text', text: JSON.stringify(data) }] };
73
+ } catch (err) {
74
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'api_error', message: String(err) }) }], isError: true };
75
+ }
76
+ },
77
+ );
78
+
79
+ // ── PAID: agentcard_mint (0.01 USDC) ──────────────────────────────────────
80
+ server.tool(
81
+ 'agentcard_mint',
82
+ {
83
+ wallet_address: z.string().describe('XRPL wallet address for the new agent identity.'),
84
+ name: z.string().describe('Human-readable agent name (max 64 chars).'),
85
+ did: z.string().describe('Optional DID (decentralized identifier) for the agent.'),
86
+ metadata: z.record(z.unknown()).describe('Optional metadata object (capabilities, version, etc.).'),
87
+ payment_wallet: z.string().describe('Wallet to pay x402 fee from (defaults to wallet_address).'),
88
+ },
89
+ async (rawArgs) => {
90
+ const args = Sandbox.validate(MintSchema, rawArgs);
91
+ const paymentWallet = args.payment_wallet ?? args.wallet_address;
92
+
93
+ if (!RateLimiter.getInstance().checkTool('agentcard_mint')) {
94
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'rate_limit_exceeded', retry_after: 60 }) }], isError: true };
95
+ }
96
+
97
+ await PriceRegistry.getInstance().seedDefaults();
98
+ const price = await PriceRegistry.getInstance().getPrice('agentcard_mint');
99
+ if (!price) {
100
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'price_unavailable' }) }], isError: true };
101
+ }
102
+
103
+ let payment;
104
+ try {
105
+ payment = await executeX402Payment({ price, currency: 'USDC', toolName: 'agentcard_mint', walletAddress: paymentWallet });
106
+ } catch (err) {
107
+ audit.warn('agentcard_mint_payment_fail', { error: String(err) });
108
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'payment_failed', message: String(err) }) }], isError: true };
109
+ }
110
+
111
+ try {
112
+ const data = await AgentCardAPI.mint({
113
+ walletAddress: args.wallet_address,
114
+ name: args.name,
115
+ did: args.did,
116
+ metadata: args.metadata,
117
+ });
118
+ audit.info('agentcard_mint_success', { name: args.name, receiptId: payment.receiptId });
119
+ return {
120
+ content: [{
121
+ type: 'text',
122
+ text: JSON.stringify({
123
+ data,
124
+ _meta: { receipt_id: payment.receiptId, tx_hash: payment.txHash, chain: payment.chain, amount_paid: `${payment.amountPaid} ${payment.currency}`, timestamp: payment.timestamp },
125
+ }),
126
+ }],
127
+ };
128
+ } catch (err) {
129
+ audit.error('agentcard_mint_api_fail', { error: String(err) });
130
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'api_error', message: String(err) }) }], isError: true };
131
+ }
132
+ },
133
+ );
134
+ }
@@ -0,0 +1,119 @@
1
+ import { z } from 'zod';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { executeX402Payment } from '../payments/x402.js';
4
+ import { RateLimiter } from '../security/rate-limit.js';
5
+ import { Sandbox } from '../security/sandbox.js';
6
+ import { AuditLogger } from '../security/audit.js';
7
+ import { PriceRegistry } from '../registry/pricing.js';
8
+ import { BacktestAPI } from '../../lib/sml-api/backtest.js';
9
+
10
+ const BacktestSchema = z.object({
11
+ ticker: z.string().min(1).max(10).toUpperCase(),
12
+ lookback_days: z.number().int().min(30).max(1260).default(252),
13
+ fees: z.number().min(0).max(0.05).default(0.001),
14
+ slippage: z.number().min(0).max(0.05).default(0.0005),
15
+ momentum_window: z.number().int().min(2).max(50).default(10),
16
+ momentum_threshold: z.number().min(0).max(0.1).default(0.001),
17
+ wallet_address: z.string().optional(),
18
+ });
19
+
20
+ const ValidateSchema = z.object({
21
+ ticker: z.string().min(1).max(10).toUpperCase(),
22
+ lookback_days: z.number().int().min(60).max(1260).default(504),
23
+ train_ratio: z.number().min(0.5).max(0.9).default(0.7),
24
+ fees: z.number().min(0).max(0.05).default(0.001),
25
+ slippage: z.number().min(0).max(0.05).default(0.0005),
26
+ wallet_address: z.string().optional(),
27
+ });
28
+
29
+ export function registerBacktest(server: McpServer): void {
30
+ const audit = AuditLogger.getInstance();
31
+
32
+ // ── backtest_run — full backtest on live price data (FREE) ─────────────────
33
+ server.tool(
34
+ 'backtest_run',
35
+ {
36
+ ticker: z.string().describe('Ticker symbol (e.g. NVDA, SPY, GME)'),
37
+ lookback_days: z.number().describe('Days of history to backtest (30–1260, default 252)'),
38
+ fees: z.number().describe('Round-trip commission rate (default 0.001 = 0.1%)'),
39
+ slippage: z.number().describe('Slippage per side (default 0.0005)'),
40
+ momentum_window: z.number().describe('Momentum rolling window in days (default 10)'),
41
+ momentum_threshold: z.number().describe('Minimum momentum to enter long (default 0.001)'),
42
+ wallet_address: z.string().describe('Agent wallet address (optional)'),
43
+ },
44
+ async (rawArgs) => {
45
+ const args = Sandbox.validate(BacktestSchema, rawArgs);
46
+ if (!RateLimiter.getInstance().checkTool('backtest_run')) {
47
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'rate_limit_exceeded' }) }], isError: true };
48
+ }
49
+ try {
50
+ const result = await BacktestAPI.backtest({
51
+ ticker: args.ticker,
52
+ lookback_days: args.lookback_days,
53
+ fees: args.fees,
54
+ slippage: args.slippage,
55
+ momentum_window: args.momentum_window,
56
+ momentum_threshold: args.momentum_threshold,
57
+ });
58
+ audit.info('backtest_run_success', { ticker: args.ticker });
59
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
60
+ } catch (err) {
61
+ audit.warn('backtest_run_fail', { error: String(err) });
62
+ return { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }], isError: true };
63
+ }
64
+ },
65
+ );
66
+
67
+ // ── backtest_validate — walk-forward OOS validation ($0.02) ───────────────
68
+ server.tool(
69
+ 'backtest_validate',
70
+ {
71
+ ticker: z.string().describe('Ticker symbol to validate'),
72
+ lookback_days: z.number().describe('Total history window (60–1260, default 504 = 2 years)'),
73
+ train_ratio: z.number().describe('Train/test split ratio (default 0.7 = 70% in-sample)'),
74
+ fees: z.number().describe('Round-trip commission rate (default 0.001)'),
75
+ slippage: z.number().describe('Slippage per side (default 0.0005)'),
76
+ wallet_address: z.string().describe('Agent wallet for x402 payment (AP2 required)'),
77
+ },
78
+ async (rawArgs) => {
79
+ const args = Sandbox.validate(ValidateSchema, rawArgs);
80
+ if (!RateLimiter.getInstance().checkTool('backtest_validate')) {
81
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'rate_limit_exceeded' }) }], isError: true };
82
+ }
83
+ await PriceRegistry.getInstance().seedDefaults();
84
+ const price = await PriceRegistry.getInstance().getPrice('backtest_validate');
85
+ if (!price) {
86
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'price_unavailable' }) }], isError: true };
87
+ }
88
+ let payment;
89
+ try {
90
+ payment = await executeX402Payment({ price, currency: 'USDC', toolName: 'backtest_validate', walletAddress: args.wallet_address });
91
+ } catch (err) {
92
+ audit.warn('backtest_validate_payment_fail', { error: String(err) });
93
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'payment_failed', message: String(err) }) }], isError: true };
94
+ }
95
+ try {
96
+ const result = await BacktestAPI.walkForward({
97
+ ticker: args.ticker,
98
+ lookback_days: args.lookback_days,
99
+ train_ratio: args.train_ratio,
100
+ fees: args.fees,
101
+ slippage: args.slippage,
102
+ });
103
+ audit.info('backtest_validate_success', { ticker: args.ticker });
104
+ return {
105
+ content: [{
106
+ type: 'text',
107
+ text: JSON.stringify({
108
+ ...(result as object),
109
+ _meta: { receipt_id: payment.receiptId, tx_hash: payment.txHash, chain: payment.chain, amount_paid: `${payment.amountPaid} ${payment.currency}` },
110
+ }),
111
+ }],
112
+ };
113
+ } catch (err) {
114
+ audit.warn('backtest_validate_fail', { error: String(err) });
115
+ return { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }], isError: true };
116
+ }
117
+ },
118
+ );
119
+ }
@@ -0,0 +1,250 @@
1
+ import { z } from 'zod';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { executeX402Payment } from '../payments/x402.js';
4
+ import { RateLimiter } from '../security/rate-limit.js';
5
+ import { Sandbox } from '../security/sandbox.js';
6
+ import { AuditLogger } from '../security/audit.js';
7
+ import { PriceRegistry } from '../registry/pricing.js';
8
+ import { TradierAPI, RobinhoodAPI } from '../../lib/sml-api/brokers.js';
9
+
10
+ // ── Schemas ────────────────────────────────────────────────────────────────
11
+
12
+ const TradierQuoteSchema = z.object({
13
+ symbols: z.string().min(1).max(200),
14
+ });
15
+
16
+ const TradierOrderSchema = z.object({
17
+ account_id: z.string().min(1),
18
+ symbol: z.string().min(1).max(10).toUpperCase(),
19
+ side: z.enum(['buy', 'sell']),
20
+ quantity: z.number().int().min(1).max(10000),
21
+ type: z.enum(['market', 'limit', 'stop', 'stop_limit']),
22
+ duration: z.enum(['day', 'gtc', 'pre', 'post']),
23
+ price: z.number().positive().optional(),
24
+ stop: z.number().positive().optional(),
25
+ wallet_address: z.string().optional(),
26
+ });
27
+
28
+ const RobinhoodQuoteSchema = z.object({
29
+ symbol: z.string().min(1).max(10).toUpperCase(),
30
+ });
31
+
32
+ const RobinhoodOrderSchema = z.object({
33
+ symbol: z.string().min(1).max(10).toUpperCase(),
34
+ side: z.enum(['buy', 'sell']),
35
+ quantity: z.number().int().min(1).max(10000),
36
+ type: z.enum(['market', 'limit']),
37
+ time_in_force: z.enum(['gfd', 'gtc', 'ioc', 'opg']),
38
+ price: z.number().positive().optional(),
39
+ wallet_address: z.string().optional(),
40
+ });
41
+
42
+ const AccountSchema = z.object({
43
+ account_id: z.string().min(1),
44
+ });
45
+
46
+ // ── Registration ───────────────────────────────────────────────────────────
47
+
48
+ export function registerBrokers(server: McpServer): void {
49
+ const audit = AuditLogger.getInstance();
50
+
51
+ // ── tradier_quote — FREE ─────────────────────────────────────────────────
52
+ server.tool(
53
+ 'tradier_quote',
54
+ {
55
+ symbols: z.string().describe('Comma-separated ticker symbols (e.g. "NVDA,SPY,AAPL")'),
56
+ },
57
+ async (rawArgs) => {
58
+ const args = Sandbox.validate(TradierQuoteSchema, rawArgs);
59
+ if (!RateLimiter.getInstance().checkTool('tradier_quote')) {
60
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'rate_limit_exceeded' }) }], isError: true };
61
+ }
62
+ try {
63
+ const data = await TradierAPI.quote({ symbols: args.symbols });
64
+ audit.info('tradier_quote_success', { symbols: args.symbols });
65
+ return { content: [{ type: 'text', text: JSON.stringify(data) }] };
66
+ } catch (err) {
67
+ audit.warn('tradier_quote_fail', { error: String(err) });
68
+ return { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }], isError: true };
69
+ }
70
+ },
71
+ );
72
+
73
+ // ── tradier_order — $0.01 x402 ──────────────────────────────────────────
74
+ server.tool(
75
+ 'tradier_order',
76
+ {
77
+ account_id: z.string().describe('Tradier brokerage account ID'),
78
+ symbol: z.string().describe('Ticker symbol (e.g. NVDA)'),
79
+ side: z.string().describe('"buy" or "sell"'),
80
+ quantity: z.number().describe('Number of shares (integer)'),
81
+ type: z.string().describe('"market" | "limit" | "stop" | "stop_limit"'),
82
+ duration: z.string().describe('"day" | "gtc" | "pre" | "post"'),
83
+ price: z.number().describe('Limit price (required for limit/stop_limit orders)'),
84
+ stop: z.number().describe('Stop price (required for stop/stop_limit orders)'),
85
+ wallet_address: z.string().describe('Agent wallet for x402 payment (AP2 required)'),
86
+ },
87
+ async (rawArgs) => {
88
+ const args = Sandbox.validate(TradierOrderSchema, rawArgs);
89
+ if (!RateLimiter.getInstance().checkTool('tradier_order')) {
90
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'rate_limit_exceeded' }) }], isError: true };
91
+ }
92
+ await PriceRegistry.getInstance().seedDefaults();
93
+ const price = await PriceRegistry.getInstance().getPrice('tradier_order');
94
+ if (!price) {
95
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'price_unavailable' }) }], isError: true };
96
+ }
97
+ let payment;
98
+ try {
99
+ payment = await executeX402Payment({ price, currency: 'USDC', toolName: 'tradier_order', walletAddress: args.wallet_address });
100
+ } catch (err) {
101
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'payment_failed', message: String(err) }) }], isError: true };
102
+ }
103
+ try {
104
+ const result = await TradierAPI.order({
105
+ account_id: args.account_id,
106
+ symbol: args.symbol,
107
+ side: args.side,
108
+ quantity: args.quantity,
109
+ type: args.type,
110
+ duration: args.duration,
111
+ price: args.price,
112
+ stop: args.stop,
113
+ });
114
+ audit.info('tradier_order_success', { symbol: args.symbol, side: args.side, quantity: args.quantity });
115
+ return {
116
+ content: [{
117
+ type: 'text',
118
+ text: JSON.stringify({
119
+ ...(result as object),
120
+ _meta: { receipt_id: payment.receiptId, tx_hash: payment.txHash, chain: payment.chain, amount_paid: `${payment.amountPaid} ${payment.currency}` },
121
+ }),
122
+ }],
123
+ };
124
+ } catch (err) {
125
+ audit.warn('tradier_order_fail', { error: String(err) });
126
+ return { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }], isError: true };
127
+ }
128
+ },
129
+ );
130
+
131
+ // ── tradier_positions — FREE ─────────────────────────────────────────────
132
+ server.tool(
133
+ 'tradier_positions',
134
+ {
135
+ account_id: z.string().describe('Tradier brokerage account ID'),
136
+ },
137
+ async (rawArgs) => {
138
+ const args = Sandbox.validate(AccountSchema, rawArgs);
139
+ if (!RateLimiter.getInstance().checkTool('tradier_positions')) {
140
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'rate_limit_exceeded' }) }], isError: true };
141
+ }
142
+ try {
143
+ const [positions, balances] = await Promise.all([
144
+ TradierAPI.positions(args.account_id),
145
+ TradierAPI.balances(args.account_id),
146
+ ]);
147
+ return { content: [{ type: 'text', text: JSON.stringify({ positions, balances }) }] };
148
+ } catch (err) {
149
+ return { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }], isError: true };
150
+ }
151
+ },
152
+ );
153
+
154
+ // ── robinhood_quote — FREE ───────────────────────────────────────────────
155
+ server.tool(
156
+ 'robinhood_quote',
157
+ {
158
+ symbol: z.string().describe('Ticker symbol (e.g. NVDA)'),
159
+ },
160
+ async (rawArgs) => {
161
+ const args = Sandbox.validate(RobinhoodQuoteSchema, rawArgs);
162
+ if (!RateLimiter.getInstance().checkTool('robinhood_quote')) {
163
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'rate_limit_exceeded' }) }], isError: true };
164
+ }
165
+ try {
166
+ const data = await RobinhoodAPI.quote(args.symbol);
167
+ audit.info('robinhood_quote_success', { symbol: args.symbol });
168
+ return { content: [{ type: 'text', text: JSON.stringify(data) }] };
169
+ } catch (err) {
170
+ audit.warn('robinhood_quote_fail', { error: String(err) });
171
+ return { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }], isError: true };
172
+ }
173
+ },
174
+ );
175
+
176
+ // ── robinhood_order — $0.01 x402 ────────────────────────────────────────
177
+ server.tool(
178
+ 'robinhood_order',
179
+ {
180
+ symbol: z.string().describe('Ticker symbol (e.g. NVDA)'),
181
+ side: z.string().describe('"buy" or "sell"'),
182
+ quantity: z.number().describe('Number of shares (integer)'),
183
+ type: z.string().describe('"market" or "limit"'),
184
+ time_in_force: z.string().describe('"gfd" (good for day) | "gtc" | "ioc" | "opg"'),
185
+ price: z.number().describe('Limit price (required for limit orders)'),
186
+ wallet_address: z.string().describe('Agent wallet for x402 payment (AP2 required)'),
187
+ },
188
+ async (rawArgs) => {
189
+ const args = Sandbox.validate(RobinhoodOrderSchema, rawArgs);
190
+ if (!RateLimiter.getInstance().checkTool('robinhood_order')) {
191
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'rate_limit_exceeded' }) }], isError: true };
192
+ }
193
+ await PriceRegistry.getInstance().seedDefaults();
194
+ const price = await PriceRegistry.getInstance().getPrice('robinhood_order');
195
+ if (!price) {
196
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'price_unavailable' }) }], isError: true };
197
+ }
198
+ let payment;
199
+ try {
200
+ payment = await executeX402Payment({ price, currency: 'USDC', toolName: 'robinhood_order', walletAddress: args.wallet_address });
201
+ } catch (err) {
202
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'payment_failed', message: String(err) }) }], isError: true };
203
+ }
204
+ try {
205
+ const result = await RobinhoodAPI.order({
206
+ symbol: args.symbol,
207
+ side: args.side,
208
+ quantity: args.quantity,
209
+ type: args.type,
210
+ time_in_force: args.time_in_force,
211
+ price: args.price,
212
+ });
213
+ audit.info('robinhood_order_success', { symbol: args.symbol, side: args.side, quantity: args.quantity });
214
+ return {
215
+ content: [{
216
+ type: 'text',
217
+ text: JSON.stringify({
218
+ ...(result as object),
219
+ _meta: { receipt_id: payment.receiptId, tx_hash: payment.txHash, chain: payment.chain, amount_paid: `${payment.amountPaid} ${payment.currency}` },
220
+ }),
221
+ }],
222
+ };
223
+ } catch (err) {
224
+ audit.warn('robinhood_order_fail', { error: String(err) });
225
+ return { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }], isError: true };
226
+ }
227
+ },
228
+ );
229
+
230
+ // ── robinhood_portfolio — FREE ───────────────────────────────────────────
231
+ server.tool(
232
+ 'robinhood_portfolio',
233
+ {},
234
+ async () => {
235
+ if (!RateLimiter.getInstance().checkTool('robinhood_portfolio')) {
236
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'rate_limit_exceeded' }) }], isError: true };
237
+ }
238
+ try {
239
+ const [portfolio, positions, orders] = await Promise.all([
240
+ RobinhoodAPI.portfolio(),
241
+ RobinhoodAPI.positions(),
242
+ RobinhoodAPI.orderHistory(),
243
+ ]);
244
+ return { content: [{ type: 'text', text: JSON.stringify({ portfolio, positions, recent_orders: orders }) }] };
245
+ } catch (err) {
246
+ return { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }], isError: true };
247
+ }
248
+ },
249
+ );
250
+ }
@@ -0,0 +1,104 @@
1
+ import { z } from 'zod';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { executeX402Payment } from '../payments/x402.js';
4
+ import { RateLimiter } from '../security/rate-limit.js';
5
+ import { Sandbox } from '../security/sandbox.js';
6
+ import { AuditLogger } from '../security/audit.js';
7
+ import { PriceRegistry } from '../registry/pricing.js';
8
+ import { CopyTraderAPI } from '../../lib/sml-api/copytrader.js';
9
+
10
+ const SubscribeSchema = z.object({
11
+ whale_address: z.string().min(10),
12
+ subscriber_address: z.string().min(10),
13
+ max_copy_amount_xrp: z.number().positive(),
14
+ wallet_address: z.string().optional(),
15
+ });
16
+
17
+ export function registerCopyTrader(server: McpServer): void {
18
+ const audit = AuditLogger.getInstance();
19
+
20
+ // ── FREE: copytrader_status ────────────────────────────────────────────────
21
+ server.tool(
22
+ 'copytrader_status',
23
+ {},
24
+ async () => {
25
+ try {
26
+ const data = await CopyTraderAPI.status();
27
+ return { content: [{ type: 'text', text: JSON.stringify(data) }] };
28
+ } catch (err) {
29
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'api_error', message: String(err) }) }], isError: true };
30
+ }
31
+ },
32
+ );
33
+
34
+ // ── FREE: copytrader_whales ────────────────────────────────────────────────
35
+ server.tool(
36
+ 'copytrader_whales',
37
+ {},
38
+ async () => {
39
+ if (!RateLimiter.getInstance().checkTool('copytrader_whales')) {
40
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'rate_limit_exceeded', retry_after: 60 }) }], isError: true };
41
+ }
42
+ try {
43
+ const data = await CopyTraderAPI.whales();
44
+ audit.info('copytrader_whales', {});
45
+ return { content: [{ type: 'text', text: JSON.stringify(data) }] };
46
+ } catch (err) {
47
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'api_error', message: String(err) }) }], isError: true };
48
+ }
49
+ },
50
+ );
51
+
52
+ // ── PAID: copytrader_subscribe (0.05 USDC) ────────────────────────────────
53
+ server.tool(
54
+ 'copytrader_subscribe',
55
+ {
56
+ whale_address: z.string().describe('XRPL address of the whale to copy.'),
57
+ subscriber_address: z.string().describe('Your XRPL address that will mirror trades.'),
58
+ max_copy_amount_xrp: z.number().describe('Maximum XRP to allocate per copied trade.'),
59
+ wallet_address: z.string().describe('Agent wallet for x402 payment.'),
60
+ },
61
+ async (rawArgs) => {
62
+ const args = Sandbox.validate(SubscribeSchema, rawArgs);
63
+
64
+ if (!RateLimiter.getInstance().checkTool('copytrader_subscribe')) {
65
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'rate_limit_exceeded', retry_after: 60 }) }], isError: true };
66
+ }
67
+
68
+ await PriceRegistry.getInstance().seedDefaults();
69
+ const price = await PriceRegistry.getInstance().getPrice('copytrader_subscribe');
70
+ if (!price) {
71
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'price_unavailable' }) }], isError: true };
72
+ }
73
+
74
+ let payment;
75
+ try {
76
+ payment = await executeX402Payment({ price, currency: 'USDC', toolName: 'copytrader_subscribe', walletAddress: args.wallet_address });
77
+ } catch (err) {
78
+ audit.warn('copytrader_subscribe_payment_fail', { error: String(err) });
79
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'payment_failed', message: String(err) }) }], isError: true };
80
+ }
81
+
82
+ try {
83
+ const data = await CopyTraderAPI.subscribe({
84
+ whaleAddress: args.whale_address,
85
+ subscriberAddress: args.subscriber_address,
86
+ maxCopyAmountXrp: args.max_copy_amount_xrp,
87
+ });
88
+ audit.info('copytrader_subscribe_success', { receiptId: payment.receiptId });
89
+ return {
90
+ content: [{
91
+ type: 'text',
92
+ text: JSON.stringify({
93
+ data,
94
+ _meta: { receipt_id: payment.receiptId, tx_hash: payment.txHash, chain: payment.chain, amount_paid: `${payment.amountPaid} ${payment.currency}`, timestamp: payment.timestamp },
95
+ }),
96
+ }],
97
+ };
98
+ } catch (err) {
99
+ audit.error('copytrader_subscribe_api_fail', { error: String(err) });
100
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'api_error', message: String(err) }) }], isError: true };
101
+ }
102
+ },
103
+ );
104
+ }
@@ -0,0 +1,70 @@
1
+ import { z } from 'zod';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { executeX402Payment } from '../payments/x402.js';
4
+ import { RateLimiter } from '../security/rate-limit.js';
5
+ import { Sandbox } from '../security/sandbox.js';
6
+ import { AuditLogger } from '../security/audit.js';
7
+ import { CrawlClient } from '../../lib/sml-api/crawl.js';
8
+ import { PriceRegistry } from '../registry/pricing.js';
9
+
10
+ const InputSchema = z.object({
11
+ url: z.string().url(),
12
+ extract: z.enum(['text', 'links', 'tables', 'structured', 'all']).default('text'),
13
+ wallet_address: z.string().optional(),
14
+ user_agent: z.string().optional(),
15
+ });
16
+
17
+ export function registerCrawl(server: McpServer): void {
18
+ server.tool(
19
+ 'crawl_paid_fetch',
20
+ {
21
+ url: z.string().describe('URL to fetch and parse. Must be http or https.'),
22
+ extract: z.enum(['text', 'links', 'tables', 'structured', 'all']).describe('What to extract. Default: text.'),
23
+ wallet_address: z.string().describe('Agent wallet for payment. Humans bypass automatically.'),
24
+ user_agent: z.string().describe('Custom user-agent string.'),
25
+ },
26
+ async (rawArgs) => {
27
+ const args = Sandbox.validate(InputSchema, rawArgs);
28
+ const audit = AuditLogger.getInstance();
29
+
30
+ // Validate URL safety
31
+ Sandbox.validateUrl(args.url);
32
+
33
+ if (!RateLimiter.getInstance().checkTool('crawl_paid_fetch')) {
34
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'rate_limit_exceeded' }) }], isError: true };
35
+ }
36
+
37
+ await PriceRegistry.getInstance().seedDefaults();
38
+ const price = await PriceRegistry.getInstance().getPrice('crawl_paid_fetch');
39
+ if (!price) {
40
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'price_unavailable' }) }], isError: true };
41
+ }
42
+
43
+ let payment;
44
+ try {
45
+ payment = await executeX402Payment({
46
+ price,
47
+ currency: 'USDC',
48
+ toolName: 'crawl_paid_fetch',
49
+ walletAddress: args.wallet_address,
50
+ });
51
+ } catch (err) {
52
+ return { content: [{ type: 'text', text: JSON.stringify({ error: 'payment_failed', message: String(err) }) }], isError: true };
53
+ }
54
+
55
+ const client = CrawlClient.getInstance();
56
+ const data = await client.fetch({ url: args.url, extract: args.extract ?? 'text', userAgent: args.user_agent });
57
+
58
+ // Sanitize response to prevent prompt injection
59
+ const safeContent = typeof data.content === 'string'
60
+ ? Sandbox.sanitizeApiResponse(data.content)
61
+ : data.content;
62
+
63
+ audit.info('crawl_success', { receiptId: payment.receiptId });
64
+
65
+ return {
66
+ content: [{ type: 'text', text: JSON.stringify({ data: { ...data, content: safeContent }, _meta: { receipt_id: payment.receiptId, tx_hash: payment.txHash, chain: payment.chain, amount_paid: `${payment.amountPaid} ${payment.currency}` } }) }],
67
+ };
68
+ },
69
+ );
70
+ }