Skip to main content
Glama
vryahn

Payment Orchestrator MCP Server

by vryahn

Zahlungsrouting-Orchestrator

Evidenz im heißen Pfad, KI an den Rändern.

Bei einer anstehenden Kartenautorisierung entscheidet er, welcher Zahlungsdienstleister sie erhalten soll – auf Basis empirischer Genehmigungs-Evidenz, innerhalb einer Gebührentoleranz, die der Betreiber in realen Einheiten angibt. Wenn der Versuch abgelehnt wird, entscheidet eine Zustandsmaschine, die auf der Fehlerklasse der Ablehnung basiert, was als Nächstes passiert: derselbe PSP später, Failover jetzt, ein anderer Kanal oder Stopp. Die Routing-Entscheidung selbst ist deterministisch und prüfbar; in ihr läuft kein Sprachmodell.

Gebaut und ausgeliefert in Claude Code: Die Engine, die KI-Randschicht, die Eval-Harness, die Web-UI, die API und diese README wurden in agentischen Sitzungen erstellt – eine orchestrierende Sitzung, die an Subagenten (Backend, UI, Publish, Fallstudie) delegierte – und sind durch die Tests und Evals abgesichert, die Sie selbst ausführen können. Die technische Disziplin ist dieselbe wie in nutri. beschrieben: Konventionen, die der Agent laden muss, strukturelle Leitplanken und eine Maschine – kein Versprechen – als Definition von „fertig". Die Leitplanken fingen ab, was sonst ausgeliefert worden wäre: ein Rewrite, das jeden API-Pfad verlor (gefunden durch Post-Deploy-Verifikation), eine UI, die gültige Emittenten als unbekannt kennzeichnete, und zwei falsche Annahmen des Autors (eine Seitenzahl-Regel, ein DNS-Setup), die Subagenten sich weigerten umzusetzen.

Live-Demo https://orchestrator.vryahn.com · Fallstudie https://vryahn.com/work/routing · API api/README.md · MCP MCP.md

Out-of-Sample-Replay auf 84.011 TEST-Transaktionen (Tage 22–31, Tabellen trainiert auf Tagen 1–21): erwartete Genehmigungsrate 72,02 % bei cost_bias=0 gegenüber 66,13 % tatsächlich beobachtet – +5,89 Prozentpunkte, richtungsweisend, kein A/B-Ergebnis. Siehe Einschränkungen.

Ausführen

Python 3.11. requirements.txt ist die Laufzeit – fastapi plus die Standardbibliothek, und das Einzige, was Vercel installiert. requirements-dev.txt ergänzt den Offline-Stack (duckdb, pandas, numpy, pyarrow, mcp, uvicorn, httpx), der benötigt wird, um Daten neu zu erzeugen, den Backtest auszuführen, MCP zu bedienen oder lokal zu testen.

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/

Die Tabellen, die die Engine liest (routing_tables.json, routing_meta.json), sind eingecheckt, sodass ein frischer Klon routet – und deployt – ohne die Offline-Pipeline. Regenerieren Sie sie nur, wenn sich das Datenmodell ändert:

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

Related MCP server: ai-log-mcp-server

Architektur

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"]

