oceanalt-aml-mcp 0.2.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 (3) hide show
  1. package/README.md +83 -0
  2. package/package.json +17 -0
  3. package/server.mjs +175 -0
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # oceanalt-aml-mcp
2
+
3
+ **Native AML compliance for AI agents.** An [MCP](https://modelcontextprotocol.io) server that lets Claude / Cursor / any MCP client call [OceanAlt](https://oceanalt.com)'s on-chain address screening and compliance gateway. Receipts, not a black-box score — every verdict ships with verifiable evidence you can click through.
4
+
5
+ Built for [agentic payments](https://oceanalt.com/en/rap): ask *"is this payee safe?"* **before** money moves, across 7 EVM chains + Tron + Solana.
6
+
7
+ ## Tools
8
+
9
+ | Tool | Input | What it does | Price |
10
+ |------|-------|--------------|-------|
11
+ | `screen_address` | `address` (0x… / T… / Solana), `network?` | AML verdict + risk score + verifiable evidence | Free |
12
+ | `recent_flagged` | — | Recently flagged high-risk addresses | Free |
13
+ | `compliance_decision` | `to`, `amountUsdc?`, `purpose?`, `network?` | Full gateway verdict (allow / review / decline) + evidence | $0.30 |
14
+ | `deep_trace` | `address` (T…) | Tron multi-hop USDT taint trace (depth ≤3) | $0.20 |
15
+ | `batch_screen` | `addresses[]` (≤25) | Batch screening | $0.10 |
16
+
17
+ `network` (for `0x` addresses): `ethereum` · `base` · `bsc` · `polygon` · `arbitrum` · `optimism` · `avalanche` (auto-detected if omitted). Tron & Solana are auto-detected.
18
+
19
+ ## Quick start (free tools)
20
+
21
+ Add to your MCP client config (Claude Desktop, Claude Code, Cursor, …):
22
+
23
+ ```json
24
+ {
25
+ "mcpServers": {
26
+ "oceanalt-aml": { "command": "npx", "args": ["-y", "oceanalt-aml-mcp"] }
27
+ }
28
+ }
29
+ ```
30
+
31
+ The free tools need no API key and no signup.
32
+
33
+ ## Enabling the paid (x402) tools
34
+
35
+ The paid tools pay per call over [x402](https://oceanalt.com/en/api-docs) — HTTP 402 → sign one USDC authorization → 200. Add a payer wallet key to `env` and install the x402 peer deps:
36
+
37
+ ```json
38
+ {
39
+ "mcpServers": {
40
+ "oceanalt-aml": {
41
+ "command": "npx", "args": ["-y", "oceanalt-aml-mcp"],
42
+ "env": { "OCEANALT_PAYER_KEY": "0x<your payer wallet private key>" }
43
+ }
44
+ }
45
+ }
46
+ ```
47
+
48
+ ```bash
49
+ npm i @x402/core @x402/evm viem # in the MCP's environment
50
+ ```
51
+
52
+ > **⚠️ Environment — currently testnet.** The paid endpoints settle on **Base Sepolia testnet** (`eip155:84532`); the USDC is **test currency, not real money**. Mainnet settlement is coming — the `$` prices above describe the mainnet tier. The live pricing/network manifest is always at <https://oceanalt.com/api/x402>.
53
+ >
54
+ > **🔐 Wallet safety.** `OCEANALT_PAYER_KEY` is used only in your local process to sign one EIP-3009 authorization. It is **never bundled in this package, never uploaded, never logged.** The facilitator pays gas and never custodies your funds. Use a **dedicated low-balance payer wallet**, not your main funds.
55
+
56
+ ## Environment variables
57
+
58
+ | Var | Purpose | Default |
59
+ |-----|---------|---------|
60
+ | `OCEANALT_BASE` | Backend base URL | `https://oceanalt.com` (production) |
61
+ | `OCEANALT_PAYER_KEY` | Payer wallet key — enables the paid tools | unset (free tools only) |
62
+
63
+ ## What it is (and isn't)
64
+
65
+ This package is a thin (~9KB) client that calls OceanAlt's **public** API. It contains no proprietary logic and no data — the AML engine, scoring, and address-intelligence all run server-side at `oceanalt.com`. Every query still enriches OceanAlt's own address-intelligence DB (it grows with use).
66
+
67
+ - **Programmatic SDK** (same capabilities, for Node / browser / Deno / Bun): [`oceanalt-aml`](https://www.npmjs.com/package/oceanalt-aml)
68
+ - **API docs & examples**: <https://oceanalt.com/en/api-docs>
69
+ - **RAP — the compliance standard behind the verdicts**: <https://oceanalt.com/en/rap>
70
+
71
+ MIT © OceanAlt
72
+
73
+ ---
74
+
75
+ ## 中文
76
+
77
+ 让 **Claude / Cursor / 任意 MCP 客户端**原生调用 OceanAlt 的链上地址合规筛查 + 合规网关。给收据,不给黑箱分:每条判决都带**可点开核验的证据**。覆盖 7 条 EVM 链 + 波场 TRON + Solana,付款前先问一句「这个收款方安不安全」。
78
+
79
+ - **免费两工具**:`screen_address`(单查 + 证据)、`recent_flagged`(近期被标记)。无需 key、无需注册。
80
+ - **付费三工具(x402)**:`compliance_decision`(网关判决)、`deep_trace`(波场深度追踪)、`batch_screen`(批量)。按需加载 `@x402`+`viem`,设置 `OCEANALT_PAYER_KEY` 才启用。
81
+ - **⚠️ 环境**:付费端点**当前在 Base Sepolia 测试网结算,USDC 为测试币(非真钱)**,主网结算即将上线;上表 `$` 价为主网档。实时网络/定价以 <https://oceanalt.com/api/x402> 为准。
82
+ - **🔐 钱包安全**:`OCEANALT_PAYER_KEY` 只在本地签一次 EIP-3009 授权,**绝不随包发布/上传/打日志**;facilitator 代付 gas、不托管资金。**建议用小额专用钱包**,别用主资金钱包私钥。
83
+ - 编程 SDK:[`oceanalt-aml`](https://www.npmjs.com/package/oceanalt-aml);在线文档:<https://oceanalt.com/en/api-docs>;合规标准:<https://oceanalt.com/en/rap>。
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "oceanalt-aml-mcp",
3
+ "version": "0.2.0",
4
+ "description": "OceanAlt AML 的 MCP server —— 让 Claude / 任意 MCP agent 原生调用地址合规筛查(免费)+ x402 付费判决/追踪/批量。",
5
+ "type": "module",
6
+ "bin": { "oceanalt-aml-mcp": "server.mjs" },
7
+ "main": "server.mjs",
8
+ "files": ["server.mjs", "README.md"],
9
+ "keywords": ["mcp", "aml", "x402", "agent", "compliance", "oceanalt", "claude", "sanctions", "usdc", "web3"],
10
+ "license": "MIT",
11
+ "author": "OceanAlt (https://oceanalt.com)",
12
+ "homepage": "https://oceanalt.com/en/api-docs",
13
+ "bugs": { "url": "https://oceanalt.com/en/partner" },
14
+ "publishConfig": { "registry": "https://registry.npmjs.org/", "access": "public" },
15
+ "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0", "zod": "^3.23.0" },
16
+ "optionalDependencies": { "@x402/core": ">=2.21.0", "@x402/evm": ">=2.21.0", "viem": ">=2.0.0" }
17
+ }
package/server.mjs ADDED
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env node
2
+ // OceanAlt AML —— MCP server。让 Claude / 任意 MCP 客户端「原生」调用地址合规筛查 + 合规网关。
3
+ // v0.2:免费两工具(单查 + 近期名单,零重依赖)+ 三个 x402 付费工具(网关判决/深度追踪/批量)。
4
+ // → 目标:成为 agent 手边「绕不开的默认合规检查」;每次调用还在喂厚 OceanAlt 自建情报库。
5
+ // → 付费工具按需加载 @x402 + viem,仅当设置了付款钱包私钥(OCEANALT_PAYER_KEY)才启用;
6
+ // 未设/未装依赖时付费工具返回清晰提示、绝不崩溃,免费工具始终可用。facilitator 代验签+代结算,不托管资金。
7
+ //
8
+ // 接入(Claude Desktop 等)配置示例:
9
+ // { "mcpServers": { "oceanalt-aml": { "command": "npx", "args": ["-y", "oceanalt-aml-mcp"],
10
+ // "env": { "OCEANALT_PAYER_KEY": "0x…(选填,启用付费工具)" } } } }
11
+
12
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14
+ import { z } from "zod";
15
+
16
+ const BASE = (process.env.OCEANALT_BASE || "https://oceanalt.com").replace(/\/$/, "");
17
+ const TIMEOUT = 15000;
18
+ // 支持的 EVM 链(与站点 lib/aml.ts 的 EVM_NETS 对齐:实测可用才在此列出)
19
+ const NETWORKS = ["ethereum", "base", "bsc", "polygon", "arbitrum", "optimism", "avalanche"];
20
+
21
+ async function apiGet(path) {
22
+ const ctrl = new AbortController();
23
+ const t = setTimeout(() => ctrl.abort(), TIMEOUT);
24
+ try {
25
+ const res = await fetch(BASE + path, { signal: ctrl.signal, headers: { accept: "application/json" } });
26
+ if (!res.ok) throw new Error(`OceanAlt ${path} → HTTP ${res.status}`);
27
+ return await res.json();
28
+ } finally { clearTimeout(t); }
29
+ }
30
+
31
+ const server = new McpServer({ name: "oceanalt-aml", version: "0.2.0" });
32
+
33
+ server.registerTool(
34
+ "screen_address",
35
+ {
36
+ title: "地址合规筛查(免费)",
37
+ description:
38
+ "对一个区块链地址做 AML 合规筛查:制裁/混币器/诈骗名单命中、发行方(USDT/USDC)冻结、链上启发式风险,并给出可核验证据与 allow/caution/decline 倾向。支持 EVM(0x…)、TRON(T…)、Solana。免费、无需 key。",
39
+ inputSchema: {
40
+ address: z.string().describe("要筛查的地址:0x…(EVM)/ T…(TRON)/ Solana base58"),
41
+ network: z
42
+ .enum(NETWORKS)
43
+ .optional()
44
+ .describe("EVM 链(可选,不填按地址推断):ethereum/base/bsc/polygon/arbitrum/optimism/avalanche"),
45
+ },
46
+ },
47
+ async ({ address, network }) => {
48
+ const q = new URLSearchParams({ addr: address });
49
+ if (network) q.set("network", network);
50
+ const r = await apiGet(`/api/risk?${q.toString()}`);
51
+ const lines = [
52
+ `地址: ${r.address}`,
53
+ `判决: ${r.verdict} | 风险分: ${r.risk}/100 | 可转出被拦: ${r.blocked ? "是" : "否"}`,
54
+ r.advice ? `建议: ${r.advice}` : "",
55
+ (r.signals && r.signals.length) ? `信号:\n- ${r.signals.join("\n- ")}` : "信号: 无",
56
+ (r.evidence && r.evidence.length)
57
+ ? `证据:\n${r.evidence.map((e) => `- ${e.label}: ${e.detail}${e.url ? ` (${e.url})` : ""}`).join("\n")}`
58
+ : "",
59
+ `标准: ${r.standard || "https://oceanalt.com/en/rap"}`,
60
+ ].filter(Boolean);
61
+ return {
62
+ content: [{ type: "text", text: lines.join("\n") }],
63
+ structuredContent: r,
64
+ };
65
+ }
66
+ );
67
+
68
+ server.registerTool(
69
+ "recent_flagged",
70
+ {
71
+ title: "近期被标记的敏感地址(免费)",
72
+ description: "返回 OceanAlt 近期筛出的高风险/被标记地址案例,用于快速了解当前威胁面。免费。",
73
+ inputSchema: {},
74
+ },
75
+ async () => {
76
+ const r = await apiGet(`/api/risk/recent`);
77
+ const items = Array.isArray(r) ? r : (r.items || []);
78
+ const text = items.length
79
+ ? items.slice(0, 20).map((it) => `- ${it.address || it.addr} | ${it.verdict || ""} | ${(it.signals && it.signals[0]) || it.top || ""}`).join("\n")
80
+ : "近期无被标记地址。";
81
+ return { content: [{ type: "text", text }], structuredContent: r };
82
+ }
83
+ );
84
+
85
+ // ── 付费(x402)工具 ──────────────────────────────────────────────────────────
86
+ // 需付款钱包私钥(环境变量 OCEANALT_PAYER_KEY,0x 开头)+ 按需依赖 @x402/core @x402/evm viem。
87
+ // 设计同官方 SDK:MCP 只用私钥签一次 EIP-3009 授权,facilitator 代验签+代结算+代付 gas,不托管资金。
88
+ const PAYER_KEY = process.env.OCEANALT_PAYER_KEY || "";
89
+
90
+ async function payFetch(method, path, body) {
91
+ if (!PAYER_KEY) throw new Error("此为付费端点(x402):请在 MCP 配置的 env 里设置 OCEANALT_PAYER_KEY(付款钱包私钥,签 EIP-3009 授权;facilitator 代付 gas)。未设置则仅免费工具 screen_address / recent_flagged 可用。");
92
+ let x402core, x402evm, viemAccounts;
93
+ try {
94
+ [x402core, x402evm, viemAccounts] = await Promise.all([
95
+ import("@x402/core/client"), import("@x402/evm/exact/client"), import("viem/accounts"),
96
+ ]);
97
+ } catch {
98
+ throw new Error("付费工具需要依赖:在 MCP 所在环境执行 npm i @x402/core @x402/evm viem。");
99
+ }
100
+ const account = viemAccounts.privateKeyToAccount(PAYER_KEY);
101
+ const client = new x402core.x402Client();
102
+ x402evm.registerExactEvmScheme(client, { signer: account });
103
+ const http = new x402core.x402HTTPClient(client);
104
+ const url = BASE + path;
105
+ const init = { method, headers: { "content-type": "application/json" } };
106
+ if (body !== undefined) init.body = JSON.stringify(body);
107
+ const ctrl = new AbortController();
108
+ const t = setTimeout(() => ctrl.abort(), 30000);
109
+ try {
110
+ const r1 = await fetch(url, { ...init, signal: ctrl.signal });
111
+ if (r1.status !== 402) return { data: await r1.json().catch(() => null), status: r1.status, paid: false }; // 未开启付费或已放行
112
+ const pr = http.getPaymentRequiredResponse((n) => r1.headers.get(n));
113
+ const payload = await http.createPaymentPayload(pr);
114
+ const payHeaders = http.encodePaymentSignatureHeader(payload);
115
+ const r2 = await fetch(url, { ...init, headers: { ...init.headers, ...payHeaders }, signal: ctrl.signal });
116
+ const data = await r2.json().catch(() => null);
117
+ let settlement = null;
118
+ try { settlement = http.getPaymentSettleResponse((n) => r2.headers.get(n)); } catch { /* 无回执 */ }
119
+ return { data, status: r2.status, paid: r2.status === 200, settlement };
120
+ } finally { clearTimeout(t); }
121
+ }
122
+
123
+ function payText(title, r) {
124
+ const head = r.paid ? `✅ 已付费并结算${r.settlement ? "(附链上回执)" : ""}` : r.status === 402 ? "⚠️ 需付费但未完成结算" : `状态 HTTP ${r.status}`;
125
+ return `【${title}】${head}\n${JSON.stringify(r.data, null, 2)}`;
126
+ }
127
+
128
+ server.registerTool(
129
+ "compliance_decision",
130
+ {
131
+ title: "网关合规判决(付费 $0.30)",
132
+ description: "对一笔待支付跑完整合规网关:AML 筛查 + RAP 各闸门 + allow/review/decline 判决 + 可核验证据。付费端点(x402,$0.30 USDC;当前 Base Sepolia 测试网结算、为测试币),需 OCEANALT_PAYER_KEY。",
133
+ inputSchema: {
134
+ to: z.string().describe("收款地址:0x…(EVM)或 T…(TRON)"),
135
+ amountUsdc: z.number().optional().describe("金额(USDC,选填,仅作记录/额度判断)"),
136
+ purpose: z.string().optional().describe("用途备注(选填)"),
137
+ network: z.enum(NETWORKS).optional().describe("EVM 链(选填)"),
138
+ },
139
+ },
140
+ async ({ to, amountUsdc, purpose, network }) => {
141
+ const r = await payFetch("POST", "/api/x402/decision", { to, amountUsdc, purpose, network });
142
+ return { content: [{ type: "text", text: payText("网关合规判决", r) }], structuredContent: r };
143
+ }
144
+ );
145
+
146
+ server.registerTool(
147
+ "deep_trace",
148
+ {
149
+ title: "深度沾染追踪(付费 $0.20)",
150
+ description: "对一个波场(TRON)地址做多跳 USDT 沾染回溯(depth ≤3):资金是否触及被 Tether 冻结/制裁的黑地址。付费端点(x402,$0.20 USDC;当前 Base Sepolia 测试网结算、为测试币),需 OCEANALT_PAYER_KEY。",
151
+ inputSchema: { address: z.string().describe("波场地址 T…") },
152
+ },
153
+ async ({ address }) => {
154
+ const r = await payFetch("GET", `/api/x402/trace?addr=${encodeURIComponent(address)}`);
155
+ return { content: [{ type: "text", text: payText("深度沾染追踪", r) }], structuredContent: r };
156
+ }
157
+ );
158
+
159
+ server.registerTool(
160
+ "batch_screen",
161
+ {
162
+ title: "批量地址筛查(付费 $0.10)",
163
+ description: "一次筛查多个地址(≤25),返回每个地址的判决/风险分/信号。付费端点(x402,$0.10 USDC;当前 Base Sepolia 测试网结算、为测试币),需 OCEANALT_PAYER_KEY。",
164
+ inputSchema: { addresses: z.array(z.string()).max(25).describe("地址数组(≤25):0x…/T…") },
165
+ },
166
+ async ({ addresses }) => {
167
+ const r = await payFetch("POST", "/api/x402/batch", { addresses });
168
+ return { content: [{ type: "text", text: payText("批量地址筛查", r) }], structuredContent: r };
169
+ }
170
+ );
171
+
172
+ const transport = new StdioServerTransport();
173
+ await server.connect(transport);
174
+ // 连上后不打印到 stdout(stdio 传输占用 stdout);诊断信息走 stderr。
175
+ console.error(`[oceanalt-aml-mcp] v0.2 已启动,base = ${BASE},付费工具 ${PAYER_KEY ? "已启用" : "未启用(未设 OCEANALT_PAYER_KEY,仅免费工具)"}`);