Skip to main content
Glama
eddii1

Payment Delay MCP

by eddii1

Payment Delay MCP — предоставление production ML-модели любой LLM через MCP

Классификатор scikit-learn, развёрнутый за микросервисом FastAPI и опубликованный для языковых моделей в виде инструментов Model Context Protocol, — так что готовый чат-клиент сам находит и корректно вызывает модель, без единой строки интеграционного кода, написанного под неё.

gpt-oss-120b выбирает нужный MCP-инструмент и вызывает API модели

Тезис

Модель — это полезная нагрузка, а не суть.

Большинство демо «на базе ИИ» зашивают вызов модели в специализированное приложение. Этот проект переворачивает подход: классификатор публикуется как протокол, поэтому LLM-клиент взаимозаменяем. Один и тот же сервер обслуживает OpenWebUI в Docker, OpenCode в CLI и Claude Desktop — без изменения кода и без клиентских адаптеров.


Обзор

Телеком-оператор хочет знать, какие клиенты заплатят с опозданием. Обученный классификатор отвечает на этот вопрос, но файл .pkl — это не продукт: кому-то всё равно придётся написать связующий код для его вызова, и этот код переписывается для каждого нового потребителя.

Этот репозиторий и есть такой связующий код, написанный один раз в виде протокола. Четыре слоя, каждый из которых разворачивается независимо:

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:#8884

Эти два пути намеренно разделены. LLM никогда ничего не исполняет. Она отправляет сообщение tool_calls, в котором указаны инструмент и его аргументы; клиент выполняет его и возвращает результат. Именно это различие делает модель заменяемой — и именно поэтому данный стек работает одинаково, независимо от того, является ли слой рассуждений Bedrock, локальной Ollama или Claude.


Related MCP server: Company API MCP Server

Ключевая идея: выбор инструмента — это задача документации

LLM выбирает инструмент по его имени, сигнатуре и docstring — и ни по чему больше. Ни тонкой настройки, ни примеров, ни логики маршрутизации. Поэтому docstrings и есть интерфейс, и их написание — инженерная работа, а не комментарий.

Два инструмента здесь во многом пересекаются. Оба предсказывают задержку платежа. Чтобы модель выбирала правильно без подсказок, потребовалось закодировать операционные ограничения прямо в описании:

Инструмент

Когда модель должна его выбрать

Различающий сигнал

predict_payment_delay

У пользователя есть CSV — путь или вставленный текст

Docstring предупреждает, что csv_path не работает, когда сервер запущен в контейнере, который не видит файловую систему пользователя, и советует использовать csv_text

predict_single_customer

Пользователь описывает одного клиента в свободной форме

Docstring гласит: «для случаев на естественном языке, когда LLM извлекает данные одного клиента в структурированные признаки»

Подтверждённый результат: для клиента, описанного простым английским языком, gpt-oss-120b без подсказок выбрал predict_single_customer вместо predict_payment_delay, заполнил словарь признаков из текста и вернул обоснованный ответ. Это подтверждено в логах обоих переходов: POST /predict_single_customer 200 на mcpo, затем POST /predict 200 в сервисе модели.

В этом и состоит всё утверждение проекта, и его можно проверить: отключите инструмент — и та же модель уверенно и неправильно ответит на тот же вопрос, а обе панели логов останутся пустыми.


Запрос: от начала до конца

То, что большинство диаграмм использования инструментов опускают: один вопрос пользователя стоит двух обращений к модели, и промежуточное сообщение ассистента должно быть воспроизведено дословно, иначе tool_call_id повиснет:

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 answer

Массив tools[] отправляется заново при каждом запросе — модель не хранит состояние и заново обнаруживает набор инструментов на каждом витке.


Что проверено

Четыре контрольные точки, каждая подтверждена логами, а не предположениями:

#

Слой

Подтверждение

1

Сервис модели

GET /health 200; POST /predict для одной строки возвращает no, confidence 0.985

2

Мост mcpo

5 инструментов отображаются на :8001/docs; predict_single_customer выполнен через curl

3

LiteLLM к Bedrock

/v1/models перечисляет модель; запрос с вызовом инструмента возвращает finish_reason: tool_calls

4

Полный автономный цикл

POST /predict_single_customer 200 на mcpo и POST /predict 200 в API — по вопросу на простом английском

Контрольная точка 3 важнее, чем кажется: finish_reason: tool_calls — единственный способ отличить «модель отказалась использовать инструмент» от «инструмент ей вообще не предлагался». В окне чата эти сбои выглядят одинаково.


Модель

Раскрытие информации о наборе данных. Обучающие данные — это публичный телеком-бенчмарк по оттоку (churn), в котором целевой столбец переименован в payment_delay для целей этого упражнения. Признаки — это поля записей звонков и аккаунтов, а не история платежей. Моделирование настоящее, и пайплайн настоящий; бизнес-обёртка синтетическая. Относитесь к цифрам как к рабочему примеру, а не как к валидированной модели кредитного риска.

Свойство

Значение

Строк / столбцов

3,000 / 20

Баланс классов

