x402node-mcp 0.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/.env.example ADDED
@@ -0,0 +1,4 @@
1
+ X402_PRIVATE_KEY=0x_replace_with_your_64hex_private_key
2
+ BASE_RPC_URL=https://mainnet.base.org
3
+ X402_MAX_PRICE_USD=0.5
4
+ X402_REFRESH_MIN=5
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 x402node
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # x402-mcp
2
+
3
+ MCP server bringing 100+ x402-paid APIs to AI agents (Claude, Cursor, MCP-aware clients). Auto-discovers tools from CDP Bazaar; handles USDC micropayments on Base.
4
+
5
+ ## Features
6
+
7
+ - Auto-discovers x402 endpoints from CDP Bazaar (no hard-coded list)
8
+ - Reads metadata directly from Bazaar (description, schema, pricing)
9
+ - Refreshes every 5 min — new endpoints appear without restart
10
+ - Handles HTTP 402 + USDC payment automatically
11
+ - Multi-chain ready via x402 protocol (Base today; Solana, Polygon, BNB, EVM expansion)
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ git clone https://github.com/x402node/x402-mcp
17
+ cd x402-mcp
18
+ npm install
19
+ cp .env.example .env
20
+ ```
21
+
22
+ Edit `.env`: set `X402_PRIVATE_KEY` to a Base EOA with USDC.
23
+
24
+ ## Use with Claude Desktop
25
+
26
+ Add to `claude_desktop_config.json`:
27
+
28
+ ```json
29
+ {
30
+ "mcpServers": {
31
+ "x402-mcp": {
32
+ "command": "node",
33
+ "args": ["/absolute/path/to/x402-mcp/mcp-server.js"],
34
+ "env": { "X402_PRIVATE_KEY": "0xYOUR_KEY" }
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ ## How it works
41
+
42
+ Agent calls tool → HTTP 402 → x402-fetch signs EIP-3009 → CDP Facilitator settles on Base → response returned. Buyer pays no gas.
43
+
44
+ ## Links
45
+
46
+ - x402 protocol: https://x402.org
47
+ - CDP Bazaar: https://docs.cdp.coinbase.com/x402/bazaar
48
+ - MCP: https://modelcontextprotocol.io
49
+
50
+ ## License
51
+
52
+ MIT
package/mcp-server.js ADDED
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
6
+ import { privateKeyToAccount } from "viem/accounts";
7
+ import { createWalletClient, http } from "viem";
8
+ import { base } from "viem/chains";
9
+ import { wrapFetchWithPayment } from "x402-fetch";
10
+
11
+ const PAY_TO = process.env.X402_PAY_TO || "0x4466d4A84b7c49a6A094ec6eef4a0712D6dd125e";
12
+ const PRIVATE_KEY = process.env.X402_PRIVATE_KEY;
13
+ const RPC_URL = process.env.BASE_RPC_URL || "https://mainnet.base.org";
14
+ const MAX_PRICE_USD = parseFloat(process.env.X402_MAX_PRICE_USD || "0.5");
15
+ const REFRESH_MIN = parseInt(process.env.X402_REFRESH_MIN || "5");
16
+
17
+ if (!PRIVATE_KEY) {
18
+ console.error("ERROR: Set X402_PRIVATE_KEY in environment");
19
+ process.exit(1);
20
+ }
21
+
22
+ const DISCOVERY = `https://api.cdp.coinbase.com/platform/v2/x402/discovery/merchant?payTo=${PAY_TO}`;
23
+
24
+ const account = privateKeyToAccount(PRIVATE_KEY);
25
+ const wallet = createWalletClient({ account, chain: base, transport: http(RPC_URL) });
26
+ const payFetch = wrapFetchWithPayment(fetch, wallet, BigInt(Math.floor(MAX_PRICE_USD * 1e6)));
27
+
28
+ async function loadResources() {
29
+ const all = [];
30
+ let offset = 0;
31
+ const limit = 100;
32
+ while (true) {
33
+ const r = await fetch(`${DISCOVERY}&limit=${limit}&offset=${offset}`);
34
+ if (!r.ok) throw new Error(`Bazaar HTTP ${r.status}`);
35
+ const j = await r.json();
36
+ const items = j.resources || [];
37
+ all.push(...items);
38
+ const total = j.pagination?.total || items.length;
39
+ if (offset + items.length >= total || items.length === 0) break;
40
+ offset += items.length;
41
+ }
42
+ return all;
43
+ }
44
+
45
+ function pathToToolName(resourceUrl) {
46
+ const u = new URL(resourceUrl);
47
+ const site = u.hostname.replace(/^api\./, "").replace(/\.(dev|com|net|io)$/, "").replace(/[^a-z0-9]/gi, "");
48
+ const path = u.pathname.replace(/^\/+/, "").replace(/\//g, "_");
49
+ return `${site}_${path}`.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 60);
50
+ }
51
+
52
+ function resourceToTool(resource) {
53
+ const accept = (resource.accepts && resource.accepts[0]) || {};
54
+ const bazaar = (accept.extensions && accept.extensions.bazaar && accept.extensions.bazaar.info) || {};
55
+ const input = bazaar.input || {};
56
+ const queryParams = input.queryParams || {};
57
+ const method = (input.method || "GET").toUpperCase();
58
+ const priceUsd = accept.amount ? parseInt(accept.amount) / 1e6 : null;
59
+ const priceStr = priceUsd !== null ? `$${priceUsd.toFixed(4)} USDC` : "unknown";
60
+
61
+ const props = {};
62
+ const required = [];
63
+ for (const [k, desc] of Object.entries(queryParams)) {
64
+ props[k] = { type: "string", description: String(desc) };
65
+ if (/required/i.test(String(desc))) required.push(k);
66
+ }
67
+
68
+ const name = pathToToolName(resource.resource);
69
+ const desc = (accept.description || resource.resource).slice(0, 500);
70
+
71
+ return {
72
+ name,
73
+ description: `${desc}\n\nPrice: ${priceStr} on Base (auto-paid in USDC).`,
74
+ inputSchema: { type: "object", properties: props, required },
75
+ _resource: resource.resource,
76
+ _method: method,
77
+ _priceUsd: priceUsd,
78
+ };
79
+ }
80
+
81
+ const server = new Server({ name: "x402-cn402-x402node", version: "0.1.0" }, { capabilities: { tools: {} } });
82
+
83
+ let TOOLS = [];
84
+ let TOOL_MAP = {};
85
+
86
+ async function refreshTools() {
87
+ try {
88
+ const resources = await loadResources();
89
+ const tools = resources.map(resourceToTool);
90
+ const seen = new Set();
91
+ TOOLS = tools.filter(t => {
92
+ if (seen.has(t.name)) return false;
93
+ seen.add(t.name);
94
+ return true;
95
+ });
96
+ TOOL_MAP = Object.fromEntries(TOOLS.map(t => [t.name, t]));
97
+ console.error(`[mcp] loaded ${TOOLS.length} tools from Bazaar (payTo=${PAY_TO})`);
98
+ } catch (e) {
99
+ console.error(`[mcp] refresh failed: ${e.message}`);
100
+ }
101
+ }
102
+
103
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
104
+ tools: TOOLS.map(t => ({ name: t.name, description: t.description, inputSchema: t.inputSchema })),
105
+ }));
106
+
107
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
108
+ const tool = TOOL_MAP[req.params.name];
109
+ if (!tool) {
110
+ return { content: [{ type: "text", text: `Unknown tool: ${req.params.name}` }], isError: true };
111
+ }
112
+ if (tool._priceUsd !== null && tool._priceUsd > MAX_PRICE_USD) {
113
+ return { content: [{ type: "text", text: `Tool costs $${tool._priceUsd} > max $${MAX_PRICE_USD}` }], isError: true };
114
+ }
115
+ const url = new URL(tool._resource);
116
+ const args = req.params.arguments || {};
117
+ try {
118
+ let resp;
119
+ if (tool._method === "GET") {
120
+ for (const [k, v] of Object.entries(args)) {
121
+ if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
122
+ }
123
+ resp = await payFetch(url.toString());
124
+ } else {
125
+ resp = await payFetch(url.toString(), {
126
+ method: tool._method,
127
+ headers: { "Content-Type": "application/json" },
128
+ body: JSON.stringify(args),
129
+ });
130
+ }
131
+ const text = await resp.text();
132
+ if (resp.status !== 200) {
133
+ return { content: [{ type: "text", text: `HTTP ${resp.status}: ${text.slice(0, 800)}` }], isError: true };
134
+ }
135
+ return { content: [{ type: "text", text }] };
136
+ } catch (e) {
137
+ return { content: [{ type: "text", text: `x402 call failed: ${e.message}` }], isError: true };
138
+ }
139
+ });
140
+
141
+ await refreshTools();
142
+ setInterval(refreshTools, REFRESH_MIN * 60 * 1000);
143
+
144
+ const transport = new StdioServerTransport();
145
+ await server.connect(transport);
146
+ console.error("[mcp] x402-cn402-x402node MCP server ready on stdio");
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "x402node-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server that exposes x402 paid APIs as Model Context Protocol tools. Auto-discovers endpoints from the Coinbase Bazaar registry and handles USDC payments on Base mainnet transparently.",
5
+ "type": "module",
6
+ "main": "mcp-server.js",
7
+ "bin": {
8
+ "x402node-mcp": "./mcp-server.js"
9
+ },
10
+ "files": [
11
+ "mcp-server.js",
12
+ "test-client.mjs",
13
+ ".env.example",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "start": "node mcp-server.js",
19
+ "test": "node test-client.mjs"
20
+ },
21
+ "keywords": [
22
+ "mcp",
23
+ "model-context-protocol",
24
+ "x402",
25
+ "usdc",
26
+ "base",
27
+ "coinbase",
28
+ "payment",
29
+ "agent",
30
+ "claude",
31
+ "anthropic",
32
+ "ai-agent",
33
+ "stablecoin"
34
+ ],
35
+ "author": "CKING LLC",
36
+ "license": "MIT",
37
+ "homepage": "https://github.com/x402node/x402-mcp#readme",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/x402node/x402-mcp.git"
41
+ },
42
+ "bugs": {
43
+ "url": "https://github.com/x402node/x402-mcp/issues"
44
+ },
45
+ "engines": {
46
+ "node": ">=18"
47
+ },
48
+ "dependencies": {
49
+ "@modelcontextprotocol/sdk": "^1.29.0",
50
+ "dotenv": "^17.4.2",
51
+ "viem": "^2.51.0",
52
+ "x402-fetch": "^1.2.0"
53
+ }
54
+ }
@@ -0,0 +1,21 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+
4
+ const transport = new StdioClientTransport({
5
+ command: "node",
6
+ args: ["mcp-server.js"],
7
+ });
8
+
9
+ const client = new Client({ name: "test", version: "1.0.0" }, { capabilities: {} });
10
+ await client.connect(transport);
11
+ console.log("Connected.");
12
+
13
+ const tools = await client.listTools();
14
+ console.log("Tool count:", tools.tools.length);
15
+
16
+ console.log("\nCalling x402node_dev_uuid...");
17
+ const result = await client.callTool({ name: "x402node_dev_uuid", arguments: {} });
18
+ console.log("Result:", JSON.stringify(result, null, 2));
19
+
20
+ await client.close();
21
+ process.exit(0);