JamiDev

Build with AI

Connect Claude, Cursor, VS Code or Codex to Jami with the official MCP server.

jami-mcp is the official Model Context Protocol server for JamiDev. Add it to your AI client and the agent can create products, charge buyers by pushing a prompt to their phone or by sending a payment link, wait for payments to settle, reconcile orders, preview payouts, debug webhook signatures, and read these docs while it writes your integration. It has 17 tools.

It's built on jami-sdk, so every tool behaves exactly like the SDK and the REST API.

npx -y jami-mcp

Install

Get a token from Developer → API Tokens. Start with a sandbox organization: its jamidev_test_… token can't move real money. The server also runs without a token; the docs, quote and webhook tools work, and the account tools explain how to add one.

claude mcp add jami -e JAMI_TOKEN=jamidev_test_xxx -- npx -y jami-mcp
claude_desktop_config.json
{
  "mcpServers": {
    "jami": {
      "command": "npx",
      "args": ["-y", "jami-mcp"],
      "env": { "JAMI_TOKEN": "jamidev_test_xxx" }
    }
  }
}
~/.cursor/mcp.json
{
  "mcpServers": {
    "jami": {
      "command": "npx",
      "args": ["-y", "jami-mcp"],
      "env": { "JAMI_TOKEN": "jamidev_test_xxx" }
    }
  }
}

VS Code asks for the token when the server starts and stores it securely.

.vscode/mcp.json
{
  "inputs": [
    { "type": "promptString", "id": "jami-token", "description": "JamiDev API token", "password": true }
  ],
  "servers": {
    "jami": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "jami-mcp"],
      "env": { "JAMI_TOKEN": "${input:jami-token}" }
    }
  }
}
~/.codeium/windsurf/mcp_config.json
{
  "mcpServers": {
    "jami": {
      "command": "npx",
      "args": ["-y", "jami-mcp"],
      "env": { "JAMI_TOKEN": "jamidev_test_xxx" }
    }
  }
}
~/.codex/config.toml
[mcp_servers.jami]
command = "npx"
args = ["-y", "jami-mcp"]
env = { JAMI_TOKEN = "jamidev_test_xxx" }

Then try: "Charge buyer@example.com 1,237 birr for order #1042 on Telebirr, phone 251900000001, and wait for it." With a sandbox token, that number pays instantly.

Tools

ToolWhat it doesToken
jami_create_checkoutCharge a product (p_… token or id) or any amount (amount_minor + description). With gateway plus the buyer's email and phone, pushes the prompt to their phone (direct). Without gateway, returns a Jami payment link to send them (redirect).Yes
jami_create_productCreate a product (fixed, pwyw or free, published by default) and get its page url and p_… token.Yes
jami_update_productChange a product's title, description, status or price amount.Yes
jami_get_productOne product by token or id.Yes
jami_list_productsProducts, newest first, filterable by status.Yes
jami_get_checkout_statusCheck a session once, with failure when it expired.Yes
jami_wait_for_checkoutWait for the buyer, with progress updates. Returns open at the timeout instead of hanging.Yes
jami_list_ordersOrders, newest first, paginated and filterable by status.Yes
jami_get_balanceWithdrawable earnings and whether the organization can withdraw.Yes
jami_quote_withdrawalFee, tax and net for an amount, plus auto-pay and daily-cap checks. Offline.No
jami_create_withdrawalPay out to Telebirr, M-Pesa or CBE Birr. Off unless you enable it.Yes
jami_get_withdrawalOne withdrawal by id.Yes
jami_list_withdrawalsWithdrawals, newest first.Yes
jami_verify_webhookCheck a captured delivery's signature, and why it fails.No
jami_create_test_webhookBuild a correctly signed test event, and optionally POST it to your local handler.No
jami_search_docsSearch these docs.No
jami_read_docRead one docs page as markdown.No

Every tool returns a short markdown summary for the model plus typed structuredContent. Pass response_format: "json" to get the full payload as text instead. Parameters that take money end in _minor and are integer santim: amount_minor: 15000 is 150.00 ETB.