no 2,587 (86.23%) · yes 413 (13.77%)

Пайплайн

ColumnTransformer -> RandomOverSampler -> RandomForestClassifier (imblearn)

Разбиение

80/20 стратифицированное

Признаки при инференсе

36 — 19 исходных плюс 17 производных флагов <column>_is_outlier

Порог решения

0.35, сохраняется как артефакт

Порог — это не 0.5, и он не захардкожен. Он поставляется как models/threshold.pkl и может быть переопределён для каждого запроса, потому что при цели с 13.77% положительных примеров порог по умолчанию оптимизирует не то. Более низкий порог ловит больше неплательщиков ценой большего числа ложных срабатываний, и какой компромисс верен — это бизнес-решение, а не модельное, поэтому API предоставляет его как параметр.

Ничто в кодовой базе не захардкоживает имя столбца. Порядок признаков берётся из feature_columns.pkl, границы выбросов — из outlier_bounds.pkl, поэтому переобучение не требует изменения кода.


Инженерные решения, которые стоит отстаивать

MCP-сервер никогда не импортирует модель. Он вызывает API по HTTP. Это сохраняет процесс MCP лёгким — ни sklearn, ни 9 MB pickle в памяти — и позволяет сервису модели масштабироваться, разворачиваться и мониториться как любому другому микросервису. Адаптер протокола не должен содержать бизнес-логики.

Предсказание выполняется вне цикла событий. Вызов инференса отправляется через run_in_threadpool, поэтому CPU-интенсивные вычисления никогда не блокируют асинхронный цикл FastAPI при конкурентных запросах.

Дисциплина stdio. MCP через stdio требует, чтобы stdout переносил только JSON-RPC кадры, поэтому случайный print() повреждает поток и убивает сессию. В связи с этим всё логирование направляется в stderr, httpx и httpcore заглушаются, а launcher.py перенаправляет вывод uvicorn в файл лога, ждёт /health и только затем передаёт клиенту чистый stdio.

Две точки входа для двух топологий. server.py — точка входа для контейнера, где API является отдельным сервисом. launcher.py — локальная точка входа, которая сама запускает API и ждёт его, — правильная форма для десктопного MCP-клиента, ожидающего, что один процесс владеет своими зависимостями.

Пин, документирующий реальный инцидент. mcp>=1.2.0,<2.0: mcp 2.x переименовал streamablehttp_client, а mcpo 0.0.20 всё ещё импортирует старое имя, поэтому mcpo зацикливается при падении на 2.x. Ограничение закомментировано в requirements.txt с указанием причины, потому что пин версии без причины удаляется следующим человеком, который его прочитает.


Структура репозитория

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 + OpenWebUI

Начало работы

Запустите сервис модели отдельно — облачные учётные данные не нужны

python3 -m venv .venv && source .venv/bin/activate
make install                       # pip install -e ".[dev]"
make api                           # http://localhost:8000/docs

Эндпоинт

Назначение

GET /health

сервис работает, модель загружена

GET /model/info

тип модели, классы, признаки, порог

GET /schema

обязательные столбцы CSV

POST /predict

одна строка (JSON-объект или CSV из одной строки) -> одно да/нет

POST /predict/batch

CSV из нескольких строк -> одно да/нет на строку

POST /predict/summary

CSV из нескольких строк -> одно да/нет для всего файла

curl -F "file=@data/telecomunicatii.csv" \
     "http://localhost:8000/predict/summary?threshold=0.35"

Подключите свой MCP-клиент

python3 -m payment_delay.mcp_server.launcher

Предоставляет инструменты через stdio и запускает API, если он ещё не здоров. opencode.json подключает это к OpenCode; Claude Desktop и любой другой stdio MCP-клиент подключаются так же.

Запустите полный стек

cp .env.example .env               # add your Bedrock key
python3 scripts/bedrock_smoke_test.py
make stack                         # http://localhost:3000

Полный рунбук, включая настройку учётных данных и устранение неполадок: docs/docker-stack.md.


MCP-поверхность

Пять инструментов, два ресурса, один шаблон промпта:

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 explanation

Ресурсы и промпты — недооценённая половина MCP. Контекстный ресурс означает, что клиенту не нужно объяснять, для чего предназначена модель, — он может прочитать это сам.


Технологический стек

FastAPI · FastMCP · mcpo · scikit-learn · imbalanced-learn · pandas · LiteLLM · AWS Bedrock · OpenWebUI · Docker Compose · uvicorn · httpx

Документация


Eduard-Gabriel Tudoran, 2026.

Maintenance

ActivityMaintained
ResponsivenessSyncing

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

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Exposes enterprise KPIs, health scores, forecasting, and anomaly detection as MCP tools, resources, and prompts for use by any MCP-compatible agent.
    2
    AGPL 3.0
  • F
    license
    Not graded
    quality
    C
    maintenance
    Exposes 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.
  • F
    license
    Not graded
    quality
    C
    maintenance
    Exposes 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.

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/eddii1/mcp-payment-delay'

If you have feedback or need assistance with the MCP directory API, please join our Discord server