Quickstart: Run the ACP Payments Server
This guide assumes Node.js 20 or newer. Everything below works the same on Linux, macOS, and Windows, and a Docker path is included at the end if you prefer containers. The module is free and open source, with the code at github.com/AIAppsAPI/acp-payment-module. If you want to embed commerce into an MCP server you already run instead of running ours, read the embedding guide after this one, the configuration concepts carry over directly.
Clone the project and install. The default install includes everything needed for a single-server deployment, including SQLite storage. The AWS drivers are optional and load only if you use them.
git clone https://github.com/AIAppsAPI/acp-payment-module.git
cd acp-payment-module
npm install
npm test
Running npm test first is a good habit, it exercises the cart math, checkout state rules, and idempotency handling on your machine before you change anything.
Non-secret settings live in
config.json, and the repository ships a complete example to start from.
cp config.example.json config.json
The example points the catalog at examples/products.example.json, a small sample store with products and variants. Leave it as is for the first run, then swap in your own file later using the products file reference. While you are in config.json, set successUrl and cancelUrl to pages on your site (where shoppers land after a hosted Stripe payment), and fill in the feed block with your seller name and URL. Every key is documented in the configuration reference.
Start the development server.
npm run dev
You will see a warning that STRIPE_SECRET_KEY is not set and the mock payment provider is active. That is the intended first run: the mock provider returns deterministic results (a fake hosted payment URL, and test tokens like tok_decline that simulate declines), so you can exercise every code path with no Stripe account and no real charges. The server listens on port 3000 by default, change it with the PORT environment variable. Four routes are live: POST /mcp for MCP clients, /checkout_sessions for the ACP API, POST /webhooks/stripe for payment confirmations, and GET /feed for the product feed.
Point any MCP client at
http://localhost:3000/mcp (streamable HTTP transport) and you should see seven tools: search_products, get_product, add_to_cart, view_cart, update_cart_item, remove_from_cart, and checkout. Ask the client to search products and add one to a cart, and note that add_to_cart returns a cart_id the client must pass back on later calls. For the ACP side, remember the endpoints are closed by default, set a token first:
export ACP_BEARER_TOKEN=devtoken
npm run dev
Then create a checkout session with curl:
curl -s -X POST http://localhost:3000/checkout_sessions \
-H "Authorization: Bearer devtoken" \
-H "API-Version: 2026-04-17" \
-H "Idempotency-Key: demo-1" \
-H "Content-Type: application/json" \
-d '{"items":[{"id":"pro-single","quantity":1}]}'
The response is a full ACP session: line items priced from the catalog, totals in integer cents, status ready_for_payment. The complete request surface is in the ACP endpoints reference.
Secrets only ever come from environment variables, never the config file. Copy the example env file and fill in your keys:
cp .env.example .env
The variables that matter first are STRIPE_SECRET_KEY (enables real payments through Stripe), STRIPE_WEBHOOK_SECRET (verifies incoming Stripe webhooks), and ACP_BEARER_TOKEN (opens the ACP endpoints to your platform partner). With the Stripe key set, the MCP checkout tool returns real hosted Stripe Checkout URLs, and the ACP complete endpoint charges delegated tokens. Set up the webhook endpoint in your Stripe dashboard next, the Stripe webhook guide walks through it. For durable carts across restarts, also set CART_DB, SESSION_DB, and IDEMPOTENCY_DB to file paths, otherwise SQLite runs in memory.
The repository includes a thin reference image based on
node:24-bookworm-slim:
docker build -t mcp-commerce .
docker run --rm -p 3000:3000 \
-v "$PWD/config.json:/app/config.json" \
--env-file .env \
mcp-commerce
The container reads the same config.json and the same environment variables, so promotion from laptop to server is a copy of two files. For serverless instead of containers, the same codebase exports a Lambda handler, covered in the Lambda deployment guide.
Where to Go Next
With the server running, the natural next steps are replacing the sample catalog with your real products, wiring the order webhook so completed orders reach your fulfillment systems, and deciding where the server will live in production. A single small VPS or container comfortably runs the whole stack for a typical software business, the working set is a catalog in memory plus tiny TTL-expiring carts and sessions. If you expect spiky traffic or want zero servers, the DynamoDB-backed Lambda deployment handles that shape well.
It is also worth running one full payment in Stripe test mode before going live: add a product to a cart through your MCP client, call checkout, open the returned URL, and pay with a Stripe test card. Confirm the webhook marks the session completed and your order webhook receives the payload. That one rehearsal validates the whole loop, catalog, cart, session, payment, webhook, and handoff, end to end.
ACP_BEARER_TOKEN, configure STRIPE_WEBHOOK_SECRET, use file-backed or DynamoDB storage rather than in-memory, serve behind HTTPS, and put real terms and privacy URLs in the links config block. The security model page explains what each control protects.
Common First-Run Issues
The ACP endpoints return 401 for everything. That is the closed-by-default posture, not a bug. Until ACP_BEARER_TOKEN is set, every checkout session request is rejected, and once it is set the Authorization: Bearer header must match exactly. A 400 complaining about a missing API-Version header or a missing Idempotency-Key means the request is reaching the API fine and just needs the required ACP headers shown in step 4.
The config file is not found. The server resolves ./config.json relative to the working directory, so running from another folder needs COMMERCE_CONFIG=/path/to/config.json. Inside Docker the file must be mounted at /app/config.json, the run command in step 6 does this with a volume flag. A JSON syntax error in the file produces a clear startup error rather than a partial boot, the server refuses to start with a half-valid config.
Carts vanish between calls. With no CART_DB path set, SQLite runs in memory, which is intended for tests and quick demos. Restarting the process clears every cart and session. Set the three database path variables for anything longer-lived than an experiment. Carts also expire on purpose after cartTtlSeconds (one hour by default), an expired cart_id returns a clean not-found error that tells the agent to start a fresh cart.
A product will not add to the cart. If the product defines variants, it must be bought by variant id, and the error message says so explicitly. Products with available set to false return an out-of-stock error. Both behaviors come from the catalog rules in the products file reference, including the rule that product and variant ids share one namespace and must be globally unique.
