Files
crank/docs/architecture.md
T
2026-03-28 00:58:56 +03:00

28 KiB
Raw Blame History

Архитектура

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
  • descriptor_set_b64

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. Основные компоненты

crank-core

Ответственность:

  • общие доменные типы,
  • идентификаторы,
  • статусы и базовые protocol-specific target types,
  • общие ошибки.

crank-schema

Ответственность:

  • нормализованные схемы входа и выхода,
  • представление типов и полей для UI и runtime,
  • валидация JSON относительно внутренней схемы.

crank-mapping

Ответственность:

  • модель mapping-правил,
  • JSONPath parser и validator,
  • применение input/output mapping,
  • генерация чернового mapping по sample-данным и схемам.

crank-proto

Ответственность:

  • загрузка .proto и descriptor set,
  • protobuf discovery,
  • извлечение services, methods и message schemas,
  • преобразование protobuf metadata в нормализованные схемы.

crank-registry

Ответственность:

  • постоянное хранение операций,
  • CRUD для draft и published операций,
  • выдача списка активных tools,
  • инвалидация кэша и сигналы на reload.

crank-runtime

Ответственность:

  • выполнение нормализованных операций,
  • выбор нужного протокольного адаптера,
  • применение input mapping,
  • применение output mapping,
  • единообразные runtime-ошибки.

crank-adapter-rest

Ответственность:

  • сборка HTTP-запроса из нормализованного входа,
  • отправка запроса через reqwest,
  • нормализация HTTP-ответа в JSON.

crank-adapter-graphql

Ответственность:

  • формирование GraphQL payload,
  • подстановка переменных,
  • отправка запроса,
  • извлечение data и ошибок из GraphQL-ответа.

crank-adapter-grpc

Ответственность:

  • сборка protobuf request message из нормализованного JSON,
  • вызов unary RPC метода,
  • преобразование protobuf response обратно в нормализованный JSON.

crank-admin-api

Ответственность:

  • CRUD endpoints для UI,
  • создание и управление version snapshots,
  • import/export конфигураций в YAML,
  • загрузка sample JSON,
  • загрузка .proto и descriptor set,
  • endpoints для тестового выполнения операций,
  • discovery endpoints для gRPC metadata.

crank-mcp-server

Ответственность:

  • список доступных MCP tools из registry,
  • валидация входа tool по нормализованной схеме,
  • делегирование выполнения в runtime,
  • возврат нормализованного результата MCP-клиенту.

crank-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-структура:

crank/
  apps/
    admin-api/
    mcp-server/
    ui/
  crates/
    crank-core/
    crank-schema/
    crank-mapping/
    crank-proto/
    crank-registry/
    crank-runtime/
    crank-adapter-rest/
    crank-adapter-graphql/
    crank-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 слоя, отдельной схемной модели и отдельного адаптера.