@civic/x402-mcp 0.0.1
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/.claude/settings.local.json +26 -0
- package/.env.example +10 -0
- package/.github/workflows/ci.yml +144 -0
- package/.github/workflows/release.yml +56 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/.idea/x402-mcp-example.iml +13 -0
- package/CLAUDE.md +31 -0
- package/DEVELOPER.md +181 -0
- package/README.md +274 -0
- package/biome.json +63 -0
- package/dist/example/client/client.d.ts +3 -0
- package/dist/example/client/client.d.ts.map +1 -0
- package/dist/example/client/client.js +93 -0
- package/dist/example/client/client.js.map +1 -0
- package/dist/example/client.d.ts +3 -0
- package/dist/example/client.d.ts.map +1 -0
- package/dist/example/client.js +105 -0
- package/dist/example/client.js.map +1 -0
- package/dist/example/config.d.ts +15 -0
- package/dist/example/config.d.ts.map +1 -0
- package/dist/example/config.js +20 -0
- package/dist/example/config.js.map +1 -0
- package/dist/example/proxy/client-proxy.d.ts +3 -0
- package/dist/example/proxy/client-proxy.d.ts.map +1 -0
- package/dist/example/proxy/client-proxy.js +88 -0
- package/dist/example/proxy/client-proxy.js.map +1 -0
- package/dist/example/proxy/client.d.ts +3 -0
- package/dist/example/proxy/client.d.ts.map +1 -0
- package/dist/example/proxy/client.js +97 -0
- package/dist/example/proxy/client.js.map +1 -0
- package/dist/example/proxy/server-proxy.d.ts +3 -0
- package/dist/example/proxy/server-proxy.d.ts.map +1 -0
- package/dist/example/proxy/server-proxy.js +72 -0
- package/dist/example/proxy/server-proxy.js.map +1 -0
- package/dist/example/server.d.ts +2 -0
- package/dist/example/server.d.ts.map +1 -0
- package/dist/example/server.js +76 -0
- package/dist/example/server.js.map +1 -0
- package/dist/example/service.d.ts +4 -0
- package/dist/example/service.d.ts.map +1 -0
- package/dist/example/service.js +15 -0
- package/dist/example/service.js.map +1 -0
- package/dist/scripts/analyzePayment.d.ts +3 -0
- package/dist/scripts/analyzePayment.d.ts.map +1 -0
- package/dist/scripts/analyzePayment.js +25 -0
- package/dist/scripts/analyzePayment.js.map +1 -0
- package/dist/scripts/client-proxy.d.ts +3 -0
- package/dist/scripts/client-proxy.d.ts.map +1 -0
- package/dist/scripts/client-proxy.js +126 -0
- package/dist/scripts/client-proxy.js.map +1 -0
- package/dist/scripts/generateWallet.d.ts +3 -0
- package/dist/scripts/generateWallet.d.ts.map +1 -0
- package/dist/scripts/generateWallet.js +15 -0
- package/dist/scripts/generateWallet.js.map +1 -0
- package/dist/src/client.d.ts +11 -0
- package/dist/src/client.d.ts.map +1 -0
- package/dist/src/client.js +52 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/client.test.d.ts +2 -0
- package/dist/src/client.test.d.ts.map +1 -0
- package/dist/src/client.test.js +178 -0
- package/dist/src/client.test.js.map +1 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +7 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/index.test.d.ts +2 -0
- package/dist/src/index.test.d.ts.map +1 -0
- package/dist/src/index.test.js +27 -0
- package/dist/src/index.test.js.map +1 -0
- package/dist/src/mcpClientWithX402.d.ts +14 -0
- package/dist/src/mcpClientWithX402.d.ts.map +1 -0
- package/dist/src/mcpClientWithX402.js +78 -0
- package/dist/src/mcpClientWithX402.js.map +1 -0
- package/dist/src/proxy/client.d.ts +23 -0
- package/dist/src/proxy/client.d.ts.map +1 -0
- package/dist/src/proxy/client.js +39 -0
- package/dist/src/proxy/client.js.map +1 -0
- package/dist/src/proxy/client.test.d.ts +2 -0
- package/dist/src/proxy/client.test.d.ts.map +1 -0
- package/dist/src/proxy/client.test.js +167 -0
- package/dist/src/proxy/client.test.js.map +1 -0
- package/dist/src/proxy/hooks/apiKeyHook.d.ts +22 -0
- package/dist/src/proxy/hooks/apiKeyHook.d.ts.map +1 -0
- package/dist/src/proxy/hooks/apiKeyHook.js +72 -0
- package/dist/src/proxy/hooks/apiKeyHook.js.map +1 -0
- package/dist/src/proxy/hooks/apiKeyHook.test.d.ts +2 -0
- package/dist/src/proxy/hooks/apiKeyHook.test.d.ts.map +1 -0
- package/dist/src/proxy/hooks/apiKeyHook.test.js +240 -0
- package/dist/src/proxy/hooks/apiKeyHook.test.js.map +1 -0
- package/dist/src/proxy/index.d.ts +4 -0
- package/dist/src/proxy/index.d.ts.map +1 -0
- package/dist/src/proxy/index.js +4 -0
- package/dist/src/proxy/index.js.map +1 -0
- package/dist/src/proxy/server.d.ts +18 -0
- package/dist/src/proxy/server.d.ts.map +1 -0
- package/dist/src/proxy/server.js +35 -0
- package/dist/src/proxy/server.js.map +1 -0
- package/dist/src/proxy/server.test.d.ts +2 -0
- package/dist/src/proxy/server.test.d.ts.map +1 -0
- package/dist/src/proxy/server.test.js +26 -0
- package/dist/src/proxy/server.test.js.map +1 -0
- package/dist/src/server.d.ts +56 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/server.js +320 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/server.test.d.ts +2 -0
- package/dist/src/server.test.d.ts.map +1 -0
- package/dist/src/server.test.js +666 -0
- package/dist/src/server.test.js.map +1 -0
- package/dist/src/util.d.ts +24 -0
- package/dist/src/util.d.ts.map +1 -0
- package/dist/src/util.js +47 -0
- package/dist/src/util.js.map +1 -0
- package/dist/src/util.test.d.ts +2 -0
- package/dist/src/util.test.d.ts.map +1 -0
- package/dist/src/util.test.js +71 -0
- package/dist/src/util.test.js.map +1 -0
- package/dist/src/x402Transport.d.ts +45 -0
- package/dist/src/x402Transport.d.ts.map +1 -0
- package/dist/src/x402Transport.js +288 -0
- package/dist/src/x402Transport.js.map +1 -0
- package/example/client.ts +125 -0
- package/example/config.ts +25 -0
- package/example/proxy/README.md +95 -0
- package/example/proxy/client-proxy.ts +102 -0
- package/example/proxy/client.ts +112 -0
- package/example/proxy/server-proxy.ts +80 -0
- package/example/server.ts +104 -0
- package/example/service.ts +19 -0
- package/example-client-proxy.sh +3 -0
- package/mcp-servers.json +9 -0
- package/package.json +53 -0
- package/scripts/analyzePayment.ts +25 -0
- package/scripts/client-proxy.ts +142 -0
- package/scripts/generateWallet.ts +17 -0
- package/src/client.test.ts +237 -0
- package/src/client.ts +61 -0
- package/src/index.test.ts +34 -0
- package/src/index.ts +7 -0
- package/src/proxy/client.test.ts +199 -0
- package/src/proxy/client.ts +61 -0
- package/src/proxy/hooks/apiKeyHook.test.ts +276 -0
- package/src/proxy/hooks/apiKeyHook.ts +77 -0
- package/src/proxy/index.ts +3 -0
- package/src/proxy/server.test.ts +33 -0
- package/src/proxy/server.ts +43 -0
- package/src/server.test.ts +822 -0
- package/src/server.ts +451 -0
- package/src/util.test.ts +83 -0
- package/src/util.ts +48 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +26 -0
|
@@ -0,0 +1,822 @@
|
|
|
1
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
2
|
+
import type { IncomingMessage, ServerResponse } from 'http';
|
|
3
|
+
import { getAddress } from 'viem';
|
|
4
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { exact } from 'x402/schemes';
|
|
6
|
+
import { findMatchingPaymentRequirements, processPriceToAtomicAmount } from 'x402/shared';
|
|
7
|
+
import { useFacilitator } from 'x402/verify';
|
|
8
|
+
import { makePaymentAwareServerTransport, X402StreamableHTTPServerTransport } from './server.js';
|
|
9
|
+
|
|
10
|
+
// Mock dependencies
|
|
11
|
+
vi.mock('@modelcontextprotocol/sdk/server/streamableHttp.js', () => ({
|
|
12
|
+
StreamableHTTPServerTransport: vi.fn().mockImplementation(() => ({
|
|
13
|
+
sessionId: 'test-session',
|
|
14
|
+
start: vi.fn().mockResolvedValue(undefined),
|
|
15
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
16
|
+
send: vi.fn().mockResolvedValue(undefined),
|
|
17
|
+
handleRequest: vi.fn().mockResolvedValue(undefined),
|
|
18
|
+
onmessage: null,
|
|
19
|
+
onclose: null,
|
|
20
|
+
onerror: null,
|
|
21
|
+
})),
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
vi.mock('x402/schemes', () => ({
|
|
25
|
+
exact: {
|
|
26
|
+
evm: {
|
|
27
|
+
decodePayment: vi.fn(),
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
vi.mock('x402/verify', () => ({
|
|
33
|
+
useFacilitator: vi.fn(),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
vi.mock('x402/shared', () => ({
|
|
37
|
+
findMatchingPaymentRequirements: vi.fn(),
|
|
38
|
+
processPriceToAtomicAmount: vi.fn(),
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
vi.mock('viem', () => ({
|
|
42
|
+
getAddress: vi.fn((addr) => addr),
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
// Helper functions for test setup
|
|
46
|
+
function createMockStreamableTransport() {
|
|
47
|
+
return {
|
|
48
|
+
sessionId: 'test-session',
|
|
49
|
+
start: vi.fn().mockResolvedValue(undefined),
|
|
50
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
51
|
+
send: vi.fn().mockResolvedValue(undefined),
|
|
52
|
+
handleRequest: vi.fn().mockResolvedValue(undefined),
|
|
53
|
+
onmessage: null,
|
|
54
|
+
onclose: null,
|
|
55
|
+
onerror: null,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function createMockFacilitator() {
|
|
60
|
+
const mockVerify = vi.fn().mockResolvedValue({ isValid: true });
|
|
61
|
+
const mockSettle = vi.fn().mockResolvedValue({ transaction: '0x123' });
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
verify: mockVerify,
|
|
65
|
+
settle: mockSettle,
|
|
66
|
+
list: vi.fn(),
|
|
67
|
+
mockVerify,
|
|
68
|
+
mockSettle,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function createMockPaymentRequirements() {
|
|
73
|
+
return {
|
|
74
|
+
scheme: 'exact' as const,
|
|
75
|
+
network: 'base-sepolia' as const,
|
|
76
|
+
maxAmountRequired: '10000',
|
|
77
|
+
resource: 'mcp://tool/test-tool',
|
|
78
|
+
description: 'Payment for MCP tool: test-tool',
|
|
79
|
+
mimeType: 'application/json',
|
|
80
|
+
payTo: '0x123',
|
|
81
|
+
maxTimeoutSeconds: 60,
|
|
82
|
+
asset: '0xUSDC',
|
|
83
|
+
outputSchema: undefined,
|
|
84
|
+
extra: {},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function createMockRequest() {
|
|
89
|
+
return {
|
|
90
|
+
method: 'POST',
|
|
91
|
+
url: '/mcp',
|
|
92
|
+
headers: {},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function createMockResponse() {
|
|
97
|
+
const writeHeadSpy = vi.fn();
|
|
98
|
+
const endSpy = vi.fn().mockReturnThis();
|
|
99
|
+
|
|
100
|
+
// Setup chained method calls
|
|
101
|
+
writeHeadSpy.mockReturnValue({ end: endSpy });
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
mockRes: {
|
|
105
|
+
writeHead: writeHeadSpy,
|
|
106
|
+
end: endSpy,
|
|
107
|
+
},
|
|
108
|
+
writeHeadSpy,
|
|
109
|
+
endSpy,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Test the isToolCallParams type guard
|
|
114
|
+
describe('isToolCallParams', () => {
|
|
115
|
+
const isToolCallParams = (params: unknown): params is { name: string; arguments?: any } => {
|
|
116
|
+
return (
|
|
117
|
+
params !== null &&
|
|
118
|
+
typeof params === 'object' &&
|
|
119
|
+
'name' in params &&
|
|
120
|
+
typeof (params as { name: unknown }).name === 'string'
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
it('should return true for valid tool call params', () => {
|
|
125
|
+
expect(isToolCallParams({ name: 'test-tool' })).toBe(true);
|
|
126
|
+
expect(isToolCallParams({ name: 'test-tool', arguments: {} })).toBe(true);
|
|
127
|
+
expect(isToolCallParams({ name: 'test-tool', arguments: { foo: 'bar' } })).toBe(true);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('should return false for invalid params', () => {
|
|
131
|
+
expect(isToolCallParams(null)).toBe(false);
|
|
132
|
+
expect(isToolCallParams(undefined)).toBe(false);
|
|
133
|
+
expect(isToolCallParams({})).toBe(false);
|
|
134
|
+
expect(isToolCallParams({ name: 123 })).toBe(false);
|
|
135
|
+
expect(isToolCallParams({ notName: 'test' })).toBe(false);
|
|
136
|
+
expect(isToolCallParams('string')).toBe(false);
|
|
137
|
+
expect(isToolCallParams(123)).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('X402StreamableHTTPServerTransport', () => {
|
|
142
|
+
let transport: X402StreamableHTTPServerTransport;
|
|
143
|
+
let mockStreamableTransport: any;
|
|
144
|
+
let mockReq: Partial<IncomingMessage>;
|
|
145
|
+
let mockRes: Partial<ServerResponse>;
|
|
146
|
+
let mockVerify: any;
|
|
147
|
+
let mockSettle: any;
|
|
148
|
+
let writeHeadSpy: any;
|
|
149
|
+
let endSpy: any;
|
|
150
|
+
|
|
151
|
+
beforeEach(() => {
|
|
152
|
+
vi.clearAllMocks();
|
|
153
|
+
|
|
154
|
+
// Setup mocks using helper functions
|
|
155
|
+
mockStreamableTransport = createMockStreamableTransport();
|
|
156
|
+
vi.mocked(StreamableHTTPServerTransport).mockImplementation(() => mockStreamableTransport);
|
|
157
|
+
|
|
158
|
+
const facilitator = createMockFacilitator();
|
|
159
|
+
mockVerify = facilitator.mockVerify;
|
|
160
|
+
mockSettle = facilitator.mockSettle;
|
|
161
|
+
vi.mocked(useFacilitator).mockReturnValue(facilitator as any);
|
|
162
|
+
|
|
163
|
+
vi.mocked(processPriceToAtomicAmount).mockReturnValue({
|
|
164
|
+
maxAmountRequired: '10000',
|
|
165
|
+
asset: {
|
|
166
|
+
address: '0xUSDC',
|
|
167
|
+
eip712: {
|
|
168
|
+
name: 'USDC',
|
|
169
|
+
version: '1.0',
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
} as any);
|
|
173
|
+
|
|
174
|
+
vi.mocked(findMatchingPaymentRequirements).mockReturnValue(createMockPaymentRequirements());
|
|
175
|
+
|
|
176
|
+
mockReq = createMockRequest();
|
|
177
|
+
|
|
178
|
+
const response = createMockResponse();
|
|
179
|
+
mockRes = response.mockRes as any;
|
|
180
|
+
writeHeadSpy = response.writeHeadSpy;
|
|
181
|
+
endSpy = response.endSpy;
|
|
182
|
+
|
|
183
|
+
transport = new X402StreamableHTTPServerTransport({
|
|
184
|
+
payTo: '0x123',
|
|
185
|
+
toolPricing: {
|
|
186
|
+
'test-tool': '$0.01',
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('constructor', () => {
|
|
192
|
+
it('should create transport with default options', () => {
|
|
193
|
+
const _transport = new X402StreamableHTTPServerTransport({
|
|
194
|
+
payTo: '0x123',
|
|
195
|
+
toolPricing: { tool1: '$0.01' },
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
expect(StreamableHTTPServerTransport).toHaveBeenCalledWith({
|
|
199
|
+
sessionIdGenerator: undefined,
|
|
200
|
+
enableJsonResponse: true,
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should pass through custom options', () => {
|
|
205
|
+
const sessionIdGen = () => 'custom-id';
|
|
206
|
+
const _transport = new X402StreamableHTTPServerTransport({
|
|
207
|
+
payTo: '0x123',
|
|
208
|
+
toolPricing: { tool1: '$0.01' },
|
|
209
|
+
sessionIdGenerator: sessionIdGen,
|
|
210
|
+
enableJsonResponse: false,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
expect(StreamableHTTPServerTransport).toHaveBeenCalledWith({
|
|
214
|
+
sessionIdGenerator: sessionIdGen,
|
|
215
|
+
enableJsonResponse: false,
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('delegate methods', () => {
|
|
221
|
+
it('should delegate start() to underlying transport', async () => {
|
|
222
|
+
await transport.start();
|
|
223
|
+
expect(mockStreamableTransport.start).toHaveBeenCalled();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should delegate close() to underlying transport', async () => {
|
|
227
|
+
await transport.close();
|
|
228
|
+
expect(mockStreamableTransport.close).toHaveBeenCalled();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should delegate sessionId getter', () => {
|
|
232
|
+
expect(transport.sessionId).toBe('test-session');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should delegate onclose handler', () => {
|
|
236
|
+
const handler = vi.fn();
|
|
237
|
+
transport.onclose = handler;
|
|
238
|
+
expect(mockStreamableTransport.onclose).toBe(handler);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should delegate onerror handler', () => {
|
|
242
|
+
const handler = vi.fn();
|
|
243
|
+
transport.onerror = handler;
|
|
244
|
+
expect(mockStreamableTransport.onerror).toBe(handler);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should delegate onmessage handler', () => {
|
|
248
|
+
const handler = vi.fn();
|
|
249
|
+
transport.onmessage = handler;
|
|
250
|
+
expect(mockStreamableTransport.onmessage).toBe(handler);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe('handleRequest', () => {
|
|
255
|
+
it('should delegate non-POST requests', async () => {
|
|
256
|
+
mockReq.method = 'GET';
|
|
257
|
+
await transport.handleRequest(mockReq as IncomingMessage, mockRes as ServerResponse);
|
|
258
|
+
|
|
259
|
+
expect(mockStreamableTransport.handleRequest).toHaveBeenCalledWith(mockReq, mockRes, undefined);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it('should delegate requests without body', async () => {
|
|
263
|
+
await transport.handleRequest(mockReq as IncomingMessage, mockRes as ServerResponse);
|
|
264
|
+
|
|
265
|
+
expect(mockStreamableTransport.handleRequest).toHaveBeenCalledWith(mockReq, mockRes, undefined);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should delegate non-tool-call requests', async () => {
|
|
269
|
+
const body = { method: 'initialize', params: {} };
|
|
270
|
+
await transport.handleRequest(mockReq as IncomingMessage, mockRes as ServerResponse, body);
|
|
271
|
+
|
|
272
|
+
expect(mockStreamableTransport.handleRequest).toHaveBeenCalledWith(mockReq, mockRes, body);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should delegate unpaid tool calls', async () => {
|
|
276
|
+
const body = {
|
|
277
|
+
method: 'tools/call',
|
|
278
|
+
params: { name: 'unpaid-tool' },
|
|
279
|
+
};
|
|
280
|
+
await transport.handleRequest(mockReq as IncomingMessage, mockRes as ServerResponse, body);
|
|
281
|
+
|
|
282
|
+
expect(mockStreamableTransport.handleRequest).toHaveBeenCalledWith(mockReq, mockRes, body);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should return 402 for paid tool without payment header', async () => {
|
|
286
|
+
const body = {
|
|
287
|
+
method: 'tools/call',
|
|
288
|
+
params: { name: 'test-tool' },
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
await transport.handleRequest(mockReq as IncomingMessage, mockRes as ServerResponse, body);
|
|
292
|
+
|
|
293
|
+
// Check the chained writeHead(402).end() call
|
|
294
|
+
expect(writeHeadSpy).toHaveBeenCalledWith(402, undefined);
|
|
295
|
+
expect(endSpy).toHaveBeenCalled();
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should return 402 for invalid payment header', async () => {
|
|
299
|
+
vi.mocked(exact.evm.decodePayment).mockImplementationOnce(() => {
|
|
300
|
+
throw new Error('Invalid payment format');
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
mockReq.headers = { 'x-payment': 'invalid-payment' };
|
|
304
|
+
const body = {
|
|
305
|
+
method: 'tools/call',
|
|
306
|
+
params: { name: 'test-tool' },
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
await transport.handleRequest(mockReq as IncomingMessage, mockRes as ServerResponse, body);
|
|
310
|
+
|
|
311
|
+
expect(writeHeadSpy).toHaveBeenCalledWith(402, undefined);
|
|
312
|
+
expect(endSpy).toHaveBeenCalled();
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it('should return 402 when payment verification fails', async () => {
|
|
316
|
+
mockVerify.mockResolvedValueOnce({ isValid: false, invalidReason: 'Insufficient funds' });
|
|
317
|
+
|
|
318
|
+
vi.mocked(exact.evm.decodePayment).mockReturnValue({
|
|
319
|
+
scheme: 'exact',
|
|
320
|
+
network: 'base-sepolia',
|
|
321
|
+
x402Version: 1,
|
|
322
|
+
payload: {
|
|
323
|
+
signature: '0xmocksignature',
|
|
324
|
+
authorization: {
|
|
325
|
+
from: '0xabc',
|
|
326
|
+
to: '0x123',
|
|
327
|
+
value: '10000',
|
|
328
|
+
validAfter: '0',
|
|
329
|
+
validBefore: '999999999999',
|
|
330
|
+
nonce: '1',
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
mockReq.headers = { 'x-payment': 'valid-payment' };
|
|
336
|
+
const body = {
|
|
337
|
+
method: 'tools/call',
|
|
338
|
+
params: { name: 'test-tool' },
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
await transport.handleRequest(mockReq as IncomingMessage, mockRes as ServerResponse, body);
|
|
342
|
+
|
|
343
|
+
expect(writeHeadSpy).toHaveBeenCalledWith(402, undefined);
|
|
344
|
+
expect(endSpy).toHaveBeenCalled();
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('should process valid payment and delegate request', async () => {
|
|
348
|
+
vi.mocked(exact.evm.decodePayment).mockReturnValue({
|
|
349
|
+
scheme: 'exact',
|
|
350
|
+
network: 'base-sepolia',
|
|
351
|
+
x402Version: 1,
|
|
352
|
+
payload: {
|
|
353
|
+
signature: '0xmocksignature',
|
|
354
|
+
authorization: {
|
|
355
|
+
from: '0xabc',
|
|
356
|
+
to: '0x123',
|
|
357
|
+
value: '10000',
|
|
358
|
+
validAfter: '0',
|
|
359
|
+
validBefore: '999999999999',
|
|
360
|
+
nonce: '1',
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
mockReq.headers = { 'x-payment': 'valid-payment' };
|
|
366
|
+
const body = {
|
|
367
|
+
method: 'tools/call',
|
|
368
|
+
params: { name: 'test-tool' },
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
await transport.handleRequest(mockReq as IncomingMessage, mockRes as ServerResponse, body);
|
|
372
|
+
|
|
373
|
+
expect(mockVerify).toHaveBeenCalled();
|
|
374
|
+
expect(mockStreamableTransport.handleRequest).toHaveBeenCalledWith(mockReq, mockRes, body);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('should handle array of messages', async () => {
|
|
378
|
+
const body = [
|
|
379
|
+
{ method: 'initialize', params: {} },
|
|
380
|
+
{ method: 'tools/call', params: { name: 'test-tool' } },
|
|
381
|
+
];
|
|
382
|
+
|
|
383
|
+
await transport.handleRequest(mockReq as IncomingMessage, mockRes as ServerResponse, body);
|
|
384
|
+
|
|
385
|
+
expect(writeHeadSpy).toHaveBeenCalledWith(402, undefined);
|
|
386
|
+
expect(endSpy).toHaveBeenCalled();
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
describe('send', () => {
|
|
391
|
+
it('should pass through requests without modification', async () => {
|
|
392
|
+
const message = {
|
|
393
|
+
jsonrpc: '2.0' as const,
|
|
394
|
+
method: 'tools/call',
|
|
395
|
+
params: { name: 'test-tool' },
|
|
396
|
+
id: 1,
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
await transport.send(message);
|
|
400
|
+
|
|
401
|
+
expect(mockStreamableTransport.send).toHaveBeenCalledWith(message, undefined);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('should include settlement info in successful responses', async () => {
|
|
405
|
+
// Setup payment info
|
|
406
|
+
const requestId = 1;
|
|
407
|
+
const paymentInfo = {
|
|
408
|
+
payment: {
|
|
409
|
+
scheme: 'exact' as const,
|
|
410
|
+
network: 'base-sepolia',
|
|
411
|
+
x402Version: 1,
|
|
412
|
+
payload: {},
|
|
413
|
+
},
|
|
414
|
+
toolName: 'test-tool',
|
|
415
|
+
toolPrice: '$0.01',
|
|
416
|
+
request: {
|
|
417
|
+
jsonrpc: '2.0' as const,
|
|
418
|
+
method: 'tools/call',
|
|
419
|
+
params: { name: 'test-tool' },
|
|
420
|
+
id: requestId,
|
|
421
|
+
},
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
// Simulate storing payment info
|
|
425
|
+
(transport as any).requestPaymentMap.set(requestId, paymentInfo);
|
|
426
|
+
|
|
427
|
+
const response = {
|
|
428
|
+
jsonrpc: '2.0' as const,
|
|
429
|
+
result: { data: 'test' },
|
|
430
|
+
id: requestId,
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
await transport.send(response);
|
|
434
|
+
|
|
435
|
+
expect(mockSettle).toHaveBeenCalled();
|
|
436
|
+
expect(mockStreamableTransport.send).toHaveBeenCalledWith(
|
|
437
|
+
expect.objectContaining({
|
|
438
|
+
result: expect.objectContaining({
|
|
439
|
+
x402Settlement: {
|
|
440
|
+
transactionHash: '0x123',
|
|
441
|
+
settled: true,
|
|
442
|
+
},
|
|
443
|
+
}),
|
|
444
|
+
}),
|
|
445
|
+
undefined
|
|
446
|
+
);
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it('should not settle error responses', async () => {
|
|
450
|
+
const requestId = 1;
|
|
451
|
+
const paymentInfo = {
|
|
452
|
+
payment: {},
|
|
453
|
+
toolName: 'test-tool',
|
|
454
|
+
request: { id: requestId },
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
(transport as any).requestPaymentMap.set(requestId, paymentInfo);
|
|
458
|
+
|
|
459
|
+
const errorResponse = {
|
|
460
|
+
jsonrpc: '2.0' as const,
|
|
461
|
+
error: { code: -32603, message: 'Internal error' },
|
|
462
|
+
id: requestId,
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
await transport.send(errorResponse);
|
|
466
|
+
|
|
467
|
+
expect(mockSettle).not.toHaveBeenCalled();
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
describe('message interception', () => {
|
|
472
|
+
it('should setup message interception on construction', async () => {
|
|
473
|
+
// Verify that the transport's onmessage handler is wrapped
|
|
474
|
+
expect(mockStreamableTransport.onmessage).toBeDefined();
|
|
475
|
+
expect(mockStreamableTransport.onmessage).toBeInstanceOf(Function);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('should handle tool call messages with payment info', async () => {
|
|
479
|
+
// Get the wrapped onmessage handler
|
|
480
|
+
const wrappedHandler = mockStreamableTransport.onmessage;
|
|
481
|
+
|
|
482
|
+
// Setup payment info
|
|
483
|
+
(transport as any).pendingPayment = {
|
|
484
|
+
payment: { scheme: 'exact' },
|
|
485
|
+
toolName: 'test-tool',
|
|
486
|
+
toolPrice: '$0.01',
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
// Create a tool call message
|
|
490
|
+
const toolCallMessage = {
|
|
491
|
+
jsonrpc: '2.0' as const,
|
|
492
|
+
method: 'tools/call',
|
|
493
|
+
params: { name: 'test-tool' },
|
|
494
|
+
id: 123,
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// Call the wrapped handler
|
|
498
|
+
await wrappedHandler.call(mockStreamableTransport, toolCallMessage);
|
|
499
|
+
|
|
500
|
+
// Verify payment info was tracked
|
|
501
|
+
expect((transport as any).requestPaymentMap.has(123)).toBe(true);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it('should pass through non-tool-call messages', async () => {
|
|
505
|
+
const wrappedHandler = mockStreamableTransport.onmessage;
|
|
506
|
+
|
|
507
|
+
const message = {
|
|
508
|
+
jsonrpc: '2.0' as const,
|
|
509
|
+
method: 'initialize',
|
|
510
|
+
params: {},
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
// Should not throw and not track payment
|
|
514
|
+
await wrappedHandler.call(mockStreamableTransport, message);
|
|
515
|
+
|
|
516
|
+
expect((transport as any).requestPaymentMap.size).toBe(0);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it('should handle tool calls without payment requirement', async () => {
|
|
520
|
+
const wrappedHandler = mockStreamableTransport.onmessage;
|
|
521
|
+
|
|
522
|
+
const message = {
|
|
523
|
+
jsonrpc: '2.0' as const,
|
|
524
|
+
method: 'tools/call',
|
|
525
|
+
params: { name: 'free-tool' },
|
|
526
|
+
id: 456,
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
await wrappedHandler.call(mockStreamableTransport, message);
|
|
530
|
+
|
|
531
|
+
// Should not track payment for free tools
|
|
532
|
+
expect((transport as any).requestPaymentMap.has(456)).toBe(false);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it('should call original handler when set', async () => {
|
|
536
|
+
const originalHandler = vi.fn();
|
|
537
|
+
transport.onmessage = originalHandler;
|
|
538
|
+
|
|
539
|
+
const wrappedHandler = mockStreamableTransport.onmessage;
|
|
540
|
+
const message = { jsonrpc: '2.0' as const, method: 'test' };
|
|
541
|
+
|
|
542
|
+
await wrappedHandler.call(mockStreamableTransport, message, { some: 'extra' });
|
|
543
|
+
|
|
544
|
+
expect(originalHandler).toHaveBeenCalledWith(message, { some: 'extra' });
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
describe('settlement errors', () => {
|
|
549
|
+
it('should handle settlement errors gracefully', async () => {
|
|
550
|
+
mockSettle.mockRejectedValueOnce(new Error('Settlement failed'));
|
|
551
|
+
|
|
552
|
+
const requestId = 1;
|
|
553
|
+
const paymentInfo = {
|
|
554
|
+
payment: {
|
|
555
|
+
scheme: 'exact' as const,
|
|
556
|
+
network: 'base-sepolia',
|
|
557
|
+
x402Version: 1,
|
|
558
|
+
payload: {},
|
|
559
|
+
},
|
|
560
|
+
request: {
|
|
561
|
+
jsonrpc: '2.0' as const,
|
|
562
|
+
method: 'tools/call',
|
|
563
|
+
params: { name: 'test-tool' },
|
|
564
|
+
id: requestId,
|
|
565
|
+
},
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
(transport as any).requestPaymentMap.set(requestId, paymentInfo);
|
|
569
|
+
|
|
570
|
+
const response = {
|
|
571
|
+
jsonrpc: '2.0' as const,
|
|
572
|
+
result: { data: 'test' },
|
|
573
|
+
id: requestId,
|
|
574
|
+
};
|
|
575
|
+
|
|
576
|
+
await transport.send(response);
|
|
577
|
+
|
|
578
|
+
const settlementInfo = (transport as any).settlementMap.get(requestId);
|
|
579
|
+
expect(settlementInfo).toEqual({ error: 'Settlement failed' });
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it('should handle missing tool pricing during settlement', async () => {
|
|
583
|
+
const requestId = 1;
|
|
584
|
+
const paymentInfo = {
|
|
585
|
+
payment: {},
|
|
586
|
+
request: {
|
|
587
|
+
jsonrpc: '2.0' as const,
|
|
588
|
+
method: 'tools/call',
|
|
589
|
+
params: { name: 'unknown-tool' },
|
|
590
|
+
id: requestId,
|
|
591
|
+
},
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
(transport as any).requestPaymentMap.set(requestId, paymentInfo);
|
|
595
|
+
|
|
596
|
+
const response = {
|
|
597
|
+
jsonrpc: '2.0' as const,
|
|
598
|
+
result: { data: 'test' },
|
|
599
|
+
id: requestId,
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
await transport.send(response);
|
|
603
|
+
|
|
604
|
+
const settlementInfo = (transport as any).settlementMap.get(requestId);
|
|
605
|
+
expect(settlementInfo.error).toBeDefined();
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it('should handle invalid request params during settlement', async () => {
|
|
609
|
+
const requestId = 1;
|
|
610
|
+
const paymentInfo = {
|
|
611
|
+
payment: {},
|
|
612
|
+
request: {
|
|
613
|
+
jsonrpc: '2.0' as const,
|
|
614
|
+
method: 'tools/call',
|
|
615
|
+
params: { notName: 'test' }, // Invalid params
|
|
616
|
+
id: requestId,
|
|
617
|
+
},
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
(transport as any).requestPaymentMap.set(requestId, paymentInfo);
|
|
621
|
+
|
|
622
|
+
const response = {
|
|
623
|
+
jsonrpc: '2.0' as const,
|
|
624
|
+
result: { data: 'test' },
|
|
625
|
+
id: requestId,
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
await transport.send(response);
|
|
629
|
+
|
|
630
|
+
const settlementInfo = (transport as any).settlementMap.get(requestId);
|
|
631
|
+
expect(settlementInfo.error).toBeDefined();
|
|
632
|
+
});
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
describe('payment requirements edge cases', () => {
|
|
636
|
+
it('should return 402 when no matching payment requirements found', async () => {
|
|
637
|
+
vi.mocked(findMatchingPaymentRequirements).mockReturnValueOnce(undefined);
|
|
638
|
+
vi.mocked(exact.evm.decodePayment).mockReturnValue({
|
|
639
|
+
scheme: 'exact',
|
|
640
|
+
network: 'base-sepolia',
|
|
641
|
+
x402Version: 1,
|
|
642
|
+
payload: {
|
|
643
|
+
signature: '0xmocksignature',
|
|
644
|
+
authorization: {
|
|
645
|
+
from: '0xabc',
|
|
646
|
+
to: '0x123',
|
|
647
|
+
value: '10000',
|
|
648
|
+
validAfter: '0',
|
|
649
|
+
validBefore: '999999999999',
|
|
650
|
+
nonce: '1',
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
mockReq.headers = { 'x-payment': 'valid-payment' };
|
|
656
|
+
const body = {
|
|
657
|
+
method: 'tools/call',
|
|
658
|
+
params: { name: 'test-tool' },
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
await transport.handleRequest(mockReq as IncomingMessage, mockRes as ServerResponse, body);
|
|
662
|
+
|
|
663
|
+
expect(writeHeadSpy).toHaveBeenCalledWith(402, undefined);
|
|
664
|
+
expect(endSpy).toHaveBeenCalled();
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
it('should handle payment requirements processing error', async () => {
|
|
668
|
+
// Create a new transport with a different tool pricing that will cause an error
|
|
669
|
+
const errorTransport = new X402StreamableHTTPServerTransport({
|
|
670
|
+
payTo: '0x123',
|
|
671
|
+
toolPricing: {
|
|
672
|
+
'error-tool': '$0.01',
|
|
673
|
+
},
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
// Mock to return an error for this specific call
|
|
677
|
+
vi.mocked(processPriceToAtomicAmount).mockReturnValueOnce({
|
|
678
|
+
error: 'Invalid price format',
|
|
679
|
+
} as any);
|
|
680
|
+
|
|
681
|
+
const body = {
|
|
682
|
+
method: 'tools/call',
|
|
683
|
+
params: { name: 'error-tool' },
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
await expect(
|
|
687
|
+
errorTransport.handleRequest(mockReq as IncomingMessage, mockRes as ServerResponse, body)
|
|
688
|
+
).rejects.toThrow('Invalid price format');
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
it('should handle verification exceptions', async () => {
|
|
692
|
+
vi.mocked(exact.evm.decodePayment).mockReturnValue({
|
|
693
|
+
scheme: 'exact',
|
|
694
|
+
network: 'base-sepolia',
|
|
695
|
+
x402Version: 1,
|
|
696
|
+
payload: {
|
|
697
|
+
signature: '0xmocksignature',
|
|
698
|
+
authorization: {
|
|
699
|
+
from: '0xabc',
|
|
700
|
+
to: '0x123',
|
|
701
|
+
value: '10000',
|
|
702
|
+
validAfter: '0',
|
|
703
|
+
validBefore: '999999999999',
|
|
704
|
+
nonce: '1',
|
|
705
|
+
},
|
|
706
|
+
},
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
mockVerify.mockRejectedValueOnce(new Error('Network error'));
|
|
710
|
+
|
|
711
|
+
mockReq.headers = { 'x-payment': 'valid-payment' };
|
|
712
|
+
const body = {
|
|
713
|
+
method: 'tools/call',
|
|
714
|
+
params: { name: 'test-tool' },
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
await transport.handleRequest(mockReq as IncomingMessage, mockRes as ServerResponse, body);
|
|
718
|
+
|
|
719
|
+
expect(writeHeadSpy).toHaveBeenCalledWith(402, undefined);
|
|
720
|
+
expect(endSpy).toHaveBeenCalled();
|
|
721
|
+
});
|
|
722
|
+
});
|
|
723
|
+
|
|
724
|
+
describe('payment response header', () => {
|
|
725
|
+
it('should add payment response header when available', async () => {
|
|
726
|
+
const mockHeaders = { 'content-type': 'application/json' };
|
|
727
|
+
const mockStatusCode = 200;
|
|
728
|
+
|
|
729
|
+
// Setup a response with payment header
|
|
730
|
+
(transport as any).currentResponse = mockRes;
|
|
731
|
+
(transport as any).responsePaymentHeaders.set(mockRes, 'payment-response-data');
|
|
732
|
+
|
|
733
|
+
// Call handleRequest to setup writeHead override
|
|
734
|
+
await transport.handleRequest(mockReq as IncomingMessage, mockRes as ServerResponse, {});
|
|
735
|
+
|
|
736
|
+
// Simulate writeHead being called
|
|
737
|
+
writeHeadSpy.mockClear();
|
|
738
|
+
writeHeadSpy.mockReturnValue(mockRes);
|
|
739
|
+
|
|
740
|
+
// Call the overridden writeHead
|
|
741
|
+
(mockRes as any).writeHead(mockStatusCode, mockHeaders);
|
|
742
|
+
|
|
743
|
+
expect(writeHeadSpy).toHaveBeenCalledWith(mockStatusCode, {
|
|
744
|
+
...mockHeaders,
|
|
745
|
+
'X-PAYMENT-RESPONSE': 'payment-response-data',
|
|
746
|
+
});
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
it('should not add payment header for array headers', async () => {
|
|
750
|
+
const mockHeaders = [['content-type', 'application/json']];
|
|
751
|
+
|
|
752
|
+
(transport as any).currentResponse = mockRes;
|
|
753
|
+
(transport as any).responsePaymentHeaders.set(mockRes, 'payment-response-data');
|
|
754
|
+
|
|
755
|
+
await transport.handleRequest(mockReq as IncomingMessage, mockRes as ServerResponse, {});
|
|
756
|
+
|
|
757
|
+
writeHeadSpy.mockClear();
|
|
758
|
+
writeHeadSpy.mockReturnValue(mockRes);
|
|
759
|
+
|
|
760
|
+
(mockRes as any).writeHead(200, mockHeaders);
|
|
761
|
+
|
|
762
|
+
expect(writeHeadSpy).toHaveBeenCalledWith(200, mockHeaders);
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
it('should handle settlement with no matching payment requirements', async () => {
|
|
766
|
+
vi.mocked(findMatchingPaymentRequirements).mockReturnValueOnce(undefined);
|
|
767
|
+
|
|
768
|
+
const requestId = 1;
|
|
769
|
+
const paymentInfo = {
|
|
770
|
+
payment: { scheme: 'exact' },
|
|
771
|
+
request: {
|
|
772
|
+
jsonrpc: '2.0' as const,
|
|
773
|
+
method: 'tools/call',
|
|
774
|
+
params: { name: 'test-tool' },
|
|
775
|
+
id: requestId,
|
|
776
|
+
},
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
(transport as any).requestPaymentMap.set(requestId, paymentInfo);
|
|
780
|
+
|
|
781
|
+
const response = {
|
|
782
|
+
jsonrpc: '2.0' as const,
|
|
783
|
+
result: { data: 'test' },
|
|
784
|
+
id: requestId,
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
await transport.send(response);
|
|
788
|
+
|
|
789
|
+
const settlementInfo = (transport as any).settlementMap.get(requestId);
|
|
790
|
+
expect(settlementInfo.error).toBeDefined();
|
|
791
|
+
});
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
describe('makePaymentAwareServerTransport', () => {
|
|
795
|
+
it('should create transport with string address', () => {
|
|
796
|
+
const transport = makePaymentAwareServerTransport('0xabc', { tool1: '$0.01' });
|
|
797
|
+
|
|
798
|
+
expect(transport).toBeInstanceOf(X402StreamableHTTPServerTransport);
|
|
799
|
+
expect(getAddress).toHaveBeenCalledWith('0xabc');
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
it('should pass through all options', () => {
|
|
803
|
+
const facilitator = { url: 'https://facilitator.example.com' as const };
|
|
804
|
+
const sessionIdGen = () => 'custom-id';
|
|
805
|
+
|
|
806
|
+
makePaymentAwareServerTransport(
|
|
807
|
+
'0xabc',
|
|
808
|
+
{ tool1: '$0.01' },
|
|
809
|
+
{
|
|
810
|
+
facilitator,
|
|
811
|
+
sessionIdGenerator: sessionIdGen,
|
|
812
|
+
enableJsonResponse: false,
|
|
813
|
+
}
|
|
814
|
+
);
|
|
815
|
+
|
|
816
|
+
expect(StreamableHTTPServerTransport).toHaveBeenCalledWith({
|
|
817
|
+
sessionIdGenerator: sessionIdGen,
|
|
818
|
+
enableJsonResponse: false,
|
|
819
|
+
});
|
|
820
|
+
});
|
|
821
|
+
});
|
|
822
|
+
});
|