Payment Orchestrator MCP Server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Payment Orchestrator MCP ServerCan you explain why the last transaction was routed to Stripe?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Payment routing orchestrator
Evidence in the hot path, AI at the edges.
Given a card authorization about to be sent, it decides which payment service provider should receive it — on empirical approval evidence, at a fee tolerance the operator states in real units. If the attempt declines, a state machine keyed on the decline's error class decides what happens next: same PSP later, failover now, a different channel, or stop. The routing decision itself is deterministic and auditable; no language model runs inside it.
Built and shipped inside Claude Code: the engine, the AI edge layer, the eval harness, the web UI, the API and this README were produced in agentic sessions — one orchestrating session delegating to subagents (backend, UI, publish, case study) — and are gated by the tests and evals you can run yourself. The engineering discipline is the same one described in nutri.: conventions the agent must load, structural guardrails, and a machine — not a promise — as the definition of done. The guardrails caught what would otherwise have shipped: a rewrite that dropped every API path (found by post-deploy verification), a UI mislabeling valid issuers as unseen, and two wrong assumptions of the author's (a page-count rule, a DNS setup) that subagents refused to act on.
Live demo https://orchestrator.vryahn.com · Case study
https://vryahn.com/work/routing · API api/README.md ·
MCP MCP.md
Out-of-sample replay on 84,011 TEST transactions (days 22–31, tables trained on
days 1–21): expected approval 72.02% at cost_bias=0 against 66.13%
actually observed — +5.89 pp, directional, not an A/B result. See
Limitations.
Run it
Python 3.11. requirements.txt is the runtime — fastapi plus the standard
library, and the only thing Vercel installs. requirements-dev.txt adds the
offline stack (duckdb, pandas, numpy, pyarrow, mcp, uvicorn, httpx) needed to
regenerate data, run the backtest, serve MCP, or test locally.
python3.11 -m venv .venv && .venv/bin/pip install -r requirements-dev.txt
.venv/bin/python cli.py --txn-file demo_transactions.json # 8 decision-boundary cases
.venv/bin/uvicorn api.index:app --reload --port 8000 # API on /api/*, UI from public/The tables the engine reads (routing_tables.json, routing_meta.json) are
committed, so a fresh clone routes — and deploys — without the offline
pipeline. Regenerate them only if the data model changes:
.venv/bin/python synth_attempts.py # seeded ~300k attempts -> attempts.parquet
.venv/bin/python build_routing_tables.py # -> routing_tables.json, routing_meta.json
.venv/bin/python backtest.py --json # -> backtest_summary.json
.venv/bin/python tests.py # engine
.venv/bin/python tests_ai.py # AI edges + HTTP contract
.venv/bin/python evals/decline_eval.py # normalizer against the golden setRelated MCP server: ai-log-mcp-server
Architecture
flowchart LR
subgraph offline["OFFLINE — batch, once per rebuild"]
G["synth_attempts.py<br/>seeded generator"] --> A[("attempts.parquet<br/>1 row = 1 attempt, ~300k")]
A -->|"build_routing_tables.py"| T[("routing_tables.json + routing_meta.json<br/>segment x PSP: n, approvals, p_hat, Wilson LB<br/>4-level hierarchy")]
A -->|"backtest.py — train d1-21, test d22-31"| B["backtest_summary.json<br/>out-of-sample lift"]
end
subgraph edgein["EDGE IN — language to enum"]
RAW["raw PSP decline<br/>ISO 8583 / decline_code / refusalReason / bank prose"] --> N{"decline_normalizer.py<br/>table -> LLM -> safe fallback"}
EV["evals/ — 48 golden declines<br/>accuracy by route, hallucination gate"] -.->|"scores"| N
end
subgraph online["ONLINE — pure engine, never touches raw data"]
X["txn: amount, bin6/issuer, funding,<br/>channel, attempt #, error history"] --> D{"decide(txn, config)"}
T --> D
N -->|"error_class"| D
D --> S1["1. resolve segment per PSP<br/>walk L0 to L3 until n >= min_support"]
S1 --> S2["2. score = Wilson LB x amount x (1 - fee)<br/>= expected net collected"]
S2 --> S3["3. pick PSP — cost_bias 0..1 maps to<br/>fee tolerance 0..10pp; psps_down excluded"]
S3 --> S4["4. retry state machine<br/>keyed on last error_class"]
S4 --> R["Decision: route_psp, eligible_psps with scores,<br/>retry policy, reasoning lines"]
end
R --> OPS["ops.py — route, explain, simulate,<br/>evidence, normalize, backtest"]
B --> OPS
OPS --> CLI["cli.py"]
OPS --> API["api/index.py — FastAPI on Vercel<br/>+ public/ web UI, same origin"]
OPS --> MCP["mcp_server.py — 6 MCP tools"]Why this design
Offline/online split.
decide(txn, config) -> Decisionis pure. It loads pre-materialized tables once and never reads raw attempts, so the decision is microseconds, testable without a database, and auditable after the fact. It is the same boundary you would draw in production between a reconciliation layer and a routing layer.Segment hierarchy with fallback. L0 is
gateway_group × funding × issuer_bucket × amount_band; L3 isgateway_groupalone. Support is resolved per PSP, one dimension at a time (amount band → issuer → funding), until a cell clearsmin_support(default 200), and the level used is reported with the decision. The channel is first-class and never dropped: user-present and off-session are different worlds.Wilson lower bound, not the raw rate. A segment with 3/3 approvals is not a 100% segment. The bound shrinks toward zero as support thins, so a well-evidenced 78% beats a lucky 100% without a separate confidence rule bolted on.
cost_biasas an explicit knob, in real units. The trade-off is stated as "percentage points of approval I will give up for a cheaper PSP" —tolerance = cost_bias × 10pp, and the cheapest PSP within that tolerance of the best approver wins. A blended score would let a fractions-of-a-point fee difference silently outvote a double-digit approval gap; a tolerance filter cannot. The backtest prices the knob: 72.02% / +5.89 pp approval atcost_bias=0, 71.56% / +5.43 pp at 0.5, 69.57% / +3.44 pp at 1.0.Retry by error class, not by a blind counter.
insufficient_fundsis an account problem and retries the same PSP on the next billing window;bank_auth_requiredoff-session cannot be satisfied without the customer, so it reschedules to a user-present channel instead of burning attempts;fraud_riskstops the chain permanently;generic_declinefails over to the next PSP by score. An unrecognized class degrades to the generic failover policy and says so.
Where AI belongs — and where it does not
There is no LLM inside decide(). Money should not move on a sampled token.
The language model is confined to the two edges where natural language actually
is the problem.
In — decline_normalizer.py. Each PSP declines in its own dialect: ISO 8583
numerics, a Stripe-like decline_code, an Adyen-like refusalReason, or raw
bank prose. The retry state machine keys on one enum, so the dialects have to
collapse before the engine sees them. A deterministic table handles the codes
that carry volume — confidence 1.0, no latency, no cost. Only a table miss
reaches the model chain (Gemini, then Mistral), which answers under a
constrained enum schema. Anything outside the enum, or below 0.6 confidence, is
discarded in favour of generic_decline, which is the retry policy's own safe
default. The repo runs green with no API keys set.
Measured, not trusted — evals/. 48 golden declines: ~60% table hits, ~40%
deliberately off-table (misspellings, verbose bank text, unusual codes) plus a
few genuinely ambiguous ones where generic_decline is the correct answer. The
evals/baseline.json records two baselines. Table-only (no keys): 32/48 =
66.67%, i.e. 100% on the 28 table-route cases and the safe generic_decline
default on the 20 that fall through. LLM (keys configured, run against the
deployed API with --remote): 48/48 = 100% — 28 table, 19 answered by
gemini-3.6-flash, 1 by the low-confidence fallback where generic_decline was
the expected answer. The runner reports accuracy by route and a per-class
confusion table, asserts zero hallucinations, and fails the build if accuracy
drops more than 2 pp below the matching baseline.
Out — mcp_server.py. Six MCP tools — route_transaction,
explain_decision, simulate, segment_evidence, normalize_decline,
backtest_summary — let an agent operate the engine in English. The agent can
interrogate every decision and change none of them. See MCP.md.
Limitations
Data is synthetic. The structure is designed to make routing decisions non-trivial, not to reproduce any real portfolio.
No live PSP connectors: the engine decides, it does not send.
No fraud scoring, 3DS orchestration, network tokens, or scheme retry-rule enforcement.
The backtest is directional. Historical routing was not randomized, capacity limits are not modeled, and "expected approval" is a TRAIN-period Wilson-LB rate applied to TEST volume — not a live A/B result.
Tables pool all attempts while the backtest trains and replays on first attempts only; first-attempt-only production tables would be the next fix.
The LLM eval is 48 cases and one run; 100% on a golden set this small is a gate against regressions, not a claim about the long tail in production.
File map
file | purpose |
| seeded generator for |
| builds |
| the engine: |
| PSP decline dialects → the engine's |
| the operator functions (route, explain, simulate, evidence, normalize, backtest) shared by the API and MCP |
| FastAPI on Vercel; contract in |
| MCP stdio server, six tools; see |
| CLI front end: one transaction via flags, or a batch via |
| static web UI, served by Vercel from the same origin as the API |
| 8 decision-boundary transactions with |
| TRAIN days 1–21 / TEST days 22–31 replay at |
| 48 golden declines, the scoring runner, and the recorded baseline |
| assert-based checks: the engine, then the AI edges and the HTTP contract |
Bryan Rodríguez Abarca · vryahn.com · Started from a technical exercise, generalized as a personal project. Synthetic data.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables natural-language investigation of Datadog data including logs, metrics, monitors, traces, hosts, dashboards, events, and incidents, all through read-only API access.2,053MIT
- FlicenseNot gradedqualityBmaintenanceEnables querying and managing AI logs through tools like listing logs, retrieving jobs, and performing AI-powered chat queries. Also provides access to gateway security reports and guardrail testing.
- AlicenseAqualityBmaintenanceInvestigate fraud directly from Claude, Cursor, or any MCP-compatible client. Analyze suspicious activity with clear, evidence-backed verdicts. Pivot from a single signup to every account sharing the same device, IP address, or email inbox. Check entities against a cross-operator abuse network, review linked accounts, and efficiently process your fraud review queue. Read-only by default, with no r10269MIT
- FlicenseNot gradedqualityBmaintenanceEnables operations teams to diagnose and resolve stuck orders via natural-language queries. It provides evidence-based resolution proposals, but any state-changing action requires explicit human confirmation.
Related MCP Connectors
See, price, and control every tool call your AI agents make: policy checks, cost, and audit tools.
A paid remote MCP for AI SDK data query MCP, built to return verdicts, receipts, usage logs, and aud
Enterprise AI Control Plane: governance, guardrails, spend tracking, compliance & smart routing.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/vryahn/payment_orchestrator'
If you have feedback or need assistance with the MCP directory API, please join our Discord server