Prompts. integrate_jami (build a complete integration in your stack), go_live_checklist (audit a codebase before switching to production) and debug_payment (explain why a session didn't complete).

Resource. jami://reference has the fees, limits, sandbox numbers, failure codes and webhook rules on one page.

Safety

The defaults stop an agent from moving money by accident.

  • Withdrawals are off. jami_create_withdrawal only exists when the server starts with --enable-withdrawals (or JAMI_ENABLE_WITHDRAWALS=true). Clients that support MCP elicitation then ask you to confirm every payout. Each call needs an idempotency_key, so a retry can't pay twice. With a sandbox token, the tool refuses before calling the API.
  • Read-only mode. --read-only (or JAMI_READ_ONLY=true) registers only the tools that read data. Use it for reporting or support agents.
  • Live mode is announced. With a jamidev_live_ token the server tells the agent that checkouts charge real buyers and to confirm with you first. A direct checkout in production sends a real payment prompt to the buyer's phone.
  • Tokens stay in the environment. They never appear in tool output.

Options

FlagEnvDefault
JAMI_TOKENNone. jamidev_test_… or jamidev_live_….
JAMI_WEBHOOK_SECRETNone. Default secret for the two webhook tools.
--read-onlyJAMI_READ_ONLYOff
--enable-withdrawalsJAMI_ENABLE_WITHDRAWALSOff
--httpJAMI_MCP_TRANSPORT=httpstdio
--port <n>PORT3333
--host <addr>HOST127.0.0.1
--base-url <url>JAMI_BASE_URLhttps://jami.bio/api/jamidev
--docs-url <url>JAMI_DOCS_URLhttps://dev.jami.bio

Remote (HTTP) mode

npx -y jami-mcp --http --port 3333
# MCP endpoint: http://127.0.0.1:3333/mcp
# Health check:  http://127.0.0.1:3333/healthz

The HTTP server is stateless Streamable HTTP: each POST /mcp gets a fresh server, so any number of clients can share one process. Without JAMI_TOKEN, each request authenticates with its own Authorization: Bearer jamidev_… header, which lets one deployment serve several teams. Bound to loopback, it rejects requests with a foreign Host header (DNS-rebinding protection). In HTTP mode, jami_create_test_webhook never sends requests; it returns the signed payload and a curl command.

Don't expose a token-holding server

If you bind to a public address with JAMI_TOKEN set, anyone who can reach the port can use your token. Put it behind your own auth, or leave JAMI_TOKEN unset so callers send their own.

What to ask it

  • "Add Jami checkout to this Next.js app. Read the docs first, use the SDK, verify webhooks, and test it in sandbox." The agent can use the integrate_jami prompt for this.
  • "Charge abebe@example.com 450 birr for a delivery fee and tell me when it's paid." No product needed: the agent sends amount_minor and a description.
  • "Make me a payment link for 1,237 birr for order #1042." The agent leaves out gateway and gives you the jami.bio/jamidev/pay/cs_… link to send.
  • "Create a 2,500 birr product called Ge’ez Calligraphy Course and give me its link."
  • "Send a signed order.completed to http://localhost:3000/api/webhooks/jami and tell me if my handler accepts it."
  • "My handler says signature mismatch. Here's the raw body and header: …"
  • "How much did we sell last week? Page through the paid orders and total them."
  • "Session 6710c2e8b1a2f0012ab34cd9 never completed. What happened, and what should I tell the buyer?"
  • "If I withdraw 25,000 ETB, how much reaches the account and does it need review?"

Without MCP

Agents that can fetch URLs can work from the docs directly:

  • /llms.txt: every page with a one-line summary.
  • /llms-full.txt: the whole documentation in one file.
  • Any page as markdown: add .md, e.g. https://dev.jami.bio/docs/api/checkout.md.
  • /openapi.json: the REST API as OpenAPI 3.1.

Or paste this prompt into your agent:

Integration prompt
Integrate JamiDev payments (https://dev.jami.bio/docs) into this project.

Read https://dev.jami.bio/llms-full.txt first and treat it as the source of truth.

My stack: [framework + language]
What I sell: [one-time digital product | tips | pay-what-you-want | free download]

1. Install jami-sdk. Create one server-side client from JAMI_TOKEN (jamidev_test_… for
   sandbox). Never send the token to a browser.
2. Add a server route that calls jami.createCheckout, either for a product
   ({ productId }, the p_… token, created with jami.products.create or in the dashboard) or
   for an amount ({ amount, description }). To push the prompt to the buyer's phone, pass
   gateway and customer: { email, phone }. To send them a Jami payment link instead, leave
   out gateway and use checkoutUrl. Handle all three results: free (orderId), redirect (send
   the buyer to checkoutUrl) and direct (the buyer confirms on their phone; poll with
   waitForCheckout).
3. Store the returned sessionId with your own order record.
4. Add a webhook route that verifies the RAW body with jami.webhooks.verify and
   JAMI_WEBHOOK_SECRET, ignores event ids it has already processed, fulfils on
   order.completed (match on data.sessionId), and returns 2xx within 10 seconds.
5. On checkout.session.expired, read data.failure.code and show the buyer a matching
   message.
6. Test in sandbox: phone 251900000001 succeeds instantly, 251900000002 fails.
7. Add JAMI_TOKEN and JAMI_WEBHOOK_SECRET to .env.example.

Rules: amounts are integer santim (10000 = 100.00 ETB). Never mark an order paid from the
browser redirect alone.

On this page