# askell-mcp [MCP](https://modelcontextprotocol.io) server for the [Askell](https://askell.is) payment and subscription API. Connect it to Cursor, Claude Desktop, or any MCP client to discover Askell endpoints, inspect customers/contracts/billing, and call the API. Reads and writes are separate tools so clients can show their own approval UI on mutations. ## Requirements - An [Askell](https://askell.is) account and **secret API key** (from the Askell dashboard). The **public** key is optional — only temporary payment-method and checkout status endpoints use it - One of: - [Bun](https://bun.sh) ≥ 1.4.0 (for `bunx`), or - a prebuilt binary from [Releases](https://github.com/Neschadin/askell-mcp/releases) (no Bun needed) ## Quick start Prefer **two server entries** if you have both production and sandbox keys. Tool names are the same on both; the client distinguishes them by the server key (`askell-prod` vs `askell-sandbox`). Each instance's instructions include the environment it is talking to. Put keys in gitignored dotenv files, not in JSON. Copy [`.env.example`](./.env.example): - `.env` — production (`ASKELL_ENV=production` and that dashboard's keys) - `.env.sandbox` — sandbox (`ASKELL_ENV=sandbox` and that dashboard's keys) Bun does not auto-load `.env.sandbox`. `--no-env-file` stops the sandbox process from also reading a production `.env` that happens to sit in the cwd. ### Cursor Project file: `.cursor/mcp.json`. [`mcp.json.example`](./mcp.json.example) is this shape. `${workspaceFolder}` is the directory that contains that `mcp.json` (the repo root when the file is `.cursor/mcp.json`). In `~/.cursor/mcp.json`, use an absolute `envFile` path. **With Bun:** ```json { "mcpServers": { "askell-prod": { "command": "bunx", "args": ["--no-env-file", "x", "askell-mcp"], "envFile": "${workspaceFolder}/.env" }, "askell-sandbox": { "command": "bunx", "args": ["--no-env-file", "x", "askell-mcp"], "envFile": "${workspaceFolder}/.env.sandbox" } } } ``` **With a binary** (download `askell-mcp--` from [Releases](https://github.com/Neschadin/askell-mcp/releases), then `chmod +x`). Same `envFile`; the binary reads the environment Cursor injects: ```json { "mcpServers": { "askell-prod": { "command": "/absolute/path/to/askell-mcp-linux-x64", "envFile": "${workspaceFolder}/.env" } } } ``` Reload the window after saving. ### Claude Desktop Config file: - Linux: `~/.config/Claude/claude_desktop_config.json` - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` - Windows: `%APPDATA%\Claude\claude_desktop_config.json` No `envFile` field. The desktop process cwd is not your repo, so a relative `.env` path does not resolve. With Bun, pass an absolute `--env-file`: ```json { "mcpServers": { "askell-prod": { "command": "bunx", "args": [ "--no-env-file", "--env-file=/absolute/path/.env", "x", "askell-mcp" ] }, "askell-sandbox": { "command": "bunx", "args": [ "--no-env-file", "--env-file=/absolute/path/.env.sandbox", "x", "askell-mcp" ] } } } ``` A binary has no `--env-file`. Put the keys in `env` (plaintext in that JSON file): ```json { "mcpServers": { "askell-prod": { "command": "/absolute/path/to/askell-mcp-linux-x64", "env": { "ASKELL_ENV": "production", "ASKELL_PRIVATE_API_KEY": "your_production_secret_api_key", "ASKELL_PUBLIC_API_KEY": "your_production_public_api_key_optional" } } } } ``` Quit Claude Desktop completely and reopen it. Saving the file is not enough. ### Claude Code Project `.mcp.json` expands `${VAR}` from the environment of the process that launched `claude`. It does not load a dotenv file. The Bun `--env-file` args from the Desktop section work here as well; a relative path is fine when you start `claude` from the repo. `${ASKELL_PRIVATE_API_KEY}` inside `env` only works when that variable is already exported in that environment. A `.env` file alone is not read. ## Configuration | Variable | Required | Default | Description | | ---------------------------------- | -------- | ------------ | ----------------------------------------------------------------------------------- | | `ASKELL_PRIVATE_API_KEY` | yes\* | — | Secret API key (_or_ `ASKELL_SECRET_API_KEY`) | | `ASKELL_PUBLIC_API_KEY` | no | — | Public key for a few checkout/payment endpoints | | `ASKELL_ENV` | no | `production` | `production` \| `sandbox` — selects the official API host | | `ASKELL_API_BASE_URL` | no | — | Custom/local API base only. Do not set together with `ASKELL_ENV` unless it matches | | `ASKELL_RESPONSE_MAX_BYTES` | no | `64000` | Max response size returned to the model | | `ASKELL_MUTATION_GATE` | no | `auto` | `auto` / `elicit` / `off` — see below | | `ASKELL_REQUIRE_MUTATION_APPROVAL` | no | — | Deprecated alias: `true`→`elicit`, `false`→`off` | `ASKELL_ENV` picks a stable host (same v1/v2 surface): - **production** — `https://askell.is/api` - **sandbox** — `https://sandbox.askell.is/api` (isolated tenant; keys from that dashboard) Point a second MCP server entry at sandbox (`ASKELL_ENV=sandbox`) rather than switching env on one process. Keys do not work across hosts. **Áskell Test Gateway** is a payment acquirer (fake cards) on either host — not the same as the sandbox API. Official prose at [docs.askell.is](https://docs.askell.is/en/getting_started/index.html) still documents Test Gateway and may omit the sandbox host. `ASKELL_MUTATION_GATE`: - **`auto` (default)** — confirmation form only if _this request's_ `_meta` envelope declared form elicitation (MCP 2026-07-28). 2025-era clients (Cursor, most hosts) do not send that envelope, so the mutation runs and their own “allow this tool” UI is the gate. - **`elicit`** — always return an elicitation form. The SDK refuses the call if the client cannot fulfil it (2026 envelope / 2025 initialize via the legacy shim). - **`off`** — never ask (eval / trusted automation). If both `ASKELL_MUTATION_GATE` and `ASKELL_REQUIRE_MUTATION_APPROVAL` are set, `ASKELL_MUTATION_GATE` wins. ## What you can do ### Tools | Tool | Description | | --------------------------- | ------------------------------------------------- | | `askell_list_operations` | Search bundled OpenAPI operations | | `askell_describe_operation` | Params and body schema for one operation | | `askell_call` | GET/HEAD any v1/v2 endpoint | | `askell_mutate` | POST/PUT/PATCH/DELETE any v1/v2 endpoint | | `askell_paginate_all` | Follow paginated list endpoints | | `askell_customer_overview` | v1 customer + subscriptions | | `askell_contract_overview` | v2 subscription contract + billing runs | | `askell_billing_run_triage` | v2 billing run (+ optional contract) | | `askell_list_webhooks` | List configured webhooks (`hmac_secret` redacted) | ### Resources | URI | Content | | ------------------------------ | ----------------------- | | `askell://spec/v1` | OpenAPI v1 | | `askell://spec/v2` | OpenAPI v2 | | `askell://docs/webhook-events` | Webhook event reference | Docs: [docs.askell.is](https://docs.askell.is/) · OpenAPI: [v1](https://askell.is/api/swagger/swagger.json) · [v2](https://askell.is/api/swagger/v2/swagger.json) ## License [MIT](./LICENSE) ## Contributing See [CONTRIBUTING.md](./CONTRIBUTING.md) for local development, tests, and releases.