### Clone Repository and Install Dependencies (Bash) Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md Clones the MCP boilerplate repository and installs all necessary Node.js dependencies. This is the initial step to get the project code and its required packages. ```bash git clone https://github.com/iannuttall/mcp-boilerplate.git cd mcp-boilerplate npm install ``` -------------------------------- ### Create Local Environment Variables File (Bash) Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md Copies the example environment variable file to a new file named '.dev.vars'. This file will be used to store local development settings and secrets. ```bash cp .dev.vars.example .dev.vars ``` -------------------------------- ### Run MCP Inspector Command Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md This bash command installs and runs the MCP Inspector tool to connect to and interact with your local MCP server. It's used for testing and debugging tools during development. ```bash npx @modelcontextprotocol/inspector@0.11.0 ``` -------------------------------- ### TypeScript Tool Export in Index Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md Exports the newly created one-time payment tool from the tools directory, making it available for import and registration elsewhere in the application. ```typescript // Add this line with your other exports export * from './myOnetimeTool'; ``` -------------------------------- ### Setup Cloudflare Wrangler and KV Namespace (Bash) Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md Installs the Cloudflare Wrangler CLI globally and creates a Key-Value (KV) namespace named 'OAUTH_KV' for user authentication data. This KV namespace is essential for storing user login information and must be named exactly 'OAUTH_KV'. ```bash npm install -g wrangler npx wrangler kv namespace create "OAUTH_KV" ``` -------------------------------- ### Environment Variable Setup for Production Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md Sets the STRIPE_ONE_TIME_PRICE_ID as a Cloudflare secret for production environments. This ensures secure handling of sensitive Stripe configuration details. ```bash npx wrangler secret put STRIPE_ONE_TIME_PRICE_ID ``` -------------------------------- ### Environment Variables Configuration and Setup Source: https://context7.com/iannuttall/mcp-boilerplate/llms.txt Configures essential environment variables for both local development and production deployment using a .dev.vars file and Wrangler CLI commands. It includes settings for OAuth clients (GitHub, Google), Stripe API keys and prices, and a cookie encryption key. The setup involves installing dependencies, creating KV namespaces, and deploying secrets to production. ```bash # .dev.vars file configuration GITHUB_CLIENT_ID=your-github-client-id GITHUB_CLIENT_SECRET=your-github-client-secret GOOGLE_CLIENT_ID=your-google-client-id GOOGLE_CLIENT_SECRET=your-google-client-secret HOSTED_DOMAIN= COOKIE_ENCRYPTION_KEY=generate-random-32-char-string STRIPE_PUBLISHABLE_KEY=pk_test_your-key STRIPE_SECRET_KEY=sk_test_your-key STRIPE_WEBHOOK_SECRET=whsec_your-secret STRIPE_ONE_TIME_PRICE_ID=price_your-onetime-id STRIPE_SUBSCRIPTION_PRICE_ID=price_your-subscription-id STRIPE_METERED_PRICE_ID=price_your-metered-id BASE_URL=http://localhost:8787 # Local development npm install npx wrangler kv namespace create "OAUTH_KV" # Copy the ID to wrangler.jsonc cp .dev.vars.example .dev.vars # Fill in your values npx wrangler dev # Production deployment npx wrangler deploy npx wrangler secret put GOOGLE_CLIENT_ID npx wrangler secret put GOOGLE_CLIENT_SECRET npx wrangler secret put STRIPE_SECRET_KEY npx wrangler secret put STRIPE_SUBSCRIPTION_PRICE_ID npx wrangler secret put STRIPE_METERED_PRICE_ID npx wrangler secret put COOKIE_ENCRYPTION_KEY npx wrangler secret put BASE_URL ``` -------------------------------- ### Deploy Worker to Cloudflare Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md This bash command deploys your worker application to Cloudflare. After deployment, you will receive a URL to access your deployed service. ```bash npx wrangler deploy ``` -------------------------------- ### Register Tool in Main Index (TypeScript) Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md This TypeScript code demonstrates how to register your custom tool within the main application's initialization method. This step makes the tool accessible and callable by the AI agent. ```typescript // Inside the init() method, add: tools.myTool(this); ``` -------------------------------- ### Create a Free AI Tool (TypeScript) Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md This TypeScript template demonstrates how to create a free AI tool within the MCP boilerplate. It uses Zod for input validation and defines a function to handle tool execution, returning text content. ```typescript import { z } from "zod"; import { experimental_PaidMcpAgent as PaidMcpAgent } from "@stripe/agent-toolkit/cloudflare"; export function myTool(agent: PaidMcpAgent) { const server = agent.server; // @ts-ignore server.tool( "my_tool_name", // The tool name "This tool does something cool.", // Description of what your tool does { // Input parameters input1: z.string(), // Parameter definitions using Zod input2: z.number() // E.g., strings, numbers, booleans }, async ({ input1, input2 }: { input1: string; input2: number }) => ({ // The function that runs when the tool is called content: [{ type: "text", text: `You provided: ${input1} and ${input2}` }] }) ); } ``` -------------------------------- ### Environment Variable Setup for Development Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md Defines the STRIPE_ONE_TIME_PRICE_ID in the .dev.vars file for local development. This variable is crucial for the one-time payment tool to identify the correct Stripe product. ```ini STRIPE_ONE_TIME_PRICE_ID="price_your-onetime-price-id-here" ``` -------------------------------- ### Curl Examples for HTTP Endpoints (Bash) Source: https://context7.com/iannuttall/mcp-boilerplate/llms.txt Demonstrates how to use Curl to interact with various HTTP endpoints provided by the MCP Boilerplate. Includes examples for the homepage, payment success, Stripe webhook testing, and OAuth authorization. ```bash # Homepage curl http://localhost:8787/ ``` ```bash # Payment success page curl http://localhost:8787/payment/success ``` ```bash # Test webhook endpoint (requires valid Stripe signature) curl -X POST http://localhost:8787/webhooks/stripe \ -H "Content-Type: application/json" \ -H "stripe-signature: your-test-signature" \ -d '{ "type": "checkout.session.completed", "data": { "object": { "id": "cs_test_123", "customer": "cus_test_123", "payment_status": "paid" } } }' ``` ```bash # OAuth authorization (requires client_id from MCP client) curl "http://localhost:8787/authorize?client_id=your-client-id&response_type=code&redirect_uri=your-redirect&scope=tools" ``` -------------------------------- ### Configure Claude Desktop for MCP Server Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md This JSON configuration snippet allows you to connect Claude Desktop to your local MCP server. It specifies the command to run and arguments for the mcp-remote client, enabling Claude to access your server's tools. ```json { "mcpServers": { "my_server": { "command": "npx", "args": [ "mcp-remote", "http://localhost:8787/sse" ] } } } ``` -------------------------------- ### Export Subscription Tool from Index Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md This code snippet shows how to export a custom subscription tool from the `src/tools/index.ts` file. It ensures that the newly created tool is available for import and use elsewhere in the application. ```typescript // Add this line with your other exports export * from './mySubscriptionTool'; ``` -------------------------------- ### Export Custom Tool in Index (TypeScript) Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md This TypeScript code snippet shows how to export a newly created custom tool from the tools index file. This makes the tool available for registration and use within the application. ```typescript // Add this line with your other exports export * from './myTool'; ``` -------------------------------- ### TypeScript One-Time Payment Tool with Stripe Agent Toolkit Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md Implements a one-time payment tool using the agent-toolkit. It requires Stripe Price ID and Base URL from environment variables. The tool defines input parameters, a core logic function, and a Stripe checkout configuration for a single payment. ```typescript import { z } from "zod"; import { experimental_PaidMcpAgent as PaidMcpAgent } from "@stripe/agent-toolkit/cloudflare"; import { REUSABLE_PAYMENT_REASON } from "../helpers/constants"; // Or a more specific reason export function myOnetimeTool( agent: PaidMcpAgent, // Adjust AgentProps if needed env?: { STRIPE_ONE_TIME_PRICE_ID: string; BASE_URL: string } ) { const priceId = env?.STRIPE_ONE_TIME_PRICE_ID || null; const baseUrl = env?.BASE_URL || null; if (!priceId || !baseUrl) { throw new Error("Stripe One-Time Price ID and Base URL must be provided for this tool"); } agent.paidTool( "my_onetime_tool_name", // The tool name { // Input parameters input1: z.string(), // Parameter definitions using Zod }, async ({ input1 }: { input1: string }) => ({ // The function that runs when the tool is called content: [ { type: "text", text: `You processed: ${input1}` }, ], }), { checkout: { // Defines a one-time payment checkout session success_url: `${baseUrl}/payment/success`, line_items: [ { price: priceId, // Uses the Stripe Price ID for a one-time payment product quantity: 1, }, ], mode: 'payment', // Specifies this is a one-time payment, not a subscription }, paymentReason: "Enter a clear reason for this one-time charge. E.g., 'Unlock premium feature X for a single use.'", // Customize this message } ); } ``` -------------------------------- ### TypeScript Tool Registration in Index Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md Registers the one-time payment tool within the application's initialization process. It passes the necessary environment variables, STRIPE_ONE_TIME_PRICE_ID and BASE_URL, to the tool's constructor. ```typescript // Inside the init() method, add: tools.myOnetimeTool(this, { STRIPE_ONE_TIME_PRICE_ID: this.env.STRIPE_ONE_TIME_PRICE_ID, // Ensure this matches your one-time payment Price ID BASE_URL: this.env.BASE_URL }); ``` -------------------------------- ### Implement Subscription Tool with Agent Toolkit Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md This TypeScript code defines a subscription-based tool using the agent-toolkit. It requires Stripe Price ID and Base URL for configuration. The tool accepts string and number inputs and returns text content. It's designed for recurring payment models. ```typescript import { z } from "zod"; import { experimental_PaidMcpAgent as PaidMcpAgent } from "@stripe/agent-toolkit/cloudflare"; import { REUSABLE_PAYMENT_REASON } from "../helpers/constants"; export function mySubscriptionTool( agent: PaidMcpAgent, env?: { STRIPE_SUBSCRIPTION_PRICE_ID: string; BASE_URL: string } ) { const priceId = env?.STRIPE_SUBSCRIPTION_PRICE_ID || null; const baseUrl = env?.BASE_URL || null; if (!priceId || !baseUrl) { throw new Error("Stripe Price ID and Base URL must be provided for paid tools"); } agent.paidTool( "my_subscription_tool_name", // The tool name { // Input parameters input1: z.string(), // Parameter definitions using Zod input2: z.number(), // E.g., strings, numbers, booleans }, async ({ input1, input2 }: { input1: string; input2: number }) => ({ // The function that runs when the tool is called content: [ { type: "text", text: `You provided: ${input1} and ${input2}` }, ], }), { priceId, // Uses the Stripe price ID for a subscription product successUrl: `${baseUrl}/payment/success`, paymentReason: REUSABLE_PAYMENT_REASON, // General reason shown to user } ); } ``` -------------------------------- ### Register Subscription Tool in Index Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md This TypeScript code demonstrates how to register a subscription tool within the `init()` method of `src/index.ts`. It passes the agent, environment variables for Stripe Price ID, and Base URL to the tool's registration function. ```typescript // Inside the init() method, add: tools.mySubscriptionTool(this, { STRIPE_SUBSCRIPTION_PRICE_ID: this.env.STRIPE_SUBSCRIPTION_PRICE_ID, // Ensure this matches a subscription Price ID BASE_URL: this.env.BASE_URL }); ``` -------------------------------- ### Configure Google OAuth Credentials (INI) Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md Sets up Google OAuth credentials in the '.dev.vars' file. This includes the Google Client ID and Client Secret obtained from the Google Cloud Console, required for Google-based user login. ```ini GOOGLE_CLIENT_ID="paste-your-client-id-here" GOOGLE_CLIENT_SECRET="paste-your-client-secret-here" ``` -------------------------------- ### Set Cloudflare Environment Secrets Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md These bash commands are used to set environment secrets for your Cloudflare worker. You will be prompted to enter values for each secret, which are essential for your application's configuration and security. ```bash npx wrangler secret put BASE_URL npx wrangler secret put COOKIE_ENCRYPTION_KEY npx wrangler secret put GOOGLE_CLIENT_ID npx wrangler secret put GOOGLE_CLIENT_SECRET npx wrangler secret put STRIPE_SECRET_KEY npx wrangler secret put STRIPE_SUBSCRIPTION_PRICE_ID npx wrangler secret put STRIPE_METERED_PRICE_ID ``` -------------------------------- ### Switch to GitHub Authentication Handler (TypeScript) Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md Modifies the authentication handler in 'src/index.ts' to use GitHub instead of Google. This involves changing the import statement and the default handler assignment to utilize the GitHub authentication logic. ```typescript // import { GoogleHandler } from "./auth/google-handler"; import { GitHubHandler } from "./auth/github-handler"; // defaultHandler: GoogleHandler as any, defaultHandler: GitHubHandler as any, ``` -------------------------------- ### Export Metered Tool (TypeScript) Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md This code snippet demonstrates how to export a custom tool, `myMeteredTool`, from its file within the `src/tools` directory. This allows the tool to be imported and registered in other parts of the application, such as the main `index.ts` file. ```typescript // Add this line with your other exports export * from './myMeteredTool'; ``` -------------------------------- ### Configure GitHub OAuth Credentials (INI) Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md Sets up GitHub OAuth credentials in the '.dev.vars' file. This includes the GitHub Client ID and Client Secret obtained from GitHub's Developer settings, required for GitHub-based user login. ```ini GITHUB_CLIENT_ID="paste-your-client-id-here" GITHUB_CLIENT_SECRET="paste-your-client-secret-here" ``` -------------------------------- ### Implement Metered Usage Tool (TypeScript) Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md This TypeScript code defines a metered tool using the Stripe agent toolkit. It requires Stripe Price ID and Base URL for configuration. The tool accepts numerical inputs, performs a simple addition, and integrates with Stripe's metered billing, sending usage events to Stripe. Ensure the `meterEvent` and `paymentReason` are customized. ```typescript import { z } from "zod"; import { experimental_PaidMcpAgent as PaidMcpAgent } from "@stripe/agent-toolkit/cloudflare"; import { METERED_TOOL_PAYMENT_REASON } from "../helpers/constants"; // You might want a specific constant export function myMeteredTool( agent: PaidMcpAgent, env?: { STRIPE_METERED_PRICE_ID: string; BASE_URL: string } ) { const priceId = env?.STRIPE_METERED_PRICE_ID || null; const baseUrl = env?.BASE_URL || null; if (!priceId || !baseUrl) { throw new Error("Stripe Metered Price ID and Base URL must be provided for metered tools"); } agent.paidTool( "my_metered_tool_name", // The tool name { // Input parameters a: z.number(), b: z.number(), }, async ({ a, b }: { a: number; b: number }) => { // The function that runs when the tool is called // IMPORTANT: Business logic for your tool const result = a + b; // Example logic return { content: [{ type: "text", text: String(result) }], }; }, { checkout: { success_url: `${baseUrl}/payment/success`, line_items: [ { price: priceId, // Uses the Stripe Price ID for a metered product }, ], mode: 'subscription', // Metered plans are usually set up as subscriptions }, paymentReason: "METER INFO: Details about your metered usage. E.g., Your first X uses are free, then $Y per use. " + METERED_TOOL_PAYMENT_REASON, // Customize this message meterEvent: "your_meter_event_name_from_stripe", // ** IMPORTANT: Use the event name from your Stripe meter setup ** // e.g., "metered_add_usage" } ); } ``` -------------------------------- ### Configure Stripe Webhook Secret Locally (.dev.vars) Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md This configuration snippet shows how to add the Stripe webhook signing secret to your `.dev.vars` file for local development. This allows your local Stripe integration to verify webhook events securely. ```ini STRIPE_WEBHOOK_SECRET="whsec_your-webhook-secret-here" ``` -------------------------------- ### Configure Wrangler with KV Namespace (JSON) Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md Configures the Cloudflare Wrangler project by specifying the 'OAUTH_KV' Key-Value namespace. This JSON snippet shows how to add the binding, id, and preview_id for the KV namespace within the 'wrangler.jsonc' file. ```json "kv_namespaces": [ { "binding": "OAUTH_KV", "id": "paste-your-id-here", "preview_id": "paste-your-preview-id-here" } ] ``` -------------------------------- ### Register Metered Tool in MCP Agent (TypeScript) Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md This TypeScript code shows how to register the `myMeteredTool` within the MCP agent's initialization method (`init()`). It ensures the tool is available for use by passing the agent instance and necessary environment variables (Stripe Metered Price ID and Base URL) to the tool's constructor. ```typescript // Inside the init() method, add: tools.myMeteredTool(this, { STRIPE_METERED_PRICE_ID: this.env.STRIPE_METERED_PRICE_ID, // Ensure this matches your metered Price ID BASE_URL: this.env.BASE_URL }); ``` -------------------------------- ### Set Stripe Webhook Secret for Production (Wrangler CLI) Source: https://github.com/iannuttall/mcp-boilerplate/blob/main/README.md This command demonstrates how to securely set the Stripe webhook signing secret as a secret variable using the Wrangler CLI for your production environment. This is essential for verifying webhook events in a live setting. ```bash npx wrangler secret put STRIPE_WEBHOOK_SECRET ``` -------------------------------- ### MCP Client Configuration for Local Connection Source: https://context7.com/iannuttall/mcp-boilerplate/llms.txt Provides a JSON configuration file (`config.json`) for connecting MCP clients, such as Claude Desktop, to a local MCP server. It specifies the server connection details, including the command to run and its arguments, pointing to the local SSE endpoint. ```json // Claude Desktop config.json { "mcpServers": { "my_server": { "command": "npx", "args": [ "mcp-remote", "http://localhost:8787/sse" ] } } } ``` -------------------------------- ### Basic Addition Tool using Stripe Agent Toolkit Source: https://context7.com/iannuttall/mcp-boilerplate/llms.txt A simple free MCP tool demonstrating basic tool structure. It requires authentication but no payment, and uses Zod for schema validation. The tool takes two numbers and returns their sum. ```typescript // src/tools/add.ts import { z } from "zod"; import { experimental_PaidMcpAgent as PaidMcpAgent } from "@stripe/agent-toolkit/cloudflare"; export function addTool(agent: PaidMcpAgent) { const server = agent.server; server.tool( "add", "This tool adds two numbers together.", { a: z.number(), b: z.number() }, async ({ a, b }: { a: number; b: number }) => ({ content: [{ type: "text", text: String(a + b) }], }) ); } ``` -------------------------------- ### Testing MCP Connection with Inspector Source: https://context7.com/iannuttall/mcp-boilerplate/llms.txt Demonstrates how to test the MCP server connection using the MCP Inspector tool. It involves running the inspector command and providing the server URL when prompted. ```bash # Test with MCP Inspector npx @modelcontextprotocol/inspector@0.11.0 # Enter server URL when prompted: # http://localhost:8787/sse ``` -------------------------------- ### Main Application Entry Point (TypeScript) Source: https://context7.com/iannuttall/mcp-boilerplate/llms.txt This TypeScript code defines the main entry point for the Cloudflare Worker. It handles incoming HTTP requests, routes them to different handlers for the homepage, payment success page, Stripe webhooks, and delegates all other requests to the OAuth provider for authentication and MCP tool execution. ```typescript export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise { const url = new URL(request.url); const path = url.pathname; // Handle homepage if (path === "/" || path === "") { const homePage = await import('./pages/index.html'); return new Response(homePage.default, { headers: { "Content-Type": "text/html" }, }); } // Handle payment success page if (path === "/payment/success") { const successPage = await import('./pages/payment-success.html'); return new Response(successPage.default, { headers: { "Content-Type": "text/html" }, }); } // Handle Stripe webhooks if (path === "/webhooks/stripe") { return stripeWebhookHandler.fetch(request, env); } // All other routes (MCP + OAuth) go to OAuth provider return oauthProvider.fetch(request, env, ctx); }, }; ``` -------------------------------- ### Calculator Tool with Error Handling using Stripe Agent Toolkit Source: https://context7.com/iannuttall/mcp-boilerplate/llms.txt A more complex free MCP tool that performs calculations. It demonstrates input validation using Zod, operation selection, and error handling for division by zero. This tool also requires authentication but no payment. ```typescript // src/tools/calculate.ts import { z } from "zod"; import { experimental_PaidMcpAgent as PaidMcpAgent } from "@stripe/agent-toolkit/cloudflare"; export function calculateTool(agent: PaidMcpAgent) { const server = agent.server; server.tool( "calculate", "This tool performs a calculation on two numbers.", { operation: z.enum(["add", "subtract", "multiply", "divide"]), a: z.number(), b: z.number(), }, async ({ operation, a, b }: { operation: string; a: number; b: number }) => { let result: number; switch (operation) { case "add": result = a + b; break; case "subtract": result = a - b; break; case "multiply": result = a * b; break; case "divide": if (b === 0) { return { content: [{ type: "text", text: "Error: Cannot divide by zero" }], }; } result = a / b; break; default: throw new Error(`Unknown operation: ${operation}`); } return { content: [{ type: "text", text: String(result) }] }; } ); } ``` -------------------------------- ### Subscription-Based Paid Tool with Stripe Source: https://context7.com/iannuttall/mcp-boilerplate/llms.txt Implements a subscription-based paid tool that requires an active Stripe subscription. It prompts users for payment if they are not subscribed. Dependencies include the Stripe agent toolkit and helper constants. It takes two numbers as input and returns their sum. Requires Stripe price ID and base URL environment variables. ```typescript // src/tools/subscriptionAdd.ts import { z } from "zod"; import { experimental_PaidMcpAgent as PaidMcpAgent } from "@stripe/agent-toolkit/cloudflare"; import { REUSABLE_PAYMENT_REASON } from "../helpers/constants"; export function subscriptionTool( agent: PaidMcpAgent, env?: { STRIPE_SUBSCRIPTION_PRICE_ID: string; BASE_URL: string } ) { const priceId = env?.STRIPE_SUBSCRIPTION_PRICE_ID || null; const baseUrl = env?.BASE_URL || null; if (!priceId || !baseUrl) { throw new Error("No env provided"); } agent.paidTool( "subscription_add", "Adds two numbers together for paid subscribers.", { a: z.number(), b: z.number() }, async ({ a, b }: { a: number; b: number }) => ({ content: [{ type: "text", text: String(a + b) }], }), { checkout: { success_url: `${baseUrl}/payment/success`, line_items: [ { price: priceId, quantity: 1, }, ], mode: 'subscription', }, paymentReason: REUSABLE_PAYMENT_REASON, } ); } ``` -------------------------------- ### Metered Usage Paid Tool with Stripe Source: https://context7.com/iannuttall/mcp-boilerplate/llms.txt Implements a metered usage paid tool that charges based on usage, with support for free tier quotas. It bills per use beyond the quota. Dependencies include the Stripe agent toolkit and helper constants. It takes two numbers as input and returns their sum. Requires Stripe price ID and base URL environment variables. ```typescript // src/tools/meteredAdd.ts import { z } from "zod"; import { experimental_PaidMcpAgent as PaidMcpAgent } from "@stripe/agent-toolkit/cloudflare"; import { METERED_TOOL_PAYMENT_REASON } from "../helpers/constants"; export function meteredAddTool( agent: PaidMcpAgent, env?: { STRIPE_METERED_PRICE_ID: string; BASE_URL: string } ) { const priceId = env?.STRIPE_METERED_PRICE_ID || null; const baseUrl = env?.BASE_URL || null; if (!priceId || !baseUrl) { throw new Error("No env provided"); } agent.paidTool( "metered_add", "Adds two numbers together for metered usage.", { a: z.number(), b: z.number() }, async ({ a, b }: { a: number; b: number }) => ({ content: [{ type: "text", text: String(a + b) }], }), { checkout: { success_url: `${baseUrl}/payment/success`, line_items: [ { price: priceId, }, ], mode: 'subscription', }, meterEvent: "metered_add_usage", paymentReason: "METER INFO: Your first 3 additions are free, then we charge 10 cents per addition. " + METERED_TOOL_PAYMENT_REASON, } ); } ``` -------------------------------- ### Google OAuth Authentication Flow with Hono Source: https://context7.com/iannuttall/mcp-boilerplate/llms.txt Handles Google OAuth authorization and callback to authenticate users. It uses Hono for routing and Cloudflare Workers bindings for OAuth operations. Dependencies include 'hono' and custom OAuth helper types. ```typescript // src/auth/google-handler.ts import { Hono } from 'hono' import { fetchUpstreamAuthToken, getUpstreamAuthorizeUrl, Props } from './oauth' const app = new Hono<{ Bindings: Env & { OAUTH_PROVIDER: OAuthHelpers } }>() // Authorization endpoint app.get('/authorize', async (c) => { const oauthReqInfo = await c.env.OAUTH_PROVIDER.parseAuthRequest(c.req.raw) const { clientId } = oauthReqInfo if (!clientId) { return c.text('Invalid request', 400) } return renderApprovalDialog(c.req.raw, { client: await c.env.OAUTH_PROVIDER.lookupClient(clientId), server: { provider: "google", name: 'MCP Boilerplate', logo: "https://avatars.githubusercontent.com/u/314135?s=200&v=4", description: 'This is a boilerplate MCP for building remote servers with Stripe integration.', }, state: { oauthReqInfo }, }) }) // OAuth callback endpoint app.get('/callback/google', async (c) => { const oauthReqInfo = JSON.parse(atob(c.req.query('state') as string)) const code = c.req.query('code') if (!code) { return c.text('Missing code', 400) } // Exchange code for access token const [accessToken, googleErrResponse] = await fetchUpstreamAuthToken({ upstream_url: 'https://accounts.google.com/o/oauth2/token', client_id: c.env.GOOGLE_CLIENT_ID, client_secret: c.env.GOOGLE_CLIENT_SECRET, code, redirect_uri: new URL('/callback/google', c.req.url).href, grant_type: 'authorization_code', }) if (googleErrResponse) { return googleErrResponse } // Fetch user info from Google const userResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', { headers: { Authorization: `Bearer ${accessToken}` }, }) const { id, name, email } = await userResponse.json() // Complete authorization and return token to MCP client const { redirectTo } = await c.env.OAUTH_PROVIDER.completeAuthorization({ request: oauthReqInfo, userId: id, metadata: { label: name }, scope: oauthReqInfo.scope, props: { name, email, accessToken, userEmail: email } as Props, }) return Response.redirect(redirectTo) }) export { app as GoogleHandler } ``` -------------------------------- ### Check Payment History Tool in TypeScript Source: https://context7.com/iannuttall/mcp-boilerplate/llms.txt This TypeScript function, `checkPaymentHistoryTool`, uses the Stripe API to retrieve a user's active subscriptions and one-time payments. It requires Stripe API keys and a base URL for generating billing portal links. The function returns formatted data including subscription details, payment history, and a link to the Stripe billing portal, or an error message if issues arise. ```typescript // src/tools/checkPaymentHistory.ts import { z } from "zod"; import { experimental_PaidMcpAgent as PaidMcpAgent } from "@stripe/agent-toolkit/cloudflare"; import Stripe from "stripe"; export function checkPaymentHistoryTool( agent: PaidMcpAgent, env: { BASE_URL: string; STRIPE_SECRET_KEY: string } ) { const baseUrl = env.BASE_URL; const stripe = new Stripe(env.STRIPE_SECRET_KEY, { httpClient: Stripe.createFetchHttpClient(), apiVersion: "2025-02-24.acacia", }); agent.server.tool( "check_payment_history", "This tool checks for active subscriptions and one-time purchases for the logged in user's Stripe customer ID.", {}, async () => { let responseData: { userEmail?: string | null; stripeCustomerId?: string | null; subscriptions?: Array; oneTimePayments?: Array; billingPortal?: { url: string | null; message: string; }; statusMessage?: string; error?: string; isError?: boolean; agentInstructions?: string; } = {}; try { let userEmail = agent.props?.userEmail; let customerId: string | null = null; // Try to get customerId from agent state if (agent.state?.stripe?.customerId) { customerId = agent.state.stripe.customerId; } // If not in state, search by email if (!customerId && userEmail) { const customers = await stripe.customers.list({ email: userEmail, limit: 1, }); if (customers.data.length > 0) { customerId = customers.data[0].id; } } responseData.userEmail = userEmail || null; if (!customerId) { responseData.statusMessage = "Could not identify your Stripe customer ID."; responseData.isError = true; return { content: [{ type: "text", text: JSON.stringify(responseData, null, 2) }], }; } responseData.stripeCustomerId = customerId; // Fetch active subscriptions const subscriptionsData = await stripe.subscriptions.list({ customer: customerId, status: 'active', limit: 10, }); responseData.subscriptions = []; if (subscriptionsData.data.length > 0) { for (const sub of subscriptionsData.data) { const subscriptionOutput = { id: sub.id, status: sub.status, items: [], current_period_end: sub.current_period_end, cancel_at_period_end: sub.cancel_at_period_end, }; for (const item of sub.items.data) { let productName = 'Unknown Product'; if (typeof item.price.product === 'string') { const product = await stripe.products.retrieve(item.price.product); if (product && product.name) { productName = product.name; } } subscriptionOutput.items.push({ productName, productId: item.price.product }); } responseData.subscriptions.push(subscriptionOutput); } } // Fetch one-time payments const charges = await stripe.charges.list({ customer: customerId, limit: 20, }); responseData.oneTimePayments = []; for (const charge of charges.data) { if (charge.paid && !charge.refunded && !charge.invoice) { responseData.oneTimePayments.push({ id: charge.id, amount: charge.amount, currency: charge.currency, status: charge.status, description: charge.description || 'N/A', created: charge.created, receipt_url: charge.receipt_url, }); } } // Generate billing portal link const portalSession = await stripe.billingPortal.sessions.create({ customer: customerId, return_url: `${baseUrl}/`, }); responseData.billingPortal = { url: portalSession.url, message: "Manage your billing and subscriptions here." }; responseData.agentInstructions = "AI_AGENT_ACTION: Present the user's subscription details in Markdown format. Display subscription status, product names, renewal dates. If billing portal is available, ask if they want to open it."; return { content: [{ type: "text", text: JSON.stringify(responseData, null, 2) }], }; } catch (error: any) { responseData = { error: `An error occurred while checking payment status: ${error.message}`, isError: true, }; return { ``` -------------------------------- ### Durable Object for MCP Tool Management (TypeScript) Source: https://context7.com/iannuttall/mcp-boilerplate/llms.txt This TypeScript code defines a Durable Object class 'BoilerplateMCP' that extends 'PaidMcpAgent'. It initializes and registers various MCP tools with different payment models (free, one-time, subscription, metered) using environment variables for Stripe configuration and the base URL. ```typescript export class BoilerplateMCP extends PaidMcpAgent { server = new McpServer({ name: "Boilerplate MCP", version: "1.0.0", }); async init() { // Register free tools (require login only) tools.addTool(this); tools.calculateTool(this); tools.checkPaymentHistoryTool(this, { BASE_URL: this.env.BASE_URL, STRIPE_SECRET_KEY: this.env.STRIPE_SECRET_KEY }); // Register paid tools with different payment models tools.onetimeAddTool(this, { STRIPE_ONE_TIME_PRICE_ID: this.env.STRIPE_ONE_TIME_PRICE_ID, BASE_URL: this.env.BASE_URL }); tools.subscriptionTool(this, { STRIPE_SUBSCRIPTION_PRICE_ID: this.env.STRIPE_SUBSCRIPTION_PRICE_ID, BASE_URL: this.env.BASE_URL }); tools.meteredAddTool(this, { STRIPE_METERED_PRICE_ID: this.env.STRIPE_METERED_PRICE_ID, BASE_URL: this.env.BASE_URL }); } } ``` -------------------------------- ### POST /webhooks/stripe Source: https://context7.com/iannuttall/mcp-boilerplate/llms.txt This endpoint receives POST requests containing Stripe webhook events. It verifies the signature, constructs the event object, and handles various event types such as checkout sessions, subscriptions, and invoices. It logs received events and specific details based on the event type. ```APIDOC ## POST /webhooks/stripe ### Description Processes Stripe webhook events for payment lifecycle management, subscription updates, and invoice status changes. It verifies the event signature and handles different event types to manage payments, subscriptions, and invoices. ### Method POST ### Endpoint /webhooks/stripe ### Parameters #### Header Parameters - **stripe-signature** (string) - Required - The signature of the webhook event, used for verification. #### Request Body The request body contains the Stripe event payload. The structure depends on the specific event type. ### Request Example ```json { "id": "evt_...", "object": "event", "api_version": "2020-08-27", "created": 1678886400, "data": { "object": { ... } }, "livemode": false, "pending_webhooks": 1, "request": { "id": "req_...", "idempotency_key": "..." }, "type": "checkout.session.completed" } ``` ### Response #### Success Response (200) - **message** (string) - Indicates that the webhook was received successfully. #### Response Example ```json { "message": "Webhook received" } ``` #### Error Responses - **400 Bad Request**: If the Stripe signature is missing or verification fails, or if there's a general webhook processing error. - **404 Not Found**: If the request method is not POST or the pathname is incorrect. - **500 Internal Server Error**: If required Stripe environment variables are missing. ``` -------------------------------- ### Stripe Webhook Handler in TypeScript Source: https://context7.com/iannuttall/mcp-boilerplate/llms.txt Handles incoming Stripe webhook events for payment processing, subscription management, and invoice updates. It requires Stripe API keys and webhook secrets to be configured as environment variables. The handler verifies the webhook signature and processes various event types like 'checkout.session.completed', 'customer.subscription.*', and 'invoice.*'. ```typescript import { Stripe } from "stripe"; export default { fetch: async (request: Request, env: Env) => { if (request.method !== "POST" || new URL(request.url).pathname !== "/webhooks/stripe") { return new Response("Not found", { status: 404 }); } if (!env.STRIPE_WEBHOOK_SECRET || !env.STRIPE_SECRET_KEY) { console.error("Missing required Stripe environment variables"); return new Response("Server configuration error", { status: 500 }); } try { const body = await request.text(); const signature = request.headers.get("stripe-signature"); if (!signature) { return new Response("No Stripe signature found", { status: 400 }); } const stripe = new Stripe(env.STRIPE_SECRET_KEY); // Verify and construct the event const event = await stripe.webhooks.constructEventAsync( body, signature, env.STRIPE_WEBHOOK_SECRET ); console.log(`Received Stripe webhook event: ${event.type}`); // Handle events based on their type switch (event.type) { case "checkout.session.completed": { const session = event.data.object as Stripe.Checkout.Session; console.log(`Payment completed for session: ${session.id}`); console.log(`Customer: ${session.customer}`); console.log(`Payment status: ${session.payment_status}`); // Add custom business logic here break; } case "customer.subscription.created": case "customer.subscription.updated": case "customer.subscription.deleted": case "customer.subscription.paused": case "customer.subscription.resumed": { const subscription = event.data.object as Stripe.Subscription; console.log(`Subscription event: ${event.type}`); console.log(`Subscription ID: ${subscription.id}`); console.log(`Customer: ${subscription.customer}`); console.log(`Status: ${subscription.status}`); // Add custom business logic here break; } case "invoice.payment_succeeded": case "invoice.payment_failed": { const invoice = event.data.object as Stripe.Invoice; console.log(`Invoice event: ${event.type}`); console.log(`Invoice ID: ${invoice.id}`); console.log(`Amount paid: ${invoice.amount_paid}`); // Add custom business logic here break; } default: console.log(`Unhandled event type: ${event.type}`); } return new Response("Webhook received", { status: 200 }); } catch (error: any) { console.error("Webhook error:", error); if (error.type === 'StripeSignatureVerificationError') { return new Response( "Webhook signature verification failed. Check that your STRIPE_WEBHOOK_SECRET matches.", { status: 400 } ); } return new Response(`Webhook error: ${error.message}`, { status: 400 }); } }, }; ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.