Developing and Deploying an x402 MCP Server to Cloudflare Workers using VibeKanban!
Stay on top of this story
Follow the names and topics behind it.
Add this story's key topics to your watchlist so LyscoNews can highlight related developments and future matches.
Create a free account to sync your watchlist, saved stories, and alerts across devices.
Quick Summary
Introduction
Hello everyone! I recently properly studied Cloudflare Workers for the first time, so I'm writing this article to share my findings! This post will cover what I tried during implementation and how to deploy an MCP server to Cloudflare Workers! cloudflare.com
Cloudflare Workers is a serverless computing platform provided by Cloudflare. While there are some constraints like bundle size, its charm lies in the ease of deploying TypeScript/JavaScript apps with a frontend-like feel! It also integrates seamlessly with other major Cloudflare services like KV and D1. hono.dev
Hono is a lightweight, fast, and modern web framework for developing web applications and APIs, primarily in TypeScript/JavaScript. Being fast and lightweight, it is extremely compatible with Cloudflare Workers! Vibe Kanban is a tool that manages AI coding agents (like Claude Code or Codex) in a Kanban format (task visualization) to realize automated development flows! I practiced the following development workflow for this implementation: 0. Formulate product vision and concept.
-
Create requirements, design documents, and task lists with cc-sdd.
-
Register tasks in VibeKanban (register as GitHub Issues).
-
Prepare working directories with git worktree.
-
Parallel execution of tasks.
-
Self-review deliverables and create PRs.
I used CodeRabbit for code reviews!
I used a prompt like this to convert tasks generated by cc-sdd into VibeKanban tasks: Review the task list generated by @(cc-sdd) and register the work plan as Tasks in vibe_kanban.
Each task should include:
- Refer to @design.md for design details.
- Specific work content for this task.
- Dependencies on other tasks or if parallel work is possible.
- If it can be executed in parallel, add a + to the title.
Register tasks in descending order.
The best part is being able to turn them directly into GitHub Issues! x402.org
x402 is a standard protocol for stablecoin payments announced by Coinbase, a prominent cryptocurrency exchange in the US. True to its name, it adopts the HTTP status code 402 Payment Required and has gained significant attention for its compliance with the HTTP protocol. Cloudflare has not only co-founded the x402 Foundation but, given its compatibility with AI agents, it's a technology that has garnered immense interest within various blockchain tech stacks. I developed and deployed an x402 backend server and an MCP server on Cloudflare Workers. I created a sample app where stablecoin payments are processed simultaneously when weather information is retrieved through a chat interface within a GPT App! What it can do: Call the get_weather tool from a GPT App to retrieve weather information. Access /weather only for requests that have passed x402 payment verification. Deploy x402server and mcpserver separately on Cloudflare Workers. Verify the integrated operation of mcpserver -> x402FetchClient -> x402server via E2E tests. The source code for this project is available in the following GitHub repository: / vibekanban-gitworktree-sample
vibekanban-gitworktree-sample VibeKanbanとGitWorktreeを掛け合わせたサンプルアプリ 概要 x402バックエンドサーバーとMCPサーバーを使ってGPT App内のチャットインターフェースから天気予報の情報を取得すると同時にステーブルコイン支払いが行われるサンプルアプリ。 このプロジェクトでできること GPT App から get_weather ツールを呼び出して天気情報を取得 x402 による支払い検証を通過したリクエストのみ /weather にアクセス Cloudflare Workers 上で x402server と mcpserver を分離デプロイ E2E テストで mcpserver -> x402FetchClient -> x402server の結合動作を検証 構成 リポジトリ構成
パス 役割 主な技術
pkgs/x402server 天気 API と x402 決済検証を提供するバックエンド Hono / x402 / Cloudflare Workers / TypeScript
pkgs/mcpserver GPT App から呼び出される MCP サーバー。x402server を決済付きで呼び出す Hono MCP / MCP SDK / x402 fetch / Cloudflare Workers / TypeScript
pkgs/*/tests 単体・結合テスト群 Vitest
ルート (package.json) monorepo の共通スクリプト・ワークスペース管理 pnpm workspace
リクエストの流れ GPT App から MCP ツール get_weather を実行 mcpserver が x402-fetch-client で /weather を呼び出し x402server が paymentMiddleware で支払いを検証 検証後に天気データを返却 機能一覧
機能 概要 提供パッケージ 補足
ヘルスチェック API サービス稼働確認 (/, /health)
x402server, mcpserver
監視・疎通確認に利用
天気情報取得 API 都市名を受けて天気を返却 (/weather) x402server 都市未登録時は 404
x402 課金付きアクセス制御
/weather を課金保護し未決済時は 402 を返却 x402server 価格・ネットワークは環境変数で設定
MCP ツール公開
get_weather ツールを外部クライアントへ公開 mcpserver 入力検証・エラー整形を実施
x402 決済付き fetch クライアント 支払い情報付きで x402server を呼び出す mcpserver Service Binding
…
View on GitHub
Path Role Key Technologies
pkgs/x402server Backend providing Weather API and x402 payment verification. Hono / x402 / Cloudflare Workers / TypeScript
pkgs/mcpserver MCP server called by GPT App. Calls x402server with payment. Hono MCP / MCP SDK / x402 fetch / Cloudflare Workers / TypeScript
Root (package.json) Common scripts and workspace management for the monorepo. pnpm workspace
Feature Overview Provided Package Notes
Health Check API Service availability check (/, /health).
x402server, mcpserver
Used for monitoring and connectivity checks.
Weather Info API Returns weather based on city name (/weather). x402server Returns 404 if city is not registered.
x402 Paid Access Control Protects /weather and returns 402 if unpaid. x402server Price and network configured via env vars.
MCP Tool Exposure Exposes get_weather tool to external clients. mcpserver Performs input validation and error formatting.
x402 Payment-enabled Fetch Client Calls x402server with payment information. mcpserver Supports both Service Binding and URLs.
E2E Integration Test Verifies flow from MCP to backend.
mcpserver tests Confirms 402/404/Success cases.
Let's pick up and introduce some important implementation parts. First, the x402 server! You need to set environment variables in wrangler.jsonc. Since there's no highly sensitive information here, I'm not using the Secret feature for these. { "$schema": "node_modules/wrangler/config-schema.json", "name": "x402server", "main": "src/index.ts", "compatibility_date": "2026-02-23", "compatibility_flags": ["nodejs_compat"], "vars": { "SERVER_WALLET_ADDRESS": "0x51908F598A5e0d8F1A3bAbFa6DF76F9704daD072", "FACILITATOR_URL": "https://x402.org/facilitator", "X402_PRICE_USD": "$0.01", "X402_NETWORK": "eip155:84532" } }
The x402 server is built on Hono. The main code is in src/app.ts. x402 middleware is applied only to specific routes. import { paymentMiddleware } from "@x402/hono"; import { Hono } from "hono"; import { createRoutes } from "./route"; import { createResourceServer, resolvePaymentOptions } from "./utils/config"; import type { CreateAppOptions, ErrorResponse, WeatherService } from "./utils/types"; import { createMockWeatherService } from "./weather/service";
const toErrorResponse = (statusCode: number, message: string): ErrorResponse => ({ statusCode, message, });
export const createApp = ( weatherService: WeatherService = createMockWeatherService(), options: CreateAppOptions = {}, ): Hono => { const app = new Hono(); const enablePayment = options.enablePayment ?? true;
if (enablePayment) { const paymentOptions = resolvePaymentOptions(options.payment); const resourceServer = createResourceServer(paymentOptions); const routes = createRoutes(paymentOptions); const protectedRouteKeys = new Set(Object.keys(routes)); let resourceServerInitialization: Promise<void> | null = null;
app.use(async (c, next) => {
const routeKey = `${c.req.method.toUpperCase()} ${c.req.path}`;
if (!protectedRouteKeys.has(routeKey)) return next();
if (!resourceServerInitialization) {
resourceServerInitialization = resourceServer.initialize().catch((error) => {
resourceServerInitialization = null;
throw error;
});
}
await resourceServerInitialization;
return next();
});
app.use(paymentMiddleware(routes, resourceServer, undefined, undefined, false));
}
app.get("/", (c) => c.json({ status: "ok" }, 200)); app.get("/health", (c) => c.json({ status: "ok" }, 200));
app.get("/weather", async (c) => { const city = c.req.query("city")?.trim(); if (!city) return c.json(toErrorResponse(400, "city query parameter is required"), 400);
try {
const weather = await weatherService.getWeatherByCity(city);
if (!weather) return c.json(toErrorResponse(404, "city not found"), 404);
return c.json(weather, 200);
} catch {
return c.json(toErrorResponse(503, "weather service unavailable"), 503);
}
});
return app; };
Regarding the weather retrieval logic: for verification purposes, I've implemented it to return hardcoded demo data instead of hitting an external API. In a production setting, this would be where you call an external service. import { WeatherData, WeatherService } from "../utils/types";
const MOCK_WEATHER_DATA: ReadonlyArray<WeatherData> = [ { city: "Tokyo", condition: "Sunny", temperatureC: 28, humidity: 60 }, { city: "Osaka", condition: "Cloudy", temperatureC: 26, humidity: 65 }, { city: "New York", condition: "Rainy", temperatureC: 22, humidity: 72 }, ];
const normalizeCity = (city: string): string => { const trimmed = city.trim().replace(/^['"]+|['"]+$/g, ""); const withoutCountry = trimmed.split(",")[0]?.trim() ?? trimmed; return withoutCountry.toLowerCase(); };
export const createMockWeatherService = (): WeatherService => { return { async getWeatherByCity(city: string): Promise<WeatherData | null> { const normalized = normalizeCity(city); return MOCK_WEATHER_DATA.find((item) => normalizeCity(item.city) === normalized) ?? null; }, }; };
x402-specific settings are consolidated in src/utils/config.ts. Implementing an x402 server requires configuring a facilitator and a resource server. Briefly, a facilitator acts as a bridge between the API you want to apply x402 to and the blockchain, handling signature verification and transaction submission for payments. Facilitators are recommended to save development effort. The MCP server is also based on Hono. import { StreamableHTTPTransport } from "@hono/mcp"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { Hono } from "hono"; import { cors } from "hono/cors"; // ... (imports)
export const createApp = (options: CreateAppOptions = {}): Hono => { const app = new Hono(); const mcpServer = new McpServer({ name: "x402-weather-payment-mcpserver", version: "1.0.0" }); // ... (logic to handle MCP via Streamable HTTP transport) return app; };
The x402 client setup is implemented in x402-fetch-client.ts. // ... (X402FetchClient implementation using wrapFetch from @x402/fetch)
And the tool for retrieving weather information: // ... (get_weather tool registration using McpServer.registerTool)
The Point I Got Stuck On I learned this for the first time: when calling one Worker from another, you cannot simply specify the URL; you must use Bindings. You need to register this in your wrangler.jsonc. { "services": [ { "binding": "X402SERVER", "service": "x402server" } ] }
Now that the code is explained, let's look at the deployment! Install dependencies: pnpm i
Deploy: pnpm x402server run deploy
Condition: x402server must already be deployed! Register the private key for the x402 client and the x402server endpoint using Secrets. pnpm mcpserver run secret CLIENT_PRIVATE_KEY --name mcpserver pnpm mcpserver run secret X402_SERVER_URL --name mcpserver
Deploy: pnpm mcpserver run deploy
Register https://mcpserver.<unique-id>.workers.dev/mcp as the GPT App URL to enable x402 payments from chat! Register the MCP server endpoint in your GPT App. Add the app from the + button and ask for the weather. If the weather is returned and USDC payment is processed, you're set! Check the block explorer to confirm the stablecoin payment! That's it for now! While you can achieve similar results using AWS Lambda or AgentCore, Cloudflare Workers is highly recommended for quick and easy experimentation. The sample code for x402 is abundant and fits perfectly! Thank you for reading until the end! (Included original Japanese references) Official Site - Vibekanban GitHub - Vibekanban