Skip to content

Stripe Checkout

Stripe Checkout payment middleware. Tools are hidden until payment is completed via Stripe.

Install

sh
pnpm add @lynq/stripe stripe
sh
npm install @lynq/stripe stripe
sh
yarn add @lynq/stripe stripe
sh
bun add @lynq/stripe stripe

Import

ts
import { stripe, handleCallback } from "@lynq/stripe";

Usage

ts
server.tool("premium_search", stripe({
  secretKey: STRIPE_SECRET_KEY,
  baseUrl: "http://localhost:3000",
  amount: 100, // $1.00
}), config, handler);

Options

OptionTypeDefaultDescription
namestring"stripe"Middleware name
secretKeystring(required)Stripe secret key
baseUrlstring(required)Base URL of your server
callbackPathstring"/payment/stripe/callback"Callback path
amountnumber(required)Price in cents (e.g. 100 = $1.00)
currencystring"usd"ISO 4217 currency code
descriptionstring"Tool access"Product name on Stripe Checkout
sessionKeystring"payment"Session key for payment data
oncebooleanfalseIf true, charge only once per session
messagestring"Payment required ($X.XX)."Elicitation message
timeoutnumber300000Timeout in ms
persistentbooleanfalseUse userStore for state that survives reconnection

Example

ts
import { createMCPServer } from "@lynq/lynq";
import { stripe, handleCallback } from "@lynq/stripe";
import { Hono } from "hono";
import { z } from "zod";

const STRIPE_KEY = process.env.STRIPE_SECRET_KEY!;

const mcp = createMCPServer({ name: "paid-api", version: "1.0.0" });

mcp.tool(
  "premium_search",
  stripe({
    secretKey: STRIPE_KEY,
    baseUrl: "http://localhost:3000",
    amount: 100,
    description: "Premium search access",
    once: true,
  }),
  { description: "Premium search", input: z.object({ query: z.string() }) },
  async (args, c) => c.text(`Results for: ${args.query}`),
);

const app = new Hono();
const handler = mcp.http();
app.all("/mcp", (c) => handler(c.req.raw));

app.get("/payment/stripe/callback", async (c) => {
  if (c.req.query("cancelled")) {
    return c.html("<p>Payment cancelled.</p>");
  }
  const result = await handleCallback(
    mcp,
    {
      checkoutSessionId: c.req.query("session_id")!,
      state: c.req.query("state")!,
    },
    { secretKey: STRIPE_KEY },
  );
  if (!result.success) return c.text(`Error: ${result.error}`, 400);
  return c.html("<p>Payment complete! You can close this tab.</p>");
});

export default { port: 3000, fetch: app.fetch };

Under the hood

stripe() wraps payment() which wraps urlAction(). When a protected tool is called, it creates a Stripe Checkout Session via the Stripe API (lazy-imported) and opens the checkout URL via URL elicitation. The state parameter in the success URL encodes sessionId:elicitationId. When the user completes payment, Stripe redirects to your callback URL. handleCallback() retrieves the Checkout Session, verifies payment_status === "paid", stores payment data in the session, and calls server.completeElicitation() to unblock the waiting middleware.