Jami

io.github.kenawakPublished by GitHub account kenawakv0.3.0Updated Nov 1, 2026

Jami creator payouts for your agent: ETB checkouts, orders, payouts, webhooks. Powered by ArifPay.

Overview

AI-generated overview

JamiDev MCP server that lets an assistant create ETB checkouts, manage products and subscriptions, track orders and payouts, and debug webhooks.

What you can do
  • Create and manage checkouts: start a checkout for a product or any amount, check its status, and wait for the buyer to pay
  • Manage products and subscriptions: create, update, read and list products, propose subscription links, list, inspect, cancel or resume subscriptions, and simulate renewals in sandbox
  • Review orders and earnings: list orders by status and read the withdrawable balance
  • Handle payouts: quote withdrawal fees and net amounts, and track withdrawals
  • Work with webhooks: verify a delivery signature and build signed test events
  • Read the developer docs: search and read JamiDev documentation pages as markdown
When to use it
Useful when building or operating a Jami/ArifPay payment integration in Ethiopian birr: letting an agent create checkouts, reconcile orders, inspect subscriptions, or debug webhook signatures while writing integration code.
Requirements
Runs locally via npx (Node.js) over stdio, or in HTTP mode with flags. A JamiDev API token in JAMI_TOKEN (sandbox or live) is needed for account tools; docs, withdrawal quotes and webhook tools work without it. JAMI_WEBHOOK_SECRET optionally supplies a default webhook secret. Network access to the Jami API and docs.
Before you install
With a live token, checkouts charge real buyers and withdrawals move real money; withdrawals are disabled unless started with --enable-withdrawals, and payouts need an idempotency key. Cancelling a subscription stops future charges and there are no refunds. The token and webhook secret are secrets kept in the environment. Use --read-only for reporting agents.

Installation

In SourceWeft

  1. Open Jami in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

jami-mcp

The official Model Context Protocol server for JamiDev, the SDK for Jami's creator payouts. Connect it to Claude, Cursor, VS Code, Codex or any MCP client and your agent can create ETB checkouts, watch them settle, reconcile orders, preview and send payouts, debug webhooks, and read the Jami docs while it writes your integration. Payments are processed by ArifPay.

Built on the official jami-sdk, so every tool behaves exactly like the SDK and the REST API.

bash
npx -y jami-mcp

Quick start

  1. Get a token: Jami dashboard → JamiDev → Developer → API Tokens. Start with a sandbox org — its jamidev_test_… token moves no real money.
  2. Add the server to your client (below), with the token as JAMI_TOKEN.
  3. Ask: "Charge [email protected] 1,237 birr for order #1042 on Telebirr, phone 251900000001, and wait for it."

No token yet? The server still runs. The docs, withdrawal-quote and webhook tools work without one, so you can start building right away.

Claude Code

bash
claude mcp add jami -e JAMI_TOKEN=jamidev_test_xxx -- npx -y jami-mcp

Claude Desktop · Cursor · Windsurf

Add to claude_desktop_config.json, ~/.cursor/mcp.json or ~/.codeium/windsurf/mcp_config.json:

json
{  "mcpServers": {    "jami": {      "command": "npx",      "args": ["-y", "jami-mcp"],      "env": { "JAMI_TOKEN": "jamidev_test_xxx" }    }  }}

VS Code

.vscode/mcp.json — VS Code prompts for the token and stores it securely:

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}" }    }  }}

Codex

~/.codex/config.toml:

toml
[mcp_servers.jami]command = "npx"args = ["-y", "jami-mcp"]env = { JAMI_TOKEN = "jamidev_test_xxx" }

Tools

ToolWhat it doesNeeds token
jami_create_checkoutCharge a product (p_… token or id) or any amount (amount_minor + description). With gateway: pushes the prompt to the buyer's phone. Without: returns a Jami payment link to send them.✓
jami_create_product · jami_update_productCreate or edit products; each comes back with its public page and p_… token.✓
jami_get_product · jami_list_productsRead the catalog.✓
jami_get_checkout_statusCheck a session once, with the failure reason when it expired.✓
jami_wait_for_checkoutWait for the buyer to finish, with progress updates. Never hangs: returns open at the timeout.✓
jami_list_ordersOrders, newest first, paginated and filterable by status.✓
jami_get_balanceWithdrawable earnings and whether the org can withdraw.✓
jami_quote_withdrawalFee, tax and net for an amount, plus auto-pay and cap checks. Offline.—
jami_create_withdrawalPay out to Telebirr, M-Pesa or CBE Birr. Opt-in (see Safety).✓
jami_get_withdrawal · jami_list_withdrawalsTrack payouts.✓
jami_verify_webhookCheck a captured delivery's signature, and why it fails.—
jami_create_test_webhookBuild a correctly signed test event, and optionally POST it to your local handler.—
jami_search_docs · jami_read_docSearch and read the live docs at dev.jami.bio.—

