Skip to content

AIAppsAPI/acp-payment-module

Repository files navigation

ACP Payment Module (mcp-commerce)

A drop-in commerce core for MCP servers, built on the Agentic Commerce Protocol (ACP). Point it at a simple products file and your tool can take money inside the conversation that was already happening: buy an asset, upgrade to Pro, renew a license, buy credits. The commerce logic is a clean library; the MCP server and the ACP REST endpoints are thin faces over it.

Full documentation lives at afcommerce.com.

It is built for companies that already run (or are building) an MCP tool and will eventually want to charge for something inside it. The commerce rides along with a real brand and a real product, so the trust problem of an unknown storefront does not apply.

What it is not

  • Not a standalone shopping destination for unknown sellers.
  • Not a CRM, order database, or customer history store. On a successful payment we hand the order to the merchant and forget it.
  • Not a frontend. There is no cart UI or chat widget, on purpose.

Three ways to use it

  1. Standalone server. Run the whole stack (npm start or the Docker image, or the Lambda handler). Our code is the MCP server.
  2. Embedded. Import the commerce core into your own MCP server and register the tools there. You never touch our transport.
  3. Library only. Call the core from your REST API or bot, no MCP at all.

Quickstart (standalone, Docker)

cp config.example.json config.json   # edit it for your store
cp .env.example .env                  # add your Stripe key (or leave blank for the mock)
docker build -t mcp-commerce .
docker run --rm -p 3000:3000 \
  -v "$PWD/config.json:/app/config.json" \
  --env-file .env \
  mcp-commerce

The server then exposes:

Path What it is
POST /mcp MCP endpoint (streamable HTTP, stateless JSON)
/checkout_sessions... ACP REST checkout endpoints
POST /webhooks/stripe Stripe webhook receiver
GET /feed?format=jsonl|csv ACP product feed

Without STRIPE_SECRET_KEY set, the server uses a mock payment provider (no real charges), which is handy for local runs and demos.

Quickstart (embedded / library)

import {
  createCommerce,
  createCheckoutService,
  BundledCatalogSource,
  SqliteCartStore,
  SqliteCheckoutSessionStore,
  MockPaymentProvider,
  NoTaxCalculator,
  NoopOrderSink,
  registerCommerceTools,
} from "mcp-commerce";

const catalogSource = new BundledCatalogSource({ path: "./products.json" });

const commerce = createCommerce({
  catalogSource,
  cartStore: new SqliteCartStore({ path: "./carts.sqlite" }),
  config: { currency: "usd", cartTtlSeconds: 3600 },
});

// Register the shopper-facing tools on your own MCP server:
registerCommerceTools(yourMcpServer, commerce /*, checkoutService */);

The AWS-backed drivers (DynamoDB stores, S3 catalog source) live in a separate entry so the optional AWS SDK only loads when you use them:

import { DynamoCartStore, S3CatalogSource } from "mcp-commerce/aws";

Quickstart (AWS Lambda)

Front an API Gateway HTTP API (payload format v2) at the exported handler:

handler = dist/lambda.handler

Set COMMERCE_CONFIG (and the secret env vars) the same way the server uses them. On Lambda, use the DynamoDB stores for carts, sessions, and idempotency (set storage.type to dynamo); an in-memory SQLite store would not survive between invocations. The S3 catalog source fits the same stack. The handler routes the ACP endpoints, the Stripe webhook, the feed, and the MCP endpoint, all through the same core as the standalone server.

Products file

Catalog only, no orders or config mixed in. Prices are integer minor units (cents) to match Stripe and avoid floating point money bugs.

{
  "currency": "usd",
  "products": [
    {
      "id": "pro-license",
      "title": "Pro License",
      "description": "Full license with all features unlocked.",
      "url": "https://example.com/products/pro-license",
      "image_url": "https://example.com/img/pro-license.png",
      "price": 4999,
      "available": true,
      "variants": [
        { "id": "pro-single", "title": "Single seat", "price": 4999 },
        { "id": "pro-team", "title": "Team (5 seats)", "price": 19999 }
      ]
    }
  ]
}

A product with variants is bought by variant id; a product without variants is bought by its own id. See examples/products.example.json for a full sample.

Configuration

Non-secret settings live in config.json (see config.example.json). Secrets only ever come from environment variables (see .env.example), never a committed file.

Config highlights:

  • currency, cartTtlSeconds
  • catalog: { "type": "bundled" | "remoteUrl" | "s3", ... }
  • storage: { "type": "sqlite" | "dynamo", ... }
  • tax: { "enabled": false } (see Tax below)
  • successUrl, cancelUrl, orderWebhookUrl, permalinkBase
  • acp: { "requireSignature": false }
  • links: terms, privacy, return policy links surfaced in the session
  • feed: merchant-level fields for the ACP product feed

Environment variables:

Variable Purpose
PORT Server port (default 3000)
COMMERCE_CONFIG Path to the config file (default ./config.json)
CART_DB, SESSION_DB, IDEMPOTENCY_DB SQLite file paths (default in-memory)
STRIPE_SECRET_KEY Stripe key; blank uses the mock provider
STRIPE_WEBHOOK_SECRET Verifies incoming Stripe webhooks
ACP_BEARER_TOKEN Bearer token agents present to the ACP endpoints
ACP_SIGNING_SECRET Optional HMAC request-signature verification
ORDER_WEBHOOK_SECRET Optional signature on the order we POST to the merchant

The ACP endpoints are closed by default: with no ACP_BEARER_TOKEN set, every request is rejected.

Storage and catalog drivers

Two data types, stored separately and swappable per deploy:

  • Catalog (read only): bundled file, remote URL, or S3 object.
  • Cart and checkout session (short lived, TTL'd, not customer data): SQLite for Docker/EC2, DynamoDB for Lambda (native per-item TTL).

Payments and tax

  • Direct Stripe (everywhere): a hosted Stripe Checkout URL the shopper opens. Used by the MCP checkout tool and any merchant-facing surface.
  • Delegated (SPT) (US, Stripe preview terms): a third-party agent grants a scoped Shared Payment Token, charged immediately by the ACP complete endpoint.

Tax is delegated to Stripe Tax rather than reimplemented. It is off by default; set tax.enabled and provide a Stripe key to turn it on. Tax is computed once the buyer's address is known (from fulfillment_details on update, or the billing address on complete) and folded into the line tax and the session total.

MCP tools

search_products, get_product, add_to_cart, view_cart, update_cart_item, remove_from_cart, and checkout (registered when a checkout service is provided). Because the transport is stateless, add_to_cart returns a cart_id that must be passed back on later calls; the tool descriptions tell the agent so.

Development

npm install
npm run dev        # tsx src/server.ts
npm test           # node --test
npm run typecheck
npm run build      # tsc -> dist/
npm run feed       # print the product feed to stdout

Documentation and related projects

License

Elastic License 2.0. Free to use, modify, and embed in your own product, including proprietary and commercial ones. The one limitation is that you may not offer the software to third parties as a hosted or managed commerce service. See the LICENSE file for the full text.

Copyright (c) 2026 Paul Crinigan, aiappsapi.com

About

ACP Payment Module: drop-in Agentic Commerce Protocol checkout and Stripe payments for MCP servers. It has an optional starter MCP server template, but I figured it was more usable as a module you could add to your own.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors