# Jami (npm · jami-mcp)

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

- Trust score: 74/100 (medium)
- Change this week: +4
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-11-05

## Components

- npm · `jami-mcp`: 74/100 (this document), [markdown](https://verifymcp.io/servers/kenawak-jami-mcp/jami-mcp.md), [page](https://verifymcp.io/servers/kenawak-jami-mcp/jami-mcp)

## Channel facts

- Registry: `npm`
- Package: `jami-mcp`
- Version: `0.3.0`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-11-05.

- **Supply Chain Security**: 98/100
  - No malware found by supply-chain analysis.
  - No known CVEs affecting this package version or its production dependencies.
  - No install/post-install scripts declared.
  - 30 of 93 dependencies flagged as unhealthy.
- **Provenance & Transparency**: 45/100
  - Source repository is publicly reachable at the declared URL.
  - Provenance check failed: no build-provenance attestation is published.
  - Clear OSI-approved license (MIT).
  - Actively maintained (last published 10 days ago).
  - Disclosure check failed: no security disclosure policy was found in the source repository.
- **Schema Quality & AI Usability**: 73/100
  - 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (good).
  - Context-footprint check failed: tool/resource definitions use about 5334 tokens (~231/item across 23 items; 22 tools + 1 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 37/100
  - Stability observed for 11 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 86/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 50% of tool parameters carry a description.
  - Structured output schemas are declared (100% of tools); any adoption earns full credit.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - We read all 22 captured tool definition(s), and no name or description among them implies an irreversible operation.
  - An AI judge read all 24 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

## Install

### How do I install the Jami MCP server?

Jami runs locally as an npm package, launched with npx -y jami-mcp. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add kenawak-jami-mcp -- npx -y jami-mcp
```

### Cursor

```json
{
  "mcpServers": {
    "kenawak-jami-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "jami-mcp"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "kenawak-jami-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "jami-mcp"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add kenawak-jami-mcp -- npx -y jami-mcp
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "kenawak-jami-mcp": {
      "type": "local",
      "command": [
        "npx",
        "-y",
        "jami-mcp"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add kenawak-jami-mcp --command npx --arg -y --arg jami-mcp
```

### Hermes

```yaml
mcp_servers:
  kenawak-jami-mcp:
    command: "npx"
    args: ["-y", "jami-mcp"]
```

### Netclaw

```json
{
  "McpServers": {
    "kenawak-jami-mcp": {
      "Transport": "stdio",
      "Command": "npx",
      "Arguments": [
        "-y",
        "jami-mcp"
      ]
    }
  }
}
```

### Vellum

```bash
assistant mcp add kenawak-jami-mcp -t stdio -c npx -a -y jami-mcp
```

### Other

```json
{
  "mcpServers": {
    "kenawak-jami-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "jami-mcp"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-11-05 (score 74, +1)

No change was recorded against any check on this day. Stability & Change Management went from 33 to 37. That category is still filling its 30-day observation window: 10 days of observed history at the previous scan, 11 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-11-03 (score 73, +1)

No change was recorded against any check on this day. Stability & Change Management went from 27 to 30. That category is still filling its 30-day observation window: 8 days of observed history at the previous scan, 9 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-11-01 (score 72, +1)

No change was recorded against any check on this day. Stability & Change Management went from 20 to 23. That category is still filling its 30-day observation window: 6 days of observed history at the previous scan, 7 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-30 (score 71, +1)

No change was recorded against any check on this day. Stability & Change Management went from 13 to 17. That category is still filling its 30-day observation window: 4 days of observed history at the previous scan, 5 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-28 (score 70, +1)

No change was recorded against any check on this day. Stability & Change Management went from 7 to 10. That category is still filling its 30-day observation window: 2 days of observed history at the previous scan, 3 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-26 (score 69, +1)

No change was recorded against any check on this day. Stability & Change Management went from 0 to 3. That category is still filling its 30-day observation window: 0 days of observed history at the previous scan, 1 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-25 (score 68)

First indexed and scored.

## MCP tools (22)

### `jami_create_checkout` (~729 tokens)

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.

Input parameters:

- `amount_minor` (integer): Integer santim. Required for custom amounts (≥ 100) and pay-what-you-want products; ignored for fixed prices
- `checkout_link` (string): Checkout link token (cl_…) to attribute this session to
- `collected_fields` (object): Answers to the product's checkout fields, keyed by field id
- `customer_email` (string): Buyer email for the receipt. Required with gateway; optional for a payment link
- `customer_external_id` (string): Your own user id, stored on the customer record
- `customer_name` (string)
- `customer_phone` (string): Buyer phone, e.g. "251911223344"
- `description` (string): Custom amount only: what the buyer is paying for, e.g. "Order #1042"
- `gateway` (string): Payment method for paid products
- `metadata` (object): Your own key/values, returned on order webhooks (max 20 keys)
- `product_id` (string): Product token (p_…) or id. Omit for a custom-amount checkout.
- `success_url` (string): Where to send the buyer after paying

Output parameters:

- `checkout_url` (string|null)
- `kind` (string)
- `order_id` (string|null)
- `session_id` (string)

### `jami_get_checkout_status` (~201 tokens)

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.

Input parameters:

- `response_format` (string): 'markdown' (default) for a readable summary, 'json' for the full structured payload
- `session_id` (string, required): Checkout session id returned by jami_create_checkout, e.g. "6710c2e8b1a2f0012ab34cd9"

Output parameters:

- `amount` (string): Human-readable amount, e.g. "150.00 ETB"
- `amount_minor`: Integer santim (100 = 1.00 ETB)
- `expires_at` (string)
- `failure`: Why the session ended without an order; null unless status is "expired"
- `order_id` (string|null)
- `order_status`
- `session_id` (string)
- `status` (string)

### `jami_wait_for_checkout` (~201 tokens)

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.

Input parameters:

- `interval_seconds` (integer)
- `response_format` (string): 'markdown' (default) for a readable summary, 'json' for the full structured payload
- `session_id` (string, required): Checkout session id returned by jami_create_checkout, e.g. "6710c2e8b1a2f0012ab34cd9"
- `timeout_seconds` (integer)

Output parameters:

- `amount` (string): Human-readable amount, e.g. "150.00 ETB"
- `amount_minor`: Integer santim (100 = 1.00 ETB)
- `expires_at` (string)
- `failure`: Why the session ended without an order; null unless status is "expired"
- `order_id` (string|null)
- `order_status`
- `session_id` (string)
- `status` (string)

### `jami_create_product` (~471 tokens)

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.

Input parameters:

- `amount_minor` (integer): Fixed price in santim
- `billing_interval` (string): Recurring only: how often the payer's wallet is charged
- `description` (string)
- `interval_count` (integer): Recurring only: periods per charge (default 1), e.g. 3 + month = every 3 months
- `minimum_amount_minor` (integer): Flexible price floor in santim
- `pricing_model` (string, required)
- `product_type` (string)
- `response_format` (string): 'markdown' (default) for a readable summary, 'json' for the full structured payload
- `status` (string)
- `title` (string, required)

Output parameters:

- `amount_minor`
- `billing_interval`
- `interval_count`
- `minimum_amount_minor`
- `price` (string)
- `pricing_model` (string)
- `product_id` (string)
- `product_type` (string): 'recurring' = a wallet subscription plan
- `status` (string)
- `title` (string)
- `token` (string): Use as product_id in jami_create_checkout
- `url` (string|null): Public Jami page where anyone can buy it (published only)

### `jami_update_product` (~162 tokens)

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).

Input parameters:

- `amount_minor` (integer)
- `description` (string)
- `minimum_amount_minor` (integer)
- `product_id` (string, required): Product p_… token or id
- `response_format` (string): 'markdown' (default) for a readable summary, 'json' for the full structured payload
- `status` (string)
- `title` (string)

Output parameters:

- `amount_minor`
- `billing_interval`
- `interval_count`
- `minimum_amount_minor`
- `price` (string)
- `pricing_model` (string)
- `product_id` (string)
- `product_type` (string): 'recurring' = a wallet subscription plan
- `status` (string)
- `title` (string)
- `token` (string): Use as product_id in jami_create_checkout
- `url` (string|null): Public Jami page where anyone can buy it (published only)

### `jami_get_product` (~127 tokens)

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

Input parameters:

- `product_id` (string, required): Product p_… token or id
- `response_format` (string): 'markdown' (default) for a readable summary, 'json' for the full structured payload

Output parameters:

- `amount_minor`
- `billing_interval`
- `interval_count`
- `minimum_amount_minor`
- `price` (string)
- `pricing_model` (string)
- `product_id` (string)
- `product_type` (string): 'recurring' = a wallet subscription plan
- `status` (string)
- `title` (string)
- `token` (string): Use as product_id in jami_create_checkout
- `url` (string|null): Public Jami page where anyone can buy it (published only)

### `jami_list_products` (~136 tokens)

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

Input parameters:

- `limit` (integer)
- `page` (integer)
- `response_format` (string): 'markdown' (default) for a readable summary, 'json' for the full structured payload
- `status` (string)

Output parameters:

- `count` (integer): Records in this page
- `has_more` (boolean)
- `limit` (integer)
- `next_page` (integer): Pass as `page` to fetch the next page
- `page` (integer)
- `products` (array)
- `total` (integer): Total matching records

### `jami_create_subscription_link` (~390 tokens)

Create subscription link

Create a hosted subscribe link for a recurring product (a plan made with jami_create_product product_type 'recurring'). Send the link to the payer.

Nothing is charged by this call. An API key can only PROPOSE a subscription: the payer opens the link, signs in to Jami, reviews the plan, amount and cadence, and approves with their Jami wallet PIN. Only then is the first period charged from their Jami wallet; Jami renews it every period after that until either side cancels. The link is single use and valid 24 hours.

Args:
  \- product_id: the recurring product's p_… token or id (must be published).
  \- customer_email: optional, prefills / tags the payer.
  \- customer_external_id: optional, your own user id (≤ 200 chars); it comes back on the subscription and its webhooks, and jami_list_subscriptions can filter by it.
  \- success_url / cancel_url: optional absolute http(s) URLs. success_url gets ?subscription_id=sub_… (and {SUBSCRIPTION_ID} is filled).
  \- metadata: optional string map (≤ 20 keys) copied onto the subscription.

Returns: { checkout_id: 'sc_…', url, expires_at, livemode }. Watch for the subscription.created webhook, or call jami_list_subscriptions, to see the approval.

Input parameters:

- `cancel_url` (string)
- `customer_email` (string)
- `customer_external_id` (string)
- `metadata` (object): Your own key/values, copied onto the subscription (max 20 keys)
- `product_id` (string, required): Recurring product p_… token or id
- `response_format` (string): 'markdown' (default) for a readable summary, 'json' for the full structured payload
- `success_url` (string)

Output parameters:

- `checkout_id` (string): sc_… subscribe-link id
- `expires_at` (string)
- `livemode` (boolean)
- `url` (string): Send the payer here to approve

### `jami_list_subscriptions` (~224 tokens)

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

Input parameters:

- `customer_external_id` (string)
- `limit` (integer)
- `page` (integer)
- `product_id` (string): Recurring product p_… token or id
- `response_format` (string): 'markdown' (default) for a readable summary, 'json' for the full structured payload
- `status` (string)

Output parameters:

- `count` (integer): Records in this page
- `has_more` (boolean)
- `limit` (integer)
- `next_page` (integer): Pass as `page` to fetch the next page
- `page` (integer)
- `subscriptions` (array)
- `total` (integer): Total matching records

### `jami_get_subscription` (~194 tokens)

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.

Input parameters:

- `response_format` (string): 'markdown' (default) for a readable summary, 'json' for the full structured payload
- `subscription_id` (string, required): Subscription id, e.g. "sub_9f2kQ…" from jami_list_subscriptions or a subscription.* webhook

Output parameters:

- `charges` (array)
- `subscription` (object)

### `jami_cancel_subscription` (~215 tokens)

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.

Input parameters:

- `at_period_end` (boolean)
- `response_format` (string): 'markdown' (default) for a readable summary, 'json' for the full structured payload
- `subscription_id` (string, required): Subscription id, e.g. "sub_9f2kQ…" from jami_list_subscriptions or a subscription.* webhook

Output parameters:

- `amount` (string)
- `amount_minor` (integer): Charged every period, integer santim (locked at approval)
- `cadence` (string): e.g. "monthly", "every 3 months"
- `cancel_at_period_end` (boolean)
- `canceled_at` (string|null)
- `cancellation_reason` (string|null)
- `created_at` (string|null)
- `current_period_end` (string|null)
- `current_period_start` (string|null)
- `customer_email` (string|null)
- `customer_external_id` (string|null)
- `customer_id` (string)
- `ended_at` (string|null)
- `failed_attempts` (integer)
- `interval` (string)
- `interval_count` (integer)
- `last_failure`
- `livemode` (boolean)
- `metadata` (object)
- `next_charge_at` (string|null): Next renewal or retry; null once canceled or set to end
- `product_id` (string)
- `product_title` (string)
- `status` (string)
- `subscription_id` (string)

### `jami_resume_subscription` (~198 tokens)

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.

Input parameters:

- `response_format` (string): 'markdown' (default) for a readable summary, 'json' for the full structured payload
- `subscription_id` (string, required): Subscription id, e.g. "sub_9f2kQ…" from jami_list_subscriptions or a subscription.* webhook

Output parameters:

- `amount` (string)
- `amount_minor` (integer): Charged every period, integer santim (locked at approval)
- `cadence` (string): e.g. "monthly", "every 3 months"
- `cancel_at_period_end` (boolean)
- `canceled_at` (string|null)
- `cancellation_reason` (string|null)
- `created_at` (string|null)
- `current_period_end` (string|null)
- `current_period_start` (string|null)
- `customer_email` (string|null)
- `customer_external_id` (string|null)
- `customer_id` (string)
- `ended_at` (string|null)
- `failed_attempts` (integer)
- `interval` (string)
- `interval_count` (integer)
- `last_failure`
- `livemode` (boolean)
- `metadata` (object)
- `next_charge_at` (string|null): Next renewal or retry; null once canceled or set to end
- `product_id` (string)
- `product_title` (string)
- `status` (string)
- `subscription_id` (string)

### `jami_simulate_subscription_renewal` (~238 tokens)

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

Input parameters:

- `outcome` (string)
- `response_format` (string): 'markdown' (default) for a readable summary, 'json' for the full structured payload
- `subscription_id` (string, required): Subscription id, e.g. "sub_9f2kQ…" from jami_list_subscriptions or a subscription.* webhook

Output parameters:

- `result` (string)
- `subscription`

### `jami_list_orders` (~223 tokens)

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.

Input parameters:

- `limit` (integer)
- `page` (integer)
- `response_format` (string): 'markdown' (default) for a readable summary, 'json' for the full structured payload
- `status` (string)

Output parameters:

- `count` (integer): Records in this page
- `has_more` (boolean)
- `limit` (integer)
- `next_page` (integer): Pass as `page` to fetch the next page
- `orders` (array)
- `page` (integer)
- `total` (integer): Total matching records

### `jami_get_balance` (~132 tokens)

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.

Input parameters:

- `response_format` (string): 'markdown' (default) for a readable summary, 'json' for the full structured payload

Output parameters:

- `balance` (string)
- `balance_minor` (integer)
- `currency` (string)
- `eligible` (boolean)

### `jami_quote_withdrawal` (~259 tokens)

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.

Input parameters:

- `amount_minor` (integer, required): Gross amount in integer santim — the fee is cut from this. 10000 = 100.00 ETB (the minimum).
- `fee_rate` (number)
- `response_format` (string): 'markdown' (default) for a readable summary, 'json' for the full structured payload
- `tax_rate` (number)

Output parameters:

- `amount_minor` (integer)
- `auto_payout` (boolean): true when ≤ 10,000 ETB — paid out without manual review
- `fee_minor` (integer)
- `fee_rate` (number)
- `meets_minimum` (boolean)
- `net_minor` (integer)
- `tax_minor` (integer)
- `tax_rate` (number)
- `within_daily_cap` (boolean)

### `jami_get_withdrawal` (~84 tokens)

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.

Input parameters:

- `response_format` (string): 'markdown' (default) for a readable summary, 'json' for the full structured payload
- `withdrawal_id` (string, required)

Output parameters:

- `account` (string)
- `amount` (string)
- `amount_minor` (integer): Gross santim debited
- `created_at` (string|null)
- `fee_minor` (integer)
- `gateway` (string)
- `idempotency_key` (string)
- `net` (string)
- `net_minor` (integer): Santim that reaches the destination
- `note` (string)
- `status` (string)
- `tax_minor` (integer)
- `withdrawal_id` (string)

### `jami_list_withdrawals` (~141 tokens)

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

Input parameters:

- `limit` (integer)
- `page` (integer)
- `response_format` (string): 'markdown' (default) for a readable summary, 'json' for the full structured payload
- `status` (string)

Output parameters:

- `count` (integer): Records in this page
- `has_more` (boolean)
- `limit` (integer)
- `next_page` (integer): Pass as `page` to fetch the next page
- `page` (integer)
- `total` (integer): Total matching records
- `withdrawals` (array)

### `jami_verify_webhook` (~208 tokens)

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.

Input parameters:

- `payload` (string, required): Raw request body
- `secret` (string)
- `signature` (string, required): X-JamiDev-Signature header value
- `tolerance_seconds` (integer)

Output parameters:

- `event_id` (string|null)
- `event_type` (string|null)
- `livemode` (boolean|null)
- `reason` (string|null)
- `valid` (boolean)

### `jami_create_test_webhook` (~277 tokens)

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.

Input parameters:

- `data` (object)
- `livemode` (boolean)
- `secret` (string)
- `target_url` (string)
- `type` (string, required)

Output parameters:

- `curl` (string)
- `delivery_error` (string)
- `delivery_status` (integer)
- `headers` (object)
- `payload` (string)
- `signature` (string)

### `jami_search_docs` (~172 tokens)

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).

Input parameters:

- `limit` (integer)
- `query` (string, required)

Output parameters:

- `results` (array)

### `jami_read_doc` (~116 tokens)

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? }.

Input parameters:

- `offset` (integer)
- `path` (string, required)

Output parameters:

- `markdown` (string)
- `next_offset` (integer)
- `offset` (integer)
- `title` (string)
- `url` (string)

## Diagnostics

Captured diagnostic sections: Provenance, Dependencies. The full working is on the page: https://verifymcp.io/servers/kenawak-jami-mcp/jami-mcp#diagnostics

## Score history

- 2026-11-05: 74
- 2026-11-04: 73
- 2026-11-03: 73
- 2026-11-02: 72
- 2026-11-01: 72
- 2026-10-31: 71
- 2026-10-30: 71
- 2026-10-29: 70
- 2026-10-28: 70
- 2026-10-27: 69
- 2026-10-26: 69
- 2026-10-25: 68

## Common questions

### What is the Jami MCP server?

Jami is an MCP server listed in the public MCP registry as io.github.kenawak/jami-mcp. Jami creator payouts for your agent: ETB checkouts, orders, payouts, webhooks. Powered by ArifPay. This page covers its npm package (jami-mcp).

### Is the Jami MCP server safe to use?

Jami scores 74 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 5 November 2026. It declares no install or post-install scripts. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the Jami MCP server expose?

Jami exposes 22 tools: jami_create_checkout, jami_get_checkout_status, jami_wait_for_checkout, jami_create_product, jami_update_product, and 17 more. Their descriptions and schemas cost roughly 5,098 tokens of context every time the server is loaded.

### Is the Jami MCP server still maintained?

Jami is still listed as active in the MCP registry. We last reached this channel on 5 November 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

### What licence is the Jami MCP server under?

Jami declares the MIT licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.

## Links

- npm package: https://www.npmjs.com/package/jami-mcp
- Socket report: https://socket.dev/npm/package/jami-mcp
- Repository: https://github.com/kenawak/jami-mcp
- Website: https://dev.jami.bio/docs/mcp
- Changelog RSS feed: https://verifymcp.io/servers/kenawak-jami-mcp/jami-mcp.xml
- Changelog JSON feed: https://verifymcp.io/servers/kenawak-jami-mcp/jami-mcp.json
- HTML version of this page: https://verifymcp.io/servers/kenawak-jami-mcp/jami-mcp
