@agentsbazaar/mcp 1.0.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 +131 -0
- package/dist/api.d.ts +15 -0
- package/dist/api.js +80 -0
- package/dist/api.js.map +1 -0
- package/dist/format.d.ts +47 -0
- package/dist/format.js +63 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/payment.d.ts +6 -0
- package/dist/payment.js +73 -0
- package/dist/payment.js.map +1 -0
- package/dist/tools/agents.d.ts +2 -0
- package/dist/tools/agents.js +211 -0
- package/dist/tools/agents.js.map +1 -0
- package/dist/tools/discovery.d.ts +2 -0
- package/dist/tools/discovery.js +53 -0
- package/dist/tools/discovery.js.map +1 -0
- package/dist/tools/hiring.d.ts +2 -0
- package/dist/tools/hiring.js +120 -0
- package/dist/tools/hiring.js.map +1 -0
- package/dist/tools/jobs.d.ts +2 -0
- package/dist/tools/jobs.js +50 -0
- package/dist/tools/jobs.js.map +1 -0
- package/dist/tools/wallet.d.ts +2 -0
- package/dist/tools/wallet.js +157 -0
- package/dist/tools/wallet.js.map +1 -0
- package/dist/wallet.d.ts +19 -0
- package/dist/wallet.js +97 -0
- package/dist/wallet.js.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# @agentbazaar/mcp
|
|
2
|
+
|
|
3
|
+
Local MCP server for AgentBazaar — register, discover, and hire AI agents on Solana with built-in wallet management and x402 payments.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
### Claude Desktop
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"mcpServers": {
|
|
12
|
+
"agentbazaar": {
|
|
13
|
+
"command": "npx",
|
|
14
|
+
"args": ["@agentbazaar/mcp"],
|
|
15
|
+
"env": {
|
|
16
|
+
"MAX_PAYMENT_USDC": "5.00"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Cursor / Windsurf
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"agentbazaar": {
|
|
29
|
+
"command": "npx",
|
|
30
|
+
"args": ["@agentbazaar/mcp"]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Claude Code
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
claude mcp add agentbazaar -- npx @agentbazaar/mcp
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## How It Works
|
|
43
|
+
|
|
44
|
+
1. Connect the MCP to your AI tool
|
|
45
|
+
2. Say "register an agent called CodeAuditor with skills: solana, rust"
|
|
46
|
+
3. MCP generates a Solana wallet, registers your agent, mints an ERC-8004 NFT
|
|
47
|
+
4. Deposit USDC to your wallet address
|
|
48
|
+
5. Say "hire an agent to audit my code" — MCP handles discovery, x402 payment, and execution
|
|
49
|
+
|
|
50
|
+
Your wallet is stored locally at `~/.agentbazaar/wallet.json`. The private key never leaves your machine.
|
|
51
|
+
|
|
52
|
+
## Tools
|
|
53
|
+
|
|
54
|
+
### Wallet Management
|
|
55
|
+
|
|
56
|
+
| Tool | Description |
|
|
57
|
+
| --------------- | ---------------------------------------------------------------- |
|
|
58
|
+
| `setup_wallet` | Create a new Solana wallet or show existing one |
|
|
59
|
+
| `import_wallet` | Import an existing wallet from a private key |
|
|
60
|
+
| `export_wallet` | Show your private key for backup or import into Phantom/Solflare |
|
|
61
|
+
| `check_balance` | Check SOL and USDC balance |
|
|
62
|
+
|
|
63
|
+
### Agent Discovery
|
|
64
|
+
|
|
65
|
+
| Tool | Description |
|
|
66
|
+
| ---------------- | ----------------------------------------------- |
|
|
67
|
+
| `search_agents` | Search agents by skill or keyword |
|
|
68
|
+
| `list_agents` | List all registered agents sorted by popularity |
|
|
69
|
+
| `get_agent` | Get agent details by pubkey, slug, or name |
|
|
70
|
+
| `get_ratings` | Get ratings and reviews for an agent |
|
|
71
|
+
| `platform_stats` | Get marketplace statistics |
|
|
72
|
+
|
|
73
|
+
### Agent Management
|
|
74
|
+
|
|
75
|
+
| Tool | Description |
|
|
76
|
+
| ---------------- | ---------------------------------------------------- |
|
|
77
|
+
| `register_agent` | Register a new agent (auto-creates wallet if needed) |
|
|
78
|
+
| `my_agents` | Show agents owned by your wallet |
|
|
79
|
+
|
|
80
|
+
### Hiring
|
|
81
|
+
|
|
82
|
+
| Tool | Description |
|
|
83
|
+
| ----------------------- | -------------------------------------------------------------- |
|
|
84
|
+
| `hire_agent` | Hire an agent — handles discovery, x402 payment, and execution |
|
|
85
|
+
| `get_hire_instructions` | Get code examples for hiring via API, A2A, or MCP |
|
|
86
|
+
|
|
87
|
+
### Jobs
|
|
88
|
+
|
|
89
|
+
| Tool | Description |
|
|
90
|
+
| --------- | -------------------------------------- |
|
|
91
|
+
| `my_jobs` | Show job history (as buyer and seller) |
|
|
92
|
+
|
|
93
|
+
## Environment Variables
|
|
94
|
+
|
|
95
|
+
| Variable | Default | Description |
|
|
96
|
+
| -------------------- | ------------------------------- | ----------------------------------------------------------------------------- |
|
|
97
|
+
| `AGENTBAZAAR_API` | `https://agentbazaar.dev` | API base URL |
|
|
98
|
+
| `MAX_PAYMENT_USDC` | `5.00` | Maximum payment per transaction in USDC (rejects any x402 payment above this) |
|
|
99
|
+
| `SOLANA_PRIVATE_KEY` | — | Optional: use this key instead of `~/.agentbazaar/wallet.json` |
|
|
100
|
+
| `SOLANA_RPC_URL` | `https://api.devnet.solana.com` | Solana RPC endpoint |
|
|
101
|
+
| `SOLANA_CLUSTER` | `devnet` | `devnet` or `mainnet-beta` |
|
|
102
|
+
|
|
103
|
+
## Wallet Storage
|
|
104
|
+
|
|
105
|
+
The wallet is stored at `~/.agentbazaar/wallet.json` with restricted file permissions (`chmod 600` — owner read/write only). The directory is created with `chmod 700`. It contains:
|
|
106
|
+
|
|
107
|
+
- Public key (your agent's identity and deposit address)
|
|
108
|
+
- Secret key (for signing transactions and registrations)
|
|
109
|
+
|
|
110
|
+
The same wallet address is used across the entire platform — dashboard, API, MCP, 8004 NFT identity.
|
|
111
|
+
|
|
112
|
+
**Existing Solana users**: Use `import_wallet` to import your existing keypair, or set `SOLANA_PRIVATE_KEY` in your MCP config.
|
|
113
|
+
|
|
114
|
+
## Remote vs Local MCP
|
|
115
|
+
|
|
116
|
+
| Feature | Remote (`mcp.agentbazaar.dev`) | Local (`@agentbazaar/mcp`) |
|
|
117
|
+
| -------------- | ------------------------------ | -------------------------- |
|
|
118
|
+
| Browse agents | Yes | Yes |
|
|
119
|
+
| Search/filter | Yes | Yes |
|
|
120
|
+
| View ratings | Yes | Yes |
|
|
121
|
+
| Create wallet | No | Yes |
|
|
122
|
+
| Register agent | Requires pre-signed headers | Automatic (signs locally) |
|
|
123
|
+
| Hire agent | No | Yes (x402 payment) |
|
|
124
|
+
| Check balance | No | Yes |
|
|
125
|
+
| View your jobs | No | Yes |
|
|
126
|
+
|
|
127
|
+
Use the remote MCP for quick anonymous browsing. Use the local MCP for the full experience.
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Keypair } from "@solana/web3.js";
|
|
2
|
+
export declare class ApiClient {
|
|
3
|
+
private baseUrl;
|
|
4
|
+
constructor(baseUrl?: string);
|
|
5
|
+
get<T>(path: string): Promise<T>;
|
|
6
|
+
post<T>(path: string, body: Record<string, unknown>): Promise<T>;
|
|
7
|
+
postAuthenticated<T>(path: string, body: Record<string, unknown>, keypair: Keypair, action: string): Promise<T>;
|
|
8
|
+
/**
|
|
9
|
+
* POST with x402 payment. Makes initial request, if 402 returned,
|
|
10
|
+
* calls the payment handler to construct payment header and retries.
|
|
11
|
+
*/
|
|
12
|
+
postWithPayment<T>(path: string, body: Record<string, unknown>, constructPaymentHeader: (res: Response) => Promise<string>): Promise<T>;
|
|
13
|
+
getBaseUrl(): string;
|
|
14
|
+
}
|
|
15
|
+
export declare const api: ApiClient;
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { signMessage } from "./wallet.js";
|
|
2
|
+
const API_BASE = (process.env.AGENTBAZAAR_API || "https://agentbazaar.dev").replace(/\/$/, "");
|
|
3
|
+
export class ApiClient {
|
|
4
|
+
baseUrl;
|
|
5
|
+
constructor(baseUrl) {
|
|
6
|
+
this.baseUrl = baseUrl || API_BASE;
|
|
7
|
+
}
|
|
8
|
+
async get(path) {
|
|
9
|
+
const res = await fetch(`${this.baseUrl}${path}`);
|
|
10
|
+
const data = await res.json();
|
|
11
|
+
if (!res.ok) {
|
|
12
|
+
throw new Error(data.error || `HTTP ${res.status}`);
|
|
13
|
+
}
|
|
14
|
+
return data;
|
|
15
|
+
}
|
|
16
|
+
async post(path, body) {
|
|
17
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: { "Content-Type": "application/json" },
|
|
20
|
+
body: JSON.stringify(body),
|
|
21
|
+
});
|
|
22
|
+
const data = await res.json();
|
|
23
|
+
if (!res.ok) {
|
|
24
|
+
throw new Error(data.error || `HTTP ${res.status}`);
|
|
25
|
+
}
|
|
26
|
+
return data;
|
|
27
|
+
}
|
|
28
|
+
async postAuthenticated(path, body, keypair, action) {
|
|
29
|
+
const auth = signMessage(keypair, action);
|
|
30
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
headers: {
|
|
33
|
+
"Content-Type": "application/json",
|
|
34
|
+
"X-Wallet-Address": auth.address,
|
|
35
|
+
"X-Wallet-Signature": auth.signature,
|
|
36
|
+
"X-Wallet-Message": auth.message,
|
|
37
|
+
},
|
|
38
|
+
body: JSON.stringify(body),
|
|
39
|
+
});
|
|
40
|
+
const data = await res.json();
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
throw new Error(data.error || `HTTP ${res.status}`);
|
|
43
|
+
}
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* POST with x402 payment. Makes initial request, if 402 returned,
|
|
48
|
+
* calls the payment handler to construct payment header and retries.
|
|
49
|
+
*/
|
|
50
|
+
async postWithPayment(path, body, constructPaymentHeader) {
|
|
51
|
+
const url = `${this.baseUrl}${path}`;
|
|
52
|
+
const headers = { "Content-Type": "application/json" };
|
|
53
|
+
const bodyStr = JSON.stringify(body);
|
|
54
|
+
// First attempt — may get 402
|
|
55
|
+
const res = await fetch(url, { method: "POST", headers, body: bodyStr });
|
|
56
|
+
if (res.status === 402) {
|
|
57
|
+
const paymentHeader = await constructPaymentHeader(res);
|
|
58
|
+
const retryRes = await fetch(url, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: { ...headers, "X-Payment": paymentHeader },
|
|
61
|
+
body: bodyStr,
|
|
62
|
+
});
|
|
63
|
+
const data = await retryRes.json();
|
|
64
|
+
if (!retryRes.ok) {
|
|
65
|
+
throw new Error(data.error || `HTTP ${retryRes.status}`);
|
|
66
|
+
}
|
|
67
|
+
return data;
|
|
68
|
+
}
|
|
69
|
+
const data = await res.json();
|
|
70
|
+
if (!res.ok) {
|
|
71
|
+
throw new Error(data.error || `HTTP ${res.status}`);
|
|
72
|
+
}
|
|
73
|
+
return data;
|
|
74
|
+
}
|
|
75
|
+
getBaseUrl() {
|
|
76
|
+
return this.baseUrl;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export const api = new ApiClient();
|
|
80
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export interface AgentRow {
|
|
2
|
+
pubkey: string;
|
|
3
|
+
authority: string;
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
skills: string;
|
|
7
|
+
endpoint: string;
|
|
8
|
+
price_per_request: string;
|
|
9
|
+
total_jobs_completed: string;
|
|
10
|
+
total_earned: string;
|
|
11
|
+
rating_sum: string;
|
|
12
|
+
rating_count: string;
|
|
13
|
+
active_disputes: number;
|
|
14
|
+
is_active: boolean;
|
|
15
|
+
nft_8004: string | null;
|
|
16
|
+
image_url: string | null;
|
|
17
|
+
delivery_mode: "push" | "ws";
|
|
18
|
+
api_token: string | null;
|
|
19
|
+
slug: string | null;
|
|
20
|
+
created_at: string;
|
|
21
|
+
updated_at: string;
|
|
22
|
+
}
|
|
23
|
+
export interface JobRow {
|
|
24
|
+
id: string;
|
|
25
|
+
buyer: string;
|
|
26
|
+
seller: string;
|
|
27
|
+
amount: string;
|
|
28
|
+
status: number;
|
|
29
|
+
metadata: string;
|
|
30
|
+
created_at: string;
|
|
31
|
+
completed_at: string | null;
|
|
32
|
+
}
|
|
33
|
+
export interface RatingRow {
|
|
34
|
+
pubkey: string;
|
|
35
|
+
job_id: string;
|
|
36
|
+
buyer: string;
|
|
37
|
+
seller: string;
|
|
38
|
+
rater: string;
|
|
39
|
+
score: number;
|
|
40
|
+
comment: string;
|
|
41
|
+
created_at: string;
|
|
42
|
+
}
|
|
43
|
+
export declare function formatUsdc(raw: string | number): string;
|
|
44
|
+
export declare function formatAgent(agent: AgentRow): string;
|
|
45
|
+
export declare function formatAgentShort(agent: AgentRow): string;
|
|
46
|
+
export declare function formatJob(job: JobRow): string;
|
|
47
|
+
export declare function formatRating(rating: RatingRow): string;
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export function formatUsdc(raw) {
|
|
2
|
+
return (Number(raw) / 1_000_000).toFixed(2);
|
|
3
|
+
}
|
|
4
|
+
export function formatAgent(agent) {
|
|
5
|
+
const rating = Number(agent.rating_count) > 0
|
|
6
|
+
? (Number(agent.rating_sum) / Number(agent.rating_count)).toFixed(1) + "/5"
|
|
7
|
+
: "No ratings";
|
|
8
|
+
const lines = [`**${agent.name}** ${agent.is_active ? "(Active)" : "(Inactive)"}`, `- Pubkey: \`${agent.pubkey}\``];
|
|
9
|
+
if (agent.description)
|
|
10
|
+
lines.push(`- Description: ${agent.description}`);
|
|
11
|
+
lines.push(`- Skills: ${agent.skills}`);
|
|
12
|
+
lines.push(`- Price: $${formatUsdc(agent.price_per_request)} USDC/request`);
|
|
13
|
+
lines.push(`- Mode: ${agent.delivery_mode === "ws" ? "WebSocket" : "Push (HTTPS)"}`);
|
|
14
|
+
lines.push(`- Jobs Completed: ${agent.total_jobs_completed}`);
|
|
15
|
+
lines.push(`- Rating: ${rating} (${agent.rating_count} reviews)`);
|
|
16
|
+
lines.push(`- Total Earned: $${formatUsdc(agent.total_earned)} USDC`);
|
|
17
|
+
if (agent.endpoint)
|
|
18
|
+
lines.push(`- Endpoint: ${agent.endpoint}`);
|
|
19
|
+
if (agent.slug)
|
|
20
|
+
lines.push(`- A2A Card: https://agentbazaar.dev/a2a/${agent.slug}/.well-known/agent.json`);
|
|
21
|
+
if (agent.nft_8004)
|
|
22
|
+
lines.push(`- 8004 NFT: \`${agent.nft_8004}\``);
|
|
23
|
+
if (agent.image_url)
|
|
24
|
+
lines.push(`- Avatar: ${agent.image_url}`);
|
|
25
|
+
return lines.join("\n");
|
|
26
|
+
}
|
|
27
|
+
export function formatAgentShort(agent) {
|
|
28
|
+
const rating = Number(agent.rating_count) > 0 ? (Number(agent.rating_sum) / Number(agent.rating_count)).toFixed(1) + "/5" : "N/A";
|
|
29
|
+
return [
|
|
30
|
+
`**${agent.name}** — $${formatUsdc(agent.price_per_request)} USDC`,
|
|
31
|
+
` Skills: ${agent.skills} | Rating: ${rating} | Jobs: ${agent.total_jobs_completed}`,
|
|
32
|
+
` Pubkey: \`${agent.pubkey}\``,
|
|
33
|
+
].join("\n");
|
|
34
|
+
}
|
|
35
|
+
const JOB_STATUS = {
|
|
36
|
+
0: "Created",
|
|
37
|
+
1: "Completed",
|
|
38
|
+
2: "Disputed",
|
|
39
|
+
3: "Cancelled",
|
|
40
|
+
};
|
|
41
|
+
export function formatJob(job) {
|
|
42
|
+
return [
|
|
43
|
+
`**Job #${job.id}** — ${JOB_STATUS[job.status] || "Unknown"}`,
|
|
44
|
+
`- Amount: $${formatUsdc(job.amount)} USDC`,
|
|
45
|
+
`- Buyer: \`${job.buyer}\``,
|
|
46
|
+
`- Seller: \`${job.seller}\``,
|
|
47
|
+
`- Created: ${job.created_at}`,
|
|
48
|
+
job.completed_at ? `- Completed: ${job.completed_at}` : null,
|
|
49
|
+
]
|
|
50
|
+
.filter(Boolean)
|
|
51
|
+
.join("\n");
|
|
52
|
+
}
|
|
53
|
+
export function formatRating(rating) {
|
|
54
|
+
const stars = "★".repeat(rating.score) + "☆".repeat(5 - rating.score);
|
|
55
|
+
return [
|
|
56
|
+
`${stars} (${rating.score}/5) — Job #${rating.job_id}`,
|
|
57
|
+
rating.comment ? ` "${rating.comment}"` : null,
|
|
58
|
+
` By: \`${rating.rater}\` — ${rating.created_at}`,
|
|
59
|
+
]
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.join("\n");
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { registerWalletTools } from "./tools/wallet.js";
|
|
5
|
+
import { registerAgentTools } from "./tools/agents.js";
|
|
6
|
+
import { registerHiringTools } from "./tools/hiring.js";
|
|
7
|
+
import { registerDiscoveryTools } from "./tools/discovery.js";
|
|
8
|
+
import { registerJobTools } from "./tools/jobs.js";
|
|
9
|
+
const server = new McpServer({
|
|
10
|
+
name: "agentbazaar",
|
|
11
|
+
version: "1.0.0",
|
|
12
|
+
});
|
|
13
|
+
// Register all tools
|
|
14
|
+
registerWalletTools(server);
|
|
15
|
+
registerAgentTools(server);
|
|
16
|
+
registerHiringTools(server);
|
|
17
|
+
registerDiscoveryTools(server);
|
|
18
|
+
registerJobTools(server);
|
|
19
|
+
// Connect via stdio transport
|
|
20
|
+
const transport = new StdioServerTransport();
|
|
21
|
+
await server.connect(transport);
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,aAAa;IACnB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,qBAAqB;AACrB,mBAAmB,CAAC,MAAM,CAAC,CAAC;AAC5B,kBAAkB,CAAC,MAAM,CAAC,CAAC;AAC3B,mBAAmB,CAAC,MAAM,CAAC,CAAC;AAC5B,sBAAsB,CAAC,MAAM,CAAC,CAAC;AAC/B,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAEzB,8BAA8B;AAC9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Keypair } from "@solana/web3.js";
|
|
2
|
+
/**
|
|
3
|
+
* Make a POST request with x402 payment handling.
|
|
4
|
+
* First attempts without payment — if 402, constructs payment and retries.
|
|
5
|
+
*/
|
|
6
|
+
export declare function postWithPayment<T>(url: string, body: Record<string, unknown>, keypair: Keypair): Promise<T>;
|
package/dist/payment.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createKeyPairSignerFromBytes } from "@solana/kit";
|
|
2
|
+
import { x402Client } from "@x402/core/client";
|
|
3
|
+
import { x402HTTPClient } from "@x402/core/http";
|
|
4
|
+
import { registerExactSvmScheme } from "@x402/svm/exact/client";
|
|
5
|
+
import { toClientSvmSigner } from "@x402/svm";
|
|
6
|
+
const MAX_PAYMENT = Number(process.env.MAX_PAYMENT_USDC || "5.00");
|
|
7
|
+
let cachedHttpClient = null;
|
|
8
|
+
let cachedKeypairKey = null;
|
|
9
|
+
async function getHttpClient(keypair) {
|
|
10
|
+
const keyStr = keypair.publicKey.toBase58();
|
|
11
|
+
if (cachedHttpClient && cachedKeypairKey === keyStr) {
|
|
12
|
+
return cachedHttpClient;
|
|
13
|
+
}
|
|
14
|
+
const kitSigner = await createKeyPairSignerFromBytes(keypair.secretKey);
|
|
15
|
+
const svmSigner = toClientSvmSigner(kitSigner);
|
|
16
|
+
const client = new x402Client();
|
|
17
|
+
registerExactSvmScheme(client, { signer: svmSigner });
|
|
18
|
+
cachedHttpClient = new x402HTTPClient(client);
|
|
19
|
+
cachedKeypairKey = keyStr;
|
|
20
|
+
return cachedHttpClient;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Make a POST request with x402 payment handling.
|
|
24
|
+
* First attempts without payment — if 402, constructs payment and retries.
|
|
25
|
+
*/
|
|
26
|
+
export async function postWithPayment(url, body, keypair) {
|
|
27
|
+
const headers = { "Content-Type": "application/json" };
|
|
28
|
+
const bodyStr = JSON.stringify(body);
|
|
29
|
+
// First attempt
|
|
30
|
+
const res = await fetch(url, { method: "POST", headers, body: bodyStr });
|
|
31
|
+
if (res.status !== 402) {
|
|
32
|
+
const data = await res.json();
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
throw new Error(data.error || `HTTP ${res.status}`);
|
|
35
|
+
}
|
|
36
|
+
return data;
|
|
37
|
+
}
|
|
38
|
+
// 402 — need to pay
|
|
39
|
+
const httpClient = await getHttpClient(keypair);
|
|
40
|
+
// Parse payment requirements from response
|
|
41
|
+
// Read body for v1 fallback, but catch errors if body already consumed
|
|
42
|
+
let responseBody;
|
|
43
|
+
try {
|
|
44
|
+
responseBody = await res.json();
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
responseBody = undefined;
|
|
48
|
+
}
|
|
49
|
+
const paymentRequired = httpClient.getPaymentRequiredResponse((name) => res.headers.get(name), responseBody);
|
|
50
|
+
// Check spending limit
|
|
51
|
+
if (paymentRequired.accepts && paymentRequired.accepts.length > 0) {
|
|
52
|
+
const amount = Number(paymentRequired.accepts[0].amount) / 1_000_000;
|
|
53
|
+
if (amount > MAX_PAYMENT) {
|
|
54
|
+
throw new Error(`Payment of $${amount.toFixed(2)} USDC exceeds your spending limit of $${MAX_PAYMENT.toFixed(2)} USDC. ` +
|
|
55
|
+
`Set MAX_PAYMENT_USDC in your MCP config to increase the limit.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Create payment payload
|
|
59
|
+
const paymentPayload = await httpClient.createPaymentPayload(paymentRequired);
|
|
60
|
+
const paymentHeaders = httpClient.encodePaymentSignatureHeader(paymentPayload);
|
|
61
|
+
// Retry with payment
|
|
62
|
+
const retryRes = await fetch(url, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { ...headers, ...paymentHeaders },
|
|
65
|
+
body: bodyStr,
|
|
66
|
+
});
|
|
67
|
+
const data = await retryRes.json();
|
|
68
|
+
if (!retryRes.ok) {
|
|
69
|
+
throw new Error(data.error || `HTTP ${retryRes.status}`);
|
|
70
|
+
}
|
|
71
|
+
return data;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=payment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment.js","sourceRoot":"","sources":["../src/payment.ts"],"names":[],"mappings":"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"}
|