Skip to content

Commit e9cfd61

Browse files
committed
Implement MCP server x402 paywall
1 parent 42f1acb commit e9cfd61

4 files changed

Lines changed: 203 additions & 31 deletions

File tree

package-lock.json

Lines changed: 89 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
},
4949
"homepage": "https://github.com/GWALN/cli",
5050
"dependencies": {
51+
"@faremeter/info": "^0.12.0",
52+
"@faremeter/middleware": "^0.12.0",
5153
"@modelcontextprotocol/sdk": "^1.22.0",
5254
"ajv": "^8.17.1",
5355
"ajv-formats": "^2.1.1",

src/lib/x402.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* @file src/lib/x402.ts
3+
* @description X402 payment middleware for MCP server using Corbits and Faremeter.
4+
*/
5+
6+
import { evm } from '@faremeter/info';
7+
import { express as faremeter } from '@faremeter/middleware';
8+
import { type NextFunction, type Request, type Response } from 'express';
9+
import { readConfig } from '../shared/config';
10+
11+
export interface X402Config {
12+
host: string;
13+
port: number;
14+
amount?: number;
15+
asset?: 'USDC';
16+
network?: 'base';
17+
facilitatorURL?: string;
18+
description?: string;
19+
}
20+
21+
export const BYPASS_PAYMENT_METHODS = [
22+
'initialize',
23+
'initialized',
24+
'notifications/initialized',
25+
'tools/list',
26+
'prompts/list',
27+
'resources/list',
28+
] as const;
29+
30+
export const PAYWALLED_TOOLS = ['query', 'publish'] as const;
31+
32+
/**
33+
* Create MCP x402 payment middleware that only requires payment for specific tools.
34+
* @returns Express middleware function
35+
*/
36+
export const initializePaymentMiddleware = async (
37+
config: X402Config,
38+
): Promise<(req: Request, res: Response, next: NextFunction) => void> => {
39+
const gwalnConfig = readConfig();
40+
const walletAddress = gwalnConfig.dkgPublicKey;
41+
42+
if (!walletAddress) {
43+
throw new Error(
44+
'No wallet address configured. Set dkgPublicKey in .gwalnrc.json or run "gwaln init".',
45+
);
46+
}
47+
48+
const baseUrl = `http://${config.host}:${config.port}/mcp`;
49+
50+
const paywalledMiddleware = await faremeter.createMiddleware({
51+
facilitatorURL: config.facilitatorURL ?? 'https://facilitator.corbits.dev',
52+
accepts: [
53+
{
54+
...evm.x402Exact({
55+
network: config.network ?? 'base',
56+
asset: config.asset ?? 'USDC',
57+
amount: config.amount ?? 10000, // $0.01 per request
58+
payTo: walletAddress,
59+
}),
60+
resource: baseUrl,
61+
description: config.description ?? 'GWALN MCP tools',
62+
},
63+
],
64+
});
65+
66+
return (req: Request, res: Response, next: NextFunction): void => {
67+
if (req.body?.method && BYPASS_PAYMENT_METHODS.includes(req.body.method)) {
68+
next();
69+
return;
70+
}
71+
72+
if (req.body?.method === 'tools/call') {
73+
const toolName = req.body?.params?.name;
74+
if (toolName && PAYWALLED_TOOLS.includes(toolName)) {
75+
paywalledMiddleware!(req, res, next);
76+
return;
77+
}
78+
}
79+
80+
next();
81+
};
82+
};

0 commit comments

Comments
 (0)