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-mcpInstall
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{
"mcpServers": {
"jami": {
"command": "npx",
"args": ["-y", "jami-mcp"],
"env": { "JAMI_TOKEN": "jamidev_test_xxx" }
}
}
}{
"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.
{
"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}" }
}
}
}{
"mcpServers": {
"jami": {
"command": "npx",
"args": ["-y", "jami-mcp"],
"env": { "JAMI_TOKEN": "jamidev_test_xxx" }
}
}
}[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
| Tool | What it does | Token |
|---|---|---|
jami_create_checkout | Charge 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_product | Create a product (fixed, pwyw or free, published by default) and get its page url and p_… token. | Yes |
jami_update_product | Change a product's title, description, status or price amount. | Yes |
jami_get_product | One product by token or id. | Yes |
jami_list_products | Products, newest first, filterable by status. | Yes |
jami_get_checkout_status | Check a session once, with failure when it expired. | Yes |
jami_wait_for_checkout | Wait for the buyer, with progress updates. Returns open at the timeout instead of hanging. | Yes |
jami_list_orders | Orders, newest first, paginated and filterable by status. | Yes |
jami_get_balance | Withdrawable earnings and whether the organization can withdraw. | Yes |
jami_quote_withdrawal | Fee, tax and net for an amount, plus auto-pay and daily-cap checks. Offline. | No |
jami_create_withdrawal | Pay out to Telebirr, M-Pesa or CBE Birr. Off unless you enable it. | Yes |
jami_get_withdrawal | One withdrawal by id. | Yes |
jami_list_withdrawals | Withdrawals, newest first. | Yes |
jami_verify_webhook | Check a captured delivery's signature, and why it fails. | No |
jami_create_test_webhook | Build a correctly signed test event, and optionally POST it to your local handler. | No |
jami_search_docs | Search these docs. | No |
jami_read_doc | Read 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_withdrawalonly exists when the server starts with--enable-withdrawals(orJAMI_ENABLE_WITHDRAWALS=true). Clients that support MCP elicitation then ask you to confirm every payout. Each call needs anidempotency_key, so a retry can't pay twice. With a sandbox token, the tool refuses before calling the API. - Read-only mode.
--read-only(orJAMI_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
| Flag | Env | Default |
|---|---|---|
JAMI_TOKEN | None. jamidev_test_… or jamidev_live_…. | |
JAMI_WEBHOOK_SECRET | None. Default secret for the two webhook tools. | |
--read-only | JAMI_READ_ONLY | Off |
--enable-withdrawals | JAMI_ENABLE_WITHDRAWALS | Off |
--http | JAMI_MCP_TRANSPORT=http | stdio |
--port <n> | PORT | 3333 |
--host <addr> | HOST | 127.0.0.1 |
--base-url <url> | JAMI_BASE_URL | https://jami.bio/api/jamidev |
--docs-url <url> | JAMI_DOCS_URL | https://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/healthzThe 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_jamiprompt 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_minorand adescription. - "Make me a payment link for 1,237 birr for order #1042." The agent leaves out
gatewayand gives you thejami.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.completedtohttp://localhost:3000/api/webhooks/jamiand 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:
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.