nullpath-mcp 1.2.0 → 1.3.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/.github/workflows/release.yml +43 -0
- package/.releaserc.json +9 -0
- package/README.md +63 -70
- package/SWARM_SPEC.md +75 -0
- package/dist/__tests__/payment.test.d.ts +2 -0
- package/dist/__tests__/payment.test.d.ts.map +1 -0
- package/dist/__tests__/payment.test.js +106 -0
- package/dist/__tests__/payment.test.js.map +1 -0
- package/dist/index.js +144 -23
- package/dist/index.js.map +1 -1
- package/dist/lib/eip3009.d.ts +128 -0
- package/dist/lib/eip3009.d.ts.map +1 -0
- package/dist/lib/eip3009.js +151 -0
- package/dist/lib/eip3009.js.map +1 -0
- package/dist/lib/payment.d.ts +99 -0
- package/dist/lib/payment.d.ts.map +1 -0
- package/dist/lib/payment.js +254 -0
- package/dist/lib/payment.js.map +1 -0
- package/dist/lib/wallet.d.ts +81 -0
- package/dist/lib/wallet.d.ts.map +1 -0
- package/dist/lib/wallet.js +131 -0
- package/dist/lib/wallet.js.map +1 -0
- package/docs/API_DESIGN.md +698 -0
- package/docs/CODE_REVIEW.md +322 -0
- package/package.json +4 -1
- package/src/__tests__/payment.test.ts +126 -0
- package/src/index.ts +160 -27
- package/src/lib/eip3009.ts +201 -0
- package/src/lib/payment.ts +334 -0
- package/src/lib/wallet.ts +164 -0
- package/dist/__tests__/x402.test.d.ts +0 -2
- package/dist/__tests__/x402.test.d.ts.map +0 -1
- package/dist/__tests__/x402.test.js +0 -187
- package/dist/__tests__/x402.test.js.map +0 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
issues: write
|
|
11
|
+
pull-requests: write
|
|
12
|
+
id-token: write
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
release:
|
|
16
|
+
name: Release
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
with:
|
|
22
|
+
fetch-depth: 0
|
|
23
|
+
|
|
24
|
+
- name: Setup Node.js
|
|
25
|
+
uses: actions/setup-node@v4
|
|
26
|
+
with:
|
|
27
|
+
node-version: '20'
|
|
28
|
+
cache: 'npm'
|
|
29
|
+
|
|
30
|
+
- name: Install dependencies
|
|
31
|
+
run: npm ci
|
|
32
|
+
|
|
33
|
+
- name: Build
|
|
34
|
+
run: npm run build
|
|
35
|
+
|
|
36
|
+
- name: Test
|
|
37
|
+
run: npm test
|
|
38
|
+
|
|
39
|
+
- name: Release
|
|
40
|
+
env:
|
|
41
|
+
GITHUB_TOKEN: ${{ secrets.GH_PAT }}
|
|
42
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
43
|
+
run: npx semantic-release
|
package/.releaserc.json
ADDED
package/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/nullpath-mcp)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
Discover
|
|
6
|
+
Discover agents on nullpath's AI agent marketplace via MCP.
|
|
7
7
|
|
|
8
8
|
**Package:** [`nullpath-mcp`](https://www.npmjs.com/package/nullpath-mcp) on npm
|
|
9
9
|
|
|
10
10
|
## Prerequisites
|
|
11
11
|
|
|
12
|
-
- Node.js 18+
|
|
12
|
+
- Node.js 18+
|
|
13
13
|
|
|
14
14
|
## Quick Start
|
|
15
15
|
|
|
@@ -22,10 +22,7 @@ Add to your `claude_desktop_config.json`:
|
|
|
22
22
|
"mcpServers": {
|
|
23
23
|
"nullpath": {
|
|
24
24
|
"command": "npx",
|
|
25
|
-
"args": ["-y", "nullpath-mcp"]
|
|
26
|
-
"env": {
|
|
27
|
-
"NULLPATH_WALLET_KEY": "0x..."
|
|
28
|
-
}
|
|
25
|
+
"args": ["-y", "nullpath-mcp"]
|
|
29
26
|
}
|
|
30
27
|
}
|
|
31
28
|
}
|
|
@@ -44,32 +41,17 @@ Add to `.cursor/mcp.json` in your project:
|
|
|
44
41
|
"mcpServers": {
|
|
45
42
|
"nullpath": {
|
|
46
43
|
"command": "npx",
|
|
47
|
-
"args": ["-y", "nullpath-mcp"]
|
|
48
|
-
"env": {
|
|
49
|
-
"NULLPATH_WALLET_KEY": "0x..."
|
|
50
|
-
}
|
|
44
|
+
"args": ["-y", "nullpath-mcp"]
|
|
51
45
|
}
|
|
52
46
|
}
|
|
53
47
|
}
|
|
54
48
|
```
|
|
55
49
|
|
|
56
|
-
## How It Works
|
|
57
|
-
|
|
58
|
-
This client connects to nullpath's remote MCP server at `https://nullpath.com/mcp` and proxies tool calls through stdio for Claude Desktop and Cursor.
|
|
59
|
-
|
|
60
|
-
For paid tools (`execute_agent`, `register_agent`), the client automatically:
|
|
61
|
-
1. Detects 402 Payment Required responses
|
|
62
|
-
2. Signs an EIP-3009 TransferWithAuthorization using your wallet
|
|
63
|
-
3. Retries the request with the X-PAYMENT header
|
|
64
|
-
4. Returns the result
|
|
65
|
-
|
|
66
|
-
**No tokens leave your wallet until the agent successfully executes.**
|
|
67
|
-
|
|
68
50
|
## Example Usage
|
|
69
51
|
|
|
70
52
|
Once configured, ask Claude:
|
|
71
53
|
|
|
72
|
-
> "Find me an agent that can summarize
|
|
54
|
+
> "Find me an agent that can summarize text"
|
|
73
55
|
|
|
74
56
|
Response:
|
|
75
57
|
```
|
|
@@ -84,44 +66,63 @@ I found 2 agents matching "summarize":
|
|
|
84
66
|
- Trust tier: Premium | Reputation: 99
|
|
85
67
|
```
|
|
86
68
|
|
|
69
|
+
### Executing a Paid Agent
|
|
70
|
+
|
|
87
71
|
> "Execute the URL Summarizer on https://example.com"
|
|
88
72
|
|
|
73
|
+
With `NULLPATH_WALLET_KEY` configured, the payment happens automatically:
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"result": {
|
|
77
|
+
"summary": "Example Domain - This domain is for illustrative examples in documents."
|
|
78
|
+
},
|
|
79
|
+
"_payment": {
|
|
80
|
+
"status": "paid",
|
|
81
|
+
"from": "0x..."
|
|
82
|
+
}
|
|
83
|
+
}
|
|
89
84
|
```
|
|
90
|
-
✓ Payment signed: $0.005 (agent fee + platform fee)
|
|
91
|
-
✓ Agent executed successfully
|
|
92
85
|
|
|
93
|
-
|
|
86
|
+
If no wallet is configured:
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"error": "Wallet not configured",
|
|
90
|
+
"message": "Set NULLPATH_WALLET_KEY environment variable with your private key to execute paid agents.",
|
|
91
|
+
"hint": "Add to Claude Desktop config: \"env\": { \"NULLPATH_WALLET_KEY\": \"0x...\" }"
|
|
92
|
+
}
|
|
94
93
|
```
|
|
95
94
|
|
|
96
95
|
## Available Tools
|
|
97
96
|
|
|
98
|
-
| Tool | Description |
|
|
97
|
+
| Tool | Description | Payment |
|
|
99
98
|
|------|-------------|---------|
|
|
100
99
|
| `discover_agents` | Search agents by capability | Free |
|
|
101
100
|
| `lookup_agent` | Get agent details by ID | Free |
|
|
102
101
|
| `get_capabilities` | List capability categories | Free |
|
|
103
102
|
| `check_reputation` | Get agent trust score | Free |
|
|
104
|
-
| `execute_agent` | Run an agent
|
|
105
|
-
| `register_agent` | Register new agent | $0.10 |
|
|
103
|
+
| `execute_agent` | Run an agent | Varies by agent |
|
|
104
|
+
| `register_agent` | Register new agent | $0.10 USDC |
|
|
106
105
|
|
|
107
|
-
##
|
|
106
|
+
## How It Works
|
|
107
|
+
|
|
108
|
+
This MCP server connects directly to nullpath's REST API (`nullpath.com/api/v1/*`) and exposes tools via stdio for Claude Desktop and Cursor.
|
|
108
109
|
|
|
109
|
-
|
|
110
|
+
## Configuration
|
|
110
111
|
|
|
111
|
-
| Variable |
|
|
112
|
-
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
112
|
+
| Variable | Description | Default |
|
|
113
|
+
|----------|-------------|---------|
|
|
114
|
+
| `NULLPATH_API_URL` | API base URL | `https://nullpath.com/api/v1` |
|
|
115
|
+
| `NULLPATH_WALLET_KEY` | Private key for x402 payments | (required for paid tools) |
|
|
115
116
|
|
|
116
|
-
###
|
|
117
|
+
### Wallet Setup for Paid Tools
|
|
117
118
|
|
|
118
|
-
|
|
119
|
-
- Use a dedicated wallet with limited funds for MCP payments
|
|
120
|
-
- The client signs EIP-3009 authorizations, which can only transfer the exact amount specified
|
|
121
|
-
- Payments are only settled after successful agent execution
|
|
119
|
+
To use `execute_agent` and `register_agent`, you need a wallet with USDC on Base:
|
|
122
120
|
|
|
123
|
-
|
|
121
|
+
1. **Get a wallet private key** - Export from MetaMask or create new
|
|
122
|
+
2. **Fund with USDC on Base** - Bridge USDC to Base network
|
|
123
|
+
3. **Add to config**:
|
|
124
124
|
|
|
125
|
+
**Claude Desktop** (`claude_desktop_config.json`):
|
|
125
126
|
```json
|
|
126
127
|
{
|
|
127
128
|
"mcpServers": {
|
|
@@ -129,43 +130,37 @@ Summary: Example.com is a simple domain used for...
|
|
|
129
130
|
"command": "npx",
|
|
130
131
|
"args": ["-y", "nullpath-mcp"],
|
|
131
132
|
"env": {
|
|
132
|
-
"NULLPATH_WALLET_KEY": "
|
|
133
|
-
"NULLPATH_MCP_URL": "https://nullpath.com/mcp"
|
|
133
|
+
"NULLPATH_WALLET_KEY": "0x..."
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
140
|
+
**Cursor** (`.cursor/mcp.json`):
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"mcpServers": {
|
|
144
|
+
"nullpath": {
|
|
145
|
+
"command": "npx",
|
|
146
|
+
"args": ["-y", "nullpath-mcp"],
|
|
147
|
+
"env": {
|
|
148
|
+
"NULLPATH_WALLET_KEY": "0x..."
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
148
154
|
|
|
149
|
-
|
|
150
|
-
- **Base** (mainnet) - Production
|
|
151
|
-
- **Base Sepolia** (testnet) - Development
|
|
155
|
+
> ⚠️ **Security**: Your private key is stored locally and used only for signing. Never share it or commit to git.
|
|
152
156
|
|
|
153
157
|
## Troubleshooting
|
|
154
158
|
|
|
155
|
-
**
|
|
156
|
-
- Set your wallet private key in the MCP config's `env` section
|
|
157
|
-
|
|
158
|
-
**"Unsupported network"**
|
|
159
|
-
- The server requested payment on an unsupported chain. Contact support.
|
|
159
|
+
**Connection errors:** Ensure you have internet access.
|
|
160
160
|
|
|
161
|
-
**
|
|
162
|
-
- Ensure you have internet access. The client connects to `https://nullpath.com/mcp`.
|
|
161
|
+
**"Command not found":** Make sure Node.js 18+ is installed.
|
|
163
162
|
|
|
164
|
-
**
|
|
165
|
-
- Make sure Node.js 18+ is installed and `npx` is in your PATH.
|
|
166
|
-
|
|
167
|
-
**Tools not showing**
|
|
168
|
-
- Restart Claude Desktop / Cursor after updating the config.
|
|
163
|
+
**Tools not showing:** Restart Claude Desktop / Cursor after config changes.
|
|
169
164
|
|
|
170
165
|
## Development
|
|
171
166
|
|
|
@@ -174,17 +169,15 @@ git clone https://github.com/nullpath-labs/mcp-client.git
|
|
|
174
169
|
cd mcp-client
|
|
175
170
|
npm install
|
|
176
171
|
npm run build
|
|
177
|
-
npm
|
|
178
|
-
npm run dev # Run locally
|
|
172
|
+
npm test
|
|
179
173
|
```
|
|
180
174
|
|
|
181
175
|
## Links
|
|
182
176
|
|
|
183
177
|
- [nullpath.com](https://nullpath.com) — Marketplace
|
|
184
178
|
- [docs.nullpath.com](https://docs.nullpath.com) — Documentation
|
|
185
|
-
- [API Reference](https://docs.nullpath.com/api-reference) — Full API docs
|
|
186
|
-
- [x402 Protocol](https://github.com/coinbase/x402) — Payment protocol spec
|
|
187
179
|
|
|
188
180
|
## License
|
|
189
181
|
|
|
190
182
|
MIT
|
|
183
|
+
|
package/SWARM_SPEC.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# x402 Payment Signing for MCP Client
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
Add automatic EIP-3009 payment signing to the nullpath MCP client so users can execute paid tools (execute_agent, register_agent) without manual payment handling.
|
|
5
|
+
|
|
6
|
+
## Current State
|
|
7
|
+
- MCP client at `/Users/tg_air/playground/mcp-client`
|
|
8
|
+
- Free tools work: discover_agents, lookup_agent, get_capabilities, check_reputation
|
|
9
|
+
- Paid tools exist on server but client can't sign payments
|
|
10
|
+
- `viem` already installed as dependency
|
|
11
|
+
|
|
12
|
+
## User Flow (Target)
|
|
13
|
+
1. User configures `NULLPATH_WALLET_KEY` in Claude Desktop config
|
|
14
|
+
2. User asks Claude: "Execute the URL Summarizer on https://example.com"
|
|
15
|
+
3. Client calls execute_agent tool
|
|
16
|
+
4. Server returns 402 Payment Required with:
|
|
17
|
+
- `X-PAYMENT-REQUIRED`: payment requirements (recipient, amount, asset, network)
|
|
18
|
+
5. Client parses 402, signs EIP-3009 TransferWithAuthorization
|
|
19
|
+
6. Client retries with `X-PAYMENT` header containing signature
|
|
20
|
+
7. Server verifies, executes agent, returns result
|
|
21
|
+
8. User sees result seamlessly
|
|
22
|
+
|
|
23
|
+
## Technical Requirements
|
|
24
|
+
|
|
25
|
+
### 1. Wallet Configuration
|
|
26
|
+
- Read `NULLPATH_WALLET_KEY` from environment
|
|
27
|
+
- Create viem wallet client for signing
|
|
28
|
+
- Support Base mainnet (chainId: 8453)
|
|
29
|
+
|
|
30
|
+
### 2. Payment Detection
|
|
31
|
+
- Detect 402 status code
|
|
32
|
+
- Parse `X-PAYMENT-REQUIRED` header (base64 JSON)
|
|
33
|
+
- Extract: recipient, amount, asset (USDC address), network, validAfter, validBefore
|
|
34
|
+
|
|
35
|
+
### 3. EIP-3009 Signing
|
|
36
|
+
- Sign `TransferWithAuthorization` for USDC
|
|
37
|
+
- Parameters: from, to, value, validAfter, validBefore, nonce
|
|
38
|
+
- Use viem's signTypedData
|
|
39
|
+
|
|
40
|
+
### 4. Payment Header
|
|
41
|
+
- Encode signature as `X-PAYMENT` header
|
|
42
|
+
- Format: base64 JSON with { signature, from, to, value, validAfter, validBefore, nonce }
|
|
43
|
+
|
|
44
|
+
### 5. Retry Logic
|
|
45
|
+
- On 402, sign and retry automatically
|
|
46
|
+
- Max 1 retry (don't loop)
|
|
47
|
+
- Return clear error if payment fails
|
|
48
|
+
|
|
49
|
+
## Files to Create/Modify
|
|
50
|
+
|
|
51
|
+
### New Files
|
|
52
|
+
- `src/lib/wallet.ts` - Wallet client setup
|
|
53
|
+
- `src/lib/payment.ts` - Payment signing logic
|
|
54
|
+
- `src/lib/eip3009.ts` - EIP-3009 typed data structure
|
|
55
|
+
- `src/__tests__/payment.test.ts` - Payment tests
|
|
56
|
+
|
|
57
|
+
### Modify
|
|
58
|
+
- `src/index.ts` - Add payment handling to execute_agent and register_agent tools
|
|
59
|
+
- `README.md` - Document wallet configuration
|
|
60
|
+
|
|
61
|
+
## USDC Contract Info
|
|
62
|
+
- Base Mainnet: `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`
|
|
63
|
+
- EIP-3009 domain: { name: "USD Coin", version: "2", chainId: 8453, verifyingContract: <address> }
|
|
64
|
+
|
|
65
|
+
## Reference
|
|
66
|
+
- x402 spec: https://github.com/coinbase/x402
|
|
67
|
+
- Server payment middleware: nullpath repo `packages/mcp/src/middleware/payment.ts`
|
|
68
|
+
- Existing x402 lib: nullpath repo `packages/mcp/src/lib/x402.ts`
|
|
69
|
+
|
|
70
|
+
## Success Criteria
|
|
71
|
+
- [ ] User can execute paid tools with just NULLPATH_WALLET_KEY env var
|
|
72
|
+
- [ ] Payment signing is automatic and transparent
|
|
73
|
+
- [ ] Clear error messages if wallet not configured or payment fails
|
|
74
|
+
- [ ] Tests pass
|
|
75
|
+
- [ ] Works in Claude Desktop
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/payment.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const vitest_1 = require("vitest");
|
|
4
|
+
const payment_js_1 = require("../lib/payment.js");
|
|
5
|
+
(0, vitest_1.describe)('payment', () => {
|
|
6
|
+
(0, vitest_1.describe)('parsePaymentRequired', () => {
|
|
7
|
+
(0, vitest_1.it)('returns null for non-402 responses', () => {
|
|
8
|
+
const response = new Response('OK', { status: 200 });
|
|
9
|
+
(0, vitest_1.expect)((0, payment_js_1.parsePaymentRequired)(response)).toBeNull();
|
|
10
|
+
});
|
|
11
|
+
(0, vitest_1.it)('throws if 402 but missing X-PAYMENT-REQUIRED header', () => {
|
|
12
|
+
const response = new Response('Payment Required', { status: 402 });
|
|
13
|
+
(0, vitest_1.expect)(() => (0, payment_js_1.parsePaymentRequired)(response)).toThrow(payment_js_1.PaymentRequiredError);
|
|
14
|
+
});
|
|
15
|
+
(0, vitest_1.it)('parses valid 402 response with payment header', () => {
|
|
16
|
+
const requirements = {
|
|
17
|
+
recipient: '0x1234567890123456789012345678901234567890',
|
|
18
|
+
amount: '1000000', // 1 USDC
|
|
19
|
+
network: 8453,
|
|
20
|
+
validAfter: 0,
|
|
21
|
+
validBefore: Math.floor(Date.now() / 1000) + 300,
|
|
22
|
+
};
|
|
23
|
+
const encoded = Buffer.from(JSON.stringify(requirements)).toString('base64');
|
|
24
|
+
const response = new Response('Payment Required', {
|
|
25
|
+
status: 402,
|
|
26
|
+
headers: { 'X-PAYMENT-REQUIRED': encoded },
|
|
27
|
+
});
|
|
28
|
+
const parsed = (0, payment_js_1.parsePaymentRequired)(response);
|
|
29
|
+
(0, vitest_1.expect)(parsed).not.toBeNull();
|
|
30
|
+
(0, vitest_1.expect)(parsed.recipient).toBe(requirements.recipient);
|
|
31
|
+
(0, vitest_1.expect)(parsed.amount).toBe(BigInt(requirements.amount));
|
|
32
|
+
(0, vitest_1.expect)(parsed.network).toBe(8453);
|
|
33
|
+
});
|
|
34
|
+
(0, vitest_1.it)('handles string amounts', () => {
|
|
35
|
+
const requirements = {
|
|
36
|
+
recipient: '0x1234567890123456789012345678901234567890',
|
|
37
|
+
amount: '500000',
|
|
38
|
+
validBefore: Math.floor(Date.now() / 1000) + 300,
|
|
39
|
+
};
|
|
40
|
+
const encoded = Buffer.from(JSON.stringify(requirements)).toString('base64');
|
|
41
|
+
const response = new Response('', {
|
|
42
|
+
status: 402,
|
|
43
|
+
headers: { 'X-PAYMENT-REQUIRED': encoded },
|
|
44
|
+
});
|
|
45
|
+
const parsed = (0, payment_js_1.parsePaymentRequired)(response);
|
|
46
|
+
(0, vitest_1.expect)(parsed.amount).toBe(500000n);
|
|
47
|
+
});
|
|
48
|
+
(0, vitest_1.it)('rejects expired validBefore', () => {
|
|
49
|
+
const requirements = {
|
|
50
|
+
recipient: '0x1234567890123456789012345678901234567890',
|
|
51
|
+
amount: '1000000',
|
|
52
|
+
validBefore: Math.floor(Date.now() / 1000) - 100, // In the past
|
|
53
|
+
};
|
|
54
|
+
const encoded = Buffer.from(JSON.stringify(requirements)).toString('base64');
|
|
55
|
+
const response = new Response('', {
|
|
56
|
+
status: 402,
|
|
57
|
+
headers: { 'X-PAYMENT-REQUIRED': encoded },
|
|
58
|
+
});
|
|
59
|
+
(0, vitest_1.expect)(() => (0, payment_js_1.parsePaymentRequired)(response)).toThrow(/expired/);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
(0, vitest_1.describe)('encodePaymentHeader', () => {
|
|
63
|
+
(0, vitest_1.it)('encodes signed authorization as base64 JSON', () => {
|
|
64
|
+
const signed = {
|
|
65
|
+
from: '0x1111111111111111111111111111111111111111',
|
|
66
|
+
to: '0x2222222222222222222222222222222222222222',
|
|
67
|
+
value: 1000000n,
|
|
68
|
+
validAfter: 0n,
|
|
69
|
+
validBefore: 9999999999n,
|
|
70
|
+
nonce: '0x' + '00'.repeat(32),
|
|
71
|
+
signature: '0x' + 'ab'.repeat(65),
|
|
72
|
+
v: 27,
|
|
73
|
+
r: '0x' + 'ab'.repeat(32),
|
|
74
|
+
s: '0x' + 'cd'.repeat(32),
|
|
75
|
+
};
|
|
76
|
+
const encoded = (0, payment_js_1.encodePaymentHeader)(signed);
|
|
77
|
+
// Should be valid base64
|
|
78
|
+
const decoded = JSON.parse(Buffer.from(encoded, 'base64').toString('utf-8'));
|
|
79
|
+
(0, vitest_1.expect)(decoded.from).toBe(signed.from);
|
|
80
|
+
(0, vitest_1.expect)(decoded.to).toBe(signed.to);
|
|
81
|
+
(0, vitest_1.expect)(decoded.value).toBe('1000000');
|
|
82
|
+
(0, vitest_1.expect)(decoded.signature).toBe(signed.signature);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
(0, vitest_1.describe)('formatUsdcAmount', () => {
|
|
86
|
+
(0, vitest_1.it)('formats small amounts correctly', () => {
|
|
87
|
+
(0, vitest_1.expect)((0, payment_js_1.formatUsdcAmount)(1000n)).toBe('$0.001000 USDC');
|
|
88
|
+
(0, vitest_1.expect)((0, payment_js_1.formatUsdcAmount)(10000n)).toBe('$0.010000 USDC');
|
|
89
|
+
(0, vitest_1.expect)((0, payment_js_1.formatUsdcAmount)(100000n)).toBe('$0.100000 USDC');
|
|
90
|
+
});
|
|
91
|
+
(0, vitest_1.it)('formats whole dollar amounts', () => {
|
|
92
|
+
(0, vitest_1.expect)((0, payment_js_1.formatUsdcAmount)(1000000n)).toBe('$1.000000 USDC');
|
|
93
|
+
(0, vitest_1.expect)((0, payment_js_1.formatUsdcAmount)(10000000n)).toBe('$10.000000 USDC');
|
|
94
|
+
});
|
|
95
|
+
(0, vitest_1.it)('handles zero', () => {
|
|
96
|
+
(0, vitest_1.expect)((0, payment_js_1.formatUsdcAmount)(0n)).toBe('$0.000000 USDC');
|
|
97
|
+
});
|
|
98
|
+
(0, vitest_1.it)('preserves precision for large amounts', () => {
|
|
99
|
+
// This would lose precision with Number conversion
|
|
100
|
+
const largeAmount = 9007199254740993n; // > Number.MAX_SAFE_INTEGER
|
|
101
|
+
const formatted = (0, payment_js_1.formatUsdcAmount)(largeAmount);
|
|
102
|
+
(0, vitest_1.expect)(formatted).toContain('9007199254');
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
//# sourceMappingURL=payment.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment.test.js","sourceRoot":"","sources":["../../src/__tests__/payment.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
18
18
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
19
19
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
20
|
+
const payment_js_1 = require("./lib/payment.js");
|
|
21
|
+
const wallet_js_1 = require("./lib/wallet.js");
|
|
20
22
|
const NULLPATH_API_URL = process.env.NULLPATH_API_URL || 'https://nullpath.com/api/v1';
|
|
21
23
|
// Tool definitions
|
|
22
24
|
const TOOLS = [
|
|
@@ -160,38 +162,157 @@ async function handleCheckReputation(args) {
|
|
|
160
162
|
};
|
|
161
163
|
}
|
|
162
164
|
async function handleExecuteAgent(args) {
|
|
163
|
-
//
|
|
164
|
-
|
|
165
|
-
const walletKey = process.env.NULLPATH_WALLET_KEY;
|
|
166
|
-
if (!walletKey) {
|
|
165
|
+
// Check wallet configuration upfront for better error messages
|
|
166
|
+
if (!(0, payment_js_1.isWalletConfigured)()) {
|
|
167
167
|
return {
|
|
168
|
-
error: '
|
|
169
|
-
|
|
168
|
+
error: 'Wallet not configured',
|
|
169
|
+
message: 'Set NULLPATH_WALLET_KEY environment variable with your private key to execute paid agents.',
|
|
170
|
+
hint: 'Add to Claude Desktop config: "env": { "NULLPATH_WALLET_KEY": "0x..." }',
|
|
170
171
|
};
|
|
171
172
|
}
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
173
|
+
// Validate wallet key is valid before making request
|
|
174
|
+
try {
|
|
175
|
+
(0, wallet_js_1.createWallet)();
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (error instanceof payment_js_1.InvalidPrivateKeyError) {
|
|
179
|
+
return {
|
|
180
|
+
error: 'Invalid wallet key',
|
|
181
|
+
message: error.message,
|
|
182
|
+
hint: 'Ensure NULLPATH_WALLET_KEY is a valid 64-character hex string (with or without 0x prefix).',
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
const url = `${NULLPATH_API_URL}/execute`;
|
|
188
|
+
const body = JSON.stringify({
|
|
189
|
+
targetAgentId: args.agentId,
|
|
190
|
+
capabilityId: args.capabilityId,
|
|
191
|
+
input: args.input,
|
|
180
192
|
});
|
|
193
|
+
try {
|
|
194
|
+
// Use fetchWithPayment for automatic 402 handling
|
|
195
|
+
const response = await (0, payment_js_1.fetchWithPayment)(url, {
|
|
196
|
+
method: 'POST',
|
|
197
|
+
body,
|
|
198
|
+
});
|
|
199
|
+
if (!response.ok) {
|
|
200
|
+
const error = await response.text();
|
|
201
|
+
throw new Error(`API error (${response.status}): ${error}`);
|
|
202
|
+
}
|
|
203
|
+
const result = await response.json();
|
|
204
|
+
// Add wallet info to response for transparency
|
|
205
|
+
const walletAddress = (0, wallet_js_1.getWalletAddress)() ?? 'unknown';
|
|
206
|
+
return {
|
|
207
|
+
...result,
|
|
208
|
+
_payment: {
|
|
209
|
+
status: 'paid',
|
|
210
|
+
from: walletAddress,
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
if (error instanceof payment_js_1.WalletNotConfiguredError) {
|
|
216
|
+
return {
|
|
217
|
+
error: 'Wallet not configured',
|
|
218
|
+
message: error.message,
|
|
219
|
+
hint: 'Add NULLPATH_WALLET_KEY to your environment variables.',
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (error instanceof payment_js_1.PaymentRequiredError) {
|
|
223
|
+
return {
|
|
224
|
+
error: 'Payment failed',
|
|
225
|
+
message: error.message,
|
|
226
|
+
requirements: error.requirements ? {
|
|
227
|
+
recipient: error.requirements.recipient,
|
|
228
|
+
amount: (0, payment_js_1.formatUsdcAmount)(error.requirements.amount),
|
|
229
|
+
} : undefined,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
if (error instanceof payment_js_1.PaymentSigningError) {
|
|
233
|
+
return {
|
|
234
|
+
error: 'Payment signing failed',
|
|
235
|
+
message: error.message,
|
|
236
|
+
hint: 'Check that your wallet has sufficient USDC balance on Base.',
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
181
241
|
}
|
|
182
242
|
async function handleRegisterAgent(args) {
|
|
183
|
-
|
|
184
|
-
if (!
|
|
243
|
+
// Check wallet configuration upfront for better error messages
|
|
244
|
+
if (!(0, payment_js_1.isWalletConfigured)()) {
|
|
185
245
|
return {
|
|
186
|
-
error: '
|
|
187
|
-
|
|
246
|
+
error: 'Wallet not configured',
|
|
247
|
+
message: 'Set NULLPATH_WALLET_KEY environment variable to register agents.',
|
|
248
|
+
hint: 'Registration costs $0.10 USDC. Add to Claude Desktop config: "env": { "NULLPATH_WALLET_KEY": "0x..." }',
|
|
188
249
|
};
|
|
189
250
|
}
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
251
|
+
// Validate wallet key is valid before making request
|
|
252
|
+
try {
|
|
253
|
+
(0, wallet_js_1.createWallet)();
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
if (error instanceof payment_js_1.InvalidPrivateKeyError) {
|
|
257
|
+
return {
|
|
258
|
+
error: 'Invalid wallet key',
|
|
259
|
+
message: error.message,
|
|
260
|
+
hint: 'Ensure NULLPATH_WALLET_KEY is a valid 64-character hex string (with or without 0x prefix).',
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
const url = `${NULLPATH_API_URL}/agents`;
|
|
266
|
+
const body = JSON.stringify(args);
|
|
267
|
+
try {
|
|
268
|
+
// Use fetchWithPayment for automatic 402 handling
|
|
269
|
+
const response = await (0, payment_js_1.fetchWithPayment)(url, {
|
|
270
|
+
method: 'POST',
|
|
271
|
+
body,
|
|
272
|
+
});
|
|
273
|
+
if (!response.ok) {
|
|
274
|
+
const error = await response.text();
|
|
275
|
+
throw new Error(`API error (${response.status}): ${error}`);
|
|
276
|
+
}
|
|
277
|
+
const result = await response.json();
|
|
278
|
+
// Add wallet info to response for transparency
|
|
279
|
+
const walletAddress = (0, wallet_js_1.getWalletAddress)() ?? 'unknown';
|
|
280
|
+
return {
|
|
281
|
+
...result,
|
|
282
|
+
_payment: {
|
|
283
|
+
status: 'paid',
|
|
284
|
+
from: walletAddress,
|
|
285
|
+
cost: '$0.10 USDC',
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
if (error instanceof payment_js_1.WalletNotConfiguredError) {
|
|
291
|
+
return {
|
|
292
|
+
error: 'Wallet not configured',
|
|
293
|
+
message: error.message,
|
|
294
|
+
cost: '$0.10 USDC required for registration',
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
if (error instanceof payment_js_1.PaymentRequiredError) {
|
|
298
|
+
return {
|
|
299
|
+
error: 'Payment failed',
|
|
300
|
+
message: error.message,
|
|
301
|
+
requirements: error.requirements ? {
|
|
302
|
+
recipient: error.requirements.recipient,
|
|
303
|
+
amount: (0, payment_js_1.formatUsdcAmount)(error.requirements.amount),
|
|
304
|
+
} : undefined,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
if (error instanceof payment_js_1.PaymentSigningError) {
|
|
308
|
+
return {
|
|
309
|
+
error: 'Payment signing failed',
|
|
310
|
+
message: error.message,
|
|
311
|
+
hint: 'Ensure your wallet has at least $0.10 USDC on Base.',
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
195
316
|
}
|
|
196
317
|
// Main server
|
|
197
318
|
async function main() {
|