docs: redesign architecture around workspaces and agents

This commit is contained in:
a.tolmachev
2026-03-29 21:11:04 +03:00
parent df2974bafa
commit 2219d1249b
11 changed files with 1321 additions and 3270 deletions
+184 -460
View File
@@ -2,78 +2,161 @@
## 1. Назначение проекта
Проект представляет собой платформу для динамической публикации внешних API в виде MCP tools. Пользователь конфигурирует операцию через административный UI вместо написания отдельного backend-обработчика. Платформа сохраняет конфигурацию, валидирует ее, позволяет выполнить тестовый вызов и публикует операцию для использования LLM через MCP.
Crank - платформа для публикации внешних API в виде MCP tools без написания отдельного backend-кода под каждую интеграцию. Пользователь конфигурирует интеграции через UI, а система:
Главная инженерная цель проекта - представить разные протоколы как единый набор операций с точки зрения MCP-слоя.
- хранит и версионирует операции;
- группирует их по workspace;
- публикует их в составе конкретных agents;
- выдает LLM не глобальный каталог tools, а curated toolset на один agent;
- собирает продуктовые логи и usage по workspace, agent и operation.
## 2. Ключевой принцип проектирования
## 2. Переход `As Is -> To Be`
Центральная абстракция системы - `Operation`.
### 2.1. As Is
Каждая операция описывает один вызываемый элемент независимо от протокола:
Текущее ядро системы построено вокруг:
- `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.
- глобальной сущности `Operation`;
- registry версий операций;
- runtime adapters `REST / GraphQL / unary gRPC`;
- `admin-api` для CRUD и тестовых вызовов;
- `mcp-server`, который публикует tools из published operations.
MCP server должен понимать только нормализованный контракт. Протокольные адаптеры должны преобразовывать нормализованную модель в конкретный REST, GraphQL или gRPC вызов и затем возвращать ответ обратно в нормализованный JSON.
### 2.2. To Be
## 3. Границы продукта
Целевая архитектура расширяет текущее ядро до модели:
### Входит в MVP
- `Workspace` - tenant boundary;
- `Operation` - интеграционный контракт;
- `Agent` - curated MCP surface;
- `Platform API key` и `Membership` - доступ к самой платформе;
- `Invocation log` и `Usage rollup` - observability слой.
- Административный UI для создания и редактирования операций.
- Динамический реестр операций.
- Runtime-выполнение REST операций.
- Runtime-выполнение GraphQL операций.
- Runtime-выполнение unary gRPC методов.
- Загрузка примеров `JSON` для ускоренного создания схем и mappings.
- Импорт и экспорт конфигураций в `YAML`.
- Тестирование операций до публикации.
- Публикация MCP tools на основе данных из реестра.
- Hot reload опубликованных операций без изменения backend-кода.
`Operation` остается фундаментом интеграции, но tools больше не публикуются глобально. MCP публикует tools в контексте конкретного `workspace` и конкретного `agent`.
### Не входит в MVP
## 3. Ключевые сущности и их роль
### `Workspace`
Изолирует:
- операции;
- auth profiles;
- agents;
- platform API keys;
- logs и usage;
- пользователей и роли.
### `Operation`
Описывает один вызываемый элемент независимо от протокола:
- `name`
- `display_name`
- `protocol`
- `target`
- `input_schema`
- `input_mapping`
- `execution_config`
- `output_mapping`
- `tool_description`
- `status`
### `Agent`
Является пользовательской MCP-поверхностью для LLM.
`Agent`:
- принадлежит одному workspace;
- имеет `slug`, `display_name`, `description`, `status`;
- ссылается на ограниченный набор published operations;
- формирует отдельный MCP endpoint;
- решает проблему "одному агенту нельзя отдавать 100 tools сразу".
### `Platform access`
Отдельный слой, не связанный с upstream auth:
- `User`
- `Membership`
- `Invitation`
- `PlatformApiKey`
### `Observability`
Отдельный продуктовый слой:
- `InvocationLog`
- `InvocationEvent`
- `UsageRollup`
- `LatencyStats`
## 4. Главный принцип проектирования
Система строится в три слоя:
1. `Operation` как низкоуровневый интеграционный контракт.
2. `Agent` как curated набор published operations.
3. `Workspace` как граница данных, доступа и observability.
Это позволяет:
- переиспользовать одну operation в нескольких agents;
- ограничивать tool catalog для конкретного LLM-сценария;
- изолировать данные команд;
- строить logs и usage не глобально, а по tenant boundary.
## 5. Границы целевого MVP
### Входит
- `Workspace` как tenant boundary.
- Операции `REST`, `GraphQL`, `unary gRPC`.
- `Agent` и привязка операций к агенту.
- Agent-scoped MCP endpoints.
- Platform API keys.
- Workspace-scoped auth profiles для upstream access.
- Product logs и usage aggregates.
- Импорт и экспорт operation-конфигураций в `YAML`.
- Hot reload опубликованных agents и operations.
### Не входит
- gRPC streaming.
- Полноценный импорт OpenAPI с автоматической генерацией маппинга.
- Полноценный визуальный конструктор GraphQL-запросов.
- SOAP.
- Выполнение произвольного кода внутри mapping-правил.
- Оркестрация нескольких операций в виде workflow.
- Мультитенантность и биллинг.
- Оркестрация workflow.
- Биллинг.
- Full RBAC policy engine.
- Traffic splitting и deployment orchestration.
## 4. Пользовательский сценарий
## 6. Пользовательские сценарии
Сценарий работы оператора должен быть одинаковым для всех протоколов:
### Оператор операций
1. Выбрать протокол.
2. Указать целевой хост или сервер.
3. Выбрать или описать внешнюю операцию.
4. Определить MCP-входные параметры.
5. Сопоставить MCP-вход с внешним запросом.
6. Сопоставить внешний ответ с MCP-выходом.
7. Добавить описание для MCP и LLM.
8. Выполнить тестовый вызов.
9. Опубликовать операцию.
1. Выбирает workspace.
2. Создает или редактирует operation.
3. Выполняет test run.
4. Публикует operation version.
5. Привязывает operation к одному или нескольким agents.
UI должен максимально скрывать протокольную сложность. REST endpoint, GraphQL operation и gRPC method должны отображаться для оператора как "операция с входными и выходными параметрами".
### Оператор агентов
## 5. Стратегия по протоколам
1. Создает agent.
2. Выбирает набор published operations.
3. Публикует agent.
4. Получает MCP endpoint вида `/mcp/v1/{workspace}/{agent}`.
### Администратор workspace
1. Управляет API keys платформы.
2. Управляет пользователями и ролями.
3. Смотрит logs и usage.
## 7. Стратегия по протоколам
### REST
REST-адаптер является базовым и должен реализовываться первым.
Поддержка в MVP:
- `GET`
- `POST`
- `PUT`
@@ -84,22 +167,9 @@ REST-адаптер является базовым и должен реализ
- 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
@@ -108,56 +178,16 @@ REST-адаптер является базовым и должен реализ
- 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.
GraphQL в MCP публикуется как фиксированная операция с предсказуемой структурой ответа.
### gRPC
gRPC - наиболее сложный протокол в этом проекте, поэтому его нужно ограничить на раннем этапе.
- только unary RPC;
- `.proto` и `descriptor set`;
- JSON-oriented schema model поверх protobuf;
- без streaming.
Поддержка в 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.
## 8. Работа с файлами и автогенерация черновика
Поддерживаемые источники:
@@ -168,368 +198,62 @@ Streaming gRPC сознательно не входит в рамки проек
Ожидаемый сценарий:
1. Оператор загружает пример входных данных и пример ответа.
2. Система строит черновую схему входа и выхода.
3. Система предлагает стартовый mapping по совпадающим или близким по структуре полям.
4. Оператор вручную корректирует результат.
5. Для точечной настройки используется `JSONPath`.
6. Готовую конфигурацию можно экспортировать в `YAML` или импортировать обратно.
1. оператор загружает артефакты;
2. система строит черновую схему и mapping;
3. оператор вручную корректирует результат;
4. готовую конфигурацию можно экспортировать в `YAML`.
Для gRPC источником структуры является не пример JSON-сообщения, а `.proto` или descriptor set. Однако после преобразования protobuf-схемы во внутреннюю JSON-ориентированную модель пользовательский опыт должен оставаться тем же: видим структуру полей, получаем стартовый mapping, затем уточняем его вручную.
## 9. Внутренняя модель данных
`YAML` используется как человекочитаемое представление конфигурации operation для:
Базовые сущности:
- переноса между окружениями;
- резервного копирования;
- хранения в git;
- редактирования вне UI;
- пакетного импорта нескольких operation.
- `Workspace`
- `Operation`
- `OperationVersion`
- `Agent`
- `AgentVersion`
- `AgentOperationBinding`
- `AuthProfile`
- `PlatformApiKey`
- `InvocationLog`
- `UsageRollup`
Storage backend для sample-файлов, `.proto`, `descriptor set` и YAML import payload в MVP должен быть локальным файловым хранилищем приложения с явным `storage_ref`. В дальнейшем этот слой можно заменить на S3-compatible storage без изменения доменной модели.
## 10. MCP publishing model
## 7. Внутренняя модель данных
Публикация tools строится так:
Система должна приводить все данные к JSON-ориентированным структурам, чтобы UI, registry и MCP runtime работали с единым контрактом.
1. `Operation` проходит versioning и publish.
2. `Agent` собирает curated набор published operations.
3. `MCP server` читает published view конкретного agent.
4. `tools/list` и `tools/call` работают в контексте `workspace + agent`.
### Operation
## 11. Observability
- `id`
- `name`
- `display_name`
- `protocol`
На каждый вызов tool сохраняются:
- `workspace_id`
- `agent_id`
- `operation_id`
- `request_id`
- `timestamp`
- `status`
- `target`
- `input_schema`
- `output_schema`
- `input_mapping`
- `output_mapping`
- `execution_config`
- `tool_description`
- `created_at`
- `updated_at`
- `duration_ms`
- `error_kind`
- `request_preview`
- `response_preview`
### Target
Сверху строятся:
REST target:
- logs page;
- usage page;
- периодические rollups;
- latency and error aggregates.
- `base_url`
- `method`
- `path_template`
## 12. Модель маппинга
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`,
- константы,
- значения по умолчанию,
- сопоставление поле-в-поле по `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-структура:
```text
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 слоя, отдельной схемной модели и отдельного адаптера.