@4pay.online/mcp-server
v1.0.0
Published
MCP server for 4pay.online platform operators
Maintainers
Readme
@4pay.online/mcp-server
MCP (Model Context Protocol) server for Surelle payment processing platform. Enables AI assistants (Claude, GPT) to interact with Surelle via standardized tools.
Two servers, not one
The platform ships two MCP servers, and they are different programs on purpose:
| Server | Who runs it | What it exposes |
|---|---|---|
| @4pay.online/mcp-server (this package) | the platform operator — the client who owns the organization | the platform itself: terminals, routing, limits, FX, organizations, partners, plus the payment counter |
| @4pay.online/payments-mcp (its own repository) | the merchant — the partner who takes payments | payments, payouts, refunds, payment links, saved cards. Nothing else |
A merchant's agent has no business seeing terminals and routing rules: given them, an agent working through a declined payment will eventually try to "fix" the live routing. That is not a prompt problem — a tool absent from the list cannot be called, whatever anyone types. So the partner package lives in its own repository and contains no operator code at all: not disabled, not filtered, absent.
Hand partners @4pay.online/payments-mcp. Keep this one for the people who run the platform.
Quick Start
Install from npm:
npm install -g @4pay.online/mcp-serverThe package contains its compiled distribution, so installation needs neither the repository nor development dependencies.
Claude Code
Add to .mcp.json in your project root. Credentials come from the environment, so no secrets
live in the file:
{
"mcpServers": {
"surelle": {
"command": "npx",
"args": ["-y", "@4pay.online/mcp-server"],
"env": {
"SURELLE_API_URL": "${SURELLE_API_URL}",
"SURELLE_LOGIN": "${SURELLE_LOGIN}",
"SURELLE_PASSWORD": "${SURELLE_PASSWORD}",
"SURELLE_ORGANIZATION_ID": "${SURELLE_ORGANIZATION_ID}"
}
}
}
}Export the variables before starting the client:
export SURELLE_API_URL=https://sandbox.4pay.online
export [email protected]
export SURELLE_PASSWORD=...
# Organization is optional — see Multi-tenancy
export SURELLE_ORGANIZATION_ID=...Then install the agent skill into the project the agent works from — the server ships with it:
npx -y @4pay.online/mcp-server install-skill /path/to/your/projectThe server logs in on its own — give it a login and password, not a token. It obtains the session
token, renews it before expiry, and logs in again when the server rejects a stale session,
retrying the request. A session token lasts a week, so a hand-copied SURELLE_BEARER_TOKEN stops
working silently — use it only for one-off runs or when there is no password (SSO).
Two-factor authentication is required by default; see below.
Updating
npm install -g fails with ENOTEMPTY while the server is running — npm renames the package
directory, and a live process holds it. Stop the MCP server in your client first (or close the
session), then update:
npm install -g @4pay.online/mcp-serverA running client keeps the old build in memory regardless: restart the MCP server to pick up a new version.
Building from source
git clone [email protected]:heth_project/surelle-mcp.git && cd surelle-mcp
npm install
npm run build # dist/ is committed, but rebuild after editing src/
npm testPoint .mcp.json at node /abs/path/to/surelle-mcp/dist/index.js instead of npx.
The catalog, typed tools and wizard registry are committed, so a fresh clone works without the
neighbouring repositories. Regenerating them (npm run generate:all) does need surelle and
admin_area checked out next to this one — see Generated artifacts.
Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json — same shape, but write
the values literally, since Desktop does not read your shell environment:
{
"mcpServers": {
"surelle": {
"command": "node",
"args": ["/path/to/surelle-mcp/dist/index.js"],
"env": {
"SURELLE_API_URL": "https://sandbox.4pay.online",
"SURELLE_API_KEY": "your-api-key"
}
}
}
}Configuration
| Environment Variable | Required | Default | Description |
|---------------------|----------|---------|-------------|
| SURELLE_API_URL | No | http://localhost:4000 | Surelle API base URL |
| SURELLE_API_KEY | Yes* | — | Partner API key (X-API-Key header) |
| SURELLE_LOGIN | Yes* | — | Login for automatic sign-in |
| SURELLE_PASSWORD | Yes* | — | Password, paired with SURELLE_LOGIN |
| SURELLE_ACCOUNT_TYPE | No | client | Role to sign in as: client / partner / admin / person |
| SURELLE_TOTP | No | — | One-time code when 2FA is enabled |
| SURELLE_BEARER_TOKEN | Yes* | — | Ready session token; the server cannot renew it |
| SURELLE_ORGANIZATION_ID | No | — | Default tenant → x-organization-id header. Required for admin tokens |
| SURELLE_TIMEOUT | No | 30000 | Request timeout (ms) |
| SURELLE_RETRY_COUNT | No | 2 | Retry count for 5xx errors |
*At least one way to authenticate: SURELLE_LOGIN + SURELLE_PASSWORD (preferred),
SURELLE_BEARER_TOKEN, or SURELLE_API_KEY.
SURELLE_API_KEY authenticates a partner, not a client: it opens the payment surface
(transactions, links, products, recurring payments) and nothing of the organization's settings.
The two can be set together — the server sends both, so payment calls go as the partner while
settings go as the client.
Tools
The API exposes 1283 operations. Registering one flat tool per operation would make tools/list
weigh ~580 KB and crowd out the actual task, so coverage is split in two: typed tools for the
hot path, a catalog for everything else. Total footprint: 22 tools, ~15 KB.
Catalog — all 1283 operations
| Tool | Description |
|------|-------------|
| surelle_list_domains | 82 domains with operation counts — start here when you don't know where a setting lives |
| surelle_search_operations | Find operations by keywords, domain, method, read-vs-write |
| surelle_describe_operation | Full parameter and body schema for one operation |
| surelle_call_operation | Execute any operation. State-changing ones require confirm=true |
Operator handbook
| Tool | Description |
|------|-------------|
| surelle_search_help | Search 224 handbook articles (ru/en) — what a thing is for and when it is used |
| surelle_get_help | One article in full: concepts, worked example, pitfalls |
Pass locale: "en" for English; Russian is the default. Both translations are complete — 224
articles each. Wizard titles inside an English article stay Russian: the wizard registry is
extracted in one language only.
The API catalog answers "how do I call this"; the handbook answers "when and why". Articles that share a route with a wizard point at it.
Setup wizards
| Tool | Description |
|------|-------------|
| surelle_list_wizards | 64 guided setup flows mirrored from admin_area, all with their API operations resolved |
| surelle_get_wizard | Prerequisites, steps, required permissions and the operations behind one wizard |
Typed tools — hot path
49 generated tools cover the settings domains an operator touches constantly: terminals and
providers, routing rules and health, limits and blacklists, FX rates and currencies,
organizations and members, clients, partners, marketplace modules, pricing plans. Generated from
the spec — the operation list lives in CURATED in scripts/build-typed-tools.ts.
16 hand-written tools cover transactions, wallets, ledger, cards, invoices and audit log.
All of them accept an optional organization_id — see Multi-tenancy below. State-changing ones
take confirm, which the dispatcher requires before it will send the request.
Platform AI tools — discovered, not hard-coded
The platform runs its own tool registry inside Elixir (Surelle.AI.ToolRegistry): permissions,
denylist, input schema, tenant scoping, audit and tier-3 confirmation all live there. This server
does not keep a copy of that list — it asks for it.
| Tool | Description |
|------|-------------|
| surelle_ai_tools | Fetch GET /api/v1/ai/tools and re-register the ai_* set. page_domain narrows the catalog |
| surelle_ai_invoke | Call a platform tool by its own name — the fallback when the catalog did not load at startup |
Every catalog entry is also registered as a real MCP tool named ai_<platform name>, whose
handler is a thin proxy onto POST /api/v1/ai/tools/{name}/invoke. The prefix is not decoration:
seven platform names (get_transaction, get_wallet_balance, get_ledger_entries,
get_partner, get_terminal, get_af_aggregates, get_routing_health) collide with tools this
server already ships, and registering over them would break clients that are already configured.
The catalog is fetched once after connect and refreshed by surelle_ai_tools. A failure is never
fatal — the server starts without ai_* tools and says why on stderr. The contour sits behind
the copilot_ai marketplace module and is served only to client roles under a session: a
partner API key gets nothing, so discovery is skipped when no login or bearer token is configured.
Tier 3 (actions). A tool marked confirmation_required refuses to run without confirm=true
— nothing reaches the platform until the operator has seen what would change. With confirm=true
the server passes the two-factor elevation window, takes a one-time token from
POST /api/v1/ai/tools/{name}/confirmations and only then invokes. If the platform still answers
confirmation_required (expired token, arguments changed since it was issued), that comes back as
an error saying the action was not performed — never as an empty success.
Amounts
Amounts cross the API as integer minor units. How many of them make one major unit is a property
of the currency and the environment, not of its symbol: USDT is 6 decimals on mainnet and 18
on sepolia, ETH is 18, BTC 8, and the platform moved IDR from 2 to 0 by migration on live
data. So this server keeps no currency table. It asks the platform — GET /api/v1/currencies
(exponent) and GET /api/v1/crypto/currencies?env= (decimals) — caches the answer per
environment, and does the arithmetic in BigInt (1 ETH is 10¹⁸ minor units, past
Number.MAX_SAFE_INTEGER).
When the scale is unknown the amount is printed as an integer with a minor units marker rather
than divided by 100. Both reference endpoints require a client or admin session, so a server
configured with only SURELLE_API_KEY will see amounts in that unformatted form — honest, and
preferable to a number that is wrong by 10¹⁶.
Client role
The server runs under a client token — an organization owner, not a platform admin. That shapes what is reachable: of 1283 operations, roughly 430 are open to a client, ~370 open up by activating a marketplace module, ~180 depend on a BaaS capability of the organization, and ~150 are admin-only.
The server logs in on its own — give it a login and password, not a token:
export [email protected]
export SURELLE_PASSWORD=...
export SURELLE_ACCOUNT_TYPE=client # default; the endpoint itself defaults to 'partner'It obtains the session token, renews it before expiry, and logs in again when the server rejects
a stale session, retrying the request. A session token lasts a week, so a hand-copied
SURELLE_BEARER_TOKEN stops working silently — use it only for one-off runs or when there is no
password (SSO).
Every operation in the catalog carries its access state, so the agent sees the obstacle before
spending a call: open, needs a module (activatable by the client), needs a BaaS capability, or
admin-only. surelle_search_operations hides admin-only operations by default; pass
include_admin_only: true to see them.
The access map lives in data/access-probe.json and was produced by probing the sandbox with a
real client token. Direct observations are authoritative; states inferred for neighbouring routes
are labelled as such and never block a call — that inference is right about 88% of the time.
Two-factor authentication
The server acts as an organization owner and can change production settings, so it requires a second factor by default:
- It refuses to start if the account has 2FA disabled. Set
SURELLE_REQUIRE_2FA=falseto work without it, accepting the risk. - Sign-in asks for a code. Surelle answers
428withtotp_required, and the server asks the operator through the MCP client — the code comes from a phone, never from a config file. - Writes need an elevation window. The first state-changing operation asks for a code; after
that, changes go through without further prompts for
SURELLE_ELEVATION_MINUTES(default 15). Reads never ask for anything.
The TOTP secret is deliberately not accepted as configuration. A secret sitting next to the
password in the same .env is reachable by the same access to the machine — that is a second
password, not a second factor.
The client must be able to prompt the user (MCP elicitation). Where it cannot — cron, CI — the server says so plainly instead of silently proceeding without the second factor.
Multi-tenancy
Surelle isolates every tenant and resolves the organization from the x-organization-id header.
A token does not carry one on its own.
You usually do not need to look the id up. If the account belongs to exactly one organization,
the server resolves it on the first call and says which one it picked. With several, it refuses
to guess and lists them with their ids — pass organization_id per call, or set
SURELLE_ORGANIZATION_ID. surelle_my_organizations shows the same list on demand.
Working through a partner API key needs none of this: the key itself carries the organization.
Operations whose path already contains {organization_id} take it as a path parameter instead —
surelle_describe_operation says which case applies.
Generated artifacts
Two artifacts are snapshots of other repositories and must be regenerated when those change:
| Artifact | Source | Command |
|----------|--------|---------|
| src/generated/catalog.json | surelle/openapi.yaml | npm run generate |
| src/generated/typed-tools.ts | the catalog + CURATED list | npm run generate:typed |
| wizards/*.yaml | admin_area routes, pages, hooks, services, i18n | npm run generate:wizards |
| src/generated/help.json | admin_area help registry + ru/en locales | npm run generate:help |
The wizard extractor resolves a wizard's page through the routes in App.tsx (names alone are
unreliable), then follows its calls through hooks, service objects, named service functions and
shared components down to concrete endpoints, matching them against the catalog.
npm run generate:all does all three. npm run check:drift verifies they still match their sources
and fails if not — run it in CI, otherwise the server keeps answering, just untruthfully.
Paths default to ../surelle/openapi.yaml and ../../3ds/admin_area; override with arguments or
the SURELLE_OPENAPI / ADMIN_AREA_PATH environment variables.
Forward-router paths
282 of the 1283 operations arrive from the spec with a truncated path: OpenApiSpex writes the path
inside a forward-mounted router without its prefix, so /standing-orders really means
/api/v1/baas/standing-orders. The generator restores the prefix from the controller namespace.
Any new forward in the router needs an entry in FORWARD_PREFIXES in scripts/build-catalog.ts,
otherwise its operations will 404.
Skill
skills/surelle-settings/ holds the agent skill that drives this server: the working order,
money and environment rules, the confirmation protocol for state-changing calls, and how to walk
an operator through a wizard step by step.
Install it into a project the agent works from:
npm run install:skill -- /path/to/projectDevelopment
npm install
npm run generate:all # Rebuild catalog + wizard registry from sources
npm run build # TypeScript compilation (+ copies the catalog into dist)
npm test # Run tests
npm run check:drift # Verify generated artifacts match their sources
npm run dev # Watch mode (tsx)Security
- PCI DSS compliant: card data is tokenized, never exposed as PAN/CVV
- Amounts in minor currency units (kopecks/cents)
- API keys transmitted via secure headers only
- All requests over HTTPS in production
License
MIT
