@crawlpay/mcp-server 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/README.md +60 -0
- package/crawlpay.md +31 -0
- package/dist/crawlpay-fetch.d.ts +3 -0
- package/dist/crawlpay-fetch.js +44 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +124 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# @crawlpay/mcp-server
|
|
2
|
+
|
|
3
|
+
CrawlPay MCP server for Claude Desktop, Cursor, Windsurf.
|
|
4
|
+
|
|
5
|
+
Automatically pays $0.001 USDC per page when an AI agent
|
|
6
|
+
encounters a CrawlPay-protected paywall.
|
|
7
|
+
|
|
8
|
+
## Setup
|
|
9
|
+
|
|
10
|
+
Add to your Claude Desktop config:
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"mcpServers": {
|
|
15
|
+
"crawlpay-server": {
|
|
16
|
+
"command": "npx",
|
|
17
|
+
"args": ["-y", "@crawlpay/mcp-server"],
|
|
18
|
+
"env": {
|
|
19
|
+
"CRAWLPAY_API_KEY": "cr_live_YOUR_KEY_HERE"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Config file location:
|
|
27
|
+
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
28
|
+
- Mac: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
29
|
+
|
|
30
|
+
## Get API key
|
|
31
|
+
|
|
32
|
+
1. Go to crawl-pay.com/connect/api-keys
|
|
33
|
+
2. Sign in with Google or GitHub
|
|
34
|
+
3. Fund your wallet with USDC on Base
|
|
35
|
+
4. Generate API key
|
|
36
|
+
5. Paste it into config above
|
|
37
|
+
|
|
38
|
+
## Tool
|
|
39
|
+
|
|
40
|
+
`handle_payment_required`
|
|
41
|
+
- url: URL that returned 402
|
|
42
|
+
- amount: USDC amount (default "0.001")
|
|
43
|
+
|
|
44
|
+
## How it works
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
Agent reads a CrawlPay-protected page
|
|
48
|
+
→ Gets HTTP 402 Payment Required
|
|
49
|
+
→ MCP server intercepts
|
|
50
|
+
→ Pays $0.001 USDC on Base automatically
|
|
51
|
+
→ Content unlocked
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Links
|
|
55
|
+
|
|
56
|
+
- [crawl-pay.com](https://crawl-pay.com)
|
|
57
|
+
- [github.com/divergenttt/CrawlPay-Vault](https://github.com/divergenttt/CrawlPay-Vault)
|
|
58
|
+
- npm: coming soon
|
|
59
|
+
|
|
60
|
+
*Built on Base · Circle · Privy*
|
package/crawlpay.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# CrawlPay Plugin
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
CrawlPay enables AI agents to automatically pay for paywalled web content
|
|
5
|
+
using USDC on Base. When an agent encounters HTTP 402 Payment Required,
|
|
6
|
+
this plugin handles the payment flow automatically.
|
|
7
|
+
|
|
8
|
+
## Tools
|
|
9
|
+
|
|
10
|
+
### handle_payment_required
|
|
11
|
+
Handles HTTP 402 responses from CrawlPay-protected pages.
|
|
12
|
+
|
|
13
|
+
**When to use:** Call this tool whenever you receive a 402 Payment Required
|
|
14
|
+
error while trying to read a webpage. The site is protected by CrawlPay
|
|
15
|
+
and requires $0.001 USDC payment per page.
|
|
16
|
+
|
|
17
|
+
**Inputs:**
|
|
18
|
+
- url: The URL that returned 402
|
|
19
|
+
- amount: Payment amount in USDC (usually "0.001")
|
|
20
|
+
|
|
21
|
+
**Flow:**
|
|
22
|
+
1. If CRAWLPAY_API_KEY is set: pays automatically via API key, returns content
|
|
23
|
+
2. If no API key: returns Base MCP approval link for manual payment
|
|
24
|
+
|
|
25
|
+
## Setup
|
|
26
|
+
Set CRAWLPAY_API_KEY=cr_live_... in your environment for automatic payments.
|
|
27
|
+
Get your API key at https://crawl-pay.com/connect/api-keys
|
|
28
|
+
|
|
29
|
+
## Example
|
|
30
|
+
User: "Read this article: example.com/premium/article"
|
|
31
|
+
Agent: fetches URL → gets 402 → calls handle_payment_required → pays → returns content
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveCrawlPayApiKey = resolveCrawlPayApiKey;
|
|
4
|
+
exports.buildAgentHeaders = buildAgentHeaders;
|
|
5
|
+
exports.fetchPaidPage = fetchPaidPage;
|
|
6
|
+
function resolveCrawlPayApiKey() {
|
|
7
|
+
const key = process.env.CRAWLPAY_API_KEY?.trim() ||
|
|
8
|
+
process.env.CRAWLPAY_AGENT_API_KEY?.trim();
|
|
9
|
+
return key?.startsWith("cr_live_") ? key : undefined;
|
|
10
|
+
}
|
|
11
|
+
function buildAgentHeaders(base = {}) {
|
|
12
|
+
const headers = {
|
|
13
|
+
"User-Agent": process.env.CRAWLPAY_BOT_USER_AGENT ??
|
|
14
|
+
"GPTBot (CrawlPay-MCP/1.0)",
|
|
15
|
+
...base,
|
|
16
|
+
};
|
|
17
|
+
const apiKey = resolveCrawlPayApiKey();
|
|
18
|
+
if (apiKey) {
|
|
19
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
20
|
+
}
|
|
21
|
+
return headers;
|
|
22
|
+
}
|
|
23
|
+
async function fetchPaidPage(url) {
|
|
24
|
+
const apiKey = resolveCrawlPayApiKey();
|
|
25
|
+
const first = await fetch(url, {
|
|
26
|
+
headers: buildAgentHeaders(),
|
|
27
|
+
cache: "no-store",
|
|
28
|
+
});
|
|
29
|
+
if (apiKey || first.status !== 402) {
|
|
30
|
+
return first;
|
|
31
|
+
}
|
|
32
|
+
const botAddress = process.env.NEXT_PUBLIC_SELLER_ADDRESS?.trim() ?? "";
|
|
33
|
+
return fetch(url, {
|
|
34
|
+
headers: {
|
|
35
|
+
...buildAgentHeaders(),
|
|
36
|
+
"payment-signature": "0xsimulated",
|
|
37
|
+
"payment-bot-address": botAddress,
|
|
38
|
+
...(first.headers.get("x-crawlpay-vault")
|
|
39
|
+
? { "x-crawlpay-vault": first.headers.get("x-crawlpay-vault") }
|
|
40
|
+
: {}),
|
|
41
|
+
},
|
|
42
|
+
cache: "no-store",
|
|
43
|
+
});
|
|
44
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
5
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
7
|
+
const viem_1 = require("viem");
|
|
8
|
+
const crawlpay_fetch_1 = require("./crawlpay-fetch");
|
|
9
|
+
const TOOL_NAME = "handle_payment_required";
|
|
10
|
+
const server = new index_js_1.Server({
|
|
11
|
+
name: "crawlpay-server",
|
|
12
|
+
version: "0.1.0",
|
|
13
|
+
}, {
|
|
14
|
+
capabilities: {
|
|
15
|
+
tools: {},
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
const SELLER_ADDRESS = process.env.NEXT_PUBLIC_SELLER_ADDRESS?.trim() ||
|
|
19
|
+
process.env.SELLER_ADDRESS?.trim() ||
|
|
20
|
+
"0x80B6173DD42a787BbFF2B2617652885a3dE9b05B"; // CrawlPay default
|
|
21
|
+
/** Base App deep link — triggers USDC transfer via wallet_sendCalls flow. */
|
|
22
|
+
function buildApprovalLink(amount) {
|
|
23
|
+
const params = new URLSearchParams({
|
|
24
|
+
to: SELLER_ADDRESS,
|
|
25
|
+
amount,
|
|
26
|
+
token: "USDC",
|
|
27
|
+
chainId: "8453",
|
|
28
|
+
});
|
|
29
|
+
return `https://www.base.org/send?${params.toString()}`;
|
|
30
|
+
}
|
|
31
|
+
function validateArgs(args) {
|
|
32
|
+
if (!args || typeof args !== "object") {
|
|
33
|
+
throw new Error("Invalid arguments: object expected");
|
|
34
|
+
}
|
|
35
|
+
const { url, amount } = args;
|
|
36
|
+
if (typeof url !== "string" || url.trim().length === 0) {
|
|
37
|
+
throw new Error("Invalid arguments: `url` must be a non-empty string");
|
|
38
|
+
}
|
|
39
|
+
if (typeof amount !== "string" || amount.trim().length === 0) {
|
|
40
|
+
throw new Error("Invalid arguments: `amount` must be a non-empty string");
|
|
41
|
+
}
|
|
42
|
+
// Validate numeric amount format in USDC precision.
|
|
43
|
+
(0, viem_1.parseUnits)(amount, 6);
|
|
44
|
+
return { url: url.trim(), amount: amount.trim() };
|
|
45
|
+
}
|
|
46
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
47
|
+
console.error("[crawlpay-mcp] Listing tools");
|
|
48
|
+
return {
|
|
49
|
+
tools: [
|
|
50
|
+
{
|
|
51
|
+
name: TOOL_NAME,
|
|
52
|
+
description: "Handle HTTP 402 Payment Required from CrawlPay-protected pages. " +
|
|
53
|
+
"Call when a webpage returns 402 — pays automatically if CRAWLPAY_API_KEY is set, " +
|
|
54
|
+
"otherwise returns a Base App USDC approval link.",
|
|
55
|
+
inputSchema: {
|
|
56
|
+
type: "object",
|
|
57
|
+
properties: {
|
|
58
|
+
url: {
|
|
59
|
+
type: "string",
|
|
60
|
+
description: "URL of the protected page/resource that returned 402",
|
|
61
|
+
},
|
|
62
|
+
amount: {
|
|
63
|
+
type: "string",
|
|
64
|
+
description: 'Requested USDC amount (example: "0.001")',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
required: ["url", "amount"],
|
|
68
|
+
additionalProperties: false,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
75
|
+
const { name, arguments: args } = request.params;
|
|
76
|
+
console.error(`[crawlpay-mcp] Tool call received: ${name}`);
|
|
77
|
+
if (name !== TOOL_NAME) {
|
|
78
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
79
|
+
}
|
|
80
|
+
const { url, amount } = validateArgs(args);
|
|
81
|
+
console.error(`[crawlpay-mcp] 402 context parsed. url=${url} amount=${amount}`);
|
|
82
|
+
const apiKey = (0, crawlpay_fetch_1.resolveCrawlPayApiKey)();
|
|
83
|
+
if (apiKey) {
|
|
84
|
+
console.error("[crawlpay-mcp] Retrying with CrawlPay API key (Base wallet)");
|
|
85
|
+
const res = await (0, crawlpay_fetch_1.fetchPaidPage)(url);
|
|
86
|
+
const body = await res.text();
|
|
87
|
+
return {
|
|
88
|
+
content: [
|
|
89
|
+
{
|
|
90
|
+
type: "text",
|
|
91
|
+
text: `CrawlPay fetch (${res.status})\n` +
|
|
92
|
+
`URL: ${url}\n` +
|
|
93
|
+
`Amount: ${amount} USDC\n` +
|
|
94
|
+
`Auth: API key (cr_live_…)\n\n` +
|
|
95
|
+
body.slice(0, 4000),
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const approvalLink = buildApprovalLink(amount);
|
|
101
|
+
console.error("[crawlpay-mcp] No API key — returning Base App approval link");
|
|
102
|
+
return {
|
|
103
|
+
content: [
|
|
104
|
+
{
|
|
105
|
+
type: "text",
|
|
106
|
+
text: "Payment required — set CRAWLPAY_API_KEY=cr_live_… for automatic Base wallet billing,\n" +
|
|
107
|
+
"or approve USDC payment via Base App:\n\n" +
|
|
108
|
+
`Protected URL: ${url}\n` +
|
|
109
|
+
`Amount: ${amount} USDC\n` +
|
|
110
|
+
`Base approval link: ${approvalLink}`,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
async function main() {
|
|
116
|
+
console.error("[crawlpay-mcp] Starting stdio server...");
|
|
117
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
118
|
+
await server.connect(transport);
|
|
119
|
+
console.error("[crawlpay-mcp] Server connected on stdio");
|
|
120
|
+
}
|
|
121
|
+
main().catch((error) => {
|
|
122
|
+
console.error("[crawlpay-mcp] Fatal startup error:", error);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crawlpay/mcp-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official CrawlPay MCP server for payment-required (402) flows",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/divergenttt/CrawlPay-MCP"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://crawl-pay.com",
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"bin": {
|
|
13
|
+
"crawlpay-mcp": "dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"crawlpay.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.json",
|
|
21
|
+
"dev": "ts-node src/index.ts",
|
|
22
|
+
"start": "node dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
26
|
+
"viem": "^2.50.4"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^24.12.4",
|
|
30
|
+
"ts-node": "^10.9.2",
|
|
31
|
+
"typescript": "^5.9.3"
|
|
32
|
+
},
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"keywords": [
|
|
35
|
+
"mcp",
|
|
36
|
+
"crawlpay",
|
|
37
|
+
"x402",
|
|
38
|
+
"base",
|
|
39
|
+
"usdc",
|
|
40
|
+
"ai-agents"
|
|
41
|
+
]
|
|
42
|
+
}
|