nullpath-mcp 1.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/dist/index.js ADDED
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * nullpath MCP Client
5
+ *
6
+ * Connects to nullpath.com/mcp - AI agent marketplace with x402 micropayments.
7
+ *
8
+ * Available tools:
9
+ * - discover_agents: Search agents by capability
10
+ * - lookup_agent: Get agent details by ID
11
+ * - execute_agent: Run an agent (paid via x402)
12
+ * - register_agent: Register a new agent (paid)
13
+ * - get_capabilities: List capability categories
14
+ * - check_reputation: Get agent trust score
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.signTransferAuthorization = signTransferAuthorization;
18
+ exports.encodePaymentHeader = encodePaymentHeader;
19
+ exports.isX402Error = isX402Error;
20
+ exports.getWallet = getWallet;
21
+ exports.handleX402Payment = handleX402Payment;
22
+ const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
23
+ const sse_js_1 = require("@modelcontextprotocol/sdk/client/sse.js");
24
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
25
+ const index_js_2 = require("@modelcontextprotocol/sdk/server/index.js");
26
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
27
+ const accounts_1 = require("viem/accounts");
28
+ const NULLPATH_MCP_URL = process.env.NULLPATH_MCP_URL || 'https://nullpath.com/mcp';
29
+ /**
30
+ * EIP-3009 TransferWithAuthorization typed data
31
+ */
32
+ const TRANSFER_WITH_AUTHORIZATION_TYPES = {
33
+ TransferWithAuthorization: [
34
+ { name: 'from', type: 'address' },
35
+ { name: 'to', type: 'address' },
36
+ { name: 'value', type: 'uint256' },
37
+ { name: 'validAfter', type: 'uint256' },
38
+ { name: 'validBefore', type: 'uint256' },
39
+ { name: 'nonce', type: 'bytes32' },
40
+ ],
41
+ };
42
+ /**
43
+ * USDC contract metadata by network
44
+ */
45
+ const USDC_CONTRACTS = {
46
+ 'base': { name: 'USD Coin', version: '2', chainId: 8453 },
47
+ 'base-sepolia': { name: 'USD Coin', version: '2', chainId: 84532 },
48
+ };
49
+ /**
50
+ * Generate a random 32-byte nonce for EIP-3009
51
+ */
52
+ function generateNonce() {
53
+ const bytes = new Uint8Array(32);
54
+ crypto.getRandomValues(bytes);
55
+ return `0x${Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('')}`;
56
+ }
57
+ /**
58
+ * Sign an EIP-3009 TransferWithAuthorization
59
+ */
60
+ async function signTransferAuthorization(wallet, requirements) {
61
+ const network = requirements.network;
62
+ const contractMeta = USDC_CONTRACTS[network];
63
+ if (!contractMeta) {
64
+ throw new Error(`Unsupported network: ${network}`);
65
+ }
66
+ const now = Math.floor(Date.now() / 1000);
67
+ const validAfter = now - 60; // Valid from 1 minute ago
68
+ const validBefore = now + requirements.maxTimeoutSeconds;
69
+ const nonce = generateNonce();
70
+ const authorization = {
71
+ from: wallet.address,
72
+ to: requirements.payTo,
73
+ value: BigInt(requirements.maxAmountRequired),
74
+ validAfter: BigInt(validAfter),
75
+ validBefore: BigInt(validBefore),
76
+ nonce,
77
+ };
78
+ // Create account for signing
79
+ const account = (0, accounts_1.privateKeyToAccount)(wallet.privateKey);
80
+ const signature = await account.signTypedData({
81
+ domain: {
82
+ name: contractMeta.name,
83
+ version: contractMeta.version,
84
+ chainId: contractMeta.chainId,
85
+ verifyingContract: requirements.asset,
86
+ },
87
+ types: TRANSFER_WITH_AUTHORIZATION_TYPES,
88
+ primaryType: 'TransferWithAuthorization',
89
+ message: authorization,
90
+ });
91
+ return {
92
+ x402Version: 1,
93
+ scheme: 'exact',
94
+ network,
95
+ payload: {
96
+ signature,
97
+ authorization: {
98
+ from: wallet.address,
99
+ to: requirements.payTo,
100
+ value: authorization.value.toString(),
101
+ validAfter: authorization.validAfter.toString(),
102
+ validBefore: authorization.validBefore.toString(),
103
+ nonce,
104
+ },
105
+ },
106
+ };
107
+ }
108
+ /**
109
+ * Encode payment payload to base64 for X-PAYMENT header
110
+ */
111
+ function encodePaymentHeader(payment) {
112
+ return Buffer.from(JSON.stringify(payment)).toString('base64');
113
+ }
114
+ /**
115
+ * Check if an error response is an x402 payment required error
116
+ */
117
+ function isX402Error(error) {
118
+ if (typeof error !== 'object' || error === null)
119
+ return false;
120
+ const err = error;
121
+ if (err.code !== -32000)
122
+ return false;
123
+ if (typeof err.data !== 'object' || err.data === null)
124
+ return false;
125
+ const data = err.data;
126
+ return typeof data.x402Version === 'number' && Array.isArray(data.accepts);
127
+ }
128
+ /**
129
+ * Get wallet from environment variable
130
+ */
131
+ function getWallet() {
132
+ const privateKey = process.env.NULLPATH_WALLET_KEY;
133
+ if (!privateKey) {
134
+ throw new Error('NULLPATH_WALLET_KEY environment variable is required for paid tool calls. ' +
135
+ 'Set it to your wallet private key (0x-prefixed hex string).');
136
+ }
137
+ // Ensure the key is properly formatted
138
+ const formattedKey = privateKey.startsWith('0x')
139
+ ? privateKey
140
+ : `0x${privateKey}`;
141
+ const account = (0, accounts_1.privateKeyToAccount)(formattedKey);
142
+ return {
143
+ address: account.address,
144
+ privateKey: formattedKey,
145
+ };
146
+ }
147
+ /**
148
+ * Handle x402 payment flow
149
+ *
150
+ * 1. Parse payment requirements from 402 error
151
+ * 2. Sign EIP-3009 authorization
152
+ * 3. Return payment header for retry
153
+ */
154
+ async function handleX402Payment(errorData) {
155
+ const wallet = getWallet();
156
+ if (errorData.accepts.length === 0) {
157
+ throw new Error('No payment options available in 402 response');
158
+ }
159
+ // Use the first payment option
160
+ const requirements = errorData.accepts[0];
161
+ // Sign the payment authorization
162
+ const payment = await signTransferAuthorization(wallet, requirements);
163
+ // Encode for header
164
+ return encodePaymentHeader(payment);
165
+ }
166
+ async function main() {
167
+ // Create a local stdio server that proxies to nullpath's remote MCP
168
+ const server = new index_js_2.Server({
169
+ name: 'nullpath-mcp',
170
+ version: '1.0.0',
171
+ }, {
172
+ capabilities: {
173
+ tools: {},
174
+ },
175
+ });
176
+ // Connect to remote nullpath MCP server
177
+ const transport = new sse_js_1.SSEClientTransport(new URL(NULLPATH_MCP_URL));
178
+ const client = new index_js_1.Client({
179
+ name: 'nullpath-mcp-proxy',
180
+ version: '1.0.0',
181
+ }, {
182
+ capabilities: {},
183
+ });
184
+ await client.connect(transport);
185
+ // List available tools from remote server
186
+ const tools = await client.listTools();
187
+ // Register tool handlers that proxy to remote
188
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
189
+ return tools;
190
+ });
191
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
192
+ try {
193
+ // First attempt without payment
194
+ const result = await client.callTool({
195
+ name: request.params.name,
196
+ arguments: request.params.arguments,
197
+ });
198
+ return result;
199
+ }
200
+ catch (error) {
201
+ // Check if this is a 402 Payment Required error
202
+ if (isX402Error(error)) {
203
+ console.error(`Payment required for tool: ${request.params.name}`);
204
+ try {
205
+ // Handle x402 payment
206
+ const paymentHeader = await handleX402Payment(error.data);
207
+ // Retry with payment header
208
+ // Note: The MCP SDK doesn't directly support custom headers on tool calls,
209
+ // so we need to make the request ourselves with the payment header
210
+ const retryResult = await retryWithPayment(request.params.name, request.params.arguments, paymentHeader);
211
+ return retryResult;
212
+ }
213
+ catch (paymentError) {
214
+ // Re-throw with more context
215
+ throw new Error(`Payment failed for tool ${request.params.name}: ${paymentError instanceof Error ? paymentError.message : String(paymentError)}`);
216
+ }
217
+ }
218
+ // Re-throw non-payment errors
219
+ throw error;
220
+ }
221
+ });
222
+ // Start local stdio transport
223
+ const stdioTransport = new stdio_js_1.StdioServerTransport();
224
+ await server.connect(stdioTransport);
225
+ console.error('nullpath MCP client connected to', NULLPATH_MCP_URL);
226
+ }
227
+ /**
228
+ * Retry a tool call with x402 payment header
229
+ *
230
+ * Makes a direct HTTP request to the MCP server with the X-PAYMENT header
231
+ */
232
+ async function retryWithPayment(toolName, args, paymentHeader) {
233
+ const requestBody = {
234
+ jsonrpc: '2.0',
235
+ method: 'tools/call',
236
+ params: {
237
+ name: toolName,
238
+ arguments: args || {},
239
+ },
240
+ id: Date.now(),
241
+ };
242
+ const response = await fetch(NULLPATH_MCP_URL, {
243
+ method: 'POST',
244
+ headers: {
245
+ 'Content-Type': 'application/json',
246
+ 'X-PAYMENT': paymentHeader,
247
+ },
248
+ body: JSON.stringify(requestBody),
249
+ });
250
+ if (!response.ok) {
251
+ const errorText = await response.text();
252
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
253
+ }
254
+ const result = await response.json();
255
+ if (result.error) {
256
+ throw new Error(`RPC Error: ${result.error.message}`);
257
+ }
258
+ return result.result || { content: [{ type: 'text', text: 'Success' }] };
259
+ }
260
+ main().catch((error) => {
261
+ console.error('Failed to start nullpath MCP client:', error);
262
+ process.exit(1);
263
+ });
264
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "nullpath-mcp",
3
+ "version": "1.1.0",
4
+ "description": "Connect to nullpath's AI agent marketplace via MCP. Discover and pay agents with x402 micropayments.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "nullpath-mcp": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js",
13
+ "dev": "tsx src/index.ts",
14
+ "test": "vitest run",
15
+ "test:watch": "vitest"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "model-context-protocol",
20
+ "ai-agents",
21
+ "nullpath",
22
+ "x402",
23
+ "micropayments",
24
+ "usdc",
25
+ "base"
26
+ ],
27
+ "author": "nullpath-labs",
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/nullpath-labs/mcp-client.git"
32
+ },
33
+ "homepage": "https://nullpath.com",
34
+ "dependencies": {
35
+ "@modelcontextprotocol/sdk": "^1.0.0",
36
+ "viem": "^2.21.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^20.0.0",
40
+ "tsx": "^4.0.0",
41
+ "typescript": "^5.0.0",
42
+ "vitest": "^2.0.0"
43
+ },
44
+ "engines": {
45
+ "node": ">=18.0.0"
46
+ }
47
+ }
@@ -0,0 +1,229 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
+ import {
3
+ signTransferAuthorization,
4
+ encodePaymentHeader,
5
+ handleX402Payment,
6
+ getWallet,
7
+ isX402Error,
8
+ type PaymentRequirements,
9
+ type X402ErrorData,
10
+ type Wallet,
11
+ } from '../index.js';
12
+
13
+ // Test wallet - DO NOT use in production
14
+ const TEST_PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
15
+ const TEST_ADDRESS = '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266';
16
+
17
+ // USDC addresses
18
+ const USDC_BASE = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
19
+ const USDC_BASE_SEPOLIA = '0x036CbD53842c5426634e7929541eC2318f3dCF7e';
20
+
21
+ describe('x402 Payment Signing', () => {
22
+ const mockRequirements: PaymentRequirements = {
23
+ scheme: 'exact',
24
+ network: 'base-sepolia',
25
+ maxAmountRequired: '100000', // 0.10 USDC (6 decimals)
26
+ resource: 'https://nullpath.com/mcp',
27
+ description: 'Payment for MCP tool: register_agent',
28
+ mimeType: 'application/json',
29
+ payTo: '0x1234567890123456789012345678901234567890' as `0x${string}`,
30
+ maxTimeoutSeconds: 300,
31
+ asset: USDC_BASE_SEPOLIA,
32
+ extra: {},
33
+ };
34
+
35
+ const testWallet: Wallet = {
36
+ address: TEST_ADDRESS as `0x${string}`,
37
+ privateKey: TEST_PRIVATE_KEY as `0x${string}`,
38
+ };
39
+
40
+ describe('signTransferAuthorization', () => {
41
+ it('should sign a valid EIP-3009 authorization for base-sepolia', async () => {
42
+ const payment = await signTransferAuthorization(testWallet, mockRequirements);
43
+
44
+ expect(payment.x402Version).toBe(1);
45
+ expect(payment.scheme).toBe('exact');
46
+ expect(payment.network).toBe('base-sepolia');
47
+ expect(payment.payload.signature).toMatch(/^0x[a-fA-F0-9]{130}$/);
48
+ expect(payment.payload.authorization.from).toBe(TEST_ADDRESS);
49
+ expect(payment.payload.authorization.to).toBe(mockRequirements.payTo);
50
+ expect(payment.payload.authorization.value).toBe('100000');
51
+ expect(payment.payload.authorization.nonce).toMatch(/^0x[a-fA-F0-9]{64}$/);
52
+ });
53
+
54
+ it('should sign a valid EIP-3009 authorization for base mainnet', async () => {
55
+ const mainnetRequirements: PaymentRequirements = {
56
+ ...mockRequirements,
57
+ network: 'base',
58
+ asset: USDC_BASE,
59
+ };
60
+
61
+ const payment = await signTransferAuthorization(testWallet, mainnetRequirements);
62
+
63
+ expect(payment.network).toBe('base');
64
+ expect(payment.payload.signature).toMatch(/^0x[a-fA-F0-9]{130}$/);
65
+ });
66
+
67
+ it('should throw for unsupported network', async () => {
68
+ const badRequirements = {
69
+ ...mockRequirements,
70
+ network: 'ethereum' as 'base' | 'base-sepolia',
71
+ };
72
+
73
+ await expect(signTransferAuthorization(testWallet, badRequirements))
74
+ .rejects.toThrow('Unsupported network: ethereum');
75
+ });
76
+
77
+ it('should set validBefore based on maxTimeoutSeconds', async () => {
78
+ const now = Math.floor(Date.now() / 1000);
79
+ const payment = await signTransferAuthorization(testWallet, mockRequirements);
80
+
81
+ const validBefore = parseInt(payment.payload.authorization.validBefore, 10);
82
+ // validBefore should be approximately now + maxTimeoutSeconds (within 5 seconds tolerance)
83
+ expect(validBefore).toBeGreaterThan(now + mockRequirements.maxTimeoutSeconds - 5);
84
+ expect(validBefore).toBeLessThan(now + mockRequirements.maxTimeoutSeconds + 5);
85
+ });
86
+ });
87
+
88
+ describe('encodePaymentHeader', () => {
89
+ it('should encode payment payload to base64', async () => {
90
+ const payment = await signTransferAuthorization(testWallet, mockRequirements);
91
+ const encoded = encodePaymentHeader(payment);
92
+
93
+ // Should be valid base64
94
+ expect(() => Buffer.from(encoded, 'base64')).not.toThrow();
95
+
96
+ // Should decode back to valid JSON
97
+ const decoded = JSON.parse(Buffer.from(encoded, 'base64').toString('utf-8'));
98
+ expect(decoded.x402Version).toBe(1);
99
+ expect(decoded.scheme).toBe('exact');
100
+ expect(decoded.payload.signature).toBe(payment.payload.signature);
101
+ });
102
+ });
103
+
104
+ describe('isX402Error', () => {
105
+ it('should return true for valid x402 error', () => {
106
+ const error = {
107
+ code: -32000,
108
+ message: 'Payment required',
109
+ data: {
110
+ x402Version: 1,
111
+ error: 'Payment required for tool: register_agent',
112
+ accepts: [mockRequirements],
113
+ },
114
+ };
115
+
116
+ expect(isX402Error(error)).toBe(true);
117
+ });
118
+
119
+ it('should return false for non-x402 errors', () => {
120
+ expect(isX402Error(null)).toBe(false);
121
+ expect(isX402Error(undefined)).toBe(false);
122
+ expect(isX402Error({ code: -32600 })).toBe(false);
123
+ expect(isX402Error({ code: -32000, data: {} })).toBe(false);
124
+ expect(isX402Error({ code: -32000, data: { x402Version: 1 } })).toBe(false);
125
+ });
126
+ });
127
+ });
128
+
129
+ describe('Wallet Management', () => {
130
+ const originalEnv = process.env.NULLPATH_WALLET_KEY;
131
+
132
+ afterEach(() => {
133
+ if (originalEnv !== undefined) {
134
+ process.env.NULLPATH_WALLET_KEY = originalEnv;
135
+ } else {
136
+ delete process.env.NULLPATH_WALLET_KEY;
137
+ }
138
+ });
139
+
140
+ describe('getWallet', () => {
141
+ it('should throw when NULLPATH_WALLET_KEY is not set', () => {
142
+ delete process.env.NULLPATH_WALLET_KEY;
143
+
144
+ expect(() => getWallet()).toThrow('NULLPATH_WALLET_KEY environment variable is required');
145
+ });
146
+
147
+ it('should return wallet when key is set with 0x prefix', () => {
148
+ process.env.NULLPATH_WALLET_KEY = TEST_PRIVATE_KEY;
149
+
150
+ const wallet = getWallet();
151
+ expect(wallet.address).toBe(TEST_ADDRESS);
152
+ expect(wallet.privateKey).toBe(TEST_PRIVATE_KEY);
153
+ });
154
+
155
+ it('should handle key without 0x prefix', () => {
156
+ process.env.NULLPATH_WALLET_KEY = TEST_PRIVATE_KEY.slice(2);
157
+
158
+ const wallet = getWallet();
159
+ expect(wallet.address).toBe(TEST_ADDRESS);
160
+ });
161
+ });
162
+ });
163
+
164
+ describe('handleX402Payment', () => {
165
+ const mockX402Data: X402ErrorData = {
166
+ x402Version: 1,
167
+ error: 'Payment required for tool: register_agent',
168
+ accepts: [
169
+ {
170
+ scheme: 'exact',
171
+ network: 'base-sepolia',
172
+ maxAmountRequired: '100000',
173
+ resource: 'https://nullpath.com/mcp',
174
+ description: 'Payment for MCP tool: register_agent',
175
+ mimeType: 'application/json',
176
+ payTo: '0x1234567890123456789012345678901234567890' as `0x${string}`,
177
+ maxTimeoutSeconds: 300,
178
+ asset: USDC_BASE_SEPOLIA,
179
+ extra: {},
180
+ },
181
+ ],
182
+ };
183
+
184
+ const originalEnv = process.env.NULLPATH_WALLET_KEY;
185
+
186
+ beforeEach(() => {
187
+ process.env.NULLPATH_WALLET_KEY = TEST_PRIVATE_KEY;
188
+ });
189
+
190
+ afterEach(() => {
191
+ if (originalEnv !== undefined) {
192
+ process.env.NULLPATH_WALLET_KEY = originalEnv;
193
+ } else {
194
+ delete process.env.NULLPATH_WALLET_KEY;
195
+ }
196
+ });
197
+
198
+ it('should generate valid payment header from 402 response', async () => {
199
+ const header = await handleX402Payment(mockX402Data);
200
+
201
+ // Should be valid base64
202
+ const decoded = JSON.parse(Buffer.from(header, 'base64').toString('utf-8'));
203
+
204
+ expect(decoded.x402Version).toBe(1);
205
+ expect(decoded.scheme).toBe('exact');
206
+ expect(decoded.network).toBe('base-sepolia');
207
+ expect(decoded.payload.signature).toMatch(/^0x[a-fA-F0-9]{130}$/);
208
+ expect(decoded.payload.authorization.from).toBe(TEST_ADDRESS);
209
+ expect(decoded.payload.authorization.to).toBe(mockX402Data.accepts[0].payTo);
210
+ expect(decoded.payload.authorization.value).toBe('100000');
211
+ });
212
+
213
+ it('should throw when no payment options available', async () => {
214
+ const emptyData: X402ErrorData = {
215
+ ...mockX402Data,
216
+ accepts: [],
217
+ };
218
+
219
+ await expect(handleX402Payment(emptyData))
220
+ .rejects.toThrow('No payment options available in 402 response');
221
+ });
222
+
223
+ it('should throw when wallet key is missing', async () => {
224
+ delete process.env.NULLPATH_WALLET_KEY;
225
+
226
+ await expect(handleX402Payment(mockX402Data))
227
+ .rejects.toThrow('NULLPATH_WALLET_KEY environment variable is required');
228
+ });
229
+ });