Warum dieses Design

  • Offline/Online-Trennung. decide(txn, config) -> Decision ist rein. Sie lädt vorab materialisierte Tabellen einmal und liest nie rohe Versuche, sodass die Entscheidung Mikrosekunden dauert, ohne Datenbank testbar und im Nachhinein prüfbar ist. Es ist dieselbe Grenze, die man in der Produktion zwischen einer Abgleichsschicht und einer Routing-Schicht ziehen würde.

  • Segmenthierarchie mit Fallback. L0 ist gateway_group × funding × issuer_bucket × amount_band; L3 ist gateway_group allein. Support wird pro PSP aufgelöst, eine Dimension nach der anderen (amount band → issuer → funding), bis eine Zelle min_support (Standard 200) überschreitet, und die verwendete Ebene wird mit der Entscheidung gemeldet. Der Kanal ist erstklassig und wird nie verworfen: kartenpräsent und off-session sind verschiedene Welten.

  • Wilson-Untergrenze, nicht die rohe Rate. Ein Segment mit 3/3 Genehmigungen ist kein 100-%-Segment. Die Grenze schrumpft gegen null, wenn der Support dünner wird, sodass eine gut belegte 78 % eine glückliche 100 % schlägt, ohne eine separate Konfidenzregel, die nachgerüstet werden müsste.

  • cost_bias als expliziter Regler, in realen Einheiten. Der Kompromiss wird formuliert als „Prozentpunkte Genehmigungsrate, die ich für einen günstigeren PSP aufgebe" – tolerance = cost_bias × 10pp, und der günstigste PSP innerhalb dieser Toleranz des besten Genehmigers gewinnt. Ein gewichteter Score würde es einer Gebührendifferenz von Bruchteilen eines Punktes erlauben, eine zweistellige Genehmigungslücke stillschweigend zu überstimmen; ein Toleranzfilter kann das nicht. Der Backtest bepreist den Regler: 72,02 % / +5,89 pp Genehmigungsrate bei cost_bias=0, 71,56 % / +5,43 pp bei 0,5, 69,57 % / +3,44 pp bei 1,0.

  • Wiederholung nach Fehlerklasse, nicht nach blindem Zähler. insufficient_funds ist ein Kontoproblem und wiederholt denselben PSP im nächsten Abrechnungsfenster; bank_auth_required off-session kann ohne den Kunden nicht erfüllt werden, also wird es auf einen kartenpräsenten Kanal umgeplant, statt Versuche zu verbrennen; fraud_risk stoppt die Kette dauerhaft; generic_decline führt einen Failover zum nächsten PSP nach Score aus. Eine unbekannte Klasse degradiert zur generischen Failover-Policy und sagt das.

Wo KI hingehört – und wo nicht

Es gibt kein LLM in decide(). Geld sollte sich nicht auf ein gesampeltes Token bewegen. Das Sprachmodell ist auf die beiden Ränder beschränkt, an denen natürliche Sprache tatsächlich das Problem ist.

Drin – decline_normalizer.py. Jeder PSP lehnt in seinem eigenen Dialekt ab: ISO-8583-Numerik, ein Stripe-ähnlicher decline_code, ein Adyen-ähnliches refusalReason oder roher Bankprosatext. Die Wiederholungs-Zustandsmaschine basiert auf einem Enum, also müssen die Dialekte kollabieren, bevor die Engine sie sieht. Eine deterministische Tabelle behandelt die Codes, die Volumen tragen – Konfidenz 1,0, keine Latenz, keine Kosten. Nur ein Tabellen-Fehlgriff erreicht die Modellkette (Gemini, dann Mistral), die unter einem eingeschränkten Enum-Schema antwortet. Alles außerhalb des Enums oder unter 0,6 Konfidenz wird zugunsten von generic_decline verworfen, dem eigenen sicheren Standard der Wiederholungs-Policy. Das Repository läuft grün ohne gesetzte API-Schlüssel.

Gemessen, nicht vertraut – evals/. 48 goldene Ablehnungen: ~60 % Tabellentreffer, ~40 % absichtlich außerhalb der Tabelle (Tippfehler, ausführlicher Banktext, ungewöhnliche Codes) plus ein paar wirklich mehrdeutige, bei denen generic_decline die richtige Antwort ist. Die evals/baseline.json verzeichnet zwei Baselines. Nur Tabelle (keine Schlüssel): 32/48 = 66,67 %, d. h. 100 % auf den 28 Tabellenfällen und der sichere generic_decline-Standard auf den 20, die durchfallen. LLM (Schlüssel konfiguriert, ausgeführt gegen die deployte API mit --remote): 48/48 = 100 % – 28 Tabelle, 19 beantwortet von gemini-3.6-flash, 1 durch den Niedrigkonfidenz-Fallback, bei dem generic_decline die erwartete Antwort war. Der Runner meldet Genauigkeit nach Route und eine Konfusionsmatrix pro Klasse, behauptet null Halluzinationen und lässt den Build fehlschlagen, wenn die Genauigkeit mehr als 2 pp unter der passenden Baseline fällt.

