Payment Delay MCP
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 Delay MCPWill customer 1042 pay their bill late?"
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 Delay MCP - Serving a Production ML Model to Any LLM Over MCP
A scikit-learn classifier deployed behind a FastAPI microservice and published to language models as Model Context Protocol tools - so an off-the-shelf chat client discovers and invokes the model correctly with zero integration code written against it.

The thesis
The model is the payload, not the point.
Most "AI-powered" demos hardcode a model call into a bespoke application. This project inverts that: the classifier is published as a protocol, so the LLM client is interchangeable. The same server drives OpenWebUI in Docker, OpenCode on the CLI, and Claude Desktop - with no code change and no client-specific adapter.
Overview
A telecom operator wants to know which customers will pay late. A trained classifier answers that, but a .pkl file is not a product - somebody still has to write glue code to call it, and that glue is rewritten for every new consumer.
This repository is the glue, written once as a protocol. Four layers, each independently deployable:
flowchart TB
subgraph reasoning["Reasoning path"]
UI["OpenWebUI<br/>:3000"] -->|OpenAI protocol| LL["LiteLLM<br/>:4000"]
LL -->|bedrock_mantle| BR["AWS Bedrock<br/>gpt-oss-120b"]
end
subgraph tools["Tool path"]
UI -->|OpenAPI| MCPO["mcpo<br/>:8001"]
MCPO -->|MCP over stdio| FM["FastMCP server<br/>5 tools · 2 resources · 1 prompt"]
FM -->|HTTP| API["FastAPI service<br/>:8000"]
API --> PRED["inference.predictor<br/>the only code that<br/>opens the pickle"]
PRED --> PKL[("models/*.pkl<br/>RandomForest +<br/>RandomOverSampler")]
end
style reasoning fill:#1f2a3710,stroke:#8884
style tools fill:#1f372a10,stroke:#8884The two paths are deliberately separate. The LLM never executes anything. It emits a tool_calls message naming a tool and its arguments; the client executes it and replays the result. That distinction is what makes the model swappable - and it is why this stack works identically whether the reasoning layer is Bedrock, a local Ollama, or Claude.
Related MCP server: Company API MCP Server
The core idea: tool selection is a documentation problem
An LLM picks a tool from its name, signature, and docstring - nothing else. No fine-tuning, no examples, no routing logic. So the docstrings are the interface, and writing them is engineering work, not commentary.
Two tools here overlap heavily. Both predict payment delay. Getting the model to choose correctly, unprompted, required encoding the operational constraints directly into the description:
Tool | When the model should pick it | The disambiguating signal |
| User has a CSV, as a path or pasted text | Docstring warns that |
| User describes one customer in prose | Docstring says "for natural-language cases where the LLM extracts a single customer into structured features" |
Verified result: given a customer described in plain English, gpt-oss-120b selected predict_single_customer over predict_payment_delay unaided, filled the feature dictionary from the prose, and returned a grounded answer. Confirmed in the logs of both hops - POST /predict_single_customer 200 at mcpo, then POST /predict 200 at the model service.
That is the whole claim of the project, and it is falsifiable: disable the tool and the same model answers the same question confidently and wrongly, with both log panes empty.
A request, end to end
The part most tool-use diagrams omit is that a single user question costs two round trips to the model, and the intermediate assistant message must be replayed verbatim or the tool_call_id dangles:
sequenceDiagram
participant U as User
participant W as OpenWebUI
participant L as LiteLLM
participant M as Bedrock model
participant O as mcpo
participant S as FastMCP
participant A as FastAPI + model
U->>W: "Will customer X pay late?"
W->>L: messages[] + tools[]
L->>M: translated to Bedrock
M-->>W: finish_reason: tool_calls
Note over W: the client executes,<br/>not the model
W->>O: POST /predict_single_customer
O->>S: MCP call over stdio
S->>A: POST /predict
A-->>S: {prediction, probability_yes}
S-->>O: result
O-->>W: 200 OK
W->>L: messages[] + assistant(tool_calls) + tool(result)
L->>M: second round trip
M-->>U: grounded natural-language answerThe tools[] array is re-sent on every request - the model is stateless and rediscovers the toolset each turn.
What is verified
Four checkpoints, each confirmed against logs rather than assumption:
# | Layer | Evidence |
1 | Model service |
|
2 | mcpo bridge | 5 tools rendered at |
3 | LiteLLM to Bedrock |
|
4 | Full autonomous loop |
|
Checkpoint 3 matters more than it looks: finish_reason: tool_calls is the only way to distinguish "the model declined to use the tool" from "the tool was never offered to it." Those failures look identical in the chat window.
The model
Dataset disclosure. The training data is a public telecom churn benchmark with the target column relabelled
payment_delayfor the purposes of this exercise. The features are call-record and account fields, not billing history. The modelling is real and the pipeline is real; the business framing is synthetic. Treat the numbers as a working example, not a validated credit-risk model.
Property | Value |
Rows / columns | 3,000 / 20 |
Class balance |
|
Pipeline |
|
Split | 80/20 stratified |
Features at inference | 36 - 19 raw plus 17 derived |
Decision threshold | 0.35, persisted as an artifact |
The threshold is not 0.5 and is not hardcoded. It ships as models/threshold.pkl and is overridable per request, because on a target that is 13.77% positive the default cutoff optimises for the wrong thing. A lower threshold catches more late payers at the cost of more false positives, and which trade-off is correct is a business decision, not a modelling one - so the API exposes it as a parameter.
Nothing in the codebase hardcodes a column name. Feature order comes from feature_columns.pkl, outlier bounds from outlier_bounds.pkl, so retraining does not require a code change.
Engineering decisions worth defending
The MCP server never imports the model. It calls the API over HTTP. That keeps the MCP process small - no sklearn, no 9 MB pickle resident - and lets the model service scale, deploy, and get monitored like any other microservice. A protocol adapter should hold no business logic.
Prediction runs off the event loop. The inference call is dispatched with run_in_threadpool, so CPU-bound scoring never blocks FastAPI's async loop under concurrent requests.
stdio discipline. MCP over stdio requires stdout to carry JSON-RPC frames and nothing else, so a stray print() corrupts the stream and kills the session. Consequently all logging is routed to stderr, httpx and httpcore are silenced, and launcher.py redirects uvicorn's output to a log file, waits for /health, and only then hands the client clean stdio.
Two entrypoints for two topologies. server.py is the container entrypoint, where the API is a separate service. launcher.py is the local entrypoint, which starts the API itself and waits for it - the right shape for a desktop MCP client that expects one process to own its dependencies.
A pin that documents a real incident. mcp>=1.2.0,<2.0: mcp 2.x renamed streamablehttp_client, and mcpo 0.0.20 still imports the old name, so mcpo crash-loops against 2.x. The cap is commented in requirements.txt with the reason, because a version pin without a reason is deleted by the next person who reads it.
Repository structure
mcp-payment-delay/
├── src/payment_delay/
│ ├── config.py # single source of truth for paths + endpoints, all env-overridable
│ ├── inference/predictor.py # the only code that opens the pickle; imports no web framework
│ ├── api/main.py # thin FastAPI adapter over the predictor
│ └── mcp_server/
│ ├── server.py # FastMCP tools, resources, prompt (container entrypoint)
│ ├── api_client.py # HTTP calls into the model service
│ └── launcher.py # starts the API, then serves MCP on clean stdio (local entrypoint)
├── models/ # model, threshold, outlier bounds, feature order
├── data/telecomunicatii.csv # sample dataset
├── deploy/litellm_config.yaml # Bedrock routing
├── scripts/bedrock_smoke_test.py # asserts a tool call comes back, not merely a 200
├── docs/ # architecture + Docker runbook
├── Dockerfile # one image, serves both the API and the mcpo bridge
└── docker-compose.yml # API + mcpo + LiteLLM + OpenWebUIGetting started
Run the model service alone - no cloud credentials needed
python3 -m venv .venv && source .venv/bin/activate
make install # pip install -e ".[dev]"
make api # http://localhost:8000/docsEndpoint | Purpose |
| service up, model loaded |
| model type, classes, features, threshold |
| required CSV columns |
| one row (JSON object or one-row CSV) -> one yes/no |
| multi-row CSV -> one yes/no per row |
| multi-row CSV -> one yes/no for the whole file |
curl -F "file=@data/telecomunicatii.csv" \
"http://localhost:8000/predict/summary?threshold=0.35"Attach your own MCP client
python3 -m payment_delay.mcp_server.launcherServes the tools over stdio and starts the API if it is not already healthy. opencode.json wires this into OpenCode; Claude Desktop and any other stdio MCP client attach the same way.
Run the full stack
cp .env.example .env # add your Bedrock key
python3 scripts/bedrock_smoke_test.py
make stack # http://localhost:3000Full runbook, including credential setup and troubleshooting: docs/docker-stack.md.
The MCP surface
Five tools, two resources, one prompt template:
get_api_health service + model status
get_model_info model metadata, classes, features, endpoints
get_input_schema expected CSV columns
predict_payment_delay CSV in (path or text), per-row or aggregate, threshold configurable
predict_single_customer one customer as a JSON object
payment-delay://context business + modelling context, injected as a resource
payment-delay://api-contract the HTTP contract these tools call
interpret_payment_delay_result prompt template for business-language explanationResources and prompts are the underused half of MCP. The context resource means the client does not need to be told what the model is for - it can read it.
Tech stack
FastAPI · FastMCP · mcpo · scikit-learn · imbalanced-learn · pandas · LiteLLM · AWS Bedrock · OpenWebUI · Docker Compose · uvicorn · httpx
Docs
Architecture - the layers, the prediction pipeline, and why the split is where it is
Docker stack runbook - credentials, startup, health checks, troubleshooting
docs/assignment/ - the original brief
Eduard-Gabriel Tudoran, 2026.
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 Connectors
Unified MCP Server is a remote MCP connector for AI agents and vertical AI products that provides access to 22,000+ authorized SaaS tools across 400+ integrations and 24 categories directly inside LLMs (Claude, GPT, Gemini, Cohere). Tools operate only on explicitly authorized customer connections, enabling agents to safely read and write against live third-party systems.
Zero-setup MCP gateway securely connecting AI to your tools with authentication and workflows
Connect MCP clients to 2,000+ AI models without managing provider API keys.
Discover and call 10,000+ production APIs from one MCP server. Pay-per-call billing for AI agents.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceExposes enterprise KPIs, health scores, forecasting, and anomaly detection as MCP tools, resources, and prompts for use by any MCP-compatible agent.2AGPL 3.0
- FlicenseNot gradedqualityCmaintenanceExposes internal company services as LLM-callable MCP tools, enabling AI agents to perform business operations like customer management, order processing, and support ticketing through natural language.
- FlicenseNot gradedqualityCmaintenanceExposes a governed lending portfolio (loans, customers, risk-tier history) to any MCP-compatible AI client via read-only tools, schema resources, and analysis prompts, wrapping an existing API gateway instead of connecting directly to the database.
- FlicenseAqualityBmaintenanceMCP server exposing a fictional payment domain as tools, resources, and prompts, enabling reasoning over transactions, payment hubs, services, and system health.8
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/eddii1/mcp-payment-delay'
If you have feedback or need assistance with the MCP directory API, please join our Discord server