535 lines
28 KiB
Markdown
535 lines
28 KiB
Markdown
# Архитектура
|
||
|
||
## 1. Назначение проекта
|
||
|
||
Проект представляет собой платформу для динамической публикации внешних API в виде MCP tools. Пользователь конфигурирует операцию через административный UI вместо написания отдельного backend-обработчика. Платформа сохраняет конфигурацию, валидирует ее, позволяет выполнить тестовый вызов и публикует операцию для использования LLM через MCP.
|
||
|
||
Главная инженерная цель проекта - представить разные протоколы как единый набор операций с точки зрения MCP-слоя.
|
||
|
||
## 2. Ключевой принцип проектирования
|
||
|
||
Центральная абстракция системы - `Operation`.
|
||
|
||
Каждая операция описывает один вызываемый элемент независимо от протокола:
|
||
|
||
- `name` - внутреннее уникальное имя.
|
||
- `display_name` - имя, отображаемое в UI.
|
||
- `protocol` - `rest`, `graphql` или `grpc`.
|
||
- `target` - хост и протокол-специфичное описание назначения.
|
||
- `input_schema` - нормализованный входной контракт.
|
||
- `input_mapping` - правила отображения MCP-входа в поля целевого запроса.
|
||
- `execution_config` - auth-профиль, таймауты, заголовки и протокол-специфичные параметры.
|
||
- `output_mapping` - правила отображения ответа внешней системы в нормализованный выход.
|
||
- `tool_description` - метаданные для MCP и LLM.
|
||
- `status` - draft, testing, published, archived.
|
||
|
||
MCP server должен понимать только нормализованный контракт. Протокольные адаптеры должны преобразовывать нормализованную модель в конкретный REST, GraphQL или gRPC вызов и затем возвращать ответ обратно в нормализованный JSON.
|
||
|
||
## 3. Границы продукта
|
||
|
||
### Входит в MVP
|
||
|
||
- Административный UI для создания и редактирования операций.
|
||
- Динамический реестр операций.
|
||
- Runtime-выполнение REST операций.
|
||
- Runtime-выполнение GraphQL операций.
|
||
- Runtime-выполнение unary gRPC методов.
|
||
- Загрузка примеров `JSON` для ускоренного создания схем и mappings.
|
||
- Импорт и экспорт конфигураций в `YAML`.
|
||
- Тестирование операций до публикации.
|
||
- Публикация MCP tools на основе данных из реестра.
|
||
- Hot reload опубликованных операций без изменения backend-кода.
|
||
|
||
### Не входит в MVP
|
||
|
||
- gRPC streaming.
|
||
- Полноценный импорт OpenAPI с автоматической генерацией маппинга.
|
||
- Полноценный визуальный конструктор GraphQL-запросов.
|
||
- SOAP.
|
||
- Выполнение произвольного кода внутри mapping-правил.
|
||
- Оркестрация нескольких операций в виде workflow.
|
||
- Мультитенантность и биллинг.
|
||
|
||
## 4. Пользовательский сценарий
|
||
|
||
Сценарий работы оператора должен быть одинаковым для всех протоколов:
|
||
|
||
1. Выбрать протокол.
|
||
2. Указать целевой хост или сервер.
|
||
3. Выбрать или описать внешнюю операцию.
|
||
4. Определить MCP-входные параметры.
|
||
5. Сопоставить MCP-вход с внешним запросом.
|
||
6. Сопоставить внешний ответ с MCP-выходом.
|
||
7. Добавить описание для MCP и LLM.
|
||
8. Выполнить тестовый вызов.
|
||
9. Опубликовать операцию.
|
||
|
||
UI должен максимально скрывать протокольную сложность. REST endpoint, GraphQL operation и gRPC method должны отображаться для оператора как "операция с входными и выходными параметрами".
|
||
|
||
## 5. Стратегия по протоколам
|
||
|
||
### REST
|
||
|
||
REST-адаптер является базовым и должен реализовываться первым.
|
||
|
||
Поддержка в MVP:
|
||
|
||
- `GET`
|
||
- `POST`
|
||
- `PUT`
|
||
- `PATCH`
|
||
- `DELETE`
|
||
- path parameters
|
||
- query parameters
|
||
- headers
|
||
- JSON request body
|
||
- JSON response body
|
||
- аутентификация `Bearer`, `Basic` и API key
|
||
|
||
Пользователь настраивает:
|
||
|
||
- base URL,
|
||
- HTTP method,
|
||
- path template,
|
||
- request mapping,
|
||
- response mapping.
|
||
|
||
### GraphQL
|
||
|
||
Поддержка GraphQL в MVP должна быть намеренно упрощена.
|
||
|
||
Поддержка в MVP:
|
||
|
||
- `query`
|
||
- `mutation`
|
||
- endpoint URL
|
||
- request headers
|
||
- operation template
|
||
- variables mapping
|
||
- извлечение результата из `data`
|
||
|
||
Пользователь настраивает:
|
||
|
||
- GraphQL endpoint,
|
||
- шаблон операции,
|
||
- схему переменных,
|
||
- маппинг переменных,
|
||
- путь к нужным данным в ответе.
|
||
|
||
Introspection может быть добавлен позже как вспомогательная функция UI, но первая рабочая версия системы не должна от него зависеть.
|
||
|
||
Ключевое ограничение GraphQL в проекте: одна MCP operation должна соответствовать одному конкретному GraphQL-запросу или mutation с заранее определенным selection set. Платформа не должна пытаться передавать LLM всю гибкость GraphQL, потому что LLM не должен формировать произвольный набор полей и произвольную структуру параметров для одного и того же tool.
|
||
|
||
С точки зрения MCP GraphQL в этой системе намеренно превращается в более жесткий интерфейс:
|
||
|
||
- один tool;
|
||
- один шаблон `query` или `mutation`;
|
||
- фиксированный набор входных параметров;
|
||
- один предсказуемый формат ответа.
|
||
|
||
Фактически на слое MCP "универсальность" GraphQL сознательно сужается до модели, близкой к RPC или REST operation. Это делается для того, чтобы tool оставался понятным для LLM, валидируемым, предсказуемым по структуре ответа и пригодным для явного mapping.
|
||
|
||
### gRPC
|
||
|
||
gRPC - наиболее сложный протокол в этом проекте, поэтому его нужно ограничить на раннем этапе.
|
||
|
||
Поддержка в MVP:
|
||
|
||
- только unary RPC,
|
||
- загрузка `.proto`,
|
||
- загрузка descriptor set,
|
||
- опционально server reflection на более позднем этапе,
|
||
- преобразование между нормализованным JSON и protobuf-сообщениями.
|
||
|
||
Рекомендуемый путь реализации:
|
||
|
||
1. Принимать descriptor set как основной машинно-читаемый источник схемы.
|
||
2. Опционально принимать `.proto` для удобства оператора.
|
||
3. Парсить descriptor во внутреннюю модель схемы, удобную для UI.
|
||
4. Показывать services, methods, входные поля и выходные поля в виде структурированной формы.
|
||
5. Позволять пользователю настраивать input и output mapping.
|
||
|
||
Такой подход превращает gRPC для оператора в тот же опыт, что и REST: выбрать метод, посмотреть параметры, сопоставить поля, протестировать, опубликовать.
|
||
|
||
Streaming gRPC сознательно не входит в рамки проекта. Платформа ориентирована на MCP tool invocation, а MCP tool в этой системе моделируется как сценарий `запрос -> один ответ`. LLM не работает с долгоживущими транспортными сессиями и не нуждается в обработке потока сообщений для такого типа интеграции. Поэтому `server streaming`, `client streaming` и `bidirectional streaming` исключаются как архитектурно избыточные для выбранной модели взаимодействия.
|
||
|
||
Тот же принцип применяется и к GraphQL: даже если внешний GraphQL endpoint допускает очень гибкий способ получения данных, в MCP публикуются только заранее зафиксированные операции с контролируемым входом и контролируемым ответом.
|
||
|
||
## 6. Работа с файлами и автогенерация черновика
|
||
|
||
Для упрощения конфигурирования система должна поддерживать загрузку файлов и примеров данных, из которых можно собрать стартовую конфигурацию operation.
|
||
|
||
Поддерживаемые источники:
|
||
|
||
- пример входного `JSON`;
|
||
- пример выходного `JSON`;
|
||
- `.proto`;
|
||
- `descriptor set`.
|
||
|
||
Ожидаемый сценарий:
|
||
|
||
1. Оператор загружает пример входных данных и пример ответа.
|
||
2. Система строит черновую схему входа и выхода.
|
||
3. Система предлагает стартовый mapping по совпадающим или близким по структуре полям.
|
||
4. Оператор вручную корректирует результат.
|
||
5. Для точечной настройки используется `JSONPath`.
|
||
6. Готовую конфигурацию можно экспортировать в `YAML` или импортировать обратно.
|
||
|
||
Для gRPC источником структуры является не пример JSON-сообщения, а `.proto` или descriptor set. Однако после преобразования protobuf-схемы во внутреннюю JSON-ориентированную модель пользовательский опыт должен оставаться тем же: видим структуру полей, получаем стартовый mapping, затем уточняем его вручную.
|
||
|
||
`YAML` используется как человекочитаемое представление конфигурации operation для:
|
||
|
||
- переноса между окружениями;
|
||
- резервного копирования;
|
||
- хранения в git;
|
||
- редактирования вне UI;
|
||
- пакетного импорта нескольких operation.
|
||
|
||
Storage backend для sample-файлов, `.proto`, `descriptor set` и YAML import payload в MVP должен быть локальным файловым хранилищем приложения с явным `storage_ref`. В дальнейшем этот слой можно заменить на S3-compatible storage без изменения доменной модели.
|
||
|
||
## 7. Внутренняя модель данных
|
||
|
||
Система должна приводить все данные к JSON-ориентированным структурам, чтобы UI, registry и MCP runtime работали с единым контрактом.
|
||
|
||
### Operation
|
||
|
||
- `id`
|
||
- `name`
|
||
- `display_name`
|
||
- `protocol`
|
||
- `status`
|
||
- `target`
|
||
- `input_schema`
|
||
- `output_schema`
|
||
- `input_mapping`
|
||
- `output_mapping`
|
||
- `execution_config`
|
||
- `tool_description`
|
||
- `created_at`
|
||
- `updated_at`
|
||
|
||
### Target
|
||
|
||
REST target:
|
||
|
||
- `base_url`
|
||
- `method`
|
||
- `path_template`
|
||
|
||
GraphQL target:
|
||
|
||
- `endpoint`
|
||
- `operation_type`
|
||
- `operation_name`
|
||
- `query_template`
|
||
|
||
gRPC target:
|
||
|
||
- `server_addr`
|
||
- `package`
|
||
- `service`
|
||
- `method`
|
||
- `descriptor_ref`
|
||
|
||
### Schema
|
||
|
||
Нормализованный формат схемы должен поддерживать:
|
||
|
||
- скалярные поля,
|
||
- вложенные объекты,
|
||
- массивы,
|
||
- enum,
|
||
- nullable-поля,
|
||
- `oneof` для схем, пришедших из protobuf.
|
||
|
||
Транспортный формат между внутренними компонентами должен оставаться JSON, даже если конкретный адаптер под капотом работает с protobuf.
|
||
|
||
## 8. Модель маппинга
|
||
|
||
Платформе нужен отдельный слой маппинга, потому что MCP-facing параметры не совпадают напрямую с payload внешнего API.
|
||
|
||
Начальная версия mapping-системы должна оставаться простой, но при этом достаточно выразительной для работы со вложенными структурами:
|
||
|
||
- сопоставление поле-в-поле по `JSONPath`,
|
||
- константы,
|
||
- значения по умолчанию,
|
||
- извлечение вложенных полей из ответа.
|
||
|
||
Примеры:
|
||
|
||
- `$.mcp.user_id -> $.request.path.userId`
|
||
- `$.mcp.limit -> $.request.query.limit`
|
||
- `$.response.data.user.name -> $.output.name`
|
||
- `$.response.user.email -> $.output.email`
|
||
|
||
`JSONPath` используется как единый способ адресации вложенных значений в input/output mapping. Это позволяет управлять структурой и вложенностью без написания пользовательского кода.
|
||
|
||
Для MVP mapping engine не должен поддерживать произвольные скрипты. Достаточно единого движка `JSONPath`, констант, defaults и ограниченного набора встроенных преобразований.
|
||
|
||
Черновой mapping может генерироваться автоматически на основе загруженных примеров данных, но итоговая конфигурация всегда остается явной и редактируемой оператором.
|
||
|
||
Для MVP допустима простая стратегия draft generation:
|
||
|
||
- искать уникальные leaf-поля с одинаковыми именами;
|
||
- предлагать только однозначные соответствия;
|
||
- не пытаться автоматически разрешать конфликты и неоднозначности.
|
||
|
||
Каноническая логическая модель остается общей для runtime и БД, но система должна уметь сериализовать и десериализовать ее также в `YAML`.
|
||
|
||
## 9. Основные компоненты
|
||
|
||
### `mcpaas-core`
|
||
|
||
Ответственность:
|
||
|
||
- общие доменные типы,
|
||
- идентификаторы,
|
||
- статусы и базовые protocol-specific target types,
|
||
- общие ошибки.
|
||
|
||
### `mcpaas-schema`
|
||
|
||
Ответственность:
|
||
|
||
- нормализованные схемы входа и выхода,
|
||
- представление типов и полей для UI и runtime,
|
||
- валидация JSON относительно внутренней схемы.
|
||
|
||
### `mcpaas-mapping`
|
||
|
||
Ответственность:
|
||
|
||
- модель mapping-правил,
|
||
- `JSONPath` parser и validator,
|
||
- применение input/output mapping,
|
||
- генерация чернового mapping по sample-данным и схемам.
|
||
|
||
### `mcpaas-proto`
|
||
|
||
Ответственность:
|
||
|
||
- загрузка `.proto` и `descriptor set`,
|
||
- protobuf discovery,
|
||
- извлечение services, methods и message schemas,
|
||
- преобразование protobuf metadata в нормализованные схемы.
|
||
|
||
### `mcpaas-registry`
|
||
|
||
Ответственность:
|
||
|
||
- постоянное хранение операций,
|
||
- CRUD для draft и published операций,
|
||
- выдача списка активных tools,
|
||
- инвалидация кэша и сигналы на reload.
|
||
|
||
### `mcpaas-runtime`
|
||
|
||
Ответственность:
|
||
|
||
- выполнение нормализованных операций,
|
||
- выбор нужного протокольного адаптера,
|
||
- применение input mapping,
|
||
- применение output mapping,
|
||
- единообразные runtime-ошибки.
|
||
|
||
### `mcpaas-adapter-rest`
|
||
|
||
Ответственность:
|
||
|
||
- сборка HTTP-запроса из нормализованного входа,
|
||
- отправка запроса через `reqwest`,
|
||
- нормализация HTTP-ответа в JSON.
|
||
|
||
### `mcpaas-adapter-graphql`
|
||
|
||
Ответственность:
|
||
|
||
- формирование GraphQL payload,
|
||
- подстановка переменных,
|
||
- отправка запроса,
|
||
- извлечение `data` и ошибок из GraphQL-ответа.
|
||
|
||
### `mcpaas-adapter-grpc`
|
||
|
||
Ответственность:
|
||
|
||
- сборка protobuf request message из нормализованного JSON,
|
||
- вызов unary RPC метода,
|
||
- преобразование protobuf response обратно в нормализованный JSON.
|
||
|
||
### `mcpaas-admin-api`
|
||
|
||
Ответственность:
|
||
|
||
- CRUD endpoints для UI,
|
||
- создание и управление version snapshots,
|
||
- import/export конфигураций в `YAML`,
|
||
- загрузка sample JSON,
|
||
- загрузка `.proto` и descriptor set,
|
||
- endpoints для тестового выполнения операций,
|
||
- discovery endpoints для gRPC metadata.
|
||
|
||
### `mcpaas-mcp-server`
|
||
|
||
Ответственность:
|
||
|
||
- список доступных MCP tools из registry,
|
||
- валидация входа tool по нормализованной схеме,
|
||
- делегирование выполнения в runtime,
|
||
- возврат нормализованного результата MCP-клиенту.
|
||
|
||
### `mcpaas-ui`
|
||
|
||
Ответственность:
|
||
|
||
- wizard создания сервиса и операции,
|
||
- editor для mapping,
|
||
- загрузка sample-файлов и schema artifacts,
|
||
- экран тестового вызова,
|
||
- браузер gRPC схемы,
|
||
- import/export конфигураций,
|
||
- workflow публикации и отображение статуса.
|
||
|
||
### Deployment layer
|
||
|
||
Ответственность:
|
||
|
||
- контейнерная упаковка приложений;
|
||
- orchestration через `docker-compose`;
|
||
- reverse proxy routing;
|
||
- healthchecks и delivery pipeline.
|
||
|
||
Этот слой не должен влиять на доменную модель и application contracts.
|
||
|
||
## 10. Предлагаемая структура репозитория
|
||
|
||
Для реализации рекомендуется workspace-структура:
|
||
|
||
```text
|
||
mcpaas/
|
||
apps/
|
||
admin-api/
|
||
mcp-server/
|
||
ui/
|
||
crates/
|
||
mcpaas-core/
|
||
mcpaas-schema/
|
||
mcpaas-mapping/
|
||
mcpaas-proto/
|
||
mcpaas-registry/
|
||
mcpaas-runtime/
|
||
mcpaas-adapter-rest/
|
||
mcpaas-adapter-graphql/
|
||
mcpaas-adapter-grpc/
|
||
docs/
|
||
```
|
||
|
||
Такая структура позволяет держать протокольные адаптеры независимыми и отдельно тестируемыми.
|
||
|
||
## 11. Технологический стек
|
||
|
||
### Backend
|
||
|
||
- Rust
|
||
- `tokio` как async runtime
|
||
- `axum` для HTTP API
|
||
- `serde` и `serde_json`
|
||
- `sqlx` для PostgreSQL
|
||
- `reqwest` для REST и GraphQL транспорта
|
||
- `tonic` и `prost` для работы с gRPC
|
||
- `tower` для middleware
|
||
- `tracing` для логирования и диагностики
|
||
|
||
### Frontend
|
||
|
||
- TypeScript
|
||
- React
|
||
- Vite
|
||
- React Router
|
||
- TanStack Query
|
||
- React Hook Form
|
||
- Zod
|
||
|
||
Этот стек прагматичен для внутреннего административного UI: быстрая итерация, удобная работа с формами, понятная интеграция с API и отсутствие лишней сложности.
|
||
|
||
## 12. Почему React + Vite для UI
|
||
|
||
Frontend в этом проекте - это операторская консоль, а не контентный сайт. Server-side rendering здесь не требуется. Основные требования:
|
||
|
||
- динамические формы,
|
||
- schema-driven рендеринг,
|
||
- экраны тестирования и предпросмотра,
|
||
- адаптивные административные страницы,
|
||
- высокая скорость локальной разработки.
|
||
|
||
`React + TypeScript + Vite` хорошо подходит под эти условия, потому что позволяет развивать frontend независимо от Rust-сервисов и быстро собирать сложные формы вроде mapping editor и gRPC method inspector.
|
||
|
||
## 13. Почему Axum для backend
|
||
|
||
`axum` выбран как основной backend-фреймворк по следующим причинам:
|
||
|
||
- он построен поверх `tower` и хорошо согласуется с современным async-стеком Rust,
|
||
- он естественно интегрируется с `tokio`, `hyper` и middleware-композицией,
|
||
- он лучше подходит для модульной структуры с несколькими сервисами,
|
||
- он удобен для typed handlers, shared state и собственных extractors,
|
||
- он лучше сочетается с `tonic`, который используется для gRPC.
|
||
|
||
Детальная декомпозиция crates и модулей вынесена в `docs/module-decomposition.md`.
|
||
Формальная модель данных вынесена в `docs/data-model.md`.
|
||
Схема БД и versioning описаны в `docs/database-schema.md`.
|
||
HTTP-контракты административного API описаны в `docs/admin-api.md`.
|
||
Диаграммы компонентов, сущностей и потоков вынесены в `docs/diagrams.md`.
|
||
MCP transport и способ публикации tools описаны в `docs/mcp-interface.md`.
|
||
Стратегия тестирования описана в `docs/testing-strategy.md`, а runtime-конфигурация и storage assumptions - в `docs/runtime-config.md`.
|
||
Rust-oriented распределение методов, `impl`, `trait` и service-слоя описано в `docs/rust-design.md`.
|
||
Правила разработки и TDD-процесс описаны в `docs/development-rules.md`, а последовательность модулей и фич - в `docs/implementation-plan.md`.
|
||
Rust-specific правила кода, linting и toolchain описаны в `docs/rust-code-rules.md`.
|
||
Требования и ограничения по конкретным протоколам вынесены в `docs/protocols/rest.md`, `docs/protocols/graphql.md` и `docs/protocols/grpc.md`.
|
||
|
||
## 14. Runtime-поток
|
||
|
||
### Создание операции
|
||
|
||
1. UI отправляет draft операции в admin API.
|
||
2. Admin API валидирует схему и mappings.
|
||
3. Registry сохраняет draft.
|
||
4. UI запускает тестовый вызов через runtime.
|
||
5. Оператор публикует операцию.
|
||
6. Registry помечает операцию как active.
|
||
7. MCP server перезагружает активные операции.
|
||
|
||
### Выполнение tool
|
||
|
||
1. MCP client вызывает tool.
|
||
2. MCP server берет определение tool из памяти.
|
||
3. Runtime валидирует вход относительно нормализованной схемы.
|
||
4. Runtime применяет input mapping.
|
||
5. Runtime вызывает нужный протокольный адаптер.
|
||
6. Runtime применяет output mapping.
|
||
7. MCP server возвращает нормализованный результат.
|
||
|
||
Эта последовательность соответствует модели `один запрос -> один ответ`. Именно поэтому поддержка streaming-протоколов не рассматривается как часть MVP: она не соответствует целевой модели вызова tools со стороны LLM.
|
||
По этой же причине GraphQL tools должны быть заранее специализированы под конкретный сценарий вызова, а не представлять собой общий конструктор запросов для LLM.
|
||
|
||
## 15. Нефункциональные требования
|
||
|
||
- Новые операции должны добавляться без изменения backend-кода.
|
||
- Опубликованные операции должны становиться видимыми для MCP-клиентов без пересборки сервиса.
|
||
- Runtime-ошибки должны быть наблюдаемыми и различимыми по этапам.
|
||
- Система должна оставаться детерминированной и пригодной для аудита.
|
||
- Протокольные адаптеры должны тестироваться независимо.
|
||
- Все опубликованные операции должны укладываться в модель синхронного или квазисинхронного вызова `запрос -> ответ`.
|
||
- Все опубликованные GraphQL operations должны иметь фиксированный шаблон запроса и фиксированную структуру ожидаемого результата.
|
||
|
||
## 16. Основные риски
|
||
|
||
- Динамическая работа с protobuf заметно сложнее, чем REST и GraphQL.
|
||
- UX для маппинга может стать слишком тяжелым, если не ограничить его заранее.
|
||
- Нормализация схем может стать непоследовательной без строгой внутренней модели.
|
||
- Попытка поддержать слишком много возможностей протоколов замедлит реализацию.
|
||
- Попытка сохранить всю динамическую гибкость GraphQL на уровне MCP приведет к слишком широким и плохо управляемым tools.
|
||
|
||
Поэтому проект должен в первую очередь реализовать один чистый end-to-end сценарий, а не широкий, но поверхностный охват возможностей.
|
||
|
||
`SOAP` в этой версии проекта сознательно отложен. Это не забытый протокол, а отдельное направление развития, которое потребует самостоятельного XML/WSDL слоя, отдельной схемной модели и отдельного адаптера.
|