Embed Commerce Tools in Your Own MCP Server
The embedded shape is the one the module was designed around. Your MCP server is the product your customers already talk to, and commerce should appear inside it as a few more tools, not as a second server to host and secure. Everything below uses the public package entry points, the same ones the bundled reference server is built from, so the behavior you get embedded is identical to the behavior documented across the rest of this documentation.
Add the module to your server project. Node.js 20 or newer is required.
npm install mcp-commerce
The default install stays lean: the AWS SDK packages are optional peer dependencies, and the DynamoDB stores and S3 catalog source live behind a separate mcp-commerce/aws entry point that never loads unless you import it. If your server runs on Lambda or you want DynamoDB storage, also install @aws-sdk/client-dynamodb, @aws-sdk/lib-dynamodb, and @aws-sdk/client-s3 as needed.
The core needs three things: a catalog source (where products come from), a cart store (where carts live), and a small config object.
import {
createCommerce,
BundledCatalogSource,
SqliteCartStore,
} from "mcp-commerce";
const commerce = createCommerce({
catalogSource: new BundledCatalogSource({ path: "./products.json" }),
cartStore: new SqliteCartStore({ path: "./carts.sqlite" }),
config: { currency: "usd", cartTtlSeconds: 3600 },
});
The returned commerce object is a plain interface: listProducts, getProduct, createCart, addItem, updateItem, removeItem, clearCart, and getCart, all returning promises. You can call these directly from any code, not just MCP tools, which is the library-only usage pattern. Swap BundledCatalogSource for RemoteUrlCatalogSource or the S3 source, and the SQLite store for the DynamoDB one, without touching anything else, the options are compared in storage and catalog drivers.
One call adds the shopper-facing tools to the MCP server you already construct today.
import { registerCommerceTools } from "mcp-commerce";
// yourMcpServer is the McpServer instance you already have
registerCommerceTools(yourMcpServer, commerce);
Your server now exposes search_products, get_product, add_to_cart, view_cart, update_cart_item, and remove_from_cart alongside your existing tools. The tool descriptions carry the usage contract for the agent, including the stateless-transport rule that add_to_cart returns a cart_id the agent must pass back on later calls. Each tool is documented with inputs and outputs in the MCP tools reference.
Commerce without payment is a demo. The checkout service turns carts into payable sessions, and passing it as the third argument enables the
checkout tool.
import {
createCheckoutService,
SqliteCheckoutSessionStore,
StripePaymentProvider,
NoTaxCalculator,
WebhookOrderSink,
} from "mcp-commerce";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const checkout = createCheckoutService({
catalogSource, // the same source the core uses
sessionStore: new SqliteCheckoutSessionStore({ path: "./sessions.sqlite" }),
paymentProvider: new StripePaymentProvider(stripe, {
successUrl: "https://yourproduct.com/thanks",
cancelUrl: "https://yourproduct.com/checkout-canceled",
webhookSecret: process.env.STRIPE_WEBHOOK_SECRET,
}),
taxCalculator: new NoTaxCalculator(),
orderSink: new WebhookOrderSink(
"https://yourproduct.com/hooks/order",
process.env.ORDER_WEBHOOK_SECRET,
),
config: { sessionTtlSeconds: 3600 },
});
registerCommerceTools(yourMcpServer, commerce, checkout);
During development, substitute MockPaymentProvider for the Stripe provider and NoopOrderSink for the webhook sink, both ship in the package, and the whole flow runs with no keys and no charges. The provider and sink seams are intentionally small interfaces, so a different processor or a direct fulfillment call are reasonable custom implementations.
The MCP
checkout tool returns a hosted Stripe payment URL and puts the session into an in_progress state. The signal that the shopper actually paid arrives out of band, as a Stripe webhook. If your server already has an HTTP layer, route the webhook path to the bundled handler:
import { handleStripeWebhook } from "mcp-commerce";
// inside your HTTP routing, with the raw request body preserved
await handleStripeWebhook(req, res, rawBody, { provider, checkout });
The handler verifies the Stripe signature against the raw body, finds the session id in the event metadata, marks the session completed, and triggers the order handoff, idempotently, so duplicate webhook deliveries are harmless. If you would rather own the webhook route entirely, verify the event yourself and call checkout.confirmPaid(sessionId, paymentRef), which applies the same state rules, including the rule that a late webhook can never resurrect a canceled session. Details live in the Stripe webhook guide.
Adding the ACP Endpoints Later
Everything above gives you conversational commerce inside your own tool. When you also want agent platforms to drive your checkout programmatically, the ACP REST surface is one more import on the same checkout service. handleAcpRequest(req, res, rawBody, deps) implements the full /checkout_sessions API, create, retrieve, update, complete, and cancel, with bearer auth, optional HMAC signatures, and idempotency built in. It needs an idempotency store (SQLite or DynamoDB implementations ship in the package) and an auth config holding your bearer token. Because it runs on the same checkout service as the MCP tool, a session created by either surface follows identical pricing and state rules. The wire contract is specified in the ACP endpoints reference.
Design Notes Worth Knowing
A few properties of the embedded integration tend to matter in real deployments. The commerce core is stateless across calls: every operation loads the cart from the store, re-resolves prices from the live catalog, and writes back, so any number of server instances can share a store without coordination. Catalog reads are cached with a TTL you control per source, which keeps a remote or S3 catalog from being fetched on every tool call. All amounts are integer minor units, and totals are always computed server-side, so your tool code never does money math. And the module stores no customer history, completed orders exit through the order sink and expire out of the session store, which keeps the privacy story of your product unchanged. Broader patterns for running commerce operations lean and automated are covered in our ecommerce automation pillar.
For sellers planning what to sell through their tool, pricing digital goods for an agent-driven funnel follows the same fundamentals as any store, our digital product pricing guide is a good companion read.
Testing the Embedded Integration
The package is built to make the commerce path testable inside your own test suite. Construct the core with a BundledCatalogSource pointed at a small fixture catalog and SQLite stores with no path (in-memory), and every test starts from a clean store with zero setup. The MockPaymentProvider is deterministic by design: createPayment returns a predictable mock URL, and completeWithToken responds to the token conventions tok_decline (declined) and tok_3ds (requires authentication), with any other token succeeding. That makes decline handling and 3DS handling ordinary unit tests rather than things you discover in production. The mock also records its calls, so a test can assert that a checkout charged exactly once.
Two integration tests are worth writing before shipping. First, the full happy path: add a product to a cart through the registered tools, run checkout, confirm the session lands in in_progress with a payment URL, then simulate the paid webhook and assert your order sink received one order with the right totals. Second, the price-change race: create a cart, change the fixture catalog price, read the cart again, and assert the totals reflect the new price, that is the anti-tampering recompute working, and it is the behavior your finance team will eventually ask you to prove. The state rules these tests exercise are specified in the security model.
