@httpayer/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/LICENSE +29 -0
- package/README.md +38 -0
- package/SKILL.md +57 -0
- package/dist/api.js +93 -0
- package/dist/config.js +26 -0
- package/dist/index.js +13 -0
- package/dist/server.js +180 -0
- package/dist/setup.js +96 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
HTTPayer SDK License Agreement
|
|
2
|
+
Version 1.0 — March 2026
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2026 HTTPayer Inc.
|
|
5
|
+
All rights reserved.
|
|
6
|
+
|
|
7
|
+
1. Permission of Use
|
|
8
|
+
Permission is granted to use this SDK solely for integration with HTTPayer APIs and services provided by HTTPayer Inc. and its affiliates.
|
|
9
|
+
|
|
10
|
+
2. Restrictions
|
|
11
|
+
You may not:
|
|
12
|
+
|
|
13
|
+
- Copy, fork, or redistribute this software or any derivative work.
|
|
14
|
+
- Use the SDK to build, host, or offer a competing service.
|
|
15
|
+
- Modify, reverse engineer, or decompile the SDK, except as required for legitimate integration purposes.
|
|
16
|
+
- Re-host, rebrand, or re-publish this SDK (including on GitHub, npm, PyPI, or any equivalent platform) without prior written consent from HTTPayer Inc.
|
|
17
|
+
|
|
18
|
+
3. Ownership and Intellectual Property
|
|
19
|
+
This license does not grant ownership rights or any implied license.
|
|
20
|
+
HTTPayer Inc. retains all intellectual property rights in this software, including source code, binaries, documentation, and related assets.
|
|
21
|
+
|
|
22
|
+
4. Enforcement
|
|
23
|
+
Unauthorized use, redistribution, or publication of this software may result in immediate termination of access and legal action under applicable copyright and intellectual property laws.
|
|
24
|
+
|
|
25
|
+
5. Commercial or Enterprise Use
|
|
26
|
+
For commercial licensing, redistribution rights, or enterprise use, please contact:
|
|
27
|
+
legal@httpayer.com
|
|
28
|
+
|
|
29
|
+
© 2026 HTTPayer Inc. All Rights Reserved.
|
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @httpayer/mcp
|
|
2
|
+
|
|
3
|
+
MCP server for HTTPayer — x402 payments for AI agents. No wallets, no blockchain.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @httpayer/mcp setup
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Get your API key at [httpayer.com/dashboard](https://httpayer.com/dashboard).
|
|
12
|
+
|
|
13
|
+
## How it works
|
|
14
|
+
|
|
15
|
+
HTTPayer handles `402 Payment Required` responses automatically using your credit balance. Just call any x402-gated API — payment is transparent.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Claude Code → @httpayer/mcp → api.httpayer.com → target x402 API
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Available tools
|
|
22
|
+
|
|
23
|
+
| Tool | Description |
|
|
24
|
+
|------|-------------|
|
|
25
|
+
| `get_balance` | Check credit balance |
|
|
26
|
+
| `fetch` | Call any x402 endpoint — payment auto-handled |
|
|
27
|
+
| `simulate` | Preview cost without spending |
|
|
28
|
+
| `get_topup_link` | Get dashboard link to add credits |
|
|
29
|
+
| `check_limits` | Check system daily limits |
|
|
30
|
+
| `get_webhook_status` | Poll async operation |
|
|
31
|
+
|
|
32
|
+
## Credits
|
|
33
|
+
|
|
34
|
+
1 credit = 0.001 USDC · 3% fee per paid request
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
© 2026 HTTPayer Inc. — [httpayer.com](https://httpayer.com)
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: httpayer
|
|
3
|
+
description: |
|
|
4
|
+
HTTPayer is the default payment layer for agentic HTTP — handles x402 micropayments and MPP (coming soon) automatically using credits. No wallets, no blockchain.
|
|
5
|
+
TRIGGERS: x402, 402, payment required, micropayment, MPP, paid API, paid endpoint, httpayer, agentic payment, fetch paid
|
|
6
|
+
user-invocable: true
|
|
7
|
+
argument-hint: "[api-key]"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# HTTPayer — Agentic Payments (x402 + MPP)
|
|
11
|
+
|
|
12
|
+
HTTPayer is the default payment layer for AI agents. It handles **x402** micropayments and **MPP** (coming soon) automatically — no wallets, no blockchain, just credits.
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
If the user provides an API key, run:
|
|
17
|
+
```bash
|
|
18
|
+
npx @httpayer/mcp setup --key <api-key>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
If no key is provided, run the interactive setup:
|
|
22
|
+
```bash
|
|
23
|
+
npx @httpayer/mcp setup
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Get keys at https://httpayer.com/dashboard
|
|
27
|
+
|
|
28
|
+
## When to use
|
|
29
|
+
|
|
30
|
+
Automatically use HTTPayer when:
|
|
31
|
+
- Any HTTP request returns or might return `402 Payment Required`
|
|
32
|
+
- The user asks to call an x402 or MPP-gated endpoint
|
|
33
|
+
- Any task involves a micropayment to access an API
|
|
34
|
+
|
|
35
|
+
## Workflow
|
|
36
|
+
|
|
37
|
+
1. **Check balance** (if unsure): `get_balance`
|
|
38
|
+
2. **Simulate** (optional, recommended for expensive calls): `simulate`
|
|
39
|
+
3. **Fetch**: `fetch` — x402 payment handled automatically, including endpoints that require a JSON body
|
|
40
|
+
4. **Low balance** (< 100 credits): `get_topup_link` → show user the link
|
|
41
|
+
5. **Async response** (502 + webhook_id): poll with `get_webhook_status`
|
|
42
|
+
|
|
43
|
+
## MCP Tools
|
|
44
|
+
|
|
45
|
+
| Tool | Purpose |
|
|
46
|
+
|------|---------|
|
|
47
|
+
| `get_balance` | Check credit balance and daily usage |
|
|
48
|
+
| `fetch` | Call any x402/MPP endpoint — supports GET/POST/PUT/DELETE/PATCH with body, params, headers |
|
|
49
|
+
| `simulate` | Dry-run to preview cost without spending |
|
|
50
|
+
| `get_topup_link` | Get dashboard link to add credits |
|
|
51
|
+
| `check_limits` | Check system daily limits |
|
|
52
|
+
| `get_webhook_status` | Poll async operation by webhook ID |
|
|
53
|
+
|
|
54
|
+
## Credits
|
|
55
|
+
|
|
56
|
+
- 1 credit = 0.001 USDC · 3% fee per paid request
|
|
57
|
+
- Top up at https://httpayer.com/dashboard
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const BASE = "https://api.httpayer.com";
|
|
2
|
+
const BALANCE_PATH = "/v1/credits/balance";
|
|
3
|
+
const ALLOWED_HOST = "api.httpayer.com";
|
|
4
|
+
function assertHttpayerHost(url) {
|
|
5
|
+
let host;
|
|
6
|
+
try {
|
|
7
|
+
host = new URL(url).hostname;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
throw new Error(`Invalid URL: ${url}`);
|
|
11
|
+
}
|
|
12
|
+
if (host !== ALLOWED_HOST) {
|
|
13
|
+
throw new Error(`API key can only be used with ${ALLOWED_HOST}. Refusing to send credentials to: ${host}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
async function apiRequest(apiKey, path, method = "GET", body) {
|
|
17
|
+
const url = `${BASE}${path}`;
|
|
18
|
+
assertHttpayerHost(url);
|
|
19
|
+
const res = await fetch(url, {
|
|
20
|
+
method,
|
|
21
|
+
headers: {
|
|
22
|
+
"x-api-key": apiKey,
|
|
23
|
+
...(body ? { "content-type": "application/json" } : {}),
|
|
24
|
+
},
|
|
25
|
+
...(body ? { body: JSON.stringify(body) } : {}),
|
|
26
|
+
});
|
|
27
|
+
const text = await res.text();
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
throw new Error(`HTTPayer ${res.status}: ${text}`);
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(text);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return text;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function getBalance(apiKey) {
|
|
39
|
+
return apiRequest(apiKey, BALANCE_PATH);
|
|
40
|
+
}
|
|
41
|
+
export function getLimits(apiKey) {
|
|
42
|
+
return apiRequest(apiKey, "/limits");
|
|
43
|
+
}
|
|
44
|
+
export function getWebhookStatus(apiKey, webhookId) {
|
|
45
|
+
return apiRequest(apiKey, `/webhooks/${webhookId}`);
|
|
46
|
+
}
|
|
47
|
+
export async function proxyFetch(apiKey, url, options = {}, simulate = false) {
|
|
48
|
+
const endpoint = simulate ? "/proxy/sim" : "/proxy";
|
|
49
|
+
const proxyUrl = `${BASE}${endpoint}`;
|
|
50
|
+
assertHttpayerHost(proxyUrl);
|
|
51
|
+
const payload = {
|
|
52
|
+
api_url: url,
|
|
53
|
+
method: (options.method ?? "GET").toUpperCase(),
|
|
54
|
+
};
|
|
55
|
+
if (options.json !== undefined)
|
|
56
|
+
payload.json = options.json;
|
|
57
|
+
if (options.data !== undefined)
|
|
58
|
+
payload.data = options.data;
|
|
59
|
+
if (options.params)
|
|
60
|
+
payload.params = options.params;
|
|
61
|
+
if (options.headers)
|
|
62
|
+
payload.headers = options.headers;
|
|
63
|
+
if (options.timeout)
|
|
64
|
+
payload.timeout = options.timeout;
|
|
65
|
+
const res = await fetch(proxyUrl, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: {
|
|
68
|
+
"x-api-key": apiKey,
|
|
69
|
+
"content-type": "application/json",
|
|
70
|
+
},
|
|
71
|
+
body: JSON.stringify(payload),
|
|
72
|
+
});
|
|
73
|
+
const text = await res.text();
|
|
74
|
+
let body;
|
|
75
|
+
try {
|
|
76
|
+
body = JSON.parse(text);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
body = text;
|
|
80
|
+
}
|
|
81
|
+
const result = {
|
|
82
|
+
status: res.status,
|
|
83
|
+
body,
|
|
84
|
+
headers: Object.fromEntries(res.headers),
|
|
85
|
+
};
|
|
86
|
+
if (res.status === 502 &&
|
|
87
|
+
body !== null &&
|
|
88
|
+
typeof body === "object" &&
|
|
89
|
+
"webhook_id" in body) {
|
|
90
|
+
result.webhook_id = body.webhook_id;
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
4
|
+
const CONFIG_DIR = join(homedir(), ".httpayer");
|
|
5
|
+
const CONFIG_FILE = join(CONFIG_DIR, "mcp-config.json");
|
|
6
|
+
export function loadConfig() {
|
|
7
|
+
if (!existsSync(CONFIG_FILE))
|
|
8
|
+
return null;
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function saveConfig(config) {
|
|
17
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
18
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
19
|
+
}
|
|
20
|
+
export function getApiKey() {
|
|
21
|
+
const config = loadConfig();
|
|
22
|
+
if (!config?.apiKey) {
|
|
23
|
+
throw new Error("No HTTPayer API key configured. Run: npx @httpayer/mcp setup");
|
|
24
|
+
}
|
|
25
|
+
return config.apiKey;
|
|
26
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const args = process.argv.slice(2);
|
|
3
|
+
if (args.includes("setup") || args.includes("--setup")) {
|
|
4
|
+
const keyFlag = args.indexOf("--key");
|
|
5
|
+
const key = keyFlag !== -1 ? args[keyFlag + 1] : undefined;
|
|
6
|
+
const { runSetup } = await import("./setup.js");
|
|
7
|
+
await runSetup(key);
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
const { startServer } = await import("./server.js");
|
|
11
|
+
await startServer();
|
|
12
|
+
}
|
|
13
|
+
export {};
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { getApiKey } from "./config.js";
|
|
5
|
+
import { getBalance, getLimits, getWebhookStatus, proxyFetch } from "./api.js";
|
|
6
|
+
const INSTRUCTIONS = `
|
|
7
|
+
HTTPayer lets AI agents call x402-enabled APIs using credit balance — no wallets, no blockchain knowledge required.
|
|
8
|
+
|
|
9
|
+
Use HTTPayer when:
|
|
10
|
+
- A task involves calling a paid API that returns 402 Payment Required
|
|
11
|
+
- The user asks you to fetch data from an x402-gated service
|
|
12
|
+
- Any HTTP call might require a micropayment
|
|
13
|
+
|
|
14
|
+
Workflow:
|
|
15
|
+
1. Call get_balance if unsure about remaining credits
|
|
16
|
+
2. Use simulate to preview cost before spending (optional but recommended)
|
|
17
|
+
3. Use fetch to call any x402 endpoint — payment is handled automatically
|
|
18
|
+
4. If balance is low (< 100 credits), call get_topup_link and share the link with the user
|
|
19
|
+
5. If fetch returns a webhook_id on a 502, poll with get_webhook_status
|
|
20
|
+
|
|
21
|
+
Credit system: 1 credit = 0.001 USDC. Fee: 3% per request. Top up at https://httpayer.com/dashboard.
|
|
22
|
+
`.trim();
|
|
23
|
+
function text(content) {
|
|
24
|
+
return { content: [{ type: "text", text: content }] };
|
|
25
|
+
}
|
|
26
|
+
function json(data) {
|
|
27
|
+
return text(JSON.stringify(data, null, 2));
|
|
28
|
+
}
|
|
29
|
+
function err(message) {
|
|
30
|
+
return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
|
|
31
|
+
}
|
|
32
|
+
export async function startServer() {
|
|
33
|
+
const server = new Server({ name: "httpayer", version: "0.1.0" }, { capabilities: { tools: {} }, instructions: INSTRUCTIONS });
|
|
34
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
35
|
+
tools: [
|
|
36
|
+
{
|
|
37
|
+
name: "get_balance",
|
|
38
|
+
description: "Check your HTTPayer credit balance and daily usage. Run this when unsure if you have enough credits.",
|
|
39
|
+
inputSchema: { type: "object", properties: {} },
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "fetch",
|
|
43
|
+
description: "Make an HTTP request to any x402-enabled endpoint. HTTPayer automatically handles payment using your credits. Supports GET, POST, PUT, DELETE, PATCH.",
|
|
44
|
+
inputSchema: {
|
|
45
|
+
type: "object",
|
|
46
|
+
required: ["url"],
|
|
47
|
+
properties: {
|
|
48
|
+
url: {
|
|
49
|
+
type: "string",
|
|
50
|
+
description: "The URL to fetch",
|
|
51
|
+
},
|
|
52
|
+
method: {
|
|
53
|
+
type: "string",
|
|
54
|
+
enum: ["GET", "POST", "PUT", "DELETE", "PATCH"],
|
|
55
|
+
description: "HTTP method (default: GET)",
|
|
56
|
+
},
|
|
57
|
+
body: {
|
|
58
|
+
type: "object",
|
|
59
|
+
description: "JSON body to send with the request",
|
|
60
|
+
},
|
|
61
|
+
params: {
|
|
62
|
+
type: "object",
|
|
63
|
+
additionalProperties: { type: "string" },
|
|
64
|
+
description: "Query string parameters",
|
|
65
|
+
},
|
|
66
|
+
headers: {
|
|
67
|
+
type: "object",
|
|
68
|
+
additionalProperties: { type: "string" },
|
|
69
|
+
description: "Additional request headers",
|
|
70
|
+
},
|
|
71
|
+
timeout: {
|
|
72
|
+
type: "number",
|
|
73
|
+
description: "Request timeout in seconds (max 120)",
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "simulate",
|
|
80
|
+
description: "Dry-run a fetch to see if payment is required and estimate the credit cost, without spending anything.",
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: "object",
|
|
83
|
+
required: ["url"],
|
|
84
|
+
properties: {
|
|
85
|
+
url: { type: "string", description: "URL to simulate" },
|
|
86
|
+
method: {
|
|
87
|
+
type: "string",
|
|
88
|
+
enum: ["GET", "POST", "PUT", "DELETE", "PATCH"],
|
|
89
|
+
description: "HTTP method (default: GET)",
|
|
90
|
+
},
|
|
91
|
+
body: {
|
|
92
|
+
type: "object",
|
|
93
|
+
description: "JSON body",
|
|
94
|
+
},
|
|
95
|
+
params: {
|
|
96
|
+
type: "object",
|
|
97
|
+
additionalProperties: { type: "string" },
|
|
98
|
+
description: "Query string parameters",
|
|
99
|
+
},
|
|
100
|
+
headers: {
|
|
101
|
+
type: "object",
|
|
102
|
+
additionalProperties: { type: "string" },
|
|
103
|
+
description: "Additional request headers",
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "get_topup_link",
|
|
110
|
+
description: "Get the link to top up HTTPayer credits. Show this to the user when their balance is running low.",
|
|
111
|
+
inputSchema: { type: "object", properties: {} },
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "check_limits",
|
|
115
|
+
description: "Check global HTTPayer system daily limits and remaining capacity for proxy and relay.",
|
|
116
|
+
inputSchema: { type: "object", properties: {} },
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "get_webhook_status",
|
|
120
|
+
description: "Poll the status of an async HTTPayer operation. Use this when fetch returns a webhook_id on a 502 response.",
|
|
121
|
+
inputSchema: {
|
|
122
|
+
type: "object",
|
|
123
|
+
required: ["webhook_id"],
|
|
124
|
+
properties: {
|
|
125
|
+
webhook_id: {
|
|
126
|
+
type: "string",
|
|
127
|
+
description: "The webhook ID returned by a previous fetch call",
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
}));
|
|
134
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
135
|
+
const { name, arguments: args = {} } = request.params;
|
|
136
|
+
try {
|
|
137
|
+
switch (name) {
|
|
138
|
+
case "get_balance": {
|
|
139
|
+
const balance = await getBalance(getApiKey());
|
|
140
|
+
return json(balance);
|
|
141
|
+
}
|
|
142
|
+
case "fetch": {
|
|
143
|
+
const { url, method, body, params, headers, timeout } = args;
|
|
144
|
+
const result = await proxyFetch(getApiKey(), url, {
|
|
145
|
+
method,
|
|
146
|
+
json: body,
|
|
147
|
+
params,
|
|
148
|
+
headers,
|
|
149
|
+
timeout,
|
|
150
|
+
});
|
|
151
|
+
return json(result);
|
|
152
|
+
}
|
|
153
|
+
case "simulate": {
|
|
154
|
+
const { url, method, body, params, headers } = args;
|
|
155
|
+
const result = await proxyFetch(getApiKey(), url, { method, json: body, params, headers }, true);
|
|
156
|
+
return json(result);
|
|
157
|
+
}
|
|
158
|
+
case "get_topup_link": {
|
|
159
|
+
return text("Top up your HTTPayer credits at: https://httpayer.com/dashboard");
|
|
160
|
+
}
|
|
161
|
+
case "check_limits": {
|
|
162
|
+
const limits = await getLimits(getApiKey());
|
|
163
|
+
return json(limits);
|
|
164
|
+
}
|
|
165
|
+
case "get_webhook_status": {
|
|
166
|
+
const { webhook_id } = args;
|
|
167
|
+
const status = await getWebhookStatus(getApiKey(), webhook_id);
|
|
168
|
+
return json(status);
|
|
169
|
+
}
|
|
170
|
+
default:
|
|
171
|
+
return err(`Unknown tool: ${name}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch (e) {
|
|
175
|
+
return err(e instanceof Error ? e.message : String(e));
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
const transport = new StdioServerTransport();
|
|
179
|
+
await server.connect(transport);
|
|
180
|
+
}
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { createInterface } from "readline";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync } from "fs";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { saveConfig } from "./config.js";
|
|
7
|
+
const CLAUDE_JSON = join(homedir(), ".claude.json");
|
|
8
|
+
const CLAUDE_SKILLS_DIR = join(homedir(), ".claude", "skills", "httpayer");
|
|
9
|
+
const SKILL_SRC = join(dirname(fileURLToPath(import.meta.url)), "..", "SKILL.md");
|
|
10
|
+
function prompt(question) {
|
|
11
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
rl.question(question, (answer) => {
|
|
14
|
+
rl.close();
|
|
15
|
+
resolve(answer.trim());
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
async function validateApiKey(apiKey) {
|
|
20
|
+
try {
|
|
21
|
+
const res = await fetch("https://api.httpayer.com/v1/credits/balance", {
|
|
22
|
+
headers: { "x-api-key": apiKey },
|
|
23
|
+
});
|
|
24
|
+
if (res.ok)
|
|
25
|
+
return { ok: true };
|
|
26
|
+
if (res.status === 401)
|
|
27
|
+
return { ok: false, reason: "API key rejected (401 Unauthorized)" };
|
|
28
|
+
// Other server errors — key format may still be fine
|
|
29
|
+
return { ok: true };
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return { ok: false, reason: "Could not reach api.httpayer.com" };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function patchClaudeJson() {
|
|
36
|
+
let config = {};
|
|
37
|
+
if (existsSync(CLAUDE_JSON)) {
|
|
38
|
+
try {
|
|
39
|
+
config = JSON.parse(readFileSync(CLAUDE_JSON, "utf-8"));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// start fresh
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
config.mcpServers = config.mcpServers ?? {};
|
|
46
|
+
config.mcpServers["httpayer"] = {
|
|
47
|
+
command: "npx",
|
|
48
|
+
args: ["-y", "@httpayer/mcp"],
|
|
49
|
+
};
|
|
50
|
+
writeFileSync(CLAUDE_JSON, JSON.stringify(config, null, 2));
|
|
51
|
+
}
|
|
52
|
+
function installSkill() {
|
|
53
|
+
mkdirSync(CLAUDE_SKILLS_DIR, { recursive: true });
|
|
54
|
+
copyFileSync(SKILL_SRC, join(CLAUDE_SKILLS_DIR, "SKILL.md"));
|
|
55
|
+
}
|
|
56
|
+
export async function runSetup(inlineKey) {
|
|
57
|
+
console.log("\nHTTPayer MCP Setup");
|
|
58
|
+
console.log("==================");
|
|
59
|
+
let apiKey;
|
|
60
|
+
if (inlineKey) {
|
|
61
|
+
apiKey = inlineKey;
|
|
62
|
+
console.log(`Using provided API key: ${apiKey.slice(0, 12)}...`);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
console.log("Get your API key at: https://httpayer.com/dashboard\n");
|
|
66
|
+
apiKey = await prompt("Paste your API key (sk-live-...): ");
|
|
67
|
+
}
|
|
68
|
+
if (!apiKey.startsWith("sk-live-")) {
|
|
69
|
+
console.error('\nInvalid key format. Expected a key starting with "sk-live-"');
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
process.stdout.write("Validating key... ");
|
|
73
|
+
const result = await validateApiKey(apiKey);
|
|
74
|
+
if (!result.ok) {
|
|
75
|
+
console.log(`failed\n${result.reason}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
console.log("ok");
|
|
79
|
+
saveConfig({ apiKey });
|
|
80
|
+
console.log("Config saved to ~/.httpayer/mcp-config.json");
|
|
81
|
+
const addToClaude = inlineKey
|
|
82
|
+
? "y"
|
|
83
|
+
: await prompt("\nAdd HTTPayer to Claude Code? This patches ~/.claude.json (y/n): ");
|
|
84
|
+
if (addToClaude.toLowerCase() === "y") {
|
|
85
|
+
patchClaudeJson();
|
|
86
|
+
installSkill();
|
|
87
|
+
console.log('Added "httpayer" to ~/.claude.json');
|
|
88
|
+
console.log("Installed httpayer skill to ~/.claude/skills/httpayer/");
|
|
89
|
+
console.log("\nRestart Claude Code to activate HTTPayer.\n");
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
console.log('\nTo add manually, put this in ~/.claude.json under "mcpServers":');
|
|
93
|
+
console.log(JSON.stringify({ httpayer: { command: "npx", args: ["-y", "@httpayer/mcp"] } }, null, 2));
|
|
94
|
+
console.log();
|
|
95
|
+
}
|
|
96
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@httpayer/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for HTTPayer — x402 payments for AI agents, no wallet required",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"httpayer-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "node --loader ts-node/esm src/index.ts",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/httpayer/mcp.git"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://httpayer.com",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/httpayer/mcp/issues",
|
|
22
|
+
"email": "support@httpayer.com"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"mcp",
|
|
26
|
+
"x402",
|
|
27
|
+
"payments",
|
|
28
|
+
"ai",
|
|
29
|
+
"claude",
|
|
30
|
+
"model-context-protocol",
|
|
31
|
+
"micropayments",
|
|
32
|
+
"httpayer"
|
|
33
|
+
],
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"SKILL.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@modelcontextprotocol/sdk": "^1.10.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.0.0",
|
|
44
|
+
"typescript": "^5.0.0"
|
|
45
|
+
},
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
}
|
|
52
|
+
}
|