Draußen – mcp_server.py. Sechs MCP-Tools – route_transaction, explain_decision, simulate, segment_evidence, normalize_decline, backtest_summary – erlauben einem Agenten, die Engine auf Englisch zu bedienen. Der Agent kann jede Entscheidung hinterfragen und keine davon ändern. Siehe MCP.md.

Einschränkungen

  • Die Daten sind synthetisch. Die Struktur ist darauf ausgelegt, Routing-Entscheidungen nicht-trivial zu machen, nicht ein reales Portfolio zu reproduzieren.

  • Keine Live-PSP-Connectors: Die Engine entscheidet, sie sendet nicht.

  • Kein Fraud-Scoring, keine 3DS-Orchestrierung, keine Network-Tokens, keine Durchsetzung von Scheme-Retry-Regeln.

  • Der Backtest ist richtungsweisend. Historisches Routing war nicht randomisiert, Kapazitätsgrenzen werden nicht modelliert, und „erwartete Genehmigungsrate" ist eine TRAIN-Perioden-Wilson-LB-Rate, angewendet auf TEST-Volumen – kein Live-A/B-Ergebnis.

  • Die Tabellen poolen alle Versuche, während der Backtest nur auf Erstversuchen trainiert und replayt; Nur-Erstversuch-Produktionstabellen wären die nächste Korrektur.

  • Das LLM-Eval umfasst 48 Fälle und einen Lauf; 100 % auf einer so kleinen goldenen Menge ist ein Tor gegen Regressionen, kein Anspruch über den langen Schwanz in der Produktion.

Dateiübersicht

Datei

Zweck

synth_attempts.py

seed-basierter Generator für attempts.parquet; Kanalmix, PSP-Gebühren, Genehmigungsmodell, Fehlermix und Wiederholungsverhalten oben dokumentiert

build_routing_tables.py

baut routing_tables.json (Segment × PSP: n, Genehmigungen, p_hat, wilson_lb, alle 4 Ebenen) und routing_meta.json (amount-band-Grenzen, Emittenten-Whitelist, bin6 → Emittent-Karte, PSP-Gebühren, error-class-Enum)

orchestrator.py

die Engine: decide(txn, config) -> Decision, Config-Dataclass. Nur Stdlib

decline_normalizer.py

PSP-Ablehnungsdialekte → das error_class-Enum der Engine: zuerst Tabelle, LLM bei Fehlgriff, sicherer Fallback

ops.py

die Operator-Funktionen (route, explain, simulate, evidence, normalize, backtest), die von API und MCP gemeinsam genutzt werden

api/index.py

FastAPI auf Vercel; Vertrag in api/README.md

mcp_server.py

MCP- stdio-Server, sechs Tools; siehe MCP.md

cli.py

CLI-Frontend: eine Transaktion über Flags oder einen Batch über --txn-file

public/

statische Web-UI, von Vercel von derselben Origin wie die API ausgeliefert

demo_transactions.json

8 entscheidungsgrenzwertige Transaktionen mit why_interesting-Notizen

backtest.py

TRAIN-Tage 1–21 / TEST-Tage 22–31-Replay bei cost_bias 0 / 0,5 / 1,0; --json schreibt backtest_summary.json neu

evals/

48 goldene Ablehnungen, der Scoring-Runner und die aufgezeichnete Baseline

tests.py / tests_ai.py

assert-basierte Prüfungen: die Engine, dann die KI-Ränder und der HTTP-Vertrag


Bryan Rodríguez Abarca · vryahn.com · Entstanden aus einer technischen Übung, verallgemeinert als persönliches Projekt. Synthetische Daten.

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

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

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables natural-language investigation of Datadog data including logs, metrics, monitors, traces, hosts, dashboards, events, and incidents, all through read-only API access.
    2,053
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Investigate 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 r
    10
    269
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    Enables 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.

View all related MCP servers

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.

View all MCP Connectors

Latest Blog Posts

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