diff --git a/src/revnuvo/README.md b/src/revnuvo/README.md new file mode 100755 index 0000000000..bfb96a747b --- /dev/null +++ b/src/revnuvo/README.md @@ -0,0 +1,338 @@ +# Revnuvo x402 MCP Server + +A [Model Context Protocol](https://modelcontextprotocol.io) server that exposes +the **Revnuvo x402 paid APIs** as AI-callable tools. Each tool call transparently +performs the [x402 v2](https://x402.org) payment flow — signing a USDC payment +on Base, retrying the request with the `X-Payment` header, and returning the +unlocked JSON payload. + +> **One sentence:** Claude Desktop / Cursor can verify a domain, dump its DNS, +> or run a full domain assessment by paying $0.001–$0.05 in USDC per call, +> without you writing a line of payment code. + +--- + +## Tools Exposed + +| Tool | Upstream Endpoint | Price | Returns | +|------|-------------------|-------|---------| +| `verify_domain` | `POST https://revnuvo-resource.revnuvo.workers.dev/domain/verify` | **$0.001** USDC | Reachability + registration metadata | +| `dns_lookup` | `POST https://revnuvo-resource.revnuvo.workers.dev/domain/dns` | **$0.002** USDC | Full DNS record dump (A / AAAA / MX / NS / TXT / CNAME / SOA) | +| `assess_domain` | `POST https://revnuvo-resource.revnuvo.workers.dev/domain/assess` | **$0.005** USDC | Quick trust-score snapshot (verification + DNS + scoring) | +| `assess_domain_full` | `POST https://assess.revnuvo.site/assess/domain` | **$0.05** USDC | Full Revnuvo assessment report (security, email, threat-intel, revenue-intel, recommendations) | + +All four tools take a single argument: + +```jsonc +{ "domain": "example.com" } // bare hostname, no scheme or path +``` + +--- + +## Prerequisites + +1. **Node.js ≥ 20** installed locally. +2. **An EVM wallet** that holds USDC on Base. You need the wallet's **private + key** (hex, with or without `0x` prefix). Each tool call deducts the + per-call price from this wallet. To fund a wallet with USDC on Base, see + [Coinbase Wallet](https://wallet.coinbase.com) or any Base-compatible + bridge. +3. **MCP client** — Claude Desktop, Cursor, or any other client that speaks + MCP over stdio. + +> **Security note:** Your private key never leaves your machine. All payment +> signing happens locally inside `@revnuvo/x402` using `viem`. The key is +> only used to sign the x402 payment payload; it is never transmitted to +> Revnuvo or to Smithery. + +--- + +## Installation + +### Option A — Build from source (recommended for local development) + +```bash +# 1. Clone or copy the four files into a working directory: +# revnuvo-mcp-server.ts +# package.json +# smithery.yaml +# README.md + +# 2. Install dependencies +npm install + +# 3. Compile the TypeScript +npm run build +# -> emits build/index.js + +# 4. Verify the binary runs (needs WALLET_PRIVATE_KEY in env) +export WALLET_PRIVATE_KEY="0xYOUR_PRIVATE_KEY_HERE" +node build/index.js +# Should print to stderr: +# [revnuvo-mcp] Server started on stdio. Network=base ... +``` + +### Option B — Install as an npm package (once published) + +```bash +npm install -g revnuvo-x402-mcp +``` + +### Option C — Run on Smithery.ai (zero local install) + +Use the `smithery.yaml` in this directory. Push the repo to GitHub, then import +it into Smithery. Smithery will install deps, build, and host the server over +HTTP for you. You'll be prompted for `WALLET_PRIVATE_KEY` in the Smithery UI +(it's marked `secret: true` so it's never logged). + +--- + +## Configure in Claude Desktop + +Claude Desktop reads its MCP servers from a JSON config file: + +| OS | Path | +|----|------| +| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` | +| Windows | `%APPDATA%\Claude\claude_desktop_config.json` | +| Linux | `~/.config/Claude/claude_desktop_config.json` | + +Open (or create) that file and add the `revnuvo-x402-mcp` entry: + +```jsonc +{ + "mcpServers": { + "revnuvo-x402-mcp": { + "command": "node", + "args": ["/absolute/path/to/revnuvo-x402-mcp/build/index.js"], + "env": { + "WALLET_PRIVATE_KEY": "0xYOUR_PRIVATE_KEY_HERE", + "REVNUVO_FACILITATOR": "https://facilitator.xpay.sh", + "REVNUVO_NETWORK": "base" + } + } + } +} +``` + +**Important:** Replace `/absolute/path/to/...` with the real absolute path to +your `build/index.js`. Relative paths will not work. + +**Restart Claude Desktop** completely (not just reload). After restart, you +should see four new tools available in any chat: + +- `verify_domain` +- `dns_lookup` +- `assess_domain` +- `assess_domain_full` + +Try it with a prompt like: + +> *"Use the assess_domain_full tool to assess stripe.com and summarize the +> trust score and security posture."* + +Claude will invoke the tool, the MCP server will pay $0.05 USDC on Base from +your wallet, and Claude will summarize the returned JSON. + +--- + +## Configure in Cursor + +Cursor supports MCP servers via `~/.cursor/mcp.json` (global) or +`.cursor/mcp.json` (per-project): + +```jsonc +{ + "mcpServers": { + "revnuvo-x402-mcp": { + "command": "node", + "args": ["/absolute/path/to/revnuvo-x402-mcp/build/index.js"], + "env": { + "WALLET_PRIVATE_KEY": "0xYOUR_PRIVATE_KEY_HERE", + "REVNUVO_FACILITATOR": "https://facilitator.xpay.sh", + "REVNUVO_NETWORK": "base" + } + } + } +} +``` + +After saving, open Cursor Settings → MCP → click the refresh icon next to +`revnuvo-x402-mcp`. The four tools become available to Cursor Chat and +Cursor Agent. + +--- + +## Configure in any MCP-compatible client (generic stdio) + +The server speaks MCP over stdio. Any client that supports the standard +`command` + `args` + `env` MCP launcher pattern can launch it: + +```yaml +# pseudocode +command: node +args: + - /absolute/path/to/build/index.js +env: + WALLET_PRIVATE_KEY: 0x... + REVNUVO_FACILITATOR: https://facilitator.xpay.sh + REVNUVO_NETWORK: base +``` + +--- + +## Environment Variables + +| Variable | Required | Default | Description | +|----------|----------|---------|-------------| +| `WALLET_PRIVATE_KEY` | **Yes** | — | Hex private key of the wallet that pays per call. Accepts `0x`-prefixed or bare hex. | +| `REVNUVO_FACILITATOR` | No | `https://facilitator.xpay.sh` | x402 facilitator URL that verifies payments server-side. Override for testnet or self-hosted facilitators. | +| `REVNUVO_NETWORK` | No | `base` | Settlement network. Use `base` for mainnet or `base-sepolia` for testnet. | + +--- + +## How the x402 Payment Flow Works + +Every tool call follows the same five-step dance — handled automatically by +the `@revnuvo/x402` SDK: + +``` +┌──────────────┐ ┌──────────────────┐ +│ MCP client │ │ Revnuvo API │ +│ (Claude / │ │ (Cloudflare) │ +│ Cursor) │ │ │ +└──────┬───────┘ └────────┬─────────┘ + │ POST /domain/verify {"domain":"example.com"} │ + │ ─────────────────────────────────────────────────>│ + │ │ + │ 402 Payment Required │ + │ WWW-Authenticate: x402 ... │ + │<─────────────────────────────────────────────────│ + │ │ + │ SDK signs USDC payment on Base locally │ + │ (no key ever leaves the process) │ + │ │ + │ POST /domain/verify {"domain":"example.com"} │ + │ X-Payment: │ + │ ─────────────────────────────────────────────────>│ + │ │ + │ Server verifies payment via facilitator │ + │ https://facilitator.xpay.sh │ + │ │ + │ 200 OK + JSON result │ + │ X-Payment-Response: │ + │<─────────────────────────────────────────────────│ + │ │ + │ Result returned to MCP client as tool output │ +``` + +--- + +## Development + +```bash +# Install deps +npm install + +# Watch mode +npm run dev + +# Run locally with hot reload (skip the build step) +WALLET_PRIVATE_KEY=0x... npx tsx revnuvo-mcp-server.ts + +# Type-check without emitting +npx tsc --noEmit +``` + +### Project layout + +``` +revnuvo-x402-mcp/ +├── revnuvo-mcp-server.ts # source — single-file MCP server +├── package.json # deps, scripts, MCP launcher metadata +├── smithery.yaml # Smithery.ai deployment config +├── README.md # this file +├── tsconfig.json # (you should add this; see below) +└── build/ + └── index.js # emitted by `npm run build` +``` + +### Recommended `tsconfig.json` + +If you don't already have one, drop this in: + +```json +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "build", + "rootDir": ".", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true + }, + "include": ["revnuvo-mcp-server.ts"] +} +``` + +--- + +## Troubleshooting + +**`WALLET_PRIVATE_KEY environment variable is required`** +The server exits immediately if the env var is missing. Set it before +launching the MCP server (in `claude_desktop_config.json` `env` block, in +your shell, or in the Smithery UI). + +**`Insufficient USDC balance` / transaction reverts** +Top up your wallet with USDC on Base. Each `assess_domain_full` call costs +$0.05; a $5 USDC balance gives you ~100 calls. Cheaper tools (`verify_domain` +at $0.001) let you stretch that to ~5,000 calls. + +**Claude Desktop doesn't see the tools** +1. Confirm the absolute path in `args` is correct. +2. Confirm `build/index.js` actually exists (`npm run build`). +3. Fully quit Claude Desktop (Cmd+Q on macOS) and reopen. +4. Check Claude Desktop's MCP logs at + `~/Library/Logs/Claude/mcp*.log` (macOS). + +**Tool returns `402 Payment Required` after retry** +The facilitator may be unreachable. Verify +`curl https://facilitator.xpay.sh/health` returns 200. If you're running +against a custom facilitator, set `REVNUVO_FACILITATOR` accordingly. + +**Tool returns `401` / `403` after retry** +The signature is being rejected — most commonly because the wallet address +derived from `WALLET_PRIVATE_KEY` doesn't match the payment sender expected by +the facilitator. Re-check the key. + +--- + +## Privacy & Security + +- **Your private key never leaves your machine.** The `@revnuvo/x402` SDK uses + `viem` to sign the x402 payment payload locally; only the signed payload is + transmitted. +- **Domain queries are sent to Revnuvo.** The domain you ask about is + transmitted to the Revnuvo API over HTTPS. Revnuvo logs the query for + billing and abuse prevention. +- **Payments are public on Base.** Every USDC transfer settles on-chain and is + visible on Basescan. + +--- + +## License + +MIT © Revnuvo Technologies Ltd. See [LICENSE](./LICENSE). + +## Links + +- **Website:** https://revnuvo.site +- **x402 protocol:** https://x402.org +- **MCP spec:** https://modelcontextprotocol.io +- **Smithery.ai:** https://smithery.ai +- **Facilitator:** https://facilitator.xpay.sh diff --git a/src/revnuvo/index.ts b/src/revnuvo/index.ts new file mode 100755 index 0000000000..1133c4e324 --- /dev/null +++ b/src/revnuvo/index.ts @@ -0,0 +1,394 @@ +/** + * Revnuvo x402 MCP Server + * ======================= + * + * A Model Context Protocol (MCP) server that exposes the Revnuvo x402 paid + * API services as MCP tools, so AI assistants (Claude Desktop, Cursor, etc.) + * can call them directly. Each tool transparently handles the x402 v2 payment + * flow: + * + * 1. Client sends a POST request to the Revnuvo endpoint. + * 2. Server responds with 402 Payment Required + WWW-Authenticate header. + * 3. We use @revnuvo/x402 SDK to pay the requested USDC amount on Base. + * 4. We retry the original request with an X-Payment header. + * 5. Server verifies the payment via the facilitator and returns 200 OK. + * + * Tools exposed: + * - verify_domain ($0.001 USDC) -> https://revnuvo-resource.revnuvo.workers.dev/domain/verify + * - dns_lookup ($0.002 USDC) -> https://revnuvo-resource.revnuvo.workers.dev/domain/dns + * - assess_domain ($0.005 USDC) -> https://revnuvo-resource.revnuvo.workers.dev/domain/assess + * - assess_domain_full ($0.05 USDC) -> https://assess.revnuvo.site/assess/domain + * + * Configuration (environment variables): + * - WALLET_PRIVATE_KEY EVM private key (hex, with or without 0x prefix) + * of the wallet that holds USDC on Base. REQUIRED. + * - REVNUVO_FACILITATOR Override the x402 facilitator URL + * (default: https://facilitator.xpay.sh). + * - REVNUVO_NETWORK Override the settlement network (default: base). + * + * Usage (stdio transport, standard MCP): + * build/index.js # reads WALLET_PRIVATE_KEY from env + * + * Usage (HTTP transport, Smithery): + * build/index.js --http --port 8080 + * + * Author: Revnuvo Technologies Ltd + * License: MIT + */ + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListToolsRequestSchema, + type Tool, + type ToolSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import { buildXPaymentHeader } from "@revnuvo/x402"; +import { privateKeyToAccount } from "viem/accounts"; +import { toHex, getAddress } from "viem"; + +// --------------------------------------------------------------------------- +// Configuration +// --------------------------------------------------------------------------- + +const WALLET_PRIVATE_KEY = process.env.WALLET_PRIVATE_KEY; +const NETWORK = "base"; +const FACILITATOR_URL = + process.env.REVNUVO_FACILITATOR ?? "https://facilitator.xpay.sh"; + +if (!WALLET_PRIVATE_KEY) { + console.error("[revnuvo-mcp] ERROR: WALLET_PRIVATE_KEY environment variable is required."); + process.exit(1); +} + +const privateKey = WALLET_PRIVATE_KEY.startsWith("0x") ? WALLET_PRIVATE_KEY : `0x${WALLET_PRIVATE_KEY}`; +const payer = privateKeyToAccount(privateKey as `0x${string}`); + +// Manual x402 payment flow: fetch → 402 → sign EIP-3009 → retry with payment header +async function paidFetch(url: string, init?: RequestInit): Promise { + const res1 = await fetch(url, init); + if (res1.status !== 402) return res1; + + const prHeader = res1.headers.get("payment-required"); + if (!prHeader) throw new Error("402 but no payment-required header"); + const challenge = JSON.parse(Buffer.from(prHeader, "base64").toString("utf-8")); + const req = challenge.accepts[0]; + + const nonce = toHex(crypto.getRandomValues(new Uint8Array(32))); + const now = Math.floor(Date.now() / 1000); + const authorization = { + from: payer.address, to: getAddress(req.payTo), value: req.amount, + validAfter: "0", validBefore: String(now + (req.maxTimeoutSeconds ?? 300)), nonce, + }; + const chainId = parseInt(req.network.split(":")[1], 10); + const domain = { name: req.extra?.name ?? "USD Coin", version: req.extra?.version ?? "2", chainId, verifyingContract: getAddress(req.asset) }; + const types = { TransferWithAuthorization: [{name:"from",type:"address"},{name:"to",type:"address"},{name:"value",type:"uint256"},{name:"validAfter",type:"uint256"},{name:"validBefore",type:"uint256"},{name:"nonce",type:"bytes32"}] }; + const message = { from: getAddress(authorization.from), to: getAddress(authorization.to), value: BigInt(authorization.value), validAfter: BigInt(authorization.validAfter), validBefore: BigInt(authorization.validBefore), nonce: authorization.nonce }; + const signature = await payer.signTypedData({ domain, types, primaryType: "TransferWithAuthorization", message }); + + const payload = { x402Version: 2, resource: challenge.resource, accepted: req, payload: { authorization, signature } }; + const header = Buffer.from(JSON.stringify(payload)).toString("base64"); + const headers = new Headers(init?.headers); + headers.set("payment-signature", header); + return fetch(url, { ...init, headers }); +} + + +// --------------------------------------------------------------------------- +// Endpoint registry +// --------------------------------------------------------------------------- + +interface RevnuvoEndpoint { + /** Stable tool name surfaced to the MCP client. */ + toolName: string; + /** Human-readable title shown in tool listings. */ + title: string; + /** Plain-English description of what the endpoint returns. */ + description: string; + /** Full HTTPS URL of the upstream Revnuvo x402 endpoint. */ + url: string; + /** Per-request price in USDC, as a human-readable string. */ + priceUsdc: string; + /** Raw USDC atomic units (6 decimals) used inside the x402 protocol. */ + priceAtomic: string; +} + +const ENDPOINTS: Record = { + verify_domain: { + toolName: "verify_domain", + title: "Verify Domain (x402, $0.001)", + description: + "Verifies that a domain is reachable and returns basic registration / " + + "reachability metadata. Costs $0.001 USDC on Base per call. Uses the " + + "Revnuvo Resource service at revnuvo-resource.revnuvo.workers.dev.", + url: "https://revnuvo-resource.revnuvo.workers.dev/domain/verify", + priceUsdc: "$0.001", + priceAtomic: "1000", // 0.001 USDC = 1000 atomic units (6 decimals) + }, + dns_lookup: { + toolName: "dns_lookup", + title: "DNS Lookup (x402, $0.002)", + description: + "Returns a full DNS record dump (A, AAAA, MX, NS, TXT, CNAME, SOA) " + + "for the requested domain. Costs $0.002 USDC on Base per call. Uses " + + "the Revnuvo Resource service at revnuvo-resource.revnuvo.workers.dev.", + url: "https://revnuvo-resource.revnuvo.workers.dev/domain/dns", + priceUsdc: "$0.002", + priceAtomic: "2000", // 0.002 USDC = 2000 atomic units + }, + assess_domain: { + toolName: "assess_domain", + title: "Assess Domain — Quick (x402, $0.005)", + description: + "Runs a quick domain assessment — combines verification + DNS lookup + " + + "lightweight scoring into a compact risk/opportunity snapshot. Costs " + + "$0.005 USDC on Base per call. Uses the Revnuvo Resource service at " + + "revnuvo-resource.revnuvo.workers.dev.", + url: "https://revnuvo-resource.revnuvo.workers.dev/domain/assess", + priceUsdc: "$0.005", + priceAtomic: "5000", // 0.005 USDC = 5000 atomic units + }, + assess_domain_full: { + toolName: "assess_domain_full", + title: "Assess Domain — Full Report (x402, $0.05)", + description: + "Returns the FULL Revnuvo domain assessment report — trust score, " + + "security posture (SPF/DKIM/DMARC/DNSSEC), MX validity, threat-intel " + + "reputation, revenue intelligence signals, and a recommended action " + + "plan. Costs $0.05 USDC on Base per call. Uses the Revnuvo Assess " + + "service at assess.revnuvo.site.", + url: "https://assess.revnuvo.site/assess/domain", + priceUsdc: "$0.05", + priceAtomic: "50000", // 0.05 USDC = 50000 atomic units + }, +}; + +// --------------------------------------------------------------------------- +// MCP tool schemas +// --------------------------------------------------------------------------- + +/** + * Per the MCP spec, ToolSchema is the JSON-Schema describing the arguments + * a tool accepts. All four Revnuvo tools take a single `domain` string. + */ +const domainArgumentSchema = { + type: "object" as const, + properties: { + domain: { + type: "string", + description: + "The domain to query (e.g. 'example.com'). Do not include a scheme " + + "or path — bare hostname only.", + pattern: "^(?!https?://)[a-zA-Z0-9-]+(\\.[a-zA-Z0-9-]+)+$", + examples: ["example.com", "revnuvo.site", "sub.example.co.uk"], + }, + }, + required: ["domain"], + additionalProperties: false, +} + +const TOOL_DEFINITIONS: Tool[] = Object.values(ENDPOINTS).map((ep) => ({ + name: ep.toolName, + title: ep.title, + description: ep.description, + inputSchema: domainArgumentSchema, +})); + +// --------------------------------------------------------------------------- +// Tool invocation logic +// --------------------------------------------------------------------------- + +interface DomainArgs { + domain: string; +} + +function isDomainArgs(value: unknown): value is DomainArgs { + if (typeof value !== "object" || value === null) return false; + const candidate = value as { domain?: unknown }; + return typeof candidate.domain === "string" && candidate.domain.length > 0; +} + +/** + * Invoke a Revnuvo x402 endpoint with the payment flow handled automatically. + * + * Returns a JSON-stringified payload suitable for the MCP `content` array. + */ +async function callRevnuvoEndpoint( + endpoint: RevnuvoEndpoint, + domain: string +): Promise<{ content: Array<{ type: "text"; text: string }>; isError?: boolean }> { + const body = JSON.stringify({ domain }); + + try { + // The wrapped fetch transparently: + // - sends the initial POST (no X-Payment header) + // - receives 402 + WWW-Authenticate + // - signs the USDC payment on Base using our private key + // - retries the POST with X-Payment: + // - returns the final 200 OK response + const response = await paidFetch(endpoint.url, { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + }, + body, + }); + + const responseText = await response.text(); + + if (!response.ok) { + return { + content: [ + { + type: "text", + text: + `Revnuvo x402 call to ${endpoint.url} failed.\n` + + `HTTP ${response.status} ${response.statusText}\n` + + `Tool: ${endpoint.toolName}\n` + + `Price: ${endpoint.priceUsdc} USDC on ${NETWORK}\n` + + `Domain: ${domain}\n\n` + + `Response body:\n${responseText}`, + }, + ], + isError: true, + }; + } + + // Pretty-print the JSON if possible, otherwise return raw text. + let formatted: string; + try { + formatted = JSON.stringify(JSON.parse(responseText), null, 2); + } catch { + formatted = responseText; + } + + const paymentReceipt = response.headers.get("X-Payment-Response"); + + return { + content: [ + { + type: "text", + text: + `Revnuvo x402 call succeeded.\n` + + `Tool: ${endpoint.toolName}\n` + + `Endpoint: ${endpoint.url}\n` + + `Price paid: ${endpoint.priceUsdc} USDC on ${NETWORK}\n` + + `Facilitator: ${FACILITATOR_URL}\n` + + `Domain: ${domain}\n` + + (paymentReceipt + ? `Payment receipt: ${paymentReceipt}\n` + : "") + + `\nResponse:\n${formatted}`, + }, + ], + }; + } catch (error) { + const message = + error instanceof Error ? error.message : String(error); + return { + content: [ + { + type: "text", + text: + `Revnuvo x402 call to ${endpoint.url} threw an error.\n` + + `Tool: ${endpoint.toolName}\n` + + `Domain: ${domain}\n\n` + + `Error: ${message}\n\n` + + `Troubleshooting:\n` + + ` - Verify WALLET_PRIVATE_KEY is set and the wallet holds enough ` + + `USDC on ${NETWORK}.\n` + + ` - Verify the facilitator is reachable: ${FACILITATOR_URL}\n` + + ` - Verify the domain is well-formed: ${domain}`, + }, + ], + isError: true, + }; + } +} + +// --------------------------------------------------------------------------- +// MCP server wiring +// --------------------------------------------------------------------------- + +const server = new Server( + { + name: "revnuvo-x402-mcp", + version: "1.0.0", + }, + { + capabilities: { + tools: {}, + }, + } +); + +// ListToolsRequest — return the static tool list. +server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: TOOL_DEFINITIONS, + }; +}); + +// CallToolRequest — dispatch to the appropriate endpoint. +server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + + const endpoint = ENDPOINTS[name]; + if (!endpoint) { + return { + content: [ + { + type: "text", + text: + `Unknown tool: ${name}. Available tools: ` + + Object.keys(ENDPOINTS).join(", "), + }, + ], + isError: true, + }; + } + + if (!isDomainArgs(args)) { + return { + content: [ + { + type: "text", + text: + `Invalid arguments for tool '${name}'. Expected: { "domain": ` + + `"" }. Received: ${JSON.stringify(args)}`, + }, + ], + isError: true, + }; + } + + return callRevnuvoEndpoint(endpoint, args.domain); +}); + +// --------------------------------------------------------------------------- +// Bootstrap +// --------------------------------------------------------------------------- + +async function main(): Promise { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error( + `[revnuvo-mcp] Server started on stdio. ` + + `Network=${NETWORK} Facilitator=${FACILITATOR_URL} ` + + `Tools=${Object.keys(ENDPOINTS).join(", ")}` + ); +} + +main().catch((error) => { + console.error("[revnuvo-mcp] Fatal error during startup:", error); + process.exit(1); +}); + +// --------------------------------------------------------------------------- +// Type re-export for downstream consumers +// --------------------------------------------------------------------------- + +export type { ToolSchema }; +export { ENDPOINTS, TOOL_DEFINITIONS }; diff --git a/src/revnuvo/package.json b/src/revnuvo/package.json new file mode 100755 index 0000000000..8053e5ae88 --- /dev/null +++ b/src/revnuvo/package.json @@ -0,0 +1,70 @@ +{ + "name": "revnuvo-x402-mcp", + "version": "1.0.0", + "description": "MCP server exposing Revnuvo x402 paid APIs (verify, dns, assess, assess-full) as Model Context Protocol tools. Handles the x402 USDC-on-Base payment flow automatically via @revnuvo/x402.", + "type": "module", + "main": "dist/index.js", + "bin": { + "revnuvo-x402-mcp": "dist/index.js" + }, + "files": [ + "dist", + "README.md", + "smithery.yaml" + ], + "scripts": { + "build": "npx tsc", + "dev": "tsc --watch", + "start": "node build/index.js", + "start:http": "node build/index.js --http --port 8080", + "prepublishOnly": "npm run build", + "test": "echo \"No tests yet\" && exit 0" + }, + "keywords": [ + "mcp", + "model-context-protocol", + "x402", + "revnuvo", + "domain-intelligence", + "usdc", + "base", + "claude-desktop", + "cursor", + "ai-tools" + ], + "author": "Revnuvo Technologies Ltd", + "license": "MIT", + "homepage": "https://revnuvo.site", + "repository": { + "type": "git", + "url": "https://github.com/revnuvo/revnuvo-x402-mcp.git" + }, + "bugs": { + "url": "https://github.com/revnuvo/revnuvo-x402-mcp/issues" + }, + "engines": { + "node": ">=20.0.0" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.4", + "@revnuvo/x402": "^0.1.4", + "viem": "^2.21.0" + }, + "devDependencies": { + "@types/node": "^20.14.0", + "typescript": "^5.5.4", + "tsx": "^4.19.0" + }, + "mcp": { + "transport": "stdio", + "command": "node", + "args": [ + "dist/index.js" + ], + "env": { + "WALLET_PRIVATE_KEY": "${WALLET_PRIVATE_KEY}", + "REVNUVO_FACILITATOR": "https://facilitator.xpay.sh", + "REVNUVO_NETWORK": "base" + } + } +} diff --git a/src/revnuvo/smithery.yaml b/src/revnuvo/smithery.yaml new file mode 100755 index 0000000000..63dabd1abc --- /dev/null +++ b/src/revnuvo/smithery.yaml @@ -0,0 +1,91 @@ +# Smithery configuration for revnuvo-x402-mcp +# Docs: https://smithery.ai/docs/config +# +# This file declares how Smithery.ai should build and deploy the +# revnuvo-x402-mcp server. Smithery will: +# 1. Install dependencies with `npm install` +# 2. Build the TypeScript source with `npm run build` +# 3. Run `startCommand` as the server entrypoint, exposing it over HTTP/SSE +# for MCP clients to connect to. + +startCommand: + type: stdio + configSchema: + # These become environment variables injected at runtime. + # `WALLET_PRIVATE_KEY` is marked secret so Smithery masks it in the UI. + type: object + properties: + WALLET_PRIVATE_KEY: + type: string + description: >- + EVM private key (hex, with or without 0x prefix) of the wallet that + holds USDC on Base. Required — every tool call pays the requested + USDC amount from this wallet. + secret: true + REVNUVO_FACILITATOR: + type: string + description: x402 facilitator URL that verifies payments server-side. + default: https://facilitator.xpay.sh + REVNUVO_NETWORK: + type: string + description: >- + Settlement network for USDC payments. Use "base" for mainnet or + "base-sepolia" for testnet. + default: base + enum: [base, base-sepolia] + required: + - WALLET_PRIVATE_KEY + command: node + args: + - build/index.js + +# Build-time configuration. Smithery runs these in order before starting the +# server process. +build: + # Smithery uses the standard Node.js builder. We just need to make sure + # devDependencies (typescript) are available for `npm run build`. + runtime: nodejs + nodeVersion: "20" + steps: + - run: npm install --omit=optional + - run: npm run build + +# Metadata shown in the Smithery registry. +metadata: + name: revnuvo-x402-mcp + displayName: Revnuvo x402 Domain Intelligence + description: >- + Paid-per-call domain intelligence MCP server. Wraps all four Revnuvo x402 + API endpoints — domain verification, DNS lookup, quick domain assessment, + and full domain assessment — and handles the x402 v2 USDC-on-Base payment + flow automatically. + category: research + tags: + - x402 + - payments + - domain-intelligence + - dns + - usdc + - base + - revnuvo + author: + name: Revnuvo Technologies Ltd + url: https://revnuvo.site + license: MIT + homepage: https://revnuvo.site + repository: https://github.com/revnuvo/revnuvo-x402-mcp + +# Pricing disclosure so Smithery can show end-users the real cost per call. +pricing: + currency: USDC + network: Base + perCall: + verify_domain: "0.001" + dns_lookup: "0.002" + assess_domain: "0.005" + assess_domain_full: "0.05" + note: >- + Each tool call settles on-chain via USDC on Base. The wallet configured + via WALLET_PRIVATE_KEY must hold sufficient USDC balance to cover the + requested call. Gas for the payment settlement is sponsored by the x402 + facilitator. diff --git a/src/revnuvo/tsconfig.json b/src/revnuvo/tsconfig.json new file mode 100644 index 0000000000..3f4c0fc810 --- /dev/null +++ b/src/revnuvo/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "esModuleInterop": true, + "strict": true, + "outDir": "./dist", + "declaration": true, + "skipLibCheck": true + }, + "include": ["index.ts"] +}