@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,167 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { createClientProxy } from './client.js';
|
|
3
|
+
import { makePaymentAwareClientTransport } from '../client.js';
|
|
4
|
+
// Mock dependencies
|
|
5
|
+
vi.mock('@civic/passthrough-mcp-server', () => ({
|
|
6
|
+
createHttpPassthroughProxy: vi.fn(),
|
|
7
|
+
createStdioPassthroughProxy: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
vi.mock('../client.js', () => ({
|
|
10
|
+
makePaymentAwareClientTransport: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
describe('createClientProxy', () => {
|
|
13
|
+
let mockWallet;
|
|
14
|
+
let mockHttpProxy;
|
|
15
|
+
let mockStdioProxy;
|
|
16
|
+
let mockTransport;
|
|
17
|
+
beforeEach(async () => {
|
|
18
|
+
vi.clearAllMocks();
|
|
19
|
+
mockWallet = {
|
|
20
|
+
account: { address: '0xabc' },
|
|
21
|
+
chain: { id: 1 },
|
|
22
|
+
};
|
|
23
|
+
mockHttpProxy = {
|
|
24
|
+
start: vi.fn().mockResolvedValue(undefined),
|
|
25
|
+
stop: vi.fn().mockResolvedValue(undefined),
|
|
26
|
+
};
|
|
27
|
+
mockStdioProxy = {
|
|
28
|
+
start: vi.fn().mockResolvedValue(undefined),
|
|
29
|
+
stop: vi.fn().mockResolvedValue(undefined),
|
|
30
|
+
};
|
|
31
|
+
mockTransport = {
|
|
32
|
+
send: vi.fn(),
|
|
33
|
+
receive: vi.fn(),
|
|
34
|
+
};
|
|
35
|
+
vi.mocked(makePaymentAwareClientTransport).mockReturnValue(mockTransport);
|
|
36
|
+
const { createHttpPassthroughProxy, createStdioPassthroughProxy } = vi.mocked(await import('@civic/passthrough-mcp-server'));
|
|
37
|
+
createHttpPassthroughProxy.mockResolvedValue(mockHttpProxy);
|
|
38
|
+
createStdioPassthroughProxy.mockResolvedValue(mockStdioProxy);
|
|
39
|
+
});
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
vi.restoreAllMocks();
|
|
42
|
+
});
|
|
43
|
+
describe('HTTP mode', () => {
|
|
44
|
+
it('should create an HTTP proxy with default port', async () => {
|
|
45
|
+
const targetUrl = 'http://example.com/mcp';
|
|
46
|
+
const proxy = await createClientProxy({
|
|
47
|
+
targetUrl,
|
|
48
|
+
wallet: mockWallet,
|
|
49
|
+
mode: 'http',
|
|
50
|
+
});
|
|
51
|
+
expect(makePaymentAwareClientTransport).toHaveBeenCalledWith(targetUrl, mockWallet);
|
|
52
|
+
const { createHttpPassthroughProxy } = vi.mocked(await import('@civic/passthrough-mcp-server'));
|
|
53
|
+
expect(createHttpPassthroughProxy).toHaveBeenCalledWith({
|
|
54
|
+
port: 4000,
|
|
55
|
+
target: {
|
|
56
|
+
transportType: 'custom',
|
|
57
|
+
transportFactory: expect.any(Function),
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
expect(proxy).toBe(mockHttpProxy);
|
|
61
|
+
});
|
|
62
|
+
it('should create an HTTP proxy with custom port', async () => {
|
|
63
|
+
const targetUrl = 'http://example.com/mcp';
|
|
64
|
+
const customPort = 5555;
|
|
65
|
+
const proxy = await createClientProxy({
|
|
66
|
+
targetUrl,
|
|
67
|
+
wallet: mockWallet,
|
|
68
|
+
mode: 'http',
|
|
69
|
+
port: customPort,
|
|
70
|
+
});
|
|
71
|
+
const { createHttpPassthroughProxy } = vi.mocked(await import('@civic/passthrough-mcp-server'));
|
|
72
|
+
expect(createHttpPassthroughProxy).toHaveBeenCalledWith({
|
|
73
|
+
port: customPort,
|
|
74
|
+
target: {
|
|
75
|
+
transportType: 'custom',
|
|
76
|
+
transportFactory: expect.any(Function),
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
expect(proxy).toBe(mockHttpProxy);
|
|
80
|
+
});
|
|
81
|
+
it('should use the transport factory correctly', async () => {
|
|
82
|
+
const targetUrl = 'http://example.com/mcp';
|
|
83
|
+
await createClientProxy({
|
|
84
|
+
targetUrl,
|
|
85
|
+
wallet: mockWallet,
|
|
86
|
+
mode: 'http',
|
|
87
|
+
});
|
|
88
|
+
const { createHttpPassthroughProxy } = vi.mocked(await import('@civic/passthrough-mcp-server'));
|
|
89
|
+
const callArgs = createHttpPassthroughProxy.mock.calls[0][0];
|
|
90
|
+
// Check that target is configured with custom transport
|
|
91
|
+
expect(callArgs.target.transportType).toBe('custom');
|
|
92
|
+
// Test that the factory returns the payment-aware transport
|
|
93
|
+
if (callArgs.target.transportType === 'custom' && 'transportFactory' in callArgs.target) {
|
|
94
|
+
const transport = callArgs.target.transportFactory();
|
|
95
|
+
expect(transport).toBe(mockTransport);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe('stdio mode', () => {
|
|
100
|
+
it('should create a stdio proxy', async () => {
|
|
101
|
+
const targetUrl = 'http://example.com/mcp';
|
|
102
|
+
const proxy = await createClientProxy({
|
|
103
|
+
targetUrl,
|
|
104
|
+
wallet: mockWallet,
|
|
105
|
+
mode: 'stdio',
|
|
106
|
+
});
|
|
107
|
+
expect(makePaymentAwareClientTransport).toHaveBeenCalledWith(targetUrl, mockWallet);
|
|
108
|
+
const { createStdioPassthroughProxy } = vi.mocked(await import('@civic/passthrough-mcp-server'));
|
|
109
|
+
expect(createStdioPassthroughProxy).toHaveBeenCalledWith({
|
|
110
|
+
target: {
|
|
111
|
+
transportType: 'custom',
|
|
112
|
+
transportFactory: expect.any(Function),
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
expect(proxy).toBe(mockStdioProxy);
|
|
116
|
+
});
|
|
117
|
+
it('should use the transport factory correctly in stdio mode', async () => {
|
|
118
|
+
const targetUrl = 'http://example.com/mcp';
|
|
119
|
+
await createClientProxy({
|
|
120
|
+
targetUrl,
|
|
121
|
+
wallet: mockWallet,
|
|
122
|
+
mode: 'stdio',
|
|
123
|
+
});
|
|
124
|
+
const { createStdioPassthroughProxy } = vi.mocked(await import('@civic/passthrough-mcp-server'));
|
|
125
|
+
const callArgs = createStdioPassthroughProxy.mock.calls[0][0];
|
|
126
|
+
// Check that target is configured with custom transport
|
|
127
|
+
expect(callArgs.target.transportType).toBe('custom');
|
|
128
|
+
// Test that the factory returns the payment-aware transport
|
|
129
|
+
if (callArgs.target.transportType === 'custom' && 'transportFactory' in callArgs.target) {
|
|
130
|
+
const transport = callArgs.target.transportFactory();
|
|
131
|
+
expect(transport).toBe(mockTransport);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
describe('transport integration', () => {
|
|
136
|
+
it('should pass wallet to payment-aware transport', async () => {
|
|
137
|
+
const targetUrl = 'https://secure.example.com/mcp';
|
|
138
|
+
const wallet = {
|
|
139
|
+
account: { address: '0x123456' },
|
|
140
|
+
chain: { id: 8453 },
|
|
141
|
+
};
|
|
142
|
+
await createClientProxy({
|
|
143
|
+
targetUrl,
|
|
144
|
+
wallet,
|
|
145
|
+
mode: 'http',
|
|
146
|
+
});
|
|
147
|
+
expect(makePaymentAwareClientTransport).toHaveBeenCalledWith(targetUrl, wallet);
|
|
148
|
+
});
|
|
149
|
+
it('should handle different URL formats', async () => {
|
|
150
|
+
const urls = [
|
|
151
|
+
'http://localhost:3000/mcp',
|
|
152
|
+
'https://api.example.com/v1/mcp',
|
|
153
|
+
'http://192.168.1.1:8080',
|
|
154
|
+
];
|
|
155
|
+
for (const url of urls) {
|
|
156
|
+
vi.clearAllMocks();
|
|
157
|
+
await createClientProxy({
|
|
158
|
+
targetUrl: url,
|
|
159
|
+
wallet: mockWallet,
|
|
160
|
+
mode: 'stdio',
|
|
161
|
+
});
|
|
162
|
+
expect(makePaymentAwareClientTransport).toHaveBeenCalledWith(url, mockWallet);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
//# sourceMappingURL=client.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.test.js","sourceRoot":"","sources":["../../../src/proxy/client.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AbstractHook } from '@civic/passthrough-mcp-server';
|
|
2
|
+
/**
|
|
3
|
+
* Hook that adds an API key to outgoing requests to authenticate with upstream MCP servers
|
|
4
|
+
*/
|
|
5
|
+
export declare class ApiKeyHook extends AbstractHook {
|
|
6
|
+
private apiKey;
|
|
7
|
+
constructor(apiKey: string);
|
|
8
|
+
get name(): string;
|
|
9
|
+
/**
|
|
10
|
+
* Add API key to tool call requests
|
|
11
|
+
*/
|
|
12
|
+
processToolCallRequest(request: any): Promise<any>;
|
|
13
|
+
/**
|
|
14
|
+
* Add API key to tools list requests
|
|
15
|
+
*/
|
|
16
|
+
processToolsListRequest(request: any): Promise<any>;
|
|
17
|
+
/**
|
|
18
|
+
* Add API key to initialize requests
|
|
19
|
+
*/
|
|
20
|
+
processInitializeRequest(request: any): Promise<any>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=apiKeyHook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiKeyHook.d.ts","sourceRoot":"","sources":["../../../../src/proxy/hooks/apiKeyHook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D;;GAEG;AACH,qBAAa,UAAW,SAAQ,YAAY;IAC9B,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,MAAM;IAIlC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;OAEG;IACG,sBAAsB,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAkBxD;;OAEG;IACG,uBAAuB,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAkBzD;;OAEG;IACG,wBAAwB,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;CAiB3D"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { AbstractHook } from '@civic/passthrough-mcp-server';
|
|
2
|
+
/**
|
|
3
|
+
* Hook that adds an API key to outgoing requests to authenticate with upstream MCP servers
|
|
4
|
+
*/
|
|
5
|
+
export class ApiKeyHook extends AbstractHook {
|
|
6
|
+
apiKey;
|
|
7
|
+
constructor(apiKey) {
|
|
8
|
+
super();
|
|
9
|
+
this.apiKey = apiKey;
|
|
10
|
+
}
|
|
11
|
+
get name() {
|
|
12
|
+
return 'api-key-injector';
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Add API key to tool call requests
|
|
16
|
+
*/
|
|
17
|
+
async processToolCallRequest(request) {
|
|
18
|
+
const modifiedRequest = {
|
|
19
|
+
...request,
|
|
20
|
+
requestContext: {
|
|
21
|
+
...request.requestContext,
|
|
22
|
+
headers: {
|
|
23
|
+
...request.requestContext?.headers,
|
|
24
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
resultType: 'continue',
|
|
30
|
+
request: modifiedRequest,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Add API key to tools list requests
|
|
35
|
+
*/
|
|
36
|
+
async processToolsListRequest(request) {
|
|
37
|
+
const modifiedRequest = {
|
|
38
|
+
...request,
|
|
39
|
+
requestContext: {
|
|
40
|
+
...request.requestContext,
|
|
41
|
+
headers: {
|
|
42
|
+
...request.requestContext?.headers,
|
|
43
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
resultType: 'continue',
|
|
49
|
+
request: modifiedRequest,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Add API key to initialize requests
|
|
54
|
+
*/
|
|
55
|
+
async processInitializeRequest(request) {
|
|
56
|
+
const modifiedRequest = {
|
|
57
|
+
...request,
|
|
58
|
+
requestContext: {
|
|
59
|
+
...request.requestContext,
|
|
60
|
+
headers: {
|
|
61
|
+
...request.requestContext?.headers,
|
|
62
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
return {
|
|
67
|
+
resultType: 'continue',
|
|
68
|
+
request: modifiedRequest,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=apiKeyHook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiKeyHook.js","sourceRoot":"","sources":["../../../../src/proxy/hooks/apiKeyHook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D;;GAEG;AACH,MAAM,OAAO,UAAW,SAAQ,YAAY;IACtB;IAApB,YAAoB,MAAc;QAChC,KAAK,EAAE,CAAC;QADU,WAAM,GAAN,MAAM,CAAQ;IAElC,CAAC;IAED,IAAI,IAAI;QACN,OAAO,kBAAkB,CAAC;IAC5B,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,sBAAsB,CAAC,OAAY;QACvC,MAAM,eAAe,GAAG;YACtB,GAAG,OAAO;YACV,cAAc,EAAE;gBACd,GAAG,OAAO,CAAC,cAAc;gBACzB,OAAO,EAAE;oBACP,GAAG,OAAO,CAAC,cAAc,EAAE,OAAO;oBAClC,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE;iBACvC;aACF;SACF,CAAC;QAEF,OAAO;YACL,UAAU,EAAE,UAAU;YACtB,OAAO,EAAE,eAAe;SACzB,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,uBAAuB,CAAC,OAAY;QACxC,MAAM,eAAe,GAAG;YACtB,GAAG,OAAO;YACV,cAAc,EAAE;gBACd,GAAG,OAAO,CAAC,cAAc;gBACzB,OAAO,EAAE;oBACP,GAAG,OAAO,CAAC,cAAc,EAAE,OAAO;oBAClC,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE;iBACvC;aACF;SACF,CAAC;QAEF,OAAO;YACL,UAAU,EAAE,UAAU;YACtB,OAAO,EAAE,eAAe;SACzB,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,wBAAwB,CAAC,OAAY;QACzC,MAAM,eAAe,GAAG;YACtB,GAAG,OAAO;YACV,cAAc,EAAE;gBACd,GAAG,OAAO,CAAC,cAAc;gBACzB,OAAO,EAAE;oBACP,GAAG,OAAO,CAAC,cAAc,EAAE,OAAO;oBAClC,aAAa,EAAE,UAAU,IAAI,CAAC,MAAM,EAAE;iBACvC;aACF;SACF,CAAC;QAEF,OAAO;YACL,UAAU,EAAE,UAAU;YACtB,OAAO,EAAE,eAAe;SACzB,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiKeyHook.test.d.ts","sourceRoot":"","sources":["../../../../src/proxy/hooks/apiKeyHook.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { ApiKeyHook } from './apiKeyHook.js';
|
|
3
|
+
describe('ApiKeyHook', () => {
|
|
4
|
+
let hook;
|
|
5
|
+
const testApiKey = 'sk-test-123456789';
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
hook = new ApiKeyHook(testApiKey);
|
|
8
|
+
});
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
vi.clearAllMocks();
|
|
11
|
+
});
|
|
12
|
+
describe('constructor and name', () => {
|
|
13
|
+
it('should initialize with provided API key', () => {
|
|
14
|
+
expect(hook).toBeDefined();
|
|
15
|
+
expect(hook).toBeInstanceOf(ApiKeyHook);
|
|
16
|
+
});
|
|
17
|
+
it('should have correct name', () => {
|
|
18
|
+
expect(hook.name).toBe('api-key-injector');
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
describe('processToolCallRequest', () => {
|
|
22
|
+
it('should add Authorization header to request', async () => {
|
|
23
|
+
const request = {
|
|
24
|
+
jsonrpc: '2.0',
|
|
25
|
+
method: 'tools/call',
|
|
26
|
+
params: { name: 'test-tool' },
|
|
27
|
+
id: 1,
|
|
28
|
+
};
|
|
29
|
+
const result = await hook.processToolCallRequest(request);
|
|
30
|
+
expect(result).toEqual({
|
|
31
|
+
resultType: 'continue',
|
|
32
|
+
request: {
|
|
33
|
+
...request,
|
|
34
|
+
requestContext: {
|
|
35
|
+
headers: {
|
|
36
|
+
Authorization: `Bearer ${testApiKey}`,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
it('should preserve existing requestContext', async () => {
|
|
43
|
+
const request = {
|
|
44
|
+
jsonrpc: '2.0',
|
|
45
|
+
method: 'tools/call',
|
|
46
|
+
params: { name: 'test-tool' },
|
|
47
|
+
id: 1,
|
|
48
|
+
requestContext: {
|
|
49
|
+
existingField: 'value',
|
|
50
|
+
headers: {
|
|
51
|
+
'X-Custom-Header': 'custom-value',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
const result = await hook.processToolCallRequest(request);
|
|
56
|
+
expect(result).toEqual({
|
|
57
|
+
resultType: 'continue',
|
|
58
|
+
request: {
|
|
59
|
+
...request,
|
|
60
|
+
requestContext: {
|
|
61
|
+
existingField: 'value',
|
|
62
|
+
headers: {
|
|
63
|
+
'X-Custom-Header': 'custom-value',
|
|
64
|
+
Authorization: `Bearer ${testApiKey}`,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
it('should override existing Authorization header', async () => {
|
|
71
|
+
const request = {
|
|
72
|
+
jsonrpc: '2.0',
|
|
73
|
+
method: 'tools/call',
|
|
74
|
+
params: {},
|
|
75
|
+
id: 3,
|
|
76
|
+
requestContext: {
|
|
77
|
+
headers: {
|
|
78
|
+
Authorization: 'Bearer old-key',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
const result = await hook.processToolCallRequest(request);
|
|
83
|
+
expect(result.request.requestContext.headers.Authorization).toBe(`Bearer ${testApiKey}`);
|
|
84
|
+
});
|
|
85
|
+
it('should handle request without requestContext', async () => {
|
|
86
|
+
const request = {
|
|
87
|
+
jsonrpc: '2.0',
|
|
88
|
+
method: 'tools/call',
|
|
89
|
+
params: {},
|
|
90
|
+
id: 4,
|
|
91
|
+
};
|
|
92
|
+
const result = await hook.processToolCallRequest(request);
|
|
93
|
+
expect(result).toEqual({
|
|
94
|
+
resultType: 'continue',
|
|
95
|
+
request: {
|
|
96
|
+
...request,
|
|
97
|
+
requestContext: {
|
|
98
|
+
headers: {
|
|
99
|
+
Authorization: `Bearer ${testApiKey}`,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('processToolsListRequest', () => {
|
|
107
|
+
it('should add Authorization header to tools list request', async () => {
|
|
108
|
+
const request = {
|
|
109
|
+
jsonrpc: '2.0',
|
|
110
|
+
method: 'tools/list',
|
|
111
|
+
params: {},
|
|
112
|
+
id: 1,
|
|
113
|
+
};
|
|
114
|
+
const result = await hook.processToolsListRequest(request);
|
|
115
|
+
expect(result).toEqual({
|
|
116
|
+
resultType: 'continue',
|
|
117
|
+
request: {
|
|
118
|
+
...request,
|
|
119
|
+
requestContext: {
|
|
120
|
+
headers: {
|
|
121
|
+
Authorization: `Bearer ${testApiKey}`,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
it('should preserve existing headers in tools list', async () => {
|
|
128
|
+
const request = {
|
|
129
|
+
jsonrpc: '2.0',
|
|
130
|
+
method: 'tools/list',
|
|
131
|
+
params: {},
|
|
132
|
+
id: 2,
|
|
133
|
+
requestContext: {
|
|
134
|
+
headers: {
|
|
135
|
+
'Content-Type': 'application/json',
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
const result = await hook.processToolsListRequest(request);
|
|
140
|
+
expect(result.request.requestContext.headers).toEqual({
|
|
141
|
+
'Content-Type': 'application/json',
|
|
142
|
+
Authorization: `Bearer ${testApiKey}`,
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
describe('processInitializeRequest', () => {
|
|
147
|
+
it('should add Authorization header to initialize request', async () => {
|
|
148
|
+
const request = {
|
|
149
|
+
jsonrpc: '2.0',
|
|
150
|
+
method: 'initialize',
|
|
151
|
+
params: {
|
|
152
|
+
clientInfo: {
|
|
153
|
+
name: 'test-client',
|
|
154
|
+
version: '1.0.0',
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
id: 1,
|
|
158
|
+
};
|
|
159
|
+
const result = await hook.processInitializeRequest(request);
|
|
160
|
+
expect(result).toEqual({
|
|
161
|
+
resultType: 'continue',
|
|
162
|
+
request: {
|
|
163
|
+
...request,
|
|
164
|
+
requestContext: {
|
|
165
|
+
headers: {
|
|
166
|
+
Authorization: `Bearer ${testApiKey}`,
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
it('should preserve existing requestContext in initialize', async () => {
|
|
173
|
+
const request = {
|
|
174
|
+
jsonrpc: '2.0',
|
|
175
|
+
method: 'initialize',
|
|
176
|
+
params: {},
|
|
177
|
+
id: 2,
|
|
178
|
+
requestContext: {
|
|
179
|
+
sessionId: 'session-123',
|
|
180
|
+
headers: {
|
|
181
|
+
'User-Agent': 'TestClient/1.0',
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
const result = await hook.processInitializeRequest(request);
|
|
186
|
+
expect(result).toEqual({
|
|
187
|
+
resultType: 'continue',
|
|
188
|
+
request: {
|
|
189
|
+
...request,
|
|
190
|
+
requestContext: {
|
|
191
|
+
sessionId: 'session-123',
|
|
192
|
+
headers: {
|
|
193
|
+
'User-Agent': 'TestClient/1.0',
|
|
194
|
+
Authorization: `Bearer ${testApiKey}`,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
describe('different API key formats', () => {
|
|
202
|
+
it('should work with various API key formats', async () => {
|
|
203
|
+
const apiKeys = [
|
|
204
|
+
'simple-key',
|
|
205
|
+
'sk-proj-very-long-api-key-with-many-characters',
|
|
206
|
+
'Bearer existing-token',
|
|
207
|
+
'ApiKey 12345',
|
|
208
|
+
];
|
|
209
|
+
for (const apiKey of apiKeys) {
|
|
210
|
+
const customHook = new ApiKeyHook(apiKey);
|
|
211
|
+
const request = {
|
|
212
|
+
jsonrpc: '2.0',
|
|
213
|
+
method: 'tools/call',
|
|
214
|
+
params: {},
|
|
215
|
+
id: 1,
|
|
216
|
+
};
|
|
217
|
+
const result = await customHook.processToolCallRequest(request);
|
|
218
|
+
expect(result.request.requestContext.headers.Authorization).toBe(`Bearer ${apiKey}`);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
describe('all methods add the same header', () => {
|
|
223
|
+
it('should add consistent headers across all methods', async () => {
|
|
224
|
+
const request = {
|
|
225
|
+
jsonrpc: '2.0',
|
|
226
|
+
method: 'test',
|
|
227
|
+
params: {},
|
|
228
|
+
id: 1,
|
|
229
|
+
};
|
|
230
|
+
const toolCallResult = await hook.processToolCallRequest(request);
|
|
231
|
+
const toolsListResult = await hook.processToolsListRequest(request);
|
|
232
|
+
const initializeResult = await hook.processInitializeRequest(request);
|
|
233
|
+
const expectedHeader = `Bearer ${testApiKey}`;
|
|
234
|
+
expect(toolCallResult.request.requestContext.headers.Authorization).toBe(expectedHeader);
|
|
235
|
+
expect(toolsListResult.request.requestContext.headers.Authorization).toBe(expectedHeader);
|
|
236
|
+
expect(initializeResult.request.requestContext.headers.Authorization).toBe(expectedHeader);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
//# sourceMappingURL=apiKeyHook.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiKeyHook.test.js","sourceRoot":"","sources":["../../../../src/proxy/hooks/apiKeyHook.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/proxy/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/proxy/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type PassthroughProxy } from '@civic/passthrough-mcp-server';
|
|
2
|
+
import type { Address } from 'viem';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a server-side proxy that:
|
|
5
|
+
* 1. Accepts x402 payments from clients
|
|
6
|
+
* 2. Adds an API key to authenticate with the upstream MCP server
|
|
7
|
+
*
|
|
8
|
+
* This allows monetizing access to API-key-protected MCP servers via micropayments.
|
|
9
|
+
*
|
|
10
|
+
* @param upstreamUrl - The URL of the API-key-protected MCP server to proxy to
|
|
11
|
+
* @param apiKey - The API key to authenticate with the upstream server
|
|
12
|
+
* @param paymentWallet - The wallet address to receive payments
|
|
13
|
+
* @param toolPricing - Mapping of tool names to prices (e.g., { "my-tool": "$0.01" })
|
|
14
|
+
* @param port - The port for the proxy to listen on (default: 5000)
|
|
15
|
+
* @returns The proxy instance
|
|
16
|
+
*/
|
|
17
|
+
export declare function createServerProxy(upstreamUrl: string, apiKey: string, paymentWallet: Address | string, toolPricing: Record<string, string>, port?: number): Promise<PassthroughProxy>;
|
|
18
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/proxy/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuC,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAC3G,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAKpC;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,OAAO,GAAG,MAAM,EAC/B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACnC,IAAI,GAAE,MAAa,GAClB,OAAO,CAAC,gBAAgB,CAAC,CAkB3B"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createPassthroughProxy } from '@civic/passthrough-mcp-server';
|
|
2
|
+
import { makePaymentAwareServerTransport } from '../server.js';
|
|
3
|
+
import { ApiKeyHook } from './hooks/apiKeyHook.js';
|
|
4
|
+
/**
|
|
5
|
+
* Creates a server-side proxy that:
|
|
6
|
+
* 1. Accepts x402 payments from clients
|
|
7
|
+
* 2. Adds an API key to authenticate with the upstream MCP server
|
|
8
|
+
*
|
|
9
|
+
* This allows monetizing access to API-key-protected MCP servers via micropayments.
|
|
10
|
+
*
|
|
11
|
+
* @param upstreamUrl - The URL of the API-key-protected MCP server to proxy to
|
|
12
|
+
* @param apiKey - The API key to authenticate with the upstream server
|
|
13
|
+
* @param paymentWallet - The wallet address to receive payments
|
|
14
|
+
* @param toolPricing - Mapping of tool names to prices (e.g., { "my-tool": "$0.01" })
|
|
15
|
+
* @param port - The port for the proxy to listen on (default: 5000)
|
|
16
|
+
* @returns The proxy instance
|
|
17
|
+
*/
|
|
18
|
+
export async function createServerProxy(upstreamUrl, apiKey, paymentWallet, toolPricing, port = 5000) {
|
|
19
|
+
// Create the payment-aware server transport
|
|
20
|
+
const paymentAwareTransport = makePaymentAwareServerTransport(paymentWallet, toolPricing);
|
|
21
|
+
// Create the API key hook
|
|
22
|
+
const apiKeyHook = new ApiKeyHook(apiKey);
|
|
23
|
+
// Create and return the proxy
|
|
24
|
+
return createPassthroughProxy({
|
|
25
|
+
sourceTransportType: 'custom',
|
|
26
|
+
sourceTransport: paymentAwareTransport,
|
|
27
|
+
target: {
|
|
28
|
+
url: upstreamUrl,
|
|
29
|
+
transportType: 'httpStream',
|
|
30
|
+
},
|
|
31
|
+
hooks: [apiKeyHook],
|
|
32
|
+
autoStart: true,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/proxy/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,sBAAsB,EAAyB,MAAM,+BAA+B,CAAC;AAE3G,OAAO,EAAE,+BAA+B,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAGnD;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,WAAmB,EACnB,MAAc,EACd,aAA+B,EAC/B,WAAmC,EACnC,OAAe,IAAI;IAEnB,4CAA4C;IAC5C,MAAM,qBAAqB,GAAG,+BAA+B,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;IAE1F,0BAA0B;IAC1B,MAAM,UAAU,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IAE1C,8BAA8B;IAC9B,OAAO,sBAAsB,CAAC;QAC5B,mBAAmB,EAAE,QAAQ;QAC7B,eAAe,EAAE,qBAAqB;QACtC,MAAM,EAAE;YACN,GAAG,EAAE,WAAW;YAChB,aAAa,EAAE,YAAY;SAC5B;QACD,KAAK,EAAE,CAAC,UAAU,CAAC;QACnB,SAAS,EAAE,IAAI;KAChB,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.test.d.ts","sourceRoot":"","sources":["../../../src/proxy/server.test.ts"],"names":[],"mappings":""}
|