alopeyk-mcp
<div align="center">
<img src="assets/banner.svg" alt="alopeyk-mcp" width="100%" />
<h1>
alopeyk-mcp
</h1>
**A self-hostable [Model Context Protocol](https://modelcontextprotocol.io) server for [Alopeyk](https://alopeyk.com) (الوپیک) — an Iranian on-demand delivery & logistics company.**
[](https://github.com/alopeyk-mcp/alopeyk-mcp/actions/workflows/ci.yml)
[](https://github.com/alopeyk-mcp/alopeyk-mcp/actions/workflows/docker.yml)
[](https://www.python.org/)
[](LICENSE)
</div>
---
> **Unofficial integration.** This is a community-built MCP server. The
> "Alopeyk" and "Alonomic" names and logos belong to their owner. Not affiliated
> with, endorsed by, or sponsored by Alopeyk. Creating real deliveries/parcels
> incurs real charges — use the order tools at your own risk.
`alopeyk-mcp` exposes Alopeyk's APIs as MCP **tools** and **prompts** so an LLM
agent (Claude, etc.) can geocode addresses, quote and track deliveries, manage
business parcels, and — when you explicitly allow it — create and cancel real
deliveries/parcels. It is a small, stateless, async Python process you run
yourself.
It mirrors the structure and quality of the sibling `ramzinex-mcp` / `roshan-*`
servers: typed `httpx` client, `pydantic-settings` configuration, full test
suite, Docker / Helm / Kubernetes / Terraform deploy assets, and generated
architecture diagrams.
## Two services in one server
Alopeyk runs **two distinct API services**, and this server wraps both behind one
process, routing each call to the right base automatically:
| Service | Base URL | Auth | Tool prefix |
| --- | --- | --- | --- |
| **On-Demand** | `https://api.alopeyk.com/api/v2/` (sandbox: `https://sandbox-api.alopeyk.com/api/v2/`) | `Authorization: Bearer <access_token>` (pre-issued JWT) | `alopeyk_` |
| **Alonomic** (الونومیک) | `https://api.alopeyk.com/business-service/api/v1/` | email/password → token (cached, re-login on 401), or pre-issued `alonomic_token` | `alonomic_` |
> ### 🔐 Required header
> **Every** call to **both** services carries:
> ```
> X-Requested-With: XMLHttpRequest
> Content-Type: application/json; charset=utf-8
> ```
> The server adds these automatically.
> ### ⚠️ Ordering safety
> Creating, updating, cancelling, or finishing **real** deliveries/parcels (and
> buying loyalty products) is gated by a single per-instance flag, default
> **`false`**:
> - **`enable_ordering`** gates `alopeyk_create_order`, `alopeyk_cancel_order`,
> `alopeyk_finish_order`, `alopeyk_loyalty_products(buy=true)`,
> `alonomic_create_parcel`, `alonomic_update_parcel`, and
> `alonomic_cancel_parcel`.
>
> A master **`read_only`** flag forces ordering off regardless of its value. When
> the gate is off the tool returns a structured refusal and **never calls the
> API**:
> ```json
> {"error": "ordering_disabled", "message": "Set enable_ordering=true (and read_only=false) to create/modify real deliveries/parcels."}
> ```
> Read-only tools (geocoding, pricing, viewing orders/parcels, days, sizes,
> address book) are always allowed. The access token, the Alonomic login token,
> and the password are never logged and are redacted from every error message.
> Production vs. sandbox — selected per instance by `environment`:

## Install
```bash
pip install -e . # from a checkout
# or, for development:
pip install -e ".[dev]"
```
Requires Python 3.11+.
## Quick start
The offline/static tools (docs, transport types, cities, URL helpers, parcel
statuses) work with zero configuration:
```bash
python -m alopeyk_mcp # stdio transport (default)
```
To use On-Demand tools, provide the pre-issued JWT; to use Alonomic tools,
provide an email + password (or a pre-issued `alonomic_token`):
```bash
export ALOPEYK_ENVIRONMENT=production # or sandbox
# On-Demand (/api/v2/):
export ALOPEYK_ACCESS_TOKEN=your-ondemand-jwt
# Alonomic (/business-service/): email + password login (cached, re-login on 401)
export ALOPEYK_EMAIL=ops@example.com
export ALOPEYK_PASSWORD=your-password
# ...or a pre-issued Alonomic token instead:
# export ALOPEYK_ALONOMIC_TOKEN=your-alonomic-token
# Opt in to the gated mutation tools (default false):
export ALOPEYK_ENABLE_ORDERING=true # allow create/cancel/finish + parcels
python -m alopeyk_mcp
```
Run over HTTP for networked clients:
```bash
python -m alopeyk_mcp --transport streamable-http --host 0.0.0.0 --port 8000
```
> Order lifecycle — creating/cancelling is gated by `enable_ordering`:

## Configuration (multi-account / multi-instance)
Configuration is read from environment variables via `pydantic-settings`. One
process can front **many Alopeyk accounts/environments** — each is a named
*instance* with its own credentials and safety policy. Every API tool accepts an
optional `instance` argument; omit it to use the default.
### Shorthand (single default instance)
| Variable | Default | Description |
| --- | --- | --- |
| `ALOPEYK_ENVIRONMENT` | `production` | `production` or `sandbox` (selects default base/tracking URLs). |
| `ALOPEYK_ACCESS_TOKEN` | — | On-Demand JWT (`Authorization: Bearer`). |
| `ALOPEYK_EMAIL` | — | Alonomic login email (with `ALOPEYK_PASSWORD`). |
| `ALOPEYK_PASSWORD` | — | Alonomic login password. |
| `ALOPEYK_ALONOMIC_TOKEN` | — | Pre-issued Alonomic token (wins over email/password). |
| `ALOPEYK_ENABLE_ORDERING` | `false` | Allow creating/modifying real deliveries/parcels. |
| `ALOPEYK_READ_ONLY` | `false` | Master switch: forces ordering off. |
| `ALOPEYK_ONDEMAND_BASE_URL` | derived | Override the On-Demand base URL. |
| `ALOPEYK_BUSINESS_BASE_URL` | derived | Override the Alonomic base URL. |
| `ALOPEYK_TRACKING_URL` | derived | Override the tracking web-app base URL. |
| `ALOPEYK_VERIFY_SSL` | `true` | Verify TLS certificates. |
| `ALOPEYK_TIMEOUT` | `30` | Per-request timeout (seconds). |
| `ALOPEYK_DEFAULT_INSTANCE` | `default` | Instance used when `instance` is omitted. |
| `ALOPEYK_LOG_LEVEL` | `INFO` | `DEBUG` / `INFO` / `WARNING` / ... |
### Nested (named instances)
| Variable | Description |
| --- | --- |
| `ALOPEYK__INSTANCES__<NAME>__ENVIRONMENT` | `production` / `sandbox` for `<NAME>`. |
| `ALOPEYK__INSTANCES__<NAME>__ACCESS_TOKEN` | On-Demand JWT for `<NAME>`. |
| `ALOPEYK__INSTANCES__<NAME>__EMAIL` | Alonomic login email for `<NAME>`. |
| `ALOPEYK__INSTANCES__<NAME>__PASSWORD` | Alonomic login password for `<NAME>`. |
| `ALOPEYK__INSTANCES__<NAME>__ALONOMIC_TOKEN` | Pre-issued Alonomic token for `<NAME>`. |
| `ALOPEYK__INSTANCES__<NAME>__ENABLE_ORDERING` | Ordering switch for `<NAME>`. |
| `ALOPEYK__INSTANCES__<NAME>__READ_ONLY` | Master switch (forces ordering off) for `<NAME>`. |
| `ALOPEYK__INSTANCES__<NAME>__ONDEMAND_BASE_URL` | On-Demand base for `<NAME>`. |
| `ALOPEYK__INSTANCES__<NAME>__BUSINESS_BASE_URL` | Alonomic base for `<NAME>`. |
| `ALOPEYK__INSTANCES__<NAME>__TRACKING_URL` | Tracking base for `<NAME>`. |
| `ALOPEYK__INSTANCES__<NAME>__VERIFY_SSL` | TLS verification (default `true`). |
| `ALOPEYK__INSTANCES__<NAME>__TIMEOUT` | Per-request timeout seconds (default `30`). |
| `ALOPEYK__DEFAULT_INSTANCE` | Instance used when `instance` is omitted. |
| `ALOPEYK__LOG_LEVEL` | `DEBUG` / `INFO` / `WARNING` / ... |
Example: a production business account, a sandbox account, and a strictly
read-only viewer.
```bash
ALOPEYK__INSTANCES__BIZ__ENVIRONMENT=production
ALOPEYK__INSTANCES__BIZ__ACCESS_TOKEN=prod-ondemand-jwt
ALOPEYK__INSTANCES__BIZ__EMAIL=ops@example.com
ALOPEYK__INSTANCES__BIZ__PASSWORD=secret
ALOPEYK__INSTANCES__BIZ__ENABLE_ORDERING=true
ALOPEYK__INSTANCES__SANDBOX__ENVIRONMENT=sandbox
ALOPEYK__INSTANCES__SANDBOX__ACCESS_TOKEN=sandbox-jwt
ALOPEYK__INSTANCES__SANDBOX__ENABLE_ORDERING=true
ALOPEYK__INSTANCES__READONLY__ACCESS_TOKEN=viewer-jwt
ALOPEYK__INSTANCES__READONLY__READ_ONLY=true
ALOPEYK__DEFAULT_INSTANCE=biz
```
See [`.env.example`](.env.example) for a complete, annotated file. Use
`list_instances` to see what's configured (names, environment, base URLs,
`has_ondemand_credentials`, `has_alonomic_credentials`, `alonomic_auth_method`,
`enable_ordering`, `read_only`, `ordering_allowed`) — it never reveals credential
values.
## Control flags & safety
| Flag | Default | Gates | Effective when |
| --- | --- | --- | --- |
| `enable_ordering` | `false` | `alopeyk_create_order`, `alopeyk_cancel_order`, `alopeyk_finish_order`, `alopeyk_loyalty_products(buy=true)`, `alonomic_create_parcel`, `alonomic_update_parcel`, `alonomic_cancel_parcel` | `enable_ordering=true` **and** `read_only=false` |
| `read_only` | `false` | forces `enable_ordering` off | — |
When the gate is off, the tool returns
`{"error": "ordering_disabled", "message": ...}` and **never contacts the API**.
### Authentication
- **On-Demand** — a pre-issued `access_token` (JWT), sent as
`Authorization: Bearer <token>`. Required for all `alopeyk_*` network tools.
- **Alonomic** — either a pre-issued `alonomic_token`, or `email` + `password`
which the server POSTs to `login`, caching the returned token in memory per
instance, reusing it, and re-authenticating once on a `401`. Call
`alonomic_login` to trigger/verify the login explicitly (it never returns the
token).
The access token, the Alonomic token, and the password are never logged or
echoed (`Authorization` / `access_token` / `alonomic_token` / `token` /
`password` are all redacted from errors).
> The `read_only` master switch overrides the `enable_ordering` gate:

## Use with an MCP client
Add it to your client's MCP server config (stdio):
```json
{
"mcpServers": {
"alopeyk": {
"command": "python",
"args": ["-m", "alopeyk_mcp"],
"env": {
"ALOPEYK_ENVIRONMENT": "production",
"ALOPEYK_ACCESS_TOKEN": "your-ondemand-jwt",
"ALOPEYK_EMAIL": "ops@example.com",
"ALOPEYK_PASSWORD": "your-password",
"ALOPEYK_ENABLE_ORDERING": "false"
}
}
}
}
```
## Tools
All 34 tools (17 On-Demand `alopeyk_*` + 14 Alonomic `alonomic_*` + 3 meta)
accept an optional `instance` except the two purely-local meta tools
(`list_instances`, `alopeyk_docs`).
### On-Demand (`alopeyk_`) — `/api/v2/`, JWT Bearer
| Tool | Method · Endpoint | Notes |
| --- | --- | --- |
| `alopeyk_get_address(lat, lng)` | `GET locations?latlng=<lat>,<lng>` | reverse geocode |
| `alopeyk_location_suggestion(input, latlng?)` | `GET locations?input=&location=` | autocomplete |
| `alopeyk_get_price(transport_type, addresses, has_return?, cashed?, optimize?)` | `POST orders/price/calc` | read-only quote |
| `alopeyk_get_batch_price(orders)` | `POST orders/batch-price` | ≤15 orders |
| `alopeyk_create_order(transport_type, addresses, has_return?, cashed?, scheduled_at?, discount_coupon?)` | `POST orders` | ⚠️ `enable_ordering` |
| `alopeyk_get_order(order_id)` | `GET orders/{id}?columns=...` | read-only |
| `alopeyk_cancel_order(order_id, comment)` | `GET orders/{id}/cancel?comment=` | ⚠️ `enable_ordering` |
| `alopeyk_finish_order(order_id, comment?, rate?)` | `POST orders/{id}/finish` | ⚠️ `enable_ordering` |
| `alopeyk_get_config()` | `GET config` | read-only |
| `alopeyk_get_profile()` | `GET show-profile?columns=*,credit` | read-only |
| `alopeyk_validate_coupon(code)` | `POST coupons` | read-only |
| `alopeyk_loyalty_products(product_id?, buy?)` | `GET\|POST loyalty/customer/products/{id?}` | buy ⚠️ `enable_ordering` |
| `alopeyk_tracking_url(order_token)` | _local_ `<tracking>#/<token>` | no network |
| `alopeyk_print_invoice_url(order_id, order_token)` | _local_ | no network |
| `alopeyk_payment_route(user_id, amount, gateway=saman)` | _local_ (saman\|zarinpal) | no network |
| `alopeyk_transport_types()` | _local_ | motorbike, motor_taxi, cargo, cargo_s, car |
| `alopeyk_cities()` | _local_ | tehran, shemiranat, rey, karaj, isfahan, tabriz, mashhad, shiraz |
### Alonomic (`alonomic_`) — `/business-service/api/v1/`, email/password login
| Tool | Method · Endpoint | Notes |
| --- | --- | --- |
| `alonomic_login()` | `POST login` | caches token (never returns it) |
| `alonomic_get_me()` | `GET me` | read-only |
| `alonomic_get_days()` | `GET days` | read-only |
| `alonomic_get_days_index()` | `GET days/index` | 7 days, each `{same, next}` |
| `alonomic_create_parcel(drop, parcel, pickup, delivery_proofs?, parcel_extra_params?)` | `POST parcels` | ⚠️ `enable_ordering` |
| `alonomic_get_parcel(parcel_id)` | `GET parcels/{id}` | read-only |
| `alonomic_update_parcel(parcel_id, ...)` | `PUT parcels/{id}` | ⚠️ `enable_ordering` |
| `alonomic_cancel_parcel(parcel_id)` | `DELETE parcels/{id}` | ⚠️ `enable_ordering` (204) |
| `alonomic_calc_parcel(drop, parcel, pickup?)` | `POST parcels/calc` | read-only estimate |
| `alonomic_get_parcel_sizes()` | `GET parcels/size` | 12 box sizes |
| `alonomic_get_saved_addresses()` | `GET pickup-saved-addresses` | read-only |
| `alonomic_add_saved_address(title, lat, lng, address, ...)` | `POST pickup-saved-addresses` | address book |
| `alonomic_delete_saved_address(address_id)` | `DELETE pickup-saved-addresses/{id}` | address book (204) |
| `alonomic_parcel_statuses()` | _local_ | static status → Persian label map |
### Meta
| Tool | Description |
| --- | --- |
| `healthcheck` | Calls the On-Demand `config` endpoint and reports reachability. |
| `list_instances` | Lists configured instances (environment, base URLs, which creds set, `enable_ordering`, `read_only`, `ordering_allowed`) — never credential values. |
| `alopeyk_docs(topic?)` | Offline reference for both services + links to https://docs.alopeyk.com. |
⚠️ = creates/modifies a real delivery/parcel; gated by `enable_ordering` (forced
off by `read_only`).
> All 34 tools at a glance, grouped by service:

## Prompts
The server also ships MCP **prompts** — safety-aware workflows your client can
list and run (details in [`prompts/README.md`](prompts/README.md)):
- **`quote_delivery`** — geocode origin/destination, then quote with
`alopeyk_get_price`.
- **`create_delivery_safely`** — a careful checklist that verifies
`enable_ordering`, confirms addresses/type/price, then creates the order.
- **`track_order`** — `alopeyk_get_order` + `alopeyk_tracking_url`.
- **`alonomic_send_parcel`** — login → `days/index` → `calc` → create the parcel
safely.
- **`address_book`** — review Alonomic saved addresses + parcel box sizes.
## Skill
A ready-to-use Claude/agent skill lives at
[`skills/alopeyk/SKILL.md`](skills/alopeyk/SKILL.md). It describes when and how to
use these tools across both services — quoting/tracking, the safe delivery flow,
and the Alonomic parcel flow — with example tool sequences.
## Architecture
The MCP client talks to one `alopeyk-mcp` process, which routes `alopeyk_*` calls
to the On-Demand API (`/api/v2/`) and `alonomic_*` calls to the Alonomic business
API (`/business-service/api/v1/`).

One process can serve many accounts/environments; the `instance` argument selects
which credentials / ordering policy to use.

A typical On-Demand flow: suggest/geocode an address, quote the price, check the
`enable_ordering` gate, then create and track the order.

The full tool surface, grouped by service:
The order lifecycle and the Alonomic parcel flow:

The `read_only` master switch over `enable_ordering`, and the two environments:
Regenerate the diagrams with `make diagrams` (uses the `diagrams` package +
Graphviz `dot` and `cairosvg`).
## Self-hosting & scaling
`alopeyk-mcp` is stateless, so you can run as many replicas as you like behind a
load balancer. One process fronts multiple accounts via
`ALOPEYK__INSTANCES__<NAME>__*` — no code change. Back off on HTTP 429 if Alopeyk
rate-limits you. See [`deploy/`](deploy/) for Docker Compose, Kubernetes (raw +
Kustomize), a Helm chart, and a Terraform module.
## Testing
```bash
make smoke # no-network smoke test (tools + prompts + descriptions)
make test # full pytest suite (offline; all HTTP incl. Alonomic login mocked with respx)
make lint # ruff
```
Live tests against the real API are skipped unless `ALOPEYK_LIVE=1` is set (and
the relevant credentials provided):
```bash
ALOPEYK_LIVE=1 ALOPEYK_ACCESS_TOKEN=... pytest tests/live -q
```
## License
[MIT](LICENSE). `alopeyk-mcp` is an unofficial, community-built integration; the
Alopeyk and Alonomic names and logos belong to their owner.
TDQS
Scored across 34 tools
Tools are clearly separated by prefix (alopeyk_ vs alonomic_) and each has a distinct purpose. Even within groups, tools like alopeyk_get_price and alopeyk_get_batch_price are well-differentiated. Overlap is minimal.
The majority follow a verb_noun pattern with consistent prefixes. However, a few tools (healthcheck, list_instances, alopeyk_payment_route, alopeyk_print_invoice_url) break the pattern, causing minor inconsistency.
34 tools is on the higher side but reasonable for a server covering two distinct service APIs with full CRUD and utility operations. Each tool earns its place without unnecessary duplication.
The tool set covers core operations for both Alopeyk and Alonomic services, but lacks listing tools for orders and parcels. Agents cannot retrieve a list of existing orders or parcels, which is a notable gap.