Every tool returns a readable markdown summary plus typed structuredContent. Pass response_format: "json" for the raw payload. Money is always integer santim, and every parameter that takes money ends in _minor: amount_minor: 15000 = 150.00 ETB.

Prompts: integrate_jami (build a full integration in your stack), go_live_checklist, debug_payment. Resource: jami://reference — fees, limits, sandbox numbers, failure codes and webhook rules in one page.

Safety

The defaults are set so an agent can't move money by accident.

  • Withdrawals are off until you start the server with --enable-withdrawals (or JAMI_ENABLE_WITHDRAWALS=true). When they're on, clients that support elicitation ask you to confirm each payout, and every payout needs an idempotency_key, so a retry can never pay twice. Sandbox tokens are refused before any request is made.
  • --read-only registers only the tools that read data. Use it for reporting agents.
  • Live mode is announced. With a jamidev_live_ token, the server tells the agent that checkouts charge real buyers and to confirm first.
  • Tokens stay in the environment. They never appear in tool output.

Options

--http                 Streamable HTTP on POST /mcp instead of stdio--port <n>             HTTP port (default 3333)--host <addr>          HTTP bind address (default 127.0.0.1)--read-only            Only read tools--enable-withdrawals   Register jami_create_withdrawal--base-url <url>       Override the API base (default https://jami.bio/api/jamidev)--docs-url <url>       Override the docs origin (default https://dev.jami.bio)
EnvPurpose
JAMI_TOKENjamidev_test_… or jamidev_live_…
JAMI_WEBHOOK_SECRETDefault secret for the webhook tools
JAMI_READ_ONLY, JAMI_ENABLE_WITHDRAWALSSame as the flags

Remote (HTTP) mode

bash
npx -y jami-mcp --http --port 3333# → http://127.0.0.1:3333/mcp   (health check: /healthz)

The HTTP server is stateless, so any number of clients can share one process. Without JAMI_TOKEN, each request authenticates with its own Authorization: Bearer jamidev_… header, which makes one deployment safe to share across teams. On loopback it rejects foreign Host headers (DNS-rebinding protection). In HTTP mode, jami_create_test_webhook never sends requests; it only returns the signed payload and a curl command.

Embedding

ts
import { createJamiServer } from 'jami-mcp';import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const server = createJamiServer({ token: process.env.JAMI_TOKEN, docsUrl: 'https://dev.jami.bio', readOnly: true, withdrawals: false });await server.connect(new StdioServerTransport());

Development

bash
npm installnpm test          # in-memory MCP client against a mocked APInpm run buildnpm run inspect   # opens the MCP Inspector on dist/index.js

Full guide: dev.jami.bio/docs/mcp · Support: [email protected]

MIT © Jami

Source: README.md at commit 98fc572

Tools

22

jami_create_checkout

Create checkout

Start a checkout and get back how the buyer pays — either for one of the organization's published products, or for ANY amount with no product ("charge this buyer 1,237 ETB for order #1042"). Buyers pay in ETB via Telebirr, M-Pesa or CBE Birr; nothing is added on top. In LIVE mode a "direct" checkout pushes a real payment prompt to the buyer's phone, so only call this when the user actually wants to charge someone. What to charge: - Product: product_id (p_… token or 24-char id, must be published). amount_minor only for flexible-price (pwyw) products. - Custom amount: omit product_id, pass amount_minor (≥ 100 = 1.00 ETB) and description (what the buyer sees, ≤ 120 chars). How to collect it: - Direct push: pass gateway + customer_email + customer_phone. The prompt goes straight to the buyer's phone (kind 'direct'). - Payment link: omit gateway. You get a Jami link in checkout_url (kind 'redirect') to send the buyer by SMS/Telegram/email; they pick the method and enter their details there, and can retry a failed attempt until the link expires (30 min). customer_* is optional. Args: - customer_email / customer_phone: required for a direct push; optional for a link. Phone like "251911223344" or "0911223344". Sandbox magic numbers: 251900000001 = instant success, 251900000002 = instant failure. - gateway: 'telebirr' | 'mpesa' | 'cbe'. Omit for a payment link. - amount_minor: integer santim. 123700 = 1,237.00 ETB. - metadata: optional string map (e.g. { "orderId": "1042" }) stored on the order and sent back on order webhooks. Returns: { session_id, kind: 'free' | 'redirect' | 'direct', checkout_url, order_id }. - free: completed instantly; order_id is set. - redirect: send the buyer to checkout_url. - direct: the buyer confirms on their phone; call jami_wait_for_checkout next.

Parametersgatewaymetadataproduct_iddescriptionsuccess_urlamount_minorcheckout_linkcustomer_namecustomer_emailcustomer_phonecollected_fieldscustomer_external_id

jami_get_checkout_status

Get checkout status

Check a checkout session once. The server reconciles with the payment provider on every call, so this resolves even when webhooks are late. Args: - session_id: from jami_create_checkout. Returns: { session_id, status: 'open' | 'completed' | 'expired', order_id, order_status, amount_minor, amount, expires_at, failure }. When status is 'expired', failure.code explains why: payment_rejected · payment_failed · payment_canceled · payment_timed_out · initiate_failed · session_expired. Use jami_wait_for_checkout instead if you want to block until the buyer finishes.

Parameterssession_idrequiredresponse_format

jami_wait_for_checkout

Wait for checkout

Poll a checkout session until it's paid or ends, up to timeout_seconds. Reports progress while it waits. If the buyer hasn't finished when the timeout hits, this returns status 'open' (not an error) — call it again to keep waiting. Sessions expire 30 minutes after creation. Args: - session_id: from jami_create_checkout. - timeout_seconds: 5–300 (default 90). - interval_seconds: 2–30 between polls (default 3). Returns the same shape as jami_get_checkout_status.

Parameterssession_idrequiredresponse_formattimeout_secondsinterval_seconds

jami_create_product

Create product

Create a product the organization sells, and get its public Jami page (url) and p_… token. Published by default, so the page and checkouts work immediately. Pricing (can't be changed after creation; the amounts can): - fixed: amount_minor is the price, e.g. 250000 = 2,500.00 ETB. - pwyw (flexible): minimum_amount_minor; each checkout then sends its own amount. - free: no price. Subscription plans: product_type 'recurring' + pricing_model 'fixed' + billing_interval ('week' | 'month' | 'year') and optional interval_count (default 1; ≤ 52 weeks, ≤ 12 months, 1 year). amount_minor is charged from the payer's Jami wallet every period, 100–1,000,000 santim (1–10,000 ETB). The interval is locked after creation; a new price only applies to new subscribers. Args: title (1–120 chars), pricing_model, amount_minor / minimum_amount_minor, product_type? ('one_time' default | 'recurring'), billing_interval?, interval_count?, description?, status? ('published' | 'draft'). Returns: { product_id, token, url, title, status, pricing_model, product_type, billing_interval, interval_count, price }. To charge for a one-time product, call jami_create_checkout with product_id = token (omit gateway for a payment link). For a recurring plan, call jami_create_subscription_link instead.

Parameterstitlerequiredstatusdescriptionamount_minorproduct_typepricing_modelrequiredinterval_countresponse_formatbilling_intervalminimum_amount_minor

jami_update_product

Update product

Change a product's title, description, status or price amount. The pricing model itself is locked. Args: product_id (p_… token or id), and any of: title, description, status ('published' | 'draft' | 'archived' — archived stops new sales), amount_minor (fixed price), minimum_amount_minor (flexible floor).

Parameterstitlestatusproduct_idrequireddescriptionamount_minorresponse_formatminimum_amount_minor

jami_get_product

Get product

Fetch one product by its p_… token or id: price, status, its public page url, and for a subscription plan its billing interval. Args: - product_id: p_… token or 24-character id. Returns: { product_id, token, url, title, status, pricing_model, product_type, billing_interval, interval_count, price }.

Parametersproduct_idrequiredresponse_format

jami_list_products

List products

List the organization's products, newest first. Args: status ('published' | 'draft' | 'archived', optional), page (default 1), limit 1–100 (default 20). Returns: { products: [{ product_id, token, url, title, status, pricing_model, price }], total, page, limit, count, has_more, next_page }.

Parameterspagelimitstatusresponse_format

jami_list_subscriptions

List subscriptions

List the organization's wallet subscriptions, newest first. Args: - status: 'active' | 'past_due' | 'canceled' (optional). past_due = a renewal failed and is being retried. - product_id: optional p_… token or id of the plan. - customer_external_id: optional, your own user id passed when the link was created. - page (default 1), limit 1–100 (default 20). Returns: { subscriptions: [{ subscription_id, status, product_title, amount_minor, cadence, next_charge_at, customer_email, … }], total, page, limit, count, has_more, next_page }.

Parameterspagelimitstatusproduct_idresponse_formatcustomer_external_id

jami_get_subscription

Get subscription

Fetch one wallet subscription with its newest 24 charges (one per billing period) — use it to see why a renewal failed or when the next one runs. Args: - subscription_id: the sub_… id. Returns: { subscription: { subscription_id, status, amount_minor, cadence, current_period_end, next_charge_at, cancel_at_period_end, cancellation_reason, last_failure, … }, charges: [{ charge_id, sequence, status, amount_minor, failure, order_id, paid_at }] }. Failure codes: insufficient_funds, kyc_required, payer_unavailable, simulated_failure, internal_error.

Parametersresponse_formatsubscription_idrequired

jami_cancel_subscription

Cancel subscription

Cancel a wallet subscription so the payer is not charged again. Confirm with the user first — a cancel the payer didn't ask for loses a customer, and there are no refunds. Args: - subscription_id: the sub_… id. - at_period_end: default true — no more charges, and it ends at current_period_end (the payer keeps what they paid for). You can undo this kind of cancel before then with jami_resume_subscription. false ends it immediately (a past_due subscription always ends immediately). Returns the updated subscription: { subscription_id, status, cancel_at_period_end, ended_at, cancellation_reason, … }. Idempotent: cancelling twice is harmless.

Parametersat_period_endresponse_formatsubscription_idrequired

jami_resume_subscription

Resume subscription

Undo a cancel that YOU scheduled (jami_cancel_subscription with at_period_end true), so the subscription keeps renewing at next_charge_at. Only works on a cancel the organization scheduled and while the subscription hasn't ended yet. A cancel the customer made can't be undone by the organization (the payer has to subscribe again), and an ended subscription can't be resumed — both return 409 not_allowed. Args: - subscription_id: the sub_… id. Returns the updated subscription: { subscription_id, status, cancel_at_period_end: false, next_charge_at, … }. Idempotent: resuming an already-renewing subscription changes nothing.

Parametersresponse_formatsubscription_idrequired

jami_simulate_subscription_renewal

Simulate subscription renewal

Sandbox only: run a subscription's next renewal right now, succeeding or failing, to test subscription.renewed / subscription.payment_failed / subscription.canceled handling without waiting a billing period. No money moves. Refused with a live (jamidev_live_) token — live renewals only run on schedule. Args: - subscription_id: the sub_… id of a sandbox subscription. - outcome: 'success' (default) or 'failure'. Failures follow the real dunning schedule: retries at 1, 3 and 7 days, then the subscription is canceled with cancellation_reason payment_failed. Returns: { result: 'renewed' | 'retry_scheduled' | 'canceled' | 'skipped' | 'deferred', subscription }.

Parametersoutcomeresponse_formatsubscription_idrequired

jami_list_orders

List orders

List the organization's orders, newest first, one page at a time. Args: - status: 'paid' | 'pending' | 'failed' (optional filter). - page: 1-based page (default 1). - limit: 1–100 per page (default 20). Returns: { orders: [{ order_id, status, amount_minor, amount, product: { id, title }, customer: { id, email, name }, paid_at, created_at, metadata }], total, page, limit, count, has_more, next_page }. To total revenue or find a specific buyer, page through with next_page until has_more is false. Amounts are integer santim (100 = 1.00 ETB); this is what buyers paid, before any withdrawal fee.

Parameterspagelimitstatusresponse_format

jami_get_balance

Get balance

Get the organization's withdrawable JamiDev earnings — the ceiling on what jami_create_withdrawal can pay out. Returns: { balance_minor, balance, eligible, currency }. eligible is false (and balance 0) when the org can't withdraw yet: it's a sandbox org, Developer Mode is off, or KYC isn't approved. The money itself sits in the owner's unified Jami wallet, so a withdrawal can still fail with 402 if that wallet was spent elsewhere.

Parametersresponse_format

jami_quote_withdrawal

Quote withdrawal

Preview what reaches the destination for a withdrawal — offline, no API call, works without a token. The JamiDev usage fee (7% unless Jami set a custom rate on the account) and any government tax are cut FROM the amount: you withdraw amount, the account receives net. Rounding is round-half-up per component. The server is authoritative; the real breakdown comes back on the withdrawal. Args: - amount_minor: gross santim (min 10000 = 100.00 ETB). - fee_rate / tax_rate: optional overrides (0–1) if you know the account's real rates. Returns the breakdown plus whether the amount auto-pays (≤ 10,000 ETB) or goes to manual review, and the per-org 100,000 ETB rolling-24h cap.

Parametersfee_ratetax_rateamount_minorrequiredresponse_format

jami_get_withdrawal

Get withdrawal

Fetch one withdrawal by id to track it from pending → processing → completed (or rejected). Args: - withdrawal_id: from jami_create_withdrawal or jami_list_withdrawals.

Parameterswithdrawal_idrequiredresponse_format

jami_list_withdrawals

List withdrawals

List the organization's withdrawals, newest first. Args: - status: 'pending' | 'reviewing' | 'approved' | 'rejected' | 'processing' | 'completed' (optional). - page (default 1), limit 1–100 (default 20). Returns: { withdrawals: [...], total, page, limit, count, has_more, next_page }.

Parameterspagelimitstatusresponse_format

jami_verify_webhook

Verify webhook

Check whether a webhook delivery is genuine — the same timing-safe HMAC check and replay guard the SDK runs. Use it to debug "signature mismatch" errors in a handler. Args: - payload: the RAW request body exactly as received (not re-serialized JSON — whitespace matters). - signature: the X-JamiDev-Signature header, "t=<unix>,v1=<hex>". - secret: the subscription's webhook secret (defaults to JAMI_WEBHOOK_SECRET). - tolerance_seconds: max age of the timestamp (default 300). Raise it to check an old captured delivery. Returns: { valid, reason, event_type, event_id, livemode }. An invalid signature is a normal result (valid: false), not an error.

Parameterssecretpayloadrequiredsignaturerequiredtolerance_seconds

jami_create_test_webhook

Create test webhook

Build a correctly signed JamiDev webhook delivery for testing a handler locally — no real event needed. Args: - type: 'order.created' | 'order.completed' | 'benefit.granted' | 'checkout.session.expired' | 'withdrawal.paid' | 'withdrawal.failed' | 'subscription.created' | 'subscription.renewed' | 'subscription.payment_failed' | 'subscription.updated' | 'subscription.canceled'. - secret: signing secret (defaults to JAMI_WEBHOOK_SECRET; any string works for a local test). - data: optional fields to merge over the realistic sample payload. - livemode: default false (sandbox). - target_url: optional. When set, the delivery is POSTed there (e.g. "http://localhost:3000/webhooks/jami") and the response status is reported. Returns: { payload, signature, headers, curl } — the exact body and X-JamiDev-Signature to send, plus delivery_status when target_url is set. Handlers must verify the RAW body and reply 2xx within 10 seconds.

Parametersdatatyperequiredsecretlivemodetarget_url

jami_search_docs

Search Jami docs

Search the official JamiDev developer docs (https://dev.jami.bio/docs) — the source of truth for integrating Jami payments. Works without an API token. Use this before writing integration code: SDK usage, REST endpoints, webhooks and signature checks, sandbox magic numbers, Sign in with Jami (OAuth/OIDC), the wallet API, the browser storefront, fees and limits. Args: - query: what you need, e.g. "verify webhook signature express", "pay what you want amount", "withdrawal fee". - limit: 1–10 results (default 5). Returns: { results: [{ title, path, url, snippet }] }. Read a full page with jami_read_doc(path).

Parameterslimitqueryrequired

jami_read_doc

Read Jami doc page

Read one JamiDev docs page as markdown, with every code sample intact. Args: - path: a docs path from jami_search_docs, e.g. "/docs/webhooks", "api/checkout", or a full URL. Use "/docs" for the overview. - offset: character offset for long pages (default 0); the result says where to continue. Returns: { title, url, markdown, offset, next_offset? }.

Parameterspathrequiredoffset

Version history

1
  1. v0.3.0LatestOct 25, 2026

Score

90% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts
  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listedRequired15 pts
  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts