697 lines
20 KiB
Markdown
697 lines
20 KiB
Markdown
# Модель данных
|
||
|
||
## 1. Назначение документа
|
||
|
||
Этот документ фиксирует формальную модель данных платформы. Его цель - определить такие структуры, которые можно без существенных изменений перенести в:
|
||
|
||
- Rust domain types,
|
||
- HTTP DTO,
|
||
- структуру таблиц БД,
|
||
- runtime-представление operation,
|
||
- UI-формы и конфигурационные экраны.
|
||
|
||
Документ не привязан к конкретной СУБД, но задает каноническую JSON-модель сущностей.
|
||
|
||
## 2. Общие принципы модели
|
||
|
||
### 2.1. Одна операция - один tool
|
||
|
||
Каждая `Operation` соответствует одному MCP tool. Это особенно важно для:
|
||
|
||
- GraphQL, где одна operation соответствует одному конкретному `query` или `mutation`;
|
||
- gRPC, где одна operation соответствует одному unary-методу;
|
||
- REST, где одна operation соответствует одному endpoint-сценарию.
|
||
|
||
### 2.2. Внутренний транспортный формат - JSON
|
||
|
||
Независимо от внешнего протокола внутри системы данные должны быть представлены в JSON-ориентированном виде. Даже если внешний вызов работает с protobuf, runtime, mapping и UI опираются на нормализованный JSON.
|
||
|
||
### 2.3. Mapping всегда явный
|
||
|
||
Даже если система умеет строить черновой mapping по примерам данных, итоговая конфигурация mapping должна быть явно сохранена в operation. Нельзя полагаться на неявную "магию" сопоставления во время выполнения.
|
||
|
||
### 2.4. JSONPath как единый язык адресации
|
||
|
||
Для input и output mapping используется `JSONPath`. Это позволяет единообразно ссылаться на вложенные поля во входе, промежуточном представлении запроса и нормализованном ответе.
|
||
|
||
### 2.5. YAML как формат обмена конфигурацией
|
||
|
||
Помимо канонической JSON-модели система должна поддерживать импорт и экспорт конфигураций в `YAML`. Это внешний формат обмена, а не отдельная доменная модель.
|
||
|
||
## 3. Корневая сущность `Operation`
|
||
|
||
`Operation` - основная конфигурационная сущность платформы.
|
||
|
||
### Поля
|
||
|
||
- `id` - уникальный идентификатор операции.
|
||
- `name` - стабильное внутреннее имя.
|
||
- `display_name` - отображаемое имя в UI.
|
||
- `protocol` - `rest`, `graphql`, `grpc`.
|
||
- `status` - `draft`, `testing`, `published`, `archived`.
|
||
- `version` - версия конфигурации операции.
|
||
- `target` - описание внешней операции.
|
||
- `input_schema` - схема MCP-входа.
|
||
- `output_schema` - схема MCP-выхода.
|
||
- `input_mapping` - правила подготовки внешнего запроса.
|
||
- `output_mapping` - правила формирования MCP-ответа.
|
||
- `execution_config` - auth, headers, timeout, retries и protocol-specific execution settings.
|
||
- `tool_description` - описание tool для MCP и LLM.
|
||
- `samples` - загруженные образцы JSON и schema artifacts.
|
||
- `generated_draft` - автоматически построенный черновик схем и mappings.
|
||
- `config_export` - опциональные метаданные экспортируемой конфигурации.
|
||
- `created_at`
|
||
- `updated_at`
|
||
- `published_at`
|
||
|
||
### Пример
|
||
|
||
```json
|
||
{
|
||
"id": "op_01hr7w0m6p8x9z4n7s2k3q5t6u",
|
||
"name": "crm_create_lead",
|
||
"display_name": "Create Lead",
|
||
"protocol": "rest",
|
||
"status": "draft",
|
||
"version": 3,
|
||
"target": {
|
||
"kind": "rest",
|
||
"base_url": "https://api.example.com",
|
||
"method": "POST",
|
||
"path_template": "/v1/leads"
|
||
},
|
||
"input_schema": {
|
||
"type": "object",
|
||
"fields": {
|
||
"name": {
|
||
"type": "string",
|
||
"required": true
|
||
},
|
||
"email": {
|
||
"type": "string",
|
||
"required": true
|
||
}
|
||
}
|
||
},
|
||
"output_schema": {
|
||
"type": "object",
|
||
"fields": {
|
||
"id": {
|
||
"type": "string",
|
||
"required": true
|
||
},
|
||
"status": {
|
||
"type": "string",
|
||
"required": true
|
||
}
|
||
}
|
||
},
|
||
"input_mapping": {
|
||
"rules": [
|
||
{
|
||
"source": "$.mcp.name",
|
||
"target": "$.request.body.name"
|
||
},
|
||
{
|
||
"source": "$.mcp.email",
|
||
"target": "$.request.body.email"
|
||
}
|
||
]
|
||
},
|
||
"output_mapping": {
|
||
"rules": [
|
||
{
|
||
"source": "$.response.body.id",
|
||
"target": "$.output.id"
|
||
},
|
||
{
|
||
"source": "$.response.body.status",
|
||
"target": "$.output.status"
|
||
}
|
||
]
|
||
},
|
||
"execution_config": {
|
||
"timeout_ms": 10000,
|
||
"auth_profile_ref": "auth_01hr7x8rj2d8nq8v0c4m4t1r9e"
|
||
},
|
||
"tool_description": {
|
||
"title": "Create CRM lead",
|
||
"description": "Creates a new lead in CRM by name and email."
|
||
},
|
||
"samples": {
|
||
"input_json_sample_ref": "file_01hr7xj7z4k1t0qm0cxsw6f1wx",
|
||
"output_json_sample_ref": "file_01hr7xkvt3m1p6ms3m7r2dr8wz"
|
||
},
|
||
"generated_draft": {
|
||
"status": "available",
|
||
"source_types": ["input_json_sample", "output_json_sample"]
|
||
},
|
||
"config_export": {
|
||
"format_version": "1",
|
||
"export_mode": "portable"
|
||
},
|
||
"created_at": "2026-03-25T08:00:00Z",
|
||
"updated_at": "2026-03-25T08:10:00Z",
|
||
"published_at": null
|
||
}
|
||
```
|
||
|
||
## 4. `Target`
|
||
|
||
`Target` описывает конкретный внешний вызов. Это discriminated union по протоколу.
|
||
|
||
### 4.1. `RestTarget`
|
||
|
||
```json
|
||
{
|
||
"kind": "rest",
|
||
"base_url": "https://api.example.com",
|
||
"method": "PATCH",
|
||
"path_template": "/v1/users/{userId}",
|
||
"static_headers": {
|
||
"X-App-Source": "mcpaas"
|
||
}
|
||
}
|
||
```
|
||
|
||
Поля:
|
||
|
||
- `kind`
|
||
- `base_url`
|
||
- `method`
|
||
- `path_template`
|
||
- `static_headers`
|
||
|
||
### 4.2. `GraphqlTarget`
|
||
|
||
```json
|
||
{
|
||
"kind": "graphql",
|
||
"endpoint": "https://api.example.com/graphql",
|
||
"operation_type": "mutation",
|
||
"operation_name": "CreateLead",
|
||
"query_template": "mutation CreateLead($input: LeadInput!) { createLead(input: $input) { id status } }",
|
||
"response_path": "$.response.body.data.createLead"
|
||
}
|
||
```
|
||
|
||
Поля:
|
||
|
||
- `kind`
|
||
- `endpoint`
|
||
- `operation_type`
|
||
- `operation_name`
|
||
- `query_template`
|
||
- `response_path`
|
||
|
||
### 4.3. `GrpcTarget`
|
||
|
||
```json
|
||
{
|
||
"kind": "grpc",
|
||
"server_addr": "https://grpc.example.com:443",
|
||
"package": "crm.v1",
|
||
"service": "LeadService",
|
||
"method": "CreateLead",
|
||
"descriptor_ref": "desc_01hr7yn4d6g1x6vwt7h9n0e7ab"
|
||
}
|
||
```
|
||
|
||
Поля:
|
||
|
||
- `kind`
|
||
- `server_addr`
|
||
- `package`
|
||
- `service`
|
||
- `method`
|
||
- `descriptor_ref`
|
||
|
||
## 5. `Schema`
|
||
|
||
`Schema` - нормализованное описание входа или выхода. Это не JSON Schema в полном объеме, а внутренняя структурная модель, удобная для UI и runtime.
|
||
|
||
### Базовая форма
|
||
|
||
```json
|
||
{
|
||
"type": "object",
|
||
"description": "Lead input",
|
||
"fields": {
|
||
"name": {
|
||
"type": "string",
|
||
"required": true,
|
||
"description": "Lead full name"
|
||
},
|
||
"tags": {
|
||
"type": "array",
|
||
"required": false,
|
||
"items": {
|
||
"type": "string"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Поддерживаемые типы
|
||
|
||
- `object`
|
||
- `array`
|
||
- `string`
|
||
- `integer`
|
||
- `number`
|
||
- `boolean`
|
||
- `enum`
|
||
- `null`
|
||
- `oneof`
|
||
|
||
### Модель поля
|
||
|
||
```json
|
||
{
|
||
"type": "string",
|
||
"required": true,
|
||
"nullable": false,
|
||
"description": "User email",
|
||
"default": null
|
||
}
|
||
```
|
||
|
||
Тип объекта:
|
||
|
||
```json
|
||
{
|
||
"type": "object",
|
||
"required": true,
|
||
"fields": {
|
||
"email": {
|
||
"type": "string",
|
||
"required": true
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Тип массива:
|
||
|
||
```json
|
||
{
|
||
"type": "array",
|
||
"required": false,
|
||
"items": {
|
||
"type": "object",
|
||
"fields": {
|
||
"id": {
|
||
"type": "string",
|
||
"required": true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 6. `MappingSet` и `MappingRule`
|
||
|
||
`MappingSet` - набор правил преобразования между внутренним MCP input/output и protocol-specific request/response model.
|
||
|
||
### `MappingSet`
|
||
|
||
```json
|
||
{
|
||
"rules": [
|
||
{
|
||
"source": "$.mcp.user_id",
|
||
"target": "$.request.path.userId"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
### `MappingRule`
|
||
|
||
Поля:
|
||
|
||
- `source` - `JSONPath` в исходном контексте.
|
||
- `target` - `JSONPath` в целевом контексте.
|
||
- `required` - обязательно ли правило для корректного вызова.
|
||
- `default_value` - значение по умолчанию.
|
||
- `transform` - встроенное преобразование.
|
||
- `condition` - условие применения правила.
|
||
- `notes` - служебное описание для UI.
|
||
|
||
Пример:
|
||
|
||
```json
|
||
{
|
||
"source": "$.mcp.profile.email",
|
||
"target": "$.request.body.contact.email",
|
||
"required": true,
|
||
"default_value": null,
|
||
"transform": {
|
||
"kind": "identity"
|
||
},
|
||
"condition": null,
|
||
"notes": "Map email to CRM contact payload"
|
||
}
|
||
```
|
||
|
||
### Контексты `source` и `target`
|
||
|
||
Для input mapping:
|
||
|
||
- `$.mcp.*`
|
||
- `$.request.path.*`
|
||
- `$.request.query.*`
|
||
- `$.request.headers.*`
|
||
- `$.request.body.*`
|
||
- `$.request.variables.*`
|
||
- `$.request.grpc.*`
|
||
|
||
Для output mapping:
|
||
|
||
- `$.response.body.*`
|
||
- `$.response.data.*`
|
||
- `$.response.grpc.*`
|
||
- `$.output.*`
|
||
|
||
### `Transform`
|
||
|
||
Для MVP transformations должны быть ограничены:
|
||
|
||
- `identity`
|
||
- `to_string`
|
||
- `to_number`
|
||
- `to_boolean`
|
||
- `join`
|
||
- `split`
|
||
- `wrap_array`
|
||
- `unwrap_singleton`
|
||
|
||
Пример:
|
||
|
||
```json
|
||
{
|
||
"kind": "to_string"
|
||
}
|
||
```
|
||
|
||
## 7. `ExecutionConfig`
|
||
|
||
`ExecutionConfig` задает параметры выполнения operation.
|
||
|
||
```json
|
||
{
|
||
"timeout_ms": 10000,
|
||
"retry_policy": {
|
||
"max_attempts": 1
|
||
},
|
||
"auth_profile_ref": "auth_01hr7x8rj2d8nq8v0c4m4t1r9e",
|
||
"headers": {
|
||
"X-Client": "mcpaas"
|
||
},
|
||
"protocol_options": {
|
||
"rest": null,
|
||
"graphql": null,
|
||
"grpc": {
|
||
"use_tls": true
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Поля:
|
||
|
||
- `timeout_ms`
|
||
- `retry_policy`
|
||
- `auth_profile_ref`
|
||
- `headers`
|
||
- `protocol_options`
|
||
|
||
Важно:
|
||
|
||
- здесь хранятся execution settings, а не описание бизнес-схемы;
|
||
- `protocol_options` должны оставаться узкими и протокол-специфичными;
|
||
- секреты не хранятся внутри operation, только ссылки на secret store или auth profile.
|
||
|
||
## 8. `AuthProfile`
|
||
|
||
`AuthProfile` - переиспользуемая конфигурация аутентификации для внешних вызовов.
|
||
|
||
Operation ссылается на auth profile через `auth_profile_ref`, а не хранит секреты внутри себя.
|
||
|
||
### Базовая форма
|
||
|
||
```json
|
||
{
|
||
"id": "auth_01hr7x8rj2d8nq8v0c4m4t1r9e",
|
||
"name": "crm-prod-bearer",
|
||
"kind": "bearer",
|
||
"config": {
|
||
"header_name": "Authorization",
|
||
"secret_ref": "secret://auth/crm-prod-token"
|
||
},
|
||
"created_at": "2026-03-25T08:00:00Z",
|
||
"updated_at": "2026-03-25T08:10:00Z"
|
||
}
|
||
```
|
||
|
||
### Поддерживаемые виды
|
||
|
||
- `bearer`
|
||
- `basic`
|
||
- `api_key_header`
|
||
- `api_key_query`
|
||
|
||
### MVP-решение по секретам
|
||
|
||
Для MVP секреты должны храниться не в open text внутри operation version, а в отдельном secret storage слое.
|
||
|
||
Рекомендуемое решение:
|
||
|
||
- логическая ссылка в формате `secret://...`;
|
||
- реальное значение хранится в приложении либо в зашифрованном хранилище, либо в env-backed secret store;
|
||
- в документации и YAML export секреты всегда представляются только через `secret_ref`.
|
||
|
||
## 9. `ToolDescription`
|
||
|
||
`ToolDescription` задает MCP-представление operation.
|
||
|
||
```json
|
||
{
|
||
"title": "Get customer profile",
|
||
"description": "Returns a customer profile by external customer identifier.",
|
||
"tags": ["crm", "customer"],
|
||
"examples": [
|
||
{
|
||
"input": {
|
||
"customer_id": "123"
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
Поля:
|
||
|
||
- `title`
|
||
- `description`
|
||
- `tags`
|
||
- `examples`
|
||
|
||
## 10. `Samples`
|
||
|
||
`Samples` связывает operation с загруженными артефактами, на основе которых может быть построен черновик.
|
||
|
||
```json
|
||
{
|
||
"input_json_sample_ref": "file_01hr7xj7z4k1t0qm0cxsw6f1wx",
|
||
"output_json_sample_ref": "file_01hr7xkvt3m1p6ms3m7r2dr8wz",
|
||
"proto_file_ref": null,
|
||
"descriptor_ref": null
|
||
}
|
||
```
|
||
|
||
Поля:
|
||
|
||
- `input_json_sample_ref`
|
||
- `output_json_sample_ref`
|
||
- `proto_file_ref`
|
||
- `descriptor_ref`
|
||
|
||
Примечания:
|
||
|
||
- для REST чаще всего используются входной и выходной JSON samples;
|
||
- для GraphQL чаще всего полезен sample ответа;
|
||
- для gRPC основным artifact остается `.proto` или descriptor set, но input/output JSON samples тоже могут использоваться для MCP-facing модели.
|
||
|
||
## 11. `GeneratedDraft`
|
||
|
||
`GeneratedDraft` хранит результат автоматической генерации схем и mappings.
|
||
|
||
```json
|
||
{
|
||
"status": "available",
|
||
"source_types": ["input_json_sample", "output_json_sample"],
|
||
"generated_at": "2026-03-25T08:05:00Z",
|
||
"input_schema_generated": true,
|
||
"output_schema_generated": true,
|
||
"input_mapping_generated": true,
|
||
"output_mapping_generated": true,
|
||
"warnings": [
|
||
"Field $.response.body.meta was not mapped automatically"
|
||
]
|
||
}
|
||
```
|
||
|
||
Поля:
|
||
|
||
- `status` - `none`, `available`, `stale`, `failed`
|
||
- `source_types`
|
||
- `generated_at`
|
||
- `input_schema_generated`
|
||
- `output_schema_generated`
|
||
- `input_mapping_generated`
|
||
- `output_mapping_generated`
|
||
- `warnings`
|
||
|
||
Важно:
|
||
|
||
- generated draft - это не runtime-источник истины;
|
||
- runtime использует только сохраненные `input_schema`, `output_schema`, `input_mapping`, `output_mapping`;
|
||
- generated draft нужен как вспомогательный слой для UI и ускорения конфигурирования.
|
||
|
||
## 12. Runtime view
|
||
|
||
Для исполнения operation должно существовать упрощенное runtime-представление без UI-специфики.
|
||
|
||
```json
|
||
{
|
||
"id": "op_01hr7w0m6p8x9z4n7s2k3q5t6u",
|
||
"protocol": "rest",
|
||
"target": {
|
||
"kind": "rest",
|
||
"base_url": "https://api.example.com",
|
||
"method": "POST",
|
||
"path_template": "/v1/leads"
|
||
},
|
||
"input_schema": { "type": "object", "fields": {} },
|
||
"output_schema": { "type": "object", "fields": {} },
|
||
"input_mapping": { "rules": [] },
|
||
"output_mapping": { "rules": [] },
|
||
"execution_config": {
|
||
"timeout_ms": 10000
|
||
}
|
||
}
|
||
```
|
||
|
||
Runtime view должно:
|
||
|
||
- не содержать UI draft metadata;
|
||
- не зависеть от raw uploaded files;
|
||
- быть готовым к немедленному исполнению адаптером.
|
||
|
||
## 13. YAML-конфигурация
|
||
|
||
Для импорта и экспорта система должна поддерживать `YAML`-представление operation.
|
||
|
||
Принцип:
|
||
|
||
- внутренняя доменная модель одна;
|
||
- `JSON` и `YAML` - это два способа сериализации одной и той же конфигурации;
|
||
- runtime не зависит от конкретного формата файла;
|
||
- `YAML` нужен для переносимости и ручного редактирования.
|
||
|
||
### Базовая структура YAML
|
||
|
||
```yaml
|
||
format_version: "1"
|
||
kind: operation
|
||
operation:
|
||
id: op_01hr7w0m6p8x9z4n7s2k3q5t6u
|
||
name: crm_create_lead
|
||
display_name: Create Lead
|
||
protocol: rest
|
||
status: draft
|
||
version: 3
|
||
target:
|
||
kind: rest
|
||
base_url: https://api.example.com
|
||
method: POST
|
||
path_template: /v1/leads
|
||
input_schema:
|
||
type: object
|
||
fields:
|
||
name:
|
||
type: string
|
||
required: true
|
||
email:
|
||
type: string
|
||
required: true
|
||
output_schema:
|
||
type: object
|
||
fields:
|
||
id:
|
||
type: string
|
||
required: true
|
||
status:
|
||
type: string
|
||
required: true
|
||
input_mapping:
|
||
rules:
|
||
- source: $.mcp.name
|
||
target: $.request.body.name
|
||
- source: $.mcp.email
|
||
target: $.request.body.email
|
||
output_mapping:
|
||
rules:
|
||
- source: $.response.body.id
|
||
target: $.output.id
|
||
- source: $.response.body.status
|
||
target: $.output.status
|
||
execution_config:
|
||
timeout_ms: 10000
|
||
auth_profile_ref: auth_01hr7x8rj2d8nq8v0c4m4t1r9e
|
||
tool_description:
|
||
title: Create CRM lead
|
||
description: Creates a new lead in CRM by name and email.
|
||
```
|
||
|
||
### Требования к YAML import/export
|
||
|
||
- формат должен быть детерминированным;
|
||
- структура должна быть человекочитаемой;
|
||
- импорт должен валидировать схему, mapping и protocol-specific target;
|
||
- экспорт не должен включать секреты в открытом виде;
|
||
- ссылки на внешние артефакты допустимы, но режим экспорта должен быть явным.
|
||
|
||
### Режимы экспорта
|
||
|
||
Минимально стоит предусмотреть два режима:
|
||
|
||
- `portable` - экспорт только конфигурации operation и ссылок на внешние артефакты;
|
||
- `bundle` - экспорт конфигурации operation вместе с вложенными sample metadata и descriptor metadata, если это допустимо.
|
||
|
||
Для MVP можно начать только с `portable`.
|
||
|
||
## 14. Что важно не допустить
|
||
|
||
- одну гигантскую `Operation`, в которой protocol-specific поля лежат вперемешку;
|
||
- неявный mapping, который не сохраняется после генерации черновика;
|
||
- смешивание uploaded artifacts и runtime-ready configuration;
|
||
- хранение секретов внутри operation;
|
||
- хранение реальных auth credentials внутри YAML export;
|
||
- произвольные пользовательские скрипты в mapping;
|
||
- YAML-экспорт, который становится отдельной несовместимой моделью по отношению к доменной структуре.
|
||
|
||
## 15. Практический итог
|
||
|
||
Эта модель задает основу для:
|
||
|
||
- Rust structs в `mcpaas-core`, `mcpaas-schema`, `mcpaas-mapping`;
|
||
- DTO для `admin-api`;
|
||
- таблиц `operations`, `operation_versions`, `operation_samples`, `operation_descriptors`;
|
||
- import/export layer для `YAML` конфигураций;
|
||
- runtime view, который будет передаваться в `mcpaas-runtime`.
|
||
|
||
Следующим логическим шагом после этого документа должна стать схема БД, в которой эти сущности будут разложены по таблицам и связям.
|