@celorodrigues/x402-conformance 2.0.0 โ 2.1.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.
- package/README.md +24 -0
- package/bin/cli.js +9 -2
- package/index.js +4 -3
- package/mcp-server.js +139 -0
- package/package.json +9 -4
- package/smithery.yaml +26 -0
package/README.md
CHANGED
|
@@ -29,6 +29,30 @@ npx @celorodrigues/x402-conformance https://your-service.com/v1/paid --svg > bad
|
|
|
29
29
|
|
|
30
30
|
---
|
|
31
31
|
|
|
32
|
+
## ๐ค MCP Server (Claude Desktop, Cursor)
|
|
33
|
+
|
|
34
|
+
The same package runs as an MCP stdio server:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"mcpServers": {
|
|
39
|
+
"automaton-x402": {
|
|
40
|
+
"command": "npx",
|
|
41
|
+
"args": ["-y", "@celorodrigues/x402-conformance", "--mcp"]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- Claude Desktop: Settings โ Developer โ Edit Config (`claude_desktop_config.json`), then restart.
|
|
48
|
+
- Cursor: `.cursor/mcp.json` in the project, or `~/.cursor/mcp.json`.
|
|
49
|
+
|
|
50
|
+
Tools: `x402_conformance_check` (free, runs locally), `security_scan` (Base token honeypot / mint-trap / contract risk), `oracle_base`, `sentiment_analysis`, `attest`, `merkle_prove`, plus free `verify_attestation`, `read_ledger`, `merkle_verify`, `pricing`, `health`. Paid tools cost 0.001 USDC on Base (3 free calls/day/IP).
|
|
51
|
+
|
|
52
|
+
Optional env: `VALUE_API_BASE` (default `https://api.automaton-sovereign.workers.dev`), `VALUE_API_PAYMENT` (payment tx hash attached to paid calls).
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
32
56
|
## ๐งช Battery of Checks
|
|
33
57
|
|
|
34
58
|
| Code | Level | Check | Requirement |
|
package/bin/cli.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
const engine = require('../index.js');
|
|
4
|
-
|
|
5
3
|
const args = process.argv.slice(2);
|
|
4
|
+
|
|
5
|
+
// MCP stdio server mode: npx -y @celorodrigues/x402-conformance --mcp
|
|
6
|
+
if (args.includes('--mcp')) {
|
|
7
|
+
require('../mcp-server.js');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const engine = require('../index.js');
|
|
6
12
|
const target = args.find(a => !a.startsWith('--'));
|
|
7
13
|
const jsonMode = args.includes('--json');
|
|
8
14
|
const svgMode = args.includes('--svg');
|
|
9
15
|
|
|
10
16
|
if (!target) {
|
|
11
17
|
console.error('\nUsage: npx @celorodrigues/x402-conformance <endpoint-url> [--json] [--svg]');
|
|
18
|
+
console.error(' npx -y @celorodrigues/x402-conformance --mcp (run as an MCP stdio server)');
|
|
12
19
|
console.error('Example: npx @celorodrigues/x402-conformance https://api.myservice.com/v1/paid --json\n');
|
|
13
20
|
process.exit(2);
|
|
14
21
|
}
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* x402-conformance
|
|
3
|
+
* x402-conformance -- Autonomous x402 Protocol Conformance Linter & Certification Engine
|
|
4
4
|
* Conforms strictly to official Coinbase x402 specifications (v1 and v2 CAIP-2) & EIP-3009.
|
|
5
5
|
* Author: Automaton-Sovereign (Base: 0x71DEAc098914A009E3720524642A6bE6F65EE528)
|
|
6
6
|
*
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
const https = require('https');
|
|
24
24
|
const http = require('http');
|
|
25
25
|
const { URL } = require('url');
|
|
26
|
+
const VERSION = require('./package.json').version;
|
|
26
27
|
|
|
27
28
|
function fetchUrl(targetUrl, options = {}) {
|
|
28
29
|
return new Promise((resolve, reject) => {
|
|
@@ -30,7 +31,7 @@ function fetchUrl(targetUrl, options = {}) {
|
|
|
30
31
|
const u = new URL(targetUrl);
|
|
31
32
|
const lib = u.protocol === 'http:' ? http : https;
|
|
32
33
|
const headers = Object.assign({
|
|
33
|
-
'User-Agent': 'Automaton-x402-Conformance-Linter/
|
|
34
|
+
'User-Agent': 'Automaton-x402-Conformance-Linter/' + VERSION,
|
|
34
35
|
'Accept': 'application/json, text/plain, */*'
|
|
35
36
|
}, options.headers || {});
|
|
36
37
|
|
|
@@ -271,7 +272,7 @@ function finish(results, targetUrl) {
|
|
|
271
272
|
|
|
272
273
|
return {
|
|
273
274
|
suite: '@celorodrigues/x402-conformance',
|
|
274
|
-
version:
|
|
275
|
+
version: VERSION,
|
|
275
276
|
target: targetUrl,
|
|
276
277
|
timestamp: new Date().toISOString(),
|
|
277
278
|
verdict: isConformant ? 'CONFORMANT' : 'NON_CONFORMANT',
|
package/mcp-server.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* Automaton-Sovereign Value API โ MCP server (stdio transport, zero deps, JSON-RPC 2.0)
|
|
5
|
+
*
|
|
6
|
+
* Exposes the x402-paid Value API as native MCP tools so any MCP-capable agent
|
|
7
|
+
* can call it directly. Paid tools forward the caller's X-PAYMENT tx hash;
|
|
8
|
+
* free tools need nothing.
|
|
9
|
+
*
|
|
10
|
+
* Also exposes the local x402 conformance linter (index.js) as a free tool.
|
|
11
|
+
*
|
|
12
|
+
* Config via env:
|
|
13
|
+
* VALUE_API_BASE base URL of the API (default https://api.automaton-sovereign.workers.dev)
|
|
14
|
+
* VALUE_API_PAYMENT default X-PAYMENT tx hash to attach to paid calls (optional)
|
|
15
|
+
*
|
|
16
|
+
* Usage (Claude Desktop / Cursor MCP config):
|
|
17
|
+
* { "mcpServers": { "automaton-x402": { "command": "npx",
|
|
18
|
+
* "args": ["-y", "@celorodrigues/x402-conformance", "--mcp"] } } }
|
|
19
|
+
*/
|
|
20
|
+
const http = require('http');
|
|
21
|
+
const https = require('https');
|
|
22
|
+
const { URL } = require('url');
|
|
23
|
+
const PKG = require('./package.json');
|
|
24
|
+
const linter = require('./index.js');
|
|
25
|
+
|
|
26
|
+
const BASE = (process.env.VALUE_API_BASE || 'https://api.automaton-sovereign.workers.dev').replace(/\/+$/, '');
|
|
27
|
+
const PAYMENT = process.env.VALUE_API_PAYMENT || '';
|
|
28
|
+
|
|
29
|
+
function httpJson(method, urlStr, headers, body) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const u = new URL(urlStr);
|
|
32
|
+
const lib = u.protocol === 'http:' ? http : https;
|
|
33
|
+
const payload = body ? (typeof body === 'string' ? body : JSON.stringify(body)) : null;
|
|
34
|
+
const h = Object.assign({ 'Accept': 'application/json', 'User-Agent': 'automaton-mcp/1.0' }, headers || {});
|
|
35
|
+
if (payload) { h['Content-Type'] = 'application/json'; h['Content-Length'] = Buffer.byteLength(payload); }
|
|
36
|
+
const req = lib.request({ hostname: u.hostname, port: u.port || (u.protocol === 'http:' ? 80 : 443), path: u.pathname + u.search, method, headers: h }, (res) => {
|
|
37
|
+
let d = ''; res.on('data', c => d += c);
|
|
38
|
+
res.on('end', () => {
|
|
39
|
+
let parsed = null; try { parsed = JSON.parse(d); } catch (e) {}
|
|
40
|
+
resolve({ status: res.statusCode, headers: res.headers, body: parsed !== null ? parsed : d });
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
req.on('error', reject); req.setTimeout(30000, () => req.destroy(new Error('timeout')));
|
|
44
|
+
if (payload) req.write(payload);
|
|
45
|
+
req.end();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const TOOLS = [
|
|
50
|
+
{ name: 'x402_conformance_check', description: 'FREE, runs locally. Lint any x402 paid endpoint for protocol conformance: HTTP 402 challenge, x402Version, accepts[] (exact scheme), Base network and addresses, EIP-712 domain, and rejection of malformed, forged, expired, underpaid and wrong-recipient EIP-3009 payments. Returns verdict, grade and per-check results.',
|
|
51
|
+
inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'Full URL of the x402-protected endpoint to check.' } }, required: ['url'] } },
|
|
52
|
+
{ name: 'security_scan', description: 'PAID 0.001 USDC/call, or free trial (3/day/IP). Base token safety analysis: honeypot detection, mint traps, selfdestruct/delegatecall, proxy and tax risks from contract bytecode, with a risk score and signed verdict. On 402, pay the accepts[] terms then retry with the payment tx hash.',
|
|
53
|
+
inputSchema: { type: 'object', properties: { address: { type: 'string', description: 'Token or contract address on Base (0x...).' }, payment: { type: 'string', description: 'Optional X-PAYMENT base tx hash of the USDC payment.' } }, required: ['address'] } },
|
|
54
|
+
{ name: 'attest', description: 'Create a SIGNED, hash-chained, append-only attestation (verifiable proof-of-existence) for a payload. PAID: 0.001 USDC/call, or free trial (3/day/IP). On 402, pay the accepts[] terms then retry with the payment tx hash.',
|
|
55
|
+
inputSchema: { type: 'object', properties: { data: { type: 'string', description: 'Payload to attest (any string).' }, payment: { type: 'string', description: 'Optional X-PAYMENT base tx hash of the USDC payment.' } }, required: ['data'] } },
|
|
56
|
+
{ name: 'verify_attestation', description: 'FREE. Verify a ledger entry by index or by data hash: recomputes hash, checks ECDSA signature and chain link.',
|
|
57
|
+
inputSchema: { type: 'object', properties: { index: { type: 'integer' }, dataHash: { type: 'string' } } } },
|
|
58
|
+
{ name: 'attestation_pubkey', description: 'FREE. Get the ECDSA P-256 public key (PEM) and keyId used to sign ledger entries.',
|
|
59
|
+
inputSchema: { type: 'object', properties: {} } },
|
|
60
|
+
{ name: 'read_ledger', description: 'FREE. Read the attestation ledger (paginated).',
|
|
61
|
+
inputSchema: { type: 'object', properties: { from: { type: 'integer' }, limit: { type: 'integer' } } } },
|
|
62
|
+
{ name: 'oracle_base', description: 'PAID 0.001 USDC/call. Get real-time Base L2 gas estimates and asset prices (ETH, USDC, cbBTC, AERO, VIRTUAL) with an ECDSA P-256 digital signature from the sovereign agent.',
|
|
63
|
+
inputSchema: { type: 'object', properties: { payment: { type: 'string', description: 'Optional X-PAYMENT base tx hash of the USDC payment.' } } } },
|
|
64
|
+
{ name: 'merkle_prove', description: 'PAID 0.001 USDC/call. Generate a cryptographic Merkle inclusion proof for a list of items and target element.',
|
|
65
|
+
inputSchema: { type: 'object', properties: { items: { type: 'array', items: { type: 'string' }, description: 'Array of items' }, target: { type: 'string', description: 'Target item or index' }, payment: { type: 'string' } }, required: ['items'] } },
|
|
66
|
+
{ name: 'merkle_verify', description: 'FREE. Verify a cryptographic Merkle inclusion proof against a root.',
|
|
67
|
+
inputSchema: { type: 'object', properties: { item: { type: 'string' }, proof: { type: 'array', items: { type: 'object' } }, root: { type: 'string' } }, required: ['item', 'proof', 'root'] } },
|
|
68
|
+
{ name: 'sentiment_analysis', description: 'PAID 0.001 USDC/call. Web3 token risk, security checks, and sentiment scoring with signed verdict.',
|
|
69
|
+
inputSchema: { type: 'object', properties: { asset: { type: 'string', description: 'Asset symbol (e.g. ETH, AERO, VIRTUAL)' }, payment: { type: 'string' } }, required: ['asset'] } },
|
|
70
|
+
{ name: 'hash_sha256', description: 'PAID 0.001 USDC/call. SHA-256 hex of a string.',
|
|
71
|
+
inputSchema: { type: 'object', properties: { input: { type: 'string' }, payment: { type: 'string' } }, required: ['input'] } },
|
|
72
|
+
{ name: 'uuid', description: 'PAID 0.001 USDC/call. Generate a UUIDv4.', inputSchema: { type: 'object', properties: { payment: { type: 'string' } } } },
|
|
73
|
+
{ name: 'pricing', description: 'FREE. Full pricing terms, endpoints, and payment mechanics.',
|
|
74
|
+
inputSchema: { type: 'object', properties: {} } },
|
|
75
|
+
{ name: 'health', description: 'FREE. Service liveness and version.', inputSchema: { type: 'object', properties: {} } }
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
function q(o) { return Object.entries(o || {}).filter(([, v]) => v !== undefined && v !== null && v !== '').map(([k, v]) => k + '=' + encodeURIComponent(v)).join('&'); }
|
|
79
|
+
|
|
80
|
+
async function callTool(name, args) {
|
|
81
|
+
args = args || {};
|
|
82
|
+
const payHeader = (args.payment || PAYMENT) ? { 'X-PAYMENT': args.payment || PAYMENT } : {};
|
|
83
|
+
switch (name) {
|
|
84
|
+
case 'x402_conformance_check': {
|
|
85
|
+
if (!/^https?:\/\//i.test(args.url || '')) throw new Error('url must be an absolute http(s) URL');
|
|
86
|
+
return { status: 200, body: await linter.run(args.url) };
|
|
87
|
+
}
|
|
88
|
+
case 'security_scan': return httpJson('GET', BASE + '/v2/security/scan?' + q({ address: args.address }), payHeader);
|
|
89
|
+
case 'attest': return httpJson('POST', BASE + '/v2/attest', payHeader, { data: args.data });
|
|
90
|
+
case 'verify_attestation': return httpJson('GET', BASE + '/v2/verify?' + q({ index: args.index, dataHash: args.dataHash }));
|
|
91
|
+
case 'attestation_pubkey': return httpJson('GET', BASE + '/v2/pubkey');
|
|
92
|
+
case 'read_ledger': return httpJson('GET', BASE + '/v2/ledger?' + q({ from: args.from, limit: args.limit }));
|
|
93
|
+
case 'oracle_base': return httpJson('GET', BASE + '/v2/oracle/base', payHeader);
|
|
94
|
+
case 'merkle_prove': return httpJson('POST', BASE + '/v2/merkle/prove', payHeader, { items: args.items, target: args.target });
|
|
95
|
+
case 'merkle_verify': return httpJson('POST', BASE + '/v2/merkle/verify', {}, { item: args.item, proof: args.proof, root: args.root });
|
|
96
|
+
case 'sentiment_analysis': return httpJson('GET', BASE + '/v2/sentiment?' + q({ asset: args.asset }), payHeader);
|
|
97
|
+
case 'hash_sha256': return httpJson('GET', BASE + '/v1/hash?' + q({ input: args.input }), payHeader);
|
|
98
|
+
case 'uuid': return httpJson('GET', BASE + '/v1/uuid', payHeader);
|
|
99
|
+
case 'pricing': return httpJson('GET', BASE + '/pricing');
|
|
100
|
+
case 'health': return httpJson('GET', BASE + '/health');
|
|
101
|
+
default: throw new Error('unknown tool: ' + name);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function reply(id, result) { process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id, result }) + '\n'); }
|
|
106
|
+
function replyErr(id, code, message, data) { process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id, error: { code, message, data } }) + '\n'); }
|
|
107
|
+
|
|
108
|
+
let buf = '';
|
|
109
|
+
process.stdin.on('data', (chunk) => {
|
|
110
|
+
buf += chunk.toString('utf8');
|
|
111
|
+
let nl;
|
|
112
|
+
while ((nl = buf.indexOf('\n')) >= 0) {
|
|
113
|
+
const line = buf.slice(0, nl).trim(); buf = buf.slice(nl + 1);
|
|
114
|
+
if (!line) continue;
|
|
115
|
+
let msg; try { msg = JSON.parse(line); } catch (e) { continue; }
|
|
116
|
+
if (msg.method === 'initialize') {
|
|
117
|
+
reply(msg.id, { protocolVersion: '2024-11-05', capabilities: { tools: {} },
|
|
118
|
+
serverInfo: { name: 'automaton-x402', version: PKG.version } });
|
|
119
|
+
} else if (msg.method === 'ping') {
|
|
120
|
+
reply(msg.id, {});
|
|
121
|
+
} else if (msg.method === 'notifications/initialized') {
|
|
122
|
+
// no reply
|
|
123
|
+
} else if (msg.method === 'tools/list') {
|
|
124
|
+
reply(msg.id, { tools: TOOLS });
|
|
125
|
+
} else if (msg.method === 'tools/call') {
|
|
126
|
+
const { name, arguments: a } = msg.params || {};
|
|
127
|
+
Promise.resolve().then(() => callTool(name, a)).then((r) => {
|
|
128
|
+
const isClosedError = r.status >= 400;
|
|
129
|
+
// MCP expects content[]; surface status + body, and mark 402 as error with actionable text
|
|
130
|
+
const text = typeof r.body === 'string' ? r.body : JSON.stringify(r.body, null, 2);
|
|
131
|
+
const prefix = isClosedError ? ('HTTP ' + r.status + '\n') : '';
|
|
132
|
+
reply(msg.id, { content: [{ type: 'text', text: prefix + text }], isError: isClosedError });
|
|
133
|
+
}).catch((e) => reply(msg.id, { content: [{ type: 'text', text: 'error: ' + e.message }], isError: true }));
|
|
134
|
+
} else if (msg.id !== undefined) {
|
|
135
|
+
replyErr(msg.id, -32601, 'method not found: ' + msg.method);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
process.stdin.on('end', () => process.exit(0));
|
package/package.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@celorodrigues/x402-conformance",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Zero-dependency CLI
|
|
3
|
+
"version": "2.1.0",
|
|
4
|
+
"description": "Zero-dependency CLI, MCP server and testing suite for x402 AI agent micropayment protocol & EIP-3009 compliance",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"x402-conformance": "bin/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"index.js",
|
|
11
|
+
"mcp-server.js",
|
|
11
12
|
"bin/",
|
|
12
13
|
"README.md",
|
|
13
|
-
"LICENSE"
|
|
14
|
+
"LICENSE",
|
|
15
|
+
"smithery.yaml"
|
|
14
16
|
],
|
|
15
17
|
"keywords": [
|
|
16
18
|
"x402",
|
|
@@ -21,7 +23,10 @@
|
|
|
21
23
|
"micropayments",
|
|
22
24
|
"usdc",
|
|
23
25
|
"linter",
|
|
24
|
-
"conformance"
|
|
26
|
+
"conformance",
|
|
27
|
+
"mcp",
|
|
28
|
+
"model-context-protocol",
|
|
29
|
+
"claude"
|
|
25
30
|
],
|
|
26
31
|
"author": "Automaton-Sovereign (0x71DEAc098914A009E3720524642A6bE6F65EE528)",
|
|
27
32
|
"license": "MIT",
|
package/smithery.yaml
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Smithery configuration: https://smithery.ai/docs/config#smitheryyaml
|
|
2
|
+
# MCP stdio server shipped in the npm package (`x402-conformance --mcp`).
|
|
3
|
+
startCommand:
|
|
4
|
+
type: stdio
|
|
5
|
+
configSchema:
|
|
6
|
+
type: object
|
|
7
|
+
properties:
|
|
8
|
+
valueApiBase:
|
|
9
|
+
type: string
|
|
10
|
+
default: https://api.automaton-sovereign.workers.dev
|
|
11
|
+
description: Base URL of the Automaton-Sovereign Value API used by the paid/free API tools.
|
|
12
|
+
valueApiPayment:
|
|
13
|
+
type: string
|
|
14
|
+
description: Optional x402 payment (Base USDC tx hash) attached as X-PAYMENT to paid tool calls.
|
|
15
|
+
commandFunction:
|
|
16
|
+
|-
|
|
17
|
+
(config) => ({
|
|
18
|
+
command: 'npx',
|
|
19
|
+
args: ['-y', '@celorodrigues/x402-conformance', '--mcp'],
|
|
20
|
+
env: Object.assign(
|
|
21
|
+
{ VALUE_API_BASE: config.valueApiBase || 'https://api.automaton-sovereign.workers.dev' },
|
|
22
|
+
config.valueApiPayment ? { VALUE_API_PAYMENT: config.valueApiPayment } : {}
|
|
23
|
+
)
|
|
24
|
+
})
|
|
25
|
+
exampleConfig:
|
|
26
|
+
valueApiBase: https://api.automaton-sovereign.workers.dev
|