Initialize project scaffold and domain model

This commit is contained in:
a.tolmachev
2026-03-25 12:20:42 +03:00
commit fb302b2a2c
51 changed files with 6815 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
/target
/.idea
/.vscode
/.DS_Store
/node_modules
/dist
/coverage
/.env
/var
*.log
+68
View File
@@ -0,0 +1,68 @@
# AGENTS
## Purpose
This repository is developed through agent-assisted workflow. Follow the repository documents first, then implement code.
## Source of truth
Use the documents in this order when there is ambiguity:
1. `docs/architecture.md`
2. `docs/module-decomposition.md`
3. `docs/data-model.md`
4. `docs/database-schema.md`
5. `docs/admin-api.md`
6. `docs/mcp-interface.md`
7. `docs/rust-design.md`
8. `docs/development-rules.md`
9. `docs/rust-code-rules.md`
10. `docs/implementation-plan.md`
If code and docs diverge, update docs first or together with code.
## Workflow
- Follow `Red -> Green -> Refactor -> Commit`.
- Every feature uses its own branch: `feat/<feature-name>`.
- Commits must be atomic.
- Push periodically after one or more logically complete `RGR + commit` cycles.
- Do not wait for the whole feature to be finished before pushing.
## Language rules
- Commit messages must be in English.
- Code identifiers must be in English.
- Code comments are avoided by default.
- If a code comment is truly unavoidable, it must be in English.
## Code rules
- Prefer self-documenting code.
- Keep domain logic separate from storage, transport, and orchestration.
- Do not create god-structs or giant services.
- Keep `pub` surface minimal.
- Avoid `unwrap`, `expect`, `todo`, `dbg`, and `panic` in production code.
- `unsafe` is forbidden by default.
## Commands
Use the canonical commands from `justfile`:
- `just fmt`
- `just fmt-check`
- `just check`
- `just clippy`
- `just test`
- `just verify`
## Current execution mode
- Build the Rust workspace first.
- Keep the UI as a separate app outside the Cargo workspace.
- Implement one vertical slice at a time.
## Task tracking
- Check `TASKS.md` before starting a new piece of work.
- Update `TASKS.md` when a task starts, finishes, or gets blocked.
Generated
+346
View File
@@ -0,0 +1,346 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "admin-api"
version = "0.1.0"
dependencies = [
"tokio",
"tracing",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "indexmap"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "mcp-server"
version = "0.1.0"
dependencies = [
"tokio",
"tracing",
]
[[package]]
name = "mcpaas-adapter-graphql"
version = "0.1.0"
dependencies = [
"mcpaas-core",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "mcpaas-adapter-grpc"
version = "0.1.0"
dependencies = [
"mcpaas-core",
"mcpaas-proto",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "mcpaas-adapter-rest"
version = "0.1.0"
dependencies = [
"mcpaas-core",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "mcpaas-core"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
"serde_yaml",
"thiserror",
]
[[package]]
name = "mcpaas-mapping"
version = "0.1.0"
dependencies = [
"mcpaas-core",
"serde",
"serde_json",
"serde_yaml",
"thiserror",
]
[[package]]
name = "mcpaas-proto"
version = "0.1.0"
dependencies = [
"mcpaas-core",
"mcpaas-schema",
"serde",
"thiserror",
]
[[package]]
name = "mcpaas-registry"
version = "0.1.0"
dependencies = [
"mcpaas-core",
"mcpaas-mapping",
"mcpaas-schema",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "mcpaas-runtime"
version = "0.1.0"
dependencies = [
"mcpaas-adapter-graphql",
"mcpaas-adapter-grpc",
"mcpaas-adapter-rest",
"mcpaas-core",
"mcpaas-mapping",
"mcpaas-schema",
"serde",
"serde_json",
"thiserror",
]
[[package]]
name = "mcpaas-schema"
version = "0.1.0"
dependencies = [
"mcpaas-core",
"serde",
"serde_json",
"serde_yaml",
"thiserror",
]
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio"
version = "1.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
dependencies = [
"pin-project-lite",
"tokio-macros",
]
[[package]]
name = "tokio-macros"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+29
View File
@@ -0,0 +1,29 @@
[workspace]
members = [
"apps/admin-api",
"apps/mcp-server",
"crates/mcpaas-core",
"crates/mcpaas-schema",
"crates/mcpaas-mapping",
"crates/mcpaas-proto",
"crates/mcpaas-registry",
"crates/mcpaas-runtime",
"crates/mcpaas-adapter-rest",
"crates/mcpaas-adapter-graphql",
"crates/mcpaas-adapter-grpc",
]
resolver = "3"
[workspace.package]
edition = "2024"
license = "MIT"
rust-version = "1.85"
version = "0.1.0"
[workspace.dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
thiserror = "2"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
tracing = "0.1"
+74
View File
@@ -0,0 +1,74 @@
# MCPaaS
MCPaaS - это low-code платформа для публикации внешних API в виде MCP tools без написания нового backend-обработчика под каждую интеграцию. Система предоставляет единый административный UI, в котором оператор может подключать REST, GraphQL и gRPC операции, настраивать маппинг входных и выходных данных, выполнять тестовый вызов и публиковать результат как MCP tool.
На текущем этапе репозиторий содержит проектную документацию и архитектурные решения, которые задают границы MVP и подход к реализации.
## Цели
- Разработать MCP server на Rust.
- Поддержать динамическое добавление интеграций через UI или конфигурацию.
- Обеспечить единый сценарий работы оператора для REST, GraphQL и gRPC.
- Нормализовать внешние протоколы в единую внутреннюю модель операции.
- Избежать генерации и деплоя нового backend-кода при добавлении каждого нового инструмента.
## Состав MVP
- Поддержка REST для `GET`, `POST`, `PUT`, `PATCH` и `DELETE`.
- Поддержка GraphQL для `query` и `mutation` на основе шаблонов и переменных.
- Поддержка только unary-методов gRPC.
- Загрузка примеров `JSON` для ускоренного создания схем и чернового маппинга.
- Загрузка `.proto` файлов или descriptor set для обнаружения схемы gRPC.
- Импорт и экспорт конфигураций операций в `YAML`.
- Использование `JSONPath` для точечного маппинга вложенных параметров и ответа.
- Настройка маппинга запроса и ответа через UI.
- Публикация tools в MCP без пересборки backend.
## Структура документации
- `docs/architecture.md` - архитектура системы, модули, потоки данных и стек.
- `docs/module-decomposition.md` - детальная декомпозиция crates и внутренних модулей.
- `docs/data-model.md` - формальная модель данных и JSON-структуры сущностей.
- `docs/database-schema.md` - схема БД, связи и versioning конфигураций.
- `docs/admin-api.md` - HTTP-контракты административного API.
- `docs/diagrams.md` - структурные диаграммы компонентов, сущностей, БД и потоков.
- `docs/mcp-interface.md` - транспорт и контракт публикации MCP tools.
- `docs/testing-strategy.md` - стратегия тестирования до и во время разработки.
- `docs/runtime-config.md` - конфигурация окружения, storage и секретов.
- `docs/rust-design.md` - распределение методов, `impl`, `trait` и service-слоя без god-struct.
- `docs/development-rules.md` - правила разработки, TDD-процесс и git workflow.
- `docs/rust-code-rules.md` - Rust-specific правила кода, toolchain и linting.
- `docs/implementation-plan.md` - последовательность модулей и фич по этапам реализации.
- `docs/protocols/rest.md` - функциональные требования и ограничения для REST.
- `docs/protocols/graphql.md` - функциональные требования и ограничения для GraphQL.
- `docs/protocols/grpc.md` - функциональные требования и ограничения для gRPC.
## Ключевая идея продукта
Система строится вокруг унифицированной сущности `Operation`. Каждая операция описывает:
- внешний протокол,
- целевой endpoint или метод,
- входную схему,
- правила маппинга входных данных,
- параметры выполнения,
- правила маппинга выходных данных,
- метаданные MCP tool.
За счет этого MCP runtime работает с единой внутренней моделью, а протокольные адаптеры уже выполняют конкретные вызовы REST, GraphQL или gRPC.
Для GraphQL это означает, что в MCP публикуется не "универсальный GraphQL endpoint", а конкретная операция с фиксированным шаблоном запроса, фиксированным набором входных параметров и предсказуемой структурой ответа.
Для упрощения настройки оператор может загружать примеры входного и выходного `JSON`, а для gRPC - `.proto` или descriptor set. На основе этих артефактов система строит черновую схему и стартовый маппинг, который затем вручную уточняется через `JSONPath`.
Конфигурации операций должны импортироваться и экспортироваться в `YAML`, чтобы их можно было переносить между окружениями, хранить в git и редактировать вне UI.
## Поддерживаемые протоколы
В MVP платформа ориентируется на три основных протокольных сценария интеграции:
- REST
- GraphQL
- gRPC
`SOAP` сознательно не входит в MVP. Он остается актуальным для части корпоративных и государственных интеграций, но требует отдельного адаптера с поддержкой WSDL, XML Schema, SOAP envelope, namespaces и XML-oriented mapping. Для первой версии это слишком большой отдельный пласт сложности.
+45
View File
@@ -0,0 +1,45 @@
# TASKS
## Current
### `feat/domain-model`
Status: completed
DoD:
- core domain types from `docs/data-model.md` exist
- basic JSON and YAML serialization works
- domain unit tests pass
## Next
### `feat/schema-engine`
Status: pending
DoD:
- schema model exists
- schema validation works
- JSON sample normalization works
### `feat/mapping-engine`
Status: pending
DoD:
- JSONPath parsing works
- input and output mapping work
- draft generation from samples works
## Backlog
- `feat/registry-storage`
- `feat/rest-vertical-slice`
- `feat/admin-api-v1`
- `feat/ui-v1`
- `feat/mcp-server`
- `feat/graphql-support`
- `feat/grpc-support`
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "admin-api"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
version.workspace = true
[dependencies]
tokio.workspace = true
tracing.workspace = true
+1
View File
@@ -0,0 +1 @@
fn main() {}
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "mcp-server"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
version.workspace = true
[dependencies]
tokio.workspace = true
tracing.workspace = true
+1
View File
@@ -0,0 +1 @@
fn main() {}
+3
View File
@@ -0,0 +1,3 @@
# UI
The UI app is planned as a separate TypeScript project and is intentionally kept outside the Cargo workspace.
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "mcpaas-adapter-graphql"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
version.workspace = true
[dependencies]
mcpaas-core = { path = "../mcpaas-core" }
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn crate_name() -> &'static str {
"mcpaas-adapter-graphql"
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mcpaas-adapter-grpc"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
version.workspace = true
[dependencies]
mcpaas-core = { path = "../mcpaas-core" }
mcpaas-proto = { path = "../mcpaas-proto" }
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn crate_name() -> &'static str {
"mcpaas-adapter-grpc"
}
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "mcpaas-adapter-rest"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
version.workspace = true
[dependencies]
mcpaas-core = { path = "../mcpaas-core" }
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn crate_name() -> &'static str {
"mcpaas-adapter-rest"
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mcpaas-core"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
version.workspace = true
[dependencies]
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
[dev-dependencies]
serde_yaml.workspace = true
+59
View File
@@ -0,0 +1,59 @@
use serde::{Deserialize, Serialize};
use crate::{ids::AuthProfileId, protocol::AuthKind};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SecretRef(pub String);
impl SecretRef {
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BearerAuthConfig {
pub header_name: String,
pub secret_ref: SecretRef,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BasicAuthConfig {
pub username_secret_ref: SecretRef,
pub password_secret_ref: SecretRef,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ApiKeyHeaderAuthConfig {
pub header_name: String,
pub secret_ref: SecretRef,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ApiKeyQueryAuthConfig {
pub param_name: String,
pub secret_ref: SecretRef,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AuthConfig {
Bearer(BearerAuthConfig),
Basic(BasicAuthConfig),
ApiKeyHeader(ApiKeyHeaderAuthConfig),
ApiKeyQuery(ApiKeyQueryAuthConfig),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AuthProfile {
pub id: AuthProfileId,
pub name: String,
pub kind: AuthKind,
pub config: AuthConfig,
pub created_at: String,
pub updated_at: String,
}
+42
View File
@@ -0,0 +1,42 @@
use serde::{Deserialize, Serialize};
macro_rules! define_id {
($name:ident) => {
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct $name(String);
impl $name {
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<String> for $name {
fn from(value: String) -> Self {
Self(value)
}
}
impl From<&str> for $name {
fn from(value: &str) -> Self {
Self(value.to_owned())
}
}
impl AsRef<str> for $name {
fn as_ref(&self) -> &str {
self.as_str()
}
}
};
}
define_id!(OperationId);
define_id!(DescriptorId);
define_id!(ToolId);
define_id!(SampleId);
define_id!(AuthProfileId);
+16
View File
@@ -0,0 +1,16 @@
pub mod auth;
pub mod ids;
pub mod operation;
pub mod protocol;
pub use auth::{
ApiKeyHeaderAuthConfig, ApiKeyQueryAuthConfig, AuthConfig, AuthProfile, BasicAuthConfig,
BearerAuthConfig, SecretRef,
};
pub use ids::{AuthProfileId, DescriptorId, OperationId, SampleId, ToolId};
pub use operation::{
ConfigExport, ExecutionConfig, GeneratedDraft, GeneratedDraftStatus, GraphqlTarget,
GrpcProtocolOptions, GrpcTarget, Operation, OperationStatus, ProtocolOptions, RestTarget,
RetryPolicy, Samples, Target, ToolDescription, ToolExample,
};
pub use protocol::{AuthKind, ExportMode, GraphqlOperationType, HttpMethod, Protocol};
+363
View File
@@ -0,0 +1,363 @@
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::{
ids::{AuthProfileId, DescriptorId, OperationId, SampleId},
protocol::{ExportMode, GraphqlOperationType, HttpMethod, Protocol},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum OperationStatus {
Draft,
Testing,
Published,
Archived,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RestTarget {
pub base_url: String,
pub method: HttpMethod,
pub path_template: String,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub static_headers: BTreeMap<String, String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct GraphqlTarget {
pub endpoint: String,
pub operation_type: GraphqlOperationType,
pub operation_name: String,
pub query_template: String,
pub response_path: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct GrpcTarget {
pub server_addr: String,
pub package: String,
pub service: String,
pub method: String,
pub descriptor_ref: DescriptorId,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum Target {
Rest(RestTarget),
Graphql(GraphqlTarget),
Grpc(GrpcTarget),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct RetryPolicy {
pub max_attempts: u32,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct GrpcProtocolOptions {
pub use_tls: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct ProtocolOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub grpc: Option<GrpcProtocolOptions>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ExecutionConfig {
pub timeout_ms: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub retry_policy: Option<RetryPolicy>,
#[serde(skip_serializing_if = "Option::is_none")]
pub auth_profile_ref: Option<AuthProfileId>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub headers: BTreeMap<String, String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub protocol_options: Option<ProtocolOptions>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolExample {
pub input: Value,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ToolDescription {
pub title: String,
pub description: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub examples: Vec<ToolExample>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct Samples {
#[serde(skip_serializing_if = "Option::is_none")]
pub input_json_sample_ref: Option<SampleId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_json_sample_ref: Option<SampleId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub proto_file_ref: Option<SampleId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub descriptor_ref: Option<DescriptorId>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GeneratedDraftStatus {
None,
Available,
Stale,
Failed,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct GeneratedDraft {
pub status: GeneratedDraftStatus,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub source_types: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub generated_at: Option<String>,
pub input_schema_generated: bool,
pub output_schema_generated: bool,
pub input_mapping_generated: bool,
pub output_mapping_generated: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub warnings: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ConfigExport {
pub format_version: String,
pub export_mode: ExportMode,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Operation<TSchema, TMapping> {
pub id: OperationId,
pub name: String,
pub display_name: String,
pub protocol: Protocol,
pub status: OperationStatus,
pub version: u32,
pub target: Target,
pub input_schema: TSchema,
pub output_schema: TSchema,
pub input_mapping: TMapping,
pub output_mapping: TMapping,
pub execution_config: ExecutionConfig,
pub tool_description: ToolDescription,
#[serde(skip_serializing_if = "Option::is_none")]
pub samples: Option<Samples>,
#[serde(skip_serializing_if = "Option::is_none")]
pub generated_draft: Option<GeneratedDraft>,
#[serde(skip_serializing_if = "Option::is_none")]
pub config_export: Option<ConfigExport>,
pub created_at: String,
pub updated_at: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub published_at: Option<String>,
}
impl<TSchema, TMapping> Operation<TSchema, TMapping> {
pub fn tool_name(&self) -> &str {
&self.name
}
pub fn is_draft(&self) -> bool {
self.status == OperationStatus::Draft
}
pub fn is_published(&self) -> bool {
self.status == OperationStatus::Published
}
pub fn protocol(&self) -> Protocol {
self.protocol
}
pub fn auth_profile_ref(&self) -> Option<&AuthProfileId> {
self.execution_config.auth_profile_ref.as_ref()
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use serde_json::json;
use crate::{
auth::{AuthConfig, AuthProfile, BearerAuthConfig, SecretRef},
ids::{AuthProfileId, OperationId},
operation::{
ConfigExport, ExecutionConfig, GraphqlTarget, Operation, OperationStatus,
ProtocolOptions, RestTarget, Samples, Target, ToolDescription, ToolExample,
},
protocol::{AuthKind, ExportMode, GraphqlOperationType, HttpMethod, Protocol},
};
#[test]
fn rest_target_serializes_with_kind_tag() {
let target = Target::Rest(RestTarget {
base_url: "https://api.example.com".to_owned(),
method: HttpMethod::Post,
path_template: "/v1/leads".to_owned(),
static_headers: BTreeMap::new(),
});
let value = serde_json::to_value(target).unwrap();
assert_eq!(value["kind"], "rest");
assert_eq!(value["method"], "POST");
}
#[test]
fn graphql_target_serializes_response_path() {
let target = Target::Graphql(GraphqlTarget {
endpoint: "https://api.example.com/graphql".to_owned(),
operation_type: GraphqlOperationType::Mutation,
operation_name: "CreateLead".to_owned(),
query_template: "mutation {}".to_owned(),
response_path: "$.response.body.data.createLead".to_owned(),
});
let value = serde_json::to_value(target).unwrap();
assert_eq!(value["kind"], "graphql");
assert_eq!(value["operation_type"], "mutation");
}
#[test]
fn operation_exposes_local_domain_helpers() {
let operation = Operation {
id: OperationId::new("op_01"),
name: "crm_create_lead".to_owned(),
display_name: "Create Lead".to_owned(),
protocol: Protocol::Rest,
status: OperationStatus::Draft,
version: 1,
target: Target::Rest(RestTarget {
base_url: "https://api.example.com".to_owned(),
method: HttpMethod::Post,
path_template: "/v1/leads".to_owned(),
static_headers: BTreeMap::new(),
}),
input_schema: json!({"type":"object"}),
output_schema: json!({"type":"object"}),
input_mapping: json!({"rules":[]}),
output_mapping: json!({"rules":[]}),
execution_config: ExecutionConfig {
timeout_ms: 10_000,
retry_policy: None,
auth_profile_ref: Some(AuthProfileId::new("auth_01")),
headers: BTreeMap::new(),
protocol_options: Some(ProtocolOptions::default()),
},
tool_description: ToolDescription {
title: "Create CRM lead".to_owned(),
description: "Creates a new lead.".to_owned(),
tags: Vec::new(),
examples: Vec::new(),
},
samples: Some(Samples::default()),
generated_draft: None,
config_export: None,
created_at: "2026-03-25T08:00:00Z".to_owned(),
updated_at: "2026-03-25T08:00:00Z".to_owned(),
published_at: None,
};
assert_eq!(operation.tool_name(), "crm_create_lead");
assert!(operation.is_draft());
assert!(!operation.is_published());
assert_eq!(operation.protocol(), Protocol::Rest);
assert_eq!(
operation.auth_profile_ref().map(|value| value.as_str()),
Some("auth_01")
);
}
#[test]
fn auth_profile_serializes_secret_refs_without_secret_values() {
let profile = AuthProfile {
id: AuthProfileId::new("auth_01"),
name: "crm-prod-bearer".to_owned(),
kind: AuthKind::Bearer,
config: AuthConfig::Bearer(BearerAuthConfig {
header_name: "Authorization".to_owned(),
secret_ref: SecretRef::new("secret://auth/crm-prod-token"),
}),
created_at: "2026-03-25T08:00:00Z".to_owned(),
updated_at: "2026-03-25T08:00:00Z".to_owned(),
};
let value = serde_json::to_value(profile).unwrap();
assert_eq!(value["kind"], "bearer");
assert_eq!(
value["config"]["bearer"]["secret_ref"],
"secret://auth/crm-prod-token"
);
}
#[test]
fn operation_roundtrips_through_yaml() {
let operation = Operation {
id: OperationId::new("op_01"),
name: "crm_create_lead".to_owned(),
display_name: "Create Lead".to_owned(),
protocol: Protocol::Rest,
status: OperationStatus::Published,
version: 3,
target: Target::Rest(RestTarget {
base_url: "https://api.example.com".to_owned(),
method: HttpMethod::Post,
path_template: "/v1/leads".to_owned(),
static_headers: BTreeMap::from([("X-App-Source".to_owned(), "mcpaas".to_owned())]),
}),
input_schema: json!({"type":"object","fields":{"email":{"type":"string","required":true}}}),
output_schema: json!({"type":"object","fields":{"id":{"type":"string","required":true}}}),
input_mapping: json!({"rules":[{"source":"$.mcp.email","target":"$.request.body.email"}]}),
output_mapping: json!({"rules":[{"source":"$.response.body.id","target":"$.output.id"}]}),
execution_config: ExecutionConfig {
timeout_ms: 10_000,
retry_policy: None,
auth_profile_ref: Some(AuthProfileId::new("auth_01")),
headers: BTreeMap::new(),
protocol_options: Some(ProtocolOptions::default()),
},
tool_description: ToolDescription {
title: "Create CRM lead".to_owned(),
description: "Creates a new lead.".to_owned(),
tags: vec!["crm".to_owned()],
examples: vec![ToolExample {
input: json!({"email":"user@example.com"}),
}],
},
samples: Some(Samples::default()),
generated_draft: None,
config_export: Some(ConfigExport {
format_version: "1".to_owned(),
export_mode: ExportMode::Portable,
}),
created_at: "2026-03-25T08:00:00Z".to_owned(),
updated_at: "2026-03-25T08:10:00Z".to_owned(),
published_at: Some("2026-03-25T08:15:00Z".to_owned()),
};
let yaml = serde_yaml::to_string(&operation).unwrap();
let restored: Operation<serde_json::Value, serde_json::Value> =
serde_yaml::from_str(&yaml).unwrap();
assert!(yaml.contains("protocol: rest"));
assert!(yaml.contains("export_mode: portable"));
assert_eq!(restored, operation);
}
}
+42
View File
@@ -0,0 +1,42 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Protocol {
Rest,
Graphql,
Grpc,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum HttpMethod {
Get,
Post,
Put,
Patch,
Delete,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GraphqlOperationType {
Query,
Mutation,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AuthKind {
Bearer,
Basic,
ApiKeyHeader,
ApiKeyQuery,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ExportMode {
Portable,
Bundle,
}
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "mcpaas-mapping"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
version.workspace = true
[dependencies]
mcpaas-core = { path = "../mcpaas-core" }
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
[dev-dependencies]
serde_yaml.workspace = true
+129
View File
@@ -0,0 +1,129 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MappingCondition {
pub source: String,
pub equals: Value,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TransformKind {
Identity,
ToString,
ToNumber,
ToBoolean,
Join,
Split,
WrapArray,
UnwrapSingleton,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Transform {
pub kind: TransformKind,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MappingRule {
pub source: String,
pub target: String,
#[serde(default)]
pub required: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_value: Option<Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub transform: Option<Transform>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub condition: Option<MappingCondition>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub notes: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
pub struct MappingSet {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub rules: Vec<MappingRule>,
}
impl MappingSet {
pub fn is_empty(&self) -> bool {
self.rules.is_empty()
}
pub fn len(&self) -> usize {
self.rules.len()
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::{MappingRule, MappingSet, Transform, TransformKind};
#[test]
fn mapping_set_reports_non_empty_rules() {
let mapping = MappingSet {
rules: vec![MappingRule {
source: "$.mcp.email".to_owned(),
target: "$.request.body.contact.email".to_owned(),
required: true,
default_value: None,
transform: Some(Transform {
kind: TransformKind::Identity,
}),
condition: None,
notes: None,
}],
};
assert!(!mapping.is_empty());
assert_eq!(mapping.len(), 1);
}
#[test]
fn mapping_rule_serializes_jsonpath_fields() {
let mapping = MappingSet {
rules: vec![MappingRule {
source: "$.response.body.id".to_owned(),
target: "$.output.id".to_owned(),
required: false,
default_value: Some(json!("lead_123")),
transform: None,
condition: None,
notes: Some("map identifier".to_owned()),
}],
};
let value = serde_json::to_value(mapping).unwrap();
assert_eq!(value["rules"][0]["source"], "$.response.body.id");
assert_eq!(value["rules"][0]["target"], "$.output.id");
assert_eq!(value["rules"][0]["default_value"], "lead_123");
}
#[test]
fn mapping_set_roundtrips_through_yaml() {
let mapping = MappingSet {
rules: vec![MappingRule {
source: "$.mcp.tags".to_owned(),
target: "$.request.body.tags".to_owned(),
required: false,
default_value: None,
transform: Some(Transform {
kind: TransformKind::WrapArray,
}),
condition: None,
notes: Some("normalize tags".to_owned()),
}],
};
let yaml = serde_yaml::to_string(&mapping).unwrap();
let restored: MappingSet = serde_yaml::from_str(&yaml).unwrap();
assert!(yaml.contains("source: $.mcp.tags"));
assert_eq!(restored, mapping);
}
}
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "mcpaas-proto"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
version.workspace = true
[dependencies]
mcpaas-core = { path = "../mcpaas-core" }
mcpaas-schema = { path = "../mcpaas-schema" }
serde.workspace = true
thiserror.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn crate_name() -> &'static str {
"mcpaas-proto"
}
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "mcpaas-registry"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
version.workspace = true
[dependencies]
mcpaas-core = { path = "../mcpaas-core" }
mcpaas-mapping = { path = "../mcpaas-mapping" }
mcpaas-schema = { path = "../mcpaas-schema" }
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn crate_name() -> &'static str {
"mcpaas-registry"
}
+18
View File
@@ -0,0 +1,18 @@
[package]
name = "mcpaas-runtime"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
version.workspace = true
[dependencies]
mcpaas-adapter-graphql = { path = "../mcpaas-adapter-graphql" }
mcpaas-adapter-grpc = { path = "../mcpaas-adapter-grpc" }
mcpaas-adapter-rest = { path = "../mcpaas-adapter-rest" }
mcpaas-core = { path = "../mcpaas-core" }
mcpaas-mapping = { path = "../mcpaas-mapping" }
mcpaas-schema = { path = "../mcpaas-schema" }
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
+3
View File
@@ -0,0 +1,3 @@
pub fn crate_name() -> &'static str {
"mcpaas-runtime"
}
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "mcpaas-schema"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
version.workspace = true
[dependencies]
mcpaas-core = { path = "../mcpaas-core" }
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
[dev-dependencies]
serde_yaml.workspace = true
+164
View File
@@ -0,0 +1,164 @@
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SchemaKind {
Object,
Array,
String,
Integer,
Number,
Boolean,
Enum,
#[serde(rename = "null")]
Null,
Oneof,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Schema {
#[serde(rename = "type")]
pub kind: SchemaKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default)]
pub required: bool,
#[serde(default)]
pub nullable: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_value: Option<Value>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub fields: BTreeMap<String, Schema>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub items: Option<Box<Schema>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub enum_values: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub variants: Vec<Schema>,
}
impl Schema {
pub fn is_object(&self) -> bool {
self.kind == SchemaKind::Object
}
pub fn field(&self, name: &str) -> Option<&Schema> {
self.fields.get(name)
}
pub fn has_required_fields(&self) -> bool {
self.fields.values().any(|field| field.required)
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use super::{Schema, SchemaKind};
#[test]
fn object_schema_exposes_fields() {
let mut fields = BTreeMap::new();
fields.insert(
"email".to_owned(),
Schema {
kind: SchemaKind::String,
description: None,
required: true,
nullable: false,
default_value: None,
fields: BTreeMap::new(),
items: None,
enum_values: Vec::new(),
variants: Vec::new(),
},
);
let schema = Schema {
kind: SchemaKind::Object,
description: Some("User input".to_owned()),
required: true,
nullable: false,
default_value: None,
fields,
items: None,
enum_values: Vec::new(),
variants: Vec::new(),
};
assert!(schema.is_object());
assert!(schema.has_required_fields());
assert_eq!(
schema.field("email").map(|field| field.required),
Some(true)
);
}
#[test]
fn schema_serializes_type_field() {
let schema = Schema {
kind: SchemaKind::Array,
description: None,
required: false,
nullable: false,
default_value: None,
fields: BTreeMap::new(),
items: Some(Box::new(Schema {
kind: SchemaKind::String,
description: None,
required: false,
nullable: false,
default_value: None,
fields: BTreeMap::new(),
items: None,
enum_values: Vec::new(),
variants: Vec::new(),
})),
enum_values: Vec::new(),
variants: Vec::new(),
};
let value = serde_json::to_value(schema).unwrap();
assert_eq!(value["type"], "array");
assert_eq!(value["items"]["type"], "string");
}
#[test]
fn schema_roundtrips_through_yaml() {
let schema = Schema {
kind: SchemaKind::Object,
description: Some("Lead output".to_owned()),
required: true,
nullable: false,
default_value: None,
fields: BTreeMap::from([(
"id".to_owned(),
Schema {
kind: SchemaKind::String,
description: None,
required: true,
nullable: false,
default_value: None,
fields: BTreeMap::new(),
items: None,
enum_values: Vec::new(),
variants: Vec::new(),
},
)]),
items: None,
enum_values: Vec::new(),
variants: Vec::new(),
};
let yaml = serde_yaml::to_string(&schema).unwrap();
let restored: Schema = serde_yaml::from_str(&yaml).unwrap();
assert!(yaml.contains("type: object"));
assert_eq!(restored, schema);
}
}
+451
View File
@@ -0,0 +1,451 @@
# Admin API
## 1. Назначение документа
Этот документ фиксирует HTTP-контракты административного API, через которое UI управляет операциями, загружает артефакты, тестирует вызовы и выполняет YAML import/export.
Документ задает логический контракт. Конкретные детали `axum` handlers, auth middleware и response envelope могут уточняться при реализации.
## 2. Общие правила API
- все payload по умолчанию в `JSON`;
- import/export конфигурации используют `YAML` как payload или файл;
- версии operation адресуются явно;
- published операция - это ссылка на конкретную version;
- ошибки валидации возвращаются отдельно от transport errors.
Базовый префикс:
```text
/api/admin
```
## 3. Основные ресурсы
- `operations`
- `versions`
- `samples`
- `descriptors`
- `auth-profiles`
- `test-runs`
- `config import/export`
## 4. CRUD операций
### `GET /api/admin/operations`
Назначение:
- список операций для UI.
Параметры:
- `protocol`
- `status`
- `search`
Ответ:
```json
{
"items": [
{
"id": "op_01",
"name": "crm_create_lead",
"display_name": "Create Lead",
"protocol": "rest",
"status": "draft",
"current_draft_version": 3,
"latest_published_version": 2,
"updated_at": "2026-03-25T09:00:00Z"
}
]
}
```
### `POST /api/admin/operations`
Назначение:
- создание новой операции и версии `1`.
Тело:
```json
{
"name": "crm_create_lead",
"display_name": "Create Lead",
"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
},
"tool_description": {
"title": "Create CRM lead",
"description": "Creates a new lead."
}
}
```
Ответ:
```json
{
"operation_id": "op_01",
"version": 1,
"status": "draft"
}
```
### `GET /api/admin/operations/{operation_id}`
Назначение:
- получить метаданные operation и ссылки на draft/published версии.
### `GET /api/admin/operations/{operation_id}/versions/{version}`
Назначение:
- получить полную конфигурацию конкретной версии.
### `POST /api/admin/operations/{operation_id}/versions`
Назначение:
- создать новую draft-версию на основе текущего payload.
Тело:
- полная конфигурация operation;
- опционально `change_note`.
Ответ:
```json
{
"operation_id": "op_01",
"version": 4,
"status": "draft"
}
```
## 5. Публикация
### `POST /api/admin/operations/{operation_id}/publish`
Назначение:
- опубликовать текущую draft-версию.
Тело:
```json
{
"version": 4
}
```
Ответ:
```json
{
"operation_id": "op_01",
"published_version": 4,
"published_at": "2026-03-25T10:00:00Z"
}
```
### `POST /api/admin/operations/{operation_id}/archive`
Назначение:
- перевести operation в archived status.
## 6. Samples и schema artifacts
### `POST /api/admin/operations/{operation_id}/samples/input-json`
Назначение:
- загрузить sample входного JSON.
Тип:
- `multipart/form-data` или raw `application/json`.
Ответ:
```json
{
"sample_id": "file_01",
"sample_kind": "input_json"
}
```
### `POST /api/admin/operations/{operation_id}/samples/output-json`
Назначение:
- загрузить sample выходного JSON.
### `POST /api/admin/operations/{operation_id}/descriptors/proto`
Назначение:
- загрузить `.proto`.
### `POST /api/admin/operations/{operation_id}/descriptors/descriptor-set`
Назначение:
- загрузить `descriptor set`.
### `GET /api/admin/operations/{operation_id}/grpc/services`
Назначение:
- получить discovery summary по services и methods.
Ответ:
```json
{
"services": [
{
"package": "crm.v1",
"service": "LeadService",
"methods": [
{
"name": "CreateLead",
"kind": "unary"
}
]
}
]
}
```
## 7. Черновая генерация схем и mappings
### `POST /api/admin/operations/{operation_id}/drafts/generate`
Назначение:
- построить черновую схему и mappings из samples и schema artifacts.
Тело:
```json
{
"sources": [
"input_json_sample",
"output_json_sample"
]
}
```
Ответ:
```json
{
"generated_draft": {
"status": "available",
"input_schema_generated": true,
"output_schema_generated": true,
"input_mapping_generated": true,
"output_mapping_generated": true,
"warnings": []
}
}
```
## 8. Тестовый запуск
### `POST /api/admin/operations/{operation_id}/test-runs`
Назначение:
- выполнить тестовый вызов draft-конфигурации.
Тело:
```json
{
"version": 4,
"input": {
"name": "Alice",
"email": "alice@example.com"
}
}
```
Ответ:
```json
{
"ok": true,
"request_preview": {
"body": {
"name": "Alice",
"email": "alice@example.com"
}
},
"response_preview": {
"id": "lead_123",
"status": "created"
},
"errors": []
}
```
## 9. Auth profiles
### `GET /api/admin/auth-profiles`
Назначение:
- список доступных профилей аутентификации.
### `POST /api/admin/auth-profiles`
Назначение:
- создать новый auth profile.
Тело:
```json
{
"name": "crm-prod-bearer",
"kind": "bearer",
"config": {
"header_name": "Authorization",
"secret_ref": "secret://auth/crm-prod-token"
}
}
```
### `GET /api/admin/auth-profiles/{auth_profile_id}`
Назначение:
- получить metadata auth profile без раскрытия секрета.
## 10. YAML export
### `GET /api/admin/operations/{operation_id}/export`
Назначение:
- экспортировать конфигурацию операции в `YAML`.
Параметры:
- `version` - опционально, если нужно экспортировать не current draft;
- `mode=portable|bundle`
Ответ:
- `Content-Type: application/yaml`
- тело ответа - YAML document
### Пример YAML response
```yaml
format_version: "1"
kind: operation
operation:
name: crm_create_lead
protocol: rest
status: draft
```
## 11. YAML import
### `POST /api/admin/operations/import`
Назначение:
- импортировать operation из YAML.
Тип:
- `application/yaml`
- или `multipart/form-data` с YAML файлом
Параметры:
- `mode=create|upsert`
Ответ:
```json
{
"operation_id": "op_01",
"version": 5,
"import_mode": "upsert",
"warnings": []
}
```
## 12. Ошибки
Рекомендуемые классы ошибок:
- `validation_error`
- `mapping_error`
- `schema_error`
- `descriptor_error`
- `auth_profile_error`
- `yaml_import_error`
- `runtime_test_error`
- `not_found`
- `conflict`
Пример:
```json
{
"error": {
"code": "validation_error",
"message": "Invalid JSONPath in input_mapping rule 2",
"details": {
"field": "input_mapping.rules[1].source"
}
}
}
```
## 13. Что важно не допустить
- смешивание CRUD и publish semantics в одном endpoint;
- обновление draft "поверх" существующей версии без создания новой version;
- YAML import как скрытый апдейт без явного режима `create|upsert`;
- возврат открытых секретов из auth-profile endpoints;
- привязку runtime к admin DTO;
- endpoints, возвращающие разные формы одной и той же сущности без причины.
## 14. Практический итог
Минимальный рабочий набор admin API для MVP:
- список и чтение operations;
- создание новой version;
- publish;
- upload samples и descriptors;
- auth profiles;
- generate draft;
- test run;
- YAML import/export.
Этого достаточно, чтобы UI полностью управлял жизненным циклом operation без ручного редактирования кода backend.
+517
View File
@@ -0,0 +1,517 @@
# Архитектура
## 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 может генерироваться автоматически на основе загруженных примеров данных, но итоговая конфигурация всегда остается явной и редактируемой оператором.
Каноническая логическая модель остается общей для 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 публикации и отображение статуса.
## 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 или SQLite
- `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 слоя, отдельной схемной модели и отдельного адаптера.
+696
View File
@@ -0,0 +1,696 @@
# Модель данных
## 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`.
Следующим логическим шагом после этого документа должна стать схема БД, в которой эти сущности будут разложены по таблицам и связям.
+355
View File
@@ -0,0 +1,355 @@
# Схема БД
## 1. Назначение документа
Этот документ фиксирует структуру хранения конфигураций, версий операций, загруженных артефактов и published runtime-view. Его цель - дать основу для SQL-миграций и для реализации `mcpaas-registry`.
В документе предполагается реляционная модель, ориентированная на `PostgreSQL`. Для MVP допускается адаптация под `SQLite`, но канонической считается схема, совместимая с `PostgreSQL`.
## 2. Общие принципы хранения
### 2.1. Версионирование обязательно
Конфигурация operation не должна храниться только в одной "живой" записи. Каждое существенное изменение должно приводить к появлению новой версии конфигурации.
### 2.2. Published и draft разделяются логически
- `draft` может меняться;
- `published` должна ссылаться на конкретную зафиксированную версию;
- runtime читает только опубликованные версии.
### 2.3. Артефакты и конфигурация не смешиваются
`.proto`, descriptor set, sample JSON и YAML import payload не должны храниться в той же структуре, что и runtime-ready configuration.
### 2.4. Секреты не хранятся внутри operation
В БД operation должны храниться только ссылки на auth profiles или secret references.
Для MVP рекомендуется отдельная таблица `auth_profiles`, где metadata и secret refs отделены от operation versions.
## 3. Основные таблицы
Минимальный набор таблиц:
- `operations`
- `operation_versions`
- `published_operations`
- `operation_samples`
- `descriptors`
- `auth_profiles`
- `yaml_import_jobs`
Опционально позже:
- `operation_test_runs`
- `audit_log`
## 4. Таблица `operations`
Хранит стабильную сущность операции, не зависящую от конкретной версии.
### Поля
- `id` `text primary key`
- `name` `text not null unique`
- `display_name` `text not null`
- `protocol` `text not null`
- `status` `text not null`
- `current_draft_version` `integer not null default 1`
- `latest_published_version` `integer null`
- `created_at` `timestamptz not null`
- `updated_at` `timestamptz not null`
- `published_at` `timestamptz null`
### Назначение
- быстрый список операций;
- стабильный идентификатор для UI и MCP;
- привязка к актуальному draft и опубликованной версии.
## 5. Таблица `operation_versions`
Хранит полную сериализованную конфигурацию конкретной версии operation.
### Поля
- `operation_id` `text not null`
- `version` `integer not null`
- `status` `text not null`
- `target_json` `jsonb not null`
- `input_schema_json` `jsonb not null`
- `output_schema_json` `jsonb not null`
- `input_mapping_json` `jsonb not null`
- `output_mapping_json` `jsonb not null`
- `execution_config_json` `jsonb not null`
- `tool_description_json` `jsonb not null`
- `samples_json` `jsonb null`
- `generated_draft_json` `jsonb null`
- `config_export_json` `jsonb null`
- `change_note` `text null`
- `created_at` `timestamptz not null`
- `created_by` `text null`
### Ключи
- primary key: `(operation_id, version)`
- foreign key: `operation_id -> operations(id)`
- рекомендованный composite foreign key для связанных таблиц: `(operation_id, version)`
### Почему так
Для MVP выгоднее хранить version snapshot целиком, а не дробить по десятку связанных таблиц. Это:
- упрощает versioning;
- упрощает откат;
- упрощает YAML export;
- хорошо сочетается с JSON-oriented доменной моделью.
## 6. Таблица `published_operations`
Хранит явную published-привязку, которую читает runtime.
### Поля
- `operation_id` `text primary key`
- `version` `integer not null`
- `published_at` `timestamptz not null`
- `published_by` `text null`
### Назначение
- быстрый доступ к published runtime-view;
- отсутствие двусмысленности, какая именно версия сейчас активна;
- простой invalidation для runtime cache.
### Рекомендуемая целостность
- `operation_id -> operations(id)`
- `(operation_id, version) -> operation_versions(operation_id, version)`
## 7. Таблица `operation_samples`
Хранит метаданные и ссылки на sample artifacts.
### Поля
- `id` `text primary key`
- `operation_id` `text not null`
- `version` `integer not null`
- `sample_kind` `text not null`
- `storage_ref` `text not null`
- `content_type` `text not null`
- `file_name` `text null`
- `created_at` `timestamptz not null`
### Варианты `sample_kind`
- `input_json`
- `output_json`
- `yaml_import_source`
### Назначение
- не класть большие sample payload в основные version records;
- иметь возможность переиспользовать или пересобирать draft mapping;
- отслеживать, из каких sample-данных строился черновик.
### Рекомендуемая целостность
- `operation_id -> operations(id)`
- `(operation_id, version) -> operation_versions(operation_id, version)`
## 8. Таблица `descriptors`
Хранит gRPC schema artifacts.
### Поля
- `id` `text primary key`
- `operation_id` `text null`
- `version` `integer null`
- `descriptor_kind` `text not null`
- `storage_ref` `text not null`
- `source_name` `text null`
- `package_index_json` `jsonb null`
- `created_at` `timestamptz not null`
### Варианты `descriptor_kind`
- `proto_upload`
- `descriptor_set`
- `reflection_snapshot`
### Назначение
- связывать gRPC operation с конкретной схемой;
- не хранить binary descriptor внутри основной operation version;
- иметь отдельную точку для discovery metadata.
### Рекомендуемая целостность
- если descriptor привязан к version, то `(operation_id, version) -> operation_versions(operation_id, version)`
## 9. Таблица `yaml_import_jobs`
Для MVP можно импортировать YAML синхронно, но таблицу под журнал импорта лучше предусмотреть сразу.
### Поля
- `id` `text primary key`
- `source_sample_id` `text null`
- `status` `text not null`
- `format_version` `text not null`
- `mode` `text not null`
- `result_operation_id` `text null`
- `result_version` `integer null`
- `error_text` `text null`
- `created_at` `timestamptz not null`
- `finished_at` `timestamptz null`
### Назначение
- аудит импортов;
- разбор ошибок валидации;
- поддержка будущего async import pipeline.
## 10. Таблица `auth_profiles`
Хранит переиспользуемые профили аутентификации для внешних вызовов.
### Поля
- `id` `text primary key`
- `name` `text not null unique`
- `kind` `text not null`
- `config_json` `jsonb not null`
- `created_at` `timestamptz not null`
- `updated_at` `timestamptz not null`
### Варианты `kind`
- `bearer`
- `basic`
- `api_key_header`
- `api_key_query`
### Правило
`config_json` должен содержать только `secret_ref`, а не открытые секреты.
## 11. Предлагаемая SQL-форма
```sql
create table operations (
id text primary key,
name text not null unique,
display_name text not null,
protocol text not null,
status text not null,
current_draft_version integer not null default 1,
latest_published_version integer null,
created_at timestamptz not null,
updated_at timestamptz not null,
published_at timestamptz null
);
create table operation_versions (
operation_id text not null references operations(id),
version integer not null,
status text not null,
target_json jsonb not null,
input_schema_json jsonb not null,
output_schema_json jsonb not null,
input_mapping_json jsonb not null,
output_mapping_json jsonb not null,
execution_config_json jsonb not null,
tool_description_json jsonb not null,
samples_json jsonb null,
generated_draft_json jsonb null,
config_export_json jsonb null,
change_note text null,
created_at timestamptz not null,
created_by text null,
primary key (operation_id, version)
);
create table published_operations (
operation_id text primary key references operations(id),
version integer not null,
published_at timestamptz not null,
published_by text null,
foreign key (operation_id, version)
references operation_versions(operation_id, version)
);
create table auth_profiles (
id text primary key,
name text not null unique,
kind text not null,
config_json jsonb not null,
created_at timestamptz not null,
updated_at timestamptz not null
);
```
## 12. Индексы
Минимально нужны:
- index on `operations(protocol)`
- index on `operations(status)`
- index on `operation_versions(operation_id, created_at desc)`
- index on `published_operations(version)`
- index on `operation_samples(operation_id, version)`
- index on `descriptors(operation_id, version)`
- index on `auth_profiles(kind)`
## 13. Versioning flow
### Создание операции
1. Создается запись в `operations`.
2. Создается версия `1` в `operation_versions`.
3. `current_draft_version = 1`.
### Изменение draft
1. Читается текущий draft.
2. Создается новая версия `n + 1`.
3. В `operations.current_draft_version` пишется новая версия.
4. Published версия не меняется.
### Публикация
1. Берется текущий draft version.
2. В `published_operations` upsert-ится ссылка на эту версию.
3. В `operations.latest_published_version` пишется та же версия.
4. Runtime cache получает сигнал на reload.
### Импорт YAML
1. YAML валидируется.
2. Определяется create или update сценарий.
3. Создается новая запись в `operation_versions`.
4. При необходимости создается запись в `yaml_import_jobs`.
## 14. Что не должно храниться в БД в таком виде
- секреты в открытом виде;
- runtime cache;
- скомпилированные adapter clients;
- невалидированные черновики, не приводимые к доменной модели.
## 15. Практический итог
Для MVP рекомендован такой подход:
- `operations` - стабильная идентичность;
- `operation_versions` - полные version snapshots;
- `published_operations` - текущая активная версия;
- `operation_samples` и `descriptors` - внешние артефакты;
- `auth_profiles` - переиспользуемая внешняя аутентификация;
- `yaml_import_jobs` - журнал импортов.
Эта схема хорошо ложится на `sqlx`, не требует избыточной нормализации и соответствует JSON-oriented модели домена.
+251
View File
@@ -0,0 +1,251 @@
# Правила разработки
## 1. Назначение документа
Этот документ фиксирует, как именно должен разрабатываться проект:
- в каком стиле писать код;
- как применять `TDD`;
- как вести git;
- как принимать архитектурные решения во время реализации.
Цель документа - сделать процесс разработки предсказуемым и не дать проекту расползтись по качеству.
## 2. Базовые принципы
- сначала проектирование, потом код;
- сначала тест, потом реализация;
- идти по циклу `RGR + commit`;
- сначала маленький модуль, потом интеграция;
- одна ответственность на один модуль;
- никаких "временных" god-struct и "потом распилим".
## 3. Основной процесс разработки
Рекомендуемый цикл для каждой фичи:
1. Зафиксировать контракт в документации или тесте.
2. Написать failing test.
3. Реализовать минимальный код, который проходит тест.
4. Выполнить refactor без изменения поведения.
5. Сделать атомарный commit.
6. Если логически завершена часть фичи, сделать push.
7. Добавить integration test, если фича выходит за границы одного модуля.
8. Обновить документацию, если изменился контракт.
Это и есть базовый `TDD`-процесс проекта в форме `Red -> Green -> Refactor -> Commit`.
## 4. RGR + commit
Для проекта принимается классический цикл:
1. `Red`
2. `Green`
3. `Refactor`
4. `Commit`
Правила:
- без commit после завершенного `RGR`-цикла шаг не считается завершенным;
- commit должен фиксировать одну логическую единицу изменения;
- если несколько `RGR + commit` логично закрывают часть фичи, после этого делается push;
- не нужно ждать полного завершения всей feature branch, чтобы отправить изменения в удаленный репозиторий.
## 5. TDD-правила
### 5.1. Что пишется через TDD обязательно
- `mcpaas-schema`
- `mcpaas-mapping`
- `mcpaas-registry`
- `mcpaas-runtime`
- YAML import/export
- versioning logic
### 5.2. Что допускается делать сначала каркасом, потом тестами
- frontend layout;
- wiring приложений;
- пустые `axum` handlers;
- начальный scaffold `cargo workspace`.
Но как только появляется логика, она должна переходить под тесты.
### 5.3. Правило минимального шага
Нельзя писать сразу большую "умную" реализацию на сотни строк без промежуточных тестов.
Особенно это запрещено для:
- mapping engine;
- protobuf normalization;
- publish flow;
- YAML import pipeline.
## 6. Правила по коду
### 6.1. Стиль модулей
- модуль должен иметь одну четкую ответственность;
- публичный API модуля должен быть минимальным;
- если модуль начинает решать две разные задачи, он делится;
- `utils`, `common`, `helpers` допускаются только в очень редких случаях и с узким смыслом.
### 6.2. Стиль структур
- маленькие `struct`;
- явные типы вместо "универсальных" JSON-объектов там, где контракт уже известен;
- protocol-specific поля не смешиваются в одной структуре без discriminated union;
- методы на `impl` не должны тащить инфраструктурные зависимости.
### 6.3. Самоописывающийся код
Для проекта принимается подход self-documenting code:
- названия функций, переменных, типов и модулей должны быть достаточно точными, чтобы код читался как текст;
- комментарии в коде считаются исключением, а не нормой;
- если код хочется "объяснить" комментарием, сначала нужно попытаться упростить названия и декомпозицию;
- комментарии в коде по умолчанию не пишутся.
Допустимое исключение:
- редкий комментарий для неочевидного инварианта или ограничения внешнего протокола.
Но базовое правило проекта:
- комментарии в коде исключаем.
### 6.4. Язык кода и git
Для проекта фиксируется:
- commit messages только на английском языке;
- комментарии в коде только на английском языке, если они все-таки неизбежны;
- имена типов, функций, переменных, модулей и тестов только на английском языке.
### 6.5. Стиль ошибок
- использовать типизированные ошибки;
- ошибка должна сохранять стадию отказа: schema, mapping, adapter, external, persistence;
- нельзя сваливать все в строковый `anyhow` на границах домена.
### 6.6. Стиль async
- `async` использовать только там, где есть реальная I/O или async boundaries;
- не превращать чистую доменную логику в `async` без причины;
- не смешивать чистую валидацию и сетевые вызовы в одном методе.
## 7. Правила проектирования
Проект разрабатывается с опорой на `Clean Architecture`.
### 7.1. Что нельзя делать
- один большой `OperationService` на весь проект;
- один `AppState` со всеми зависимостями мира;
- один adapter с `match protocol` на сотни строк;
- доменные методы, которые знают про SQL, HTTP и файлы одновременно;
- скрытую магию в mapping generation.
### 7.2. Что нужно делать
- использовать `trait` на инфраструктурных границах;
- использовать `service/use case` для orchestration;
- держать доменные типы отдельно от DTO;
- создавать explicit runtime view для исполнения;
- держать published flow отдельно от draft editing.
### 7.3. Dependency rule
Для проекта фиксируется dependency rule:
- внешние слои могут зависеть от внутренних;
- внутренние слои не зависят от внешних;
- домен не знает про HTTP, SQL, storage и transport;
- adapters и repositories реализуют контракты, заданные ближе к домену.
## 8. Git workflow
## 8.1. Remote
Удаленный репозиторий проекта:
```text
git@github.com:bsodfather/rmcp.git
```
### 8.2. Ветки
Фиксируем такой workflow:
- `main` - стабильная ветка;
- каждая фича делается в отдельной ветке `feat/<feature-name>`.
Примеры:
- `feat/workspace-scaffold`
- `feat/schema-model`
- `feat/mapping-engine`
- `feat/admin-api-v1`
### 8.3. Коммиты
Коммит должен:
- быть маленьким;
- содержать одну логическую единицу;
- по возможности включать тесты вместе с реализацией;
- не смешивать refactor и новую фичу без причины.
Коммиты пишутся только на английском языке.
Хороший порядок:
1. `test: add failing tests for schema validation`
2. `feat: implement schema validator`
3. `refactor: simplify schema field traversal`
### 8.4. Push policy
Фиксируем такую политику push:
- push делается периодически;
- не нужно ждать полного закрытия feature branch;
- push делается после одного или нескольких логически связанных `RGR + commit`;
- push должен оставлять ветку в консистентном состоянии.
## 9. Definition of Done
Фича считается законченной, если:
- код реализован;
- unit/integration tests добавлены и проходят;
- документация обновлена, если изменился контракт;
- нет явного архитектурного долга "починим потом";
- фича вписывается в принятые границы слоев.
При этом для каждой конкретной фичи должен существовать свой локальный `DoD`, описанный в плане реализации.
## 10. Порядок принятия решений
Если в ходе реализации возникает спорное решение:
1. Проверяется текущая документация.
2. Если решение уже зафиксировано, следуем ему.
3. Если решение не зафиксировано, сначала обновляется документация.
4. Только после этого пишется код.
Это важно, чтобы код не начал определять архитектуру задним числом.
## 11. Практический итог
Для этого проекта правильный режим разработки такой:
- проектируем заранее;
- пишем через `TDD` в форме `RGR + commit` там, где есть логика;
- двигаемся маленькими этапами;
- ведем каждую фичу в отдельной ветке `feat/<feature-name>`;
- пушим атомарно и периодически;
- пишем commit messages и неизбежные code comments только на английском;
- стараемся вообще обходиться без комментариев в коде за счет самоописывающегося дизайна;
- не допускаем временных архитектурных компромиссов, которые потом невозможно разгрести.
+386
View File
@@ -0,0 +1,386 @@
# Диаграммы
## 1. Назначение документа
Этот документ собирает диаграммы, которые фиксируют проект до начала разработки:
- компонентную структуру;
- связи между доменными сущностями;
- хранение данных в БД;
- основные runtime и admin-потоки.
Диаграммы даны в формате `Mermaid`, чтобы их можно было хранить прямо в репозитории и рендерить в Markdown-compatible tooling.
## 2. Компонентная диаграмма
```mermaid
flowchart LR
UI[mcpaas-ui]
ADMIN[admin-api]
MCP[mcp-server]
REG[mcpaas-registry]
RUN[mcpaas-runtime]
CORE[mcpaas-core]
SCHEMA[mcpaas-schema]
MAP[mcpaas-mapping]
PROTO[mcpaas-proto]
REST[adapter-rest]
GQL[adapter-graphql]
GRPC[adapter-grpc]
DB[(PostgreSQL/SQLite)]
STORE[(Artifact Storage)]
UI --> ADMIN
MCP --> REG
MCP --> RUN
ADMIN --> REG
ADMIN --> RUN
ADMIN --> PROTO
REG --> DB
REG --> CORE
REG --> SCHEMA
REG --> MAP
RUN --> CORE
RUN --> SCHEMA
RUN --> MAP
RUN --> REST
RUN --> GQL
RUN --> GRPC
GRPC --> PROTO
PROTO --> STORE
ADMIN --> STORE
```
## 3. Диаграмма зависимостей crates
```mermaid
flowchart TD
CORE[mcpaas-core]
SCHEMA[mcpaas-schema]
MAP[mcpaas-mapping]
PROTO[mcpaas-proto]
REG[mcpaas-registry]
RUN[mcpaas-runtime]
REST[mcpaas-adapter-rest]
GQL[mcpaas-adapter-graphql]
GRPC[mcpaas-adapter-grpc]
ADMIN[apps/admin-api]
MCP[apps/mcp-server]
SCHEMA --> CORE
MAP --> CORE
PROTO --> CORE
PROTO --> SCHEMA
REG --> CORE
REG --> SCHEMA
REG --> MAP
REST --> CORE
GQL --> CORE
GRPC --> CORE
GRPC --> PROTO
RUN --> CORE
RUN --> SCHEMA
RUN --> MAP
RUN --> REST
RUN --> GQL
RUN --> GRPC
ADMIN --> CORE
ADMIN --> SCHEMA
ADMIN --> MAP
ADMIN --> PROTO
ADMIN --> REG
ADMIN --> RUN
MCP --> CORE
MCP --> REG
MCP --> RUN
```
## 4. Структурная диаграмма доменной модели
```mermaid
classDiagram
class Operation {
+id
+name
+display_name
+protocol
+status
+version
+target
+input_schema
+output_schema
+input_mapping
+output_mapping
+execution_config
+tool_description
+samples
+generated_draft
+config_export
}
class RestTarget {
+base_url
+method
+path_template
+static_headers
}
class GraphqlTarget {
+endpoint
+operation_type
+operation_name
+query_template
+response_path
}
class GrpcTarget {
+server_addr
+package
+service
+method
+descriptor_ref
}
class Schema {
+type
+description
+fields
}
class MappingSet {
+rules[]
}
class MappingRule {
+source
+target
+required
+default_value
+transform
+condition
}
class ExecutionConfig {
+timeout_ms
+retry_policy
+auth_profile_ref
+headers
+protocol_options
}
class AuthProfile {
+id
+name
+kind
+config
}
class ToolDescription {
+title
+description
+tags
+examples
}
class Samples {
+input_json_sample_ref
+output_json_sample_ref
+proto_file_ref
+descriptor_ref
}
class GeneratedDraft {
+status
+source_types
+generated_at
+warnings
}
Operation --> RestTarget : target
Operation --> GraphqlTarget : target
Operation --> GrpcTarget : target
Operation --> Schema : input_schema
Operation --> Schema : output_schema
Operation --> MappingSet : input_mapping
Operation --> MappingSet : output_mapping
Operation --> ExecutionConfig : execution_config
Operation --> ToolDescription : tool_description
Operation --> Samples : samples
Operation --> GeneratedDraft : generated_draft
ExecutionConfig --> AuthProfile : auth_profile_ref
MappingSet --> MappingRule : contains
```
## 5. ER-диаграмма БД
```mermaid
erDiagram
OPERATIONS ||--o{ OPERATION_VERSIONS : has
OPERATIONS ||--o| PUBLISHED_OPERATIONS : publishes
OPERATIONS ||--o{ OPERATION_SAMPLES : owns
OPERATIONS ||--o{ DESCRIPTORS : may_use
OPERATIONS ||--o{ YAML_IMPORT_JOBS : may_create
AUTH_PROFILES ||--o{ OPERATION_VERSIONS : referenced_by
OPERATIONS {
text id PK
text name
text display_name
text protocol
text status
int current_draft_version
int latest_published_version
timestamptz created_at
timestamptz updated_at
timestamptz published_at
}
OPERATION_VERSIONS {
text operation_id FK
int version
text status
jsonb target_json
jsonb input_schema_json
jsonb output_schema_json
jsonb input_mapping_json
jsonb output_mapping_json
jsonb execution_config_json
jsonb tool_description_json
jsonb samples_json
jsonb generated_draft_json
jsonb config_export_json
timestamptz created_at
}
PUBLISHED_OPERATIONS {
text operation_id PK
int version
timestamptz published_at
text published_by
}
OPERATION_SAMPLES {
text id PK
text operation_id FK
int version
text sample_kind
text storage_ref
text content_type
text file_name
timestamptz created_at
}
DESCRIPTORS {
text id PK
text operation_id FK
int version
text descriptor_kind
text storage_ref
jsonb package_index_json
timestamptz created_at
}
YAML_IMPORT_JOBS {
text id PK
text source_sample_id
text status
text format_version
text mode
text result_operation_id
int result_version
text error_text
timestamptz created_at
timestamptz finished_at
}
AUTH_PROFILES {
text id PK
text name
text kind
jsonb config_json
timestamptz created_at
timestamptz updated_at
}
```
## 6. Sequence: создание и публикация operation
```mermaid
sequenceDiagram
participant UI
participant API as admin-api
participant REG as registry
participant RUN as runtime
participant MCP as mcp-server
UI->>API: POST /operations
API->>REG: create operation v1
REG-->>API: created
API-->>UI: operation_id, version
UI->>API: upload samples / descriptors
API-->>UI: artifact refs
UI->>API: POST /drafts/generate
API->>REG: save generated draft metadata
API-->>UI: generated draft
UI->>API: POST /test-runs
API->>RUN: execute draft version
RUN-->>API: request_preview + response_preview
API-->>UI: test result
UI->>API: POST /publish
API->>REG: mark version as published
REG-->>API: published
API-->>MCP: reload signal
API-->>UI: published_version
```
## 7. Sequence: MCP tool execution
```mermaid
sequenceDiagram
participant Client as MCP Client
participant MCP as mcp-server
participant REG as registry/cache
participant RUN as runtime
participant ADP as protocol adapter
Client->>MCP: call tool(name, input)
MCP->>REG: resolve published runtime view
REG-->>MCP: runtime operation
MCP->>RUN: execute(operation, input)
RUN->>RUN: validate input schema
RUN->>RUN: apply input mapping
RUN->>ADP: execute prepared request
ADP-->>RUN: normalized response
RUN->>RUN: apply output mapping
RUN-->>MCP: output
MCP-->>Client: tool result
```
## 8. Sequence: YAML import
```mermaid
sequenceDiagram
participant UI
participant API as admin-api
participant REG as registry
UI->>API: POST /operations/import (YAML)
API->>API: parse YAML
API->>API: validate schema, target, mapping
API->>REG: create or upsert new version
REG-->>API: operation_id, version
API-->>UI: import result
```
## 9. Что важно помнить
- Диаграммы фиксируют целевую архитектуру, а не точную реализацию каждого файла.
- Если меняется модель данных или поток исполнения, сначала нужно обновлять документы, потом код.
- Для старта разработки этого набора достаточно: компоненты, сущности, БД и ключевые sequence flows уже описаны.
+399
View File
@@ -0,0 +1,399 @@
# План реализации
## 1. Назначение документа
Этот документ фиксирует порядок реализации модулей и фич. Он нужен затем, чтобы разработка шла последовательно, а не параллельно во все стороны сразу.
Принцип:
- сначала фундамент;
- потом минимальный end-to-end сценарий;
- потом расширение протоколов;
- потом polish и demo readiness.
## 2. Этап 0. Scaffold проекта
Цель:
- создать `cargo workspace`;
- создать приложения и crates;
- подключить базовый CI/test workflow;
- зафиксировать структуру каталогов.
Состав:
- `apps/admin-api`
- `apps/mcp-server`
- `apps/ui`
- `crates/mcpaas-core`
- `crates/mcpaas-schema`
- `crates/mcpaas-mapping`
- `crates/mcpaas-proto`
- `crates/mcpaas-registry`
- `crates/mcpaas-runtime`
- `crates/mcpaas-adapter-rest`
- `crates/mcpaas-adapter-graphql`
- `crates/mcpaas-adapter-grpc`
Результат:
- проект собирается;
- тестовый pipeline запускается;
- есть пустые crate boundaries.
DoD:
- создан `cargo workspace`;
- все crates и apps объявлены в workspace;
- проект собирается без бизнес-логики;
- базовые test targets запускаются;
- сделан атомарный commit со scaffold.
## 3. Этап 1. Базовая доменная модель
Цель:
- реализовать типы из `data-model`.
Фичи:
- `Operation`
- `Target`
- `Schema`
- `MappingSet`
- `ExecutionConfig`
- `ToolDescription`
- `AuthProfile`
Параллельно:
- unit tests на доменные типы;
- базовая сериализация `JSON`/`YAML`.
Результат:
- модель данных существует как код;
- нет инфраструктурных зависимостей внутри домена.
DoD:
- типы из `data-model` реализованы;
- базовая сериализация `JSON` и `YAML` проходит тесты;
- доменные `impl` не содержат инфраструктурной логики;
- unit tests на ключевые типы проходят;
- изменения зафиксированы через один или несколько `RGR + commit`.
## 4. Этап 2. Schema engine
Цель:
- реализовать `mcpaas-schema`.
Фичи:
- model полей и типов;
- schema validation;
- field traversal;
- нормализация JSON samples;
- protobuf -> schema bridge contracts.
Результат:
- можно описывать и валидировать вход/выход.
DoD:
- реализована схема полей и типов;
- работает schema validation;
- JSON sample normalization покрыт тестами;
- контракты protobuf -> schema зафиксированы;
- нет смешивания schema logic с adapter logic.
## 5. Этап 3. Mapping engine
Цель:
- реализовать `mcpaas-mapping`.
Фичи:
- `JSONPath` parsing и validation;
- input mapping;
- output mapping;
- transforms;
- generation draft mapping из samples.
Результат:
- можно преобразовывать MCP input в request model и response в output model.
DoD:
- `JSONPath` parsing и validation работают;
- input/output mapping проходят unit tests;
- generation draft mapping покрыта фикстурами;
- transforms ограничены и задокументированы;
- mapping engine не знает о конкретных protocol adapters.
## 6. Этап 4. Registry и БД
Цель:
- реализовать `mcpaas-registry` и миграции.
Фичи:
- таблицы из `database-schema`;
- version snapshots;
- published operations;
- auth profiles;
- sample metadata;
- descriptor metadata;
- YAML import job log.
Результат:
- конфигурации можно хранить и версионировать.
DoD:
- миграции создают таблицы из `database-schema`;
- version snapshots работают корректно;
- publish linkage реализован;
- auth profiles и artifact metadata сохраняются;
- integration tests на registry проходят на реальной БД.
## 7. Этап 5. REST vertical slice
Цель:
- получить первый рабочий end-to-end сценарий.
Фичи:
- `mcpaas-adapter-rest`
- `mcpaas-runtime` для REST
- REST test run
- создание REST operation
- publish REST operation
- вызов published REST tool из MCP слоя
Результат:
- MVP работает хотя бы для REST.
DoD:
- REST operation можно создать, протестировать и опубликовать;
- runtime исполняет REST operation end-to-end;
- published REST tool вызывается через MCP слой;
- negative tests на mapping и external errors существуют;
- есть демонстрационный REST сценарий.
## 8. Этап 6. Admin API v1
Цель:
- дать UI полный backend-контракт для базового сценария.
Фичи:
- CRUD operations;
- create version;
- publish;
- upload input/output JSON samples;
- generate draft;
- test run;
- auth profiles CRUD;
- YAML import/export.
Результат:
- UI может полностью управлять REST operation без ручных правок кода.
DoD:
- доступны CRUD, versioning, publish, samples, draft generation, test runs;
- доступны auth profiles и YAML import/export;
- API контракты соответствуют документации;
- integration tests на ключевые endpoints проходят;
- нет скрытой бизнес-логики в handlers.
## 9. Этап 7. UI v1
Цель:
- собрать рабочую административную консоль.
Фичи:
- список операций;
- мастер создания операции;
- sample upload;
- schema viewer;
- mapping editor;
- test run screen;
- publish flow;
- YAML import/export screen.
Результат:
- есть демонстрируемый пользовательский интерфейс.
DoD:
- UI покрывает основной сценарий от создания operation до publish;
- sample upload и mapping editor работают;
- YAML import/export доступен из UI;
- нет блокирующих заглушек на критическом пути демо;
- основные пользовательские сценарии проверены вручную или integration tests.
## 10. Этап 8. MCP server
Цель:
- публиковать published operations как MCP tools.
Фичи:
- `Streamable HTTP`;
- list tools;
- call tool;
- reload published tools;
- error mapping MCP layer.
Результат:
- REST operation доступна как полноценный MCP tool.
DoD:
- `Streamable HTTP` transport работает;
- list tools и call tool реализованы;
- reload published tools работает без перезапуска;
- ошибки runtime корректно транслируются в MCP слой;
- есть end-to-end test или demo flow вызова published REST tool.
## 11. Этап 9. GraphQL support
Цель:
- добавить второй протокол без разрушения архитектуры.
Фичи:
- `mcpaas-adapter-graphql`
- GraphQL target support;
- variables mapping;
- `response_path`;
- GraphQL test runs;
- publish и вызов через MCP.
Результат:
- второй end-to-end сценарий.
DoD:
- GraphQL operation можно создать, протестировать и опубликовать;
- variables mapping и `response_path` работают;
- GraphQL `errors` корректно обрабатываются;
- published GraphQL tool вызывается через MCP слой;
- есть demo fixture или integration scenario.
## 12. Этап 10. gRPC support
Цель:
- добавить unary gRPC.
Фичи:
- `mcpaas-proto`
- descriptor loading;
- service/method discovery;
- protobuf normalization;
- `mcpaas-adapter-grpc`
- unary test runs;
- publish и вызов через MCP.
Результат:
- третий end-to-end сценарий.
DoD:
- `.proto` или descriptor set можно загрузить;
- unary method discovery работает;
- JSON <-> protobuf conversion покрыта тестами;
- gRPC operation можно протестировать и опубликовать;
- published gRPC tool вызывается через MCP слой.
## 13. Этап 11. Harden и demo readiness
Цель:
- довести проект до стабильного demo state.
Фичи:
- логирование и tracing;
- улучшение ошибок;
- фикстуры и demo scenarios;
- polish UI;
- документация по запуску;
- проверка YAML roundtrip;
- проверка publish/reload flow.
DoD:
- демонстрационные сценарии воспроизводимы;
- логирование и ошибки читаемы;
- YAML roundtrip проверен;
- publish/reload flow стабилен;
- документация по запуску достаточна для повторения демо.
## 14. Приоритеты по реализации
Если времени не хватает, сохраняется такой приоритет:
1. REST end-to-end
2. Registry + versioning
3. YAML import/export
4. MCP server
5. GraphQL
6. gRPC
Причина:
- диплом должен показать работающую платформу;
- лучше один полный вертикальный сценарий, чем три недоделанных адаптера.
## 15. Разбиение по фичам
Каждый этап желательно бить на маленькие фичи:
- `schema-field-model`
- `schema-validator`
- `jsonpath-parser`
- `input-mapping-engine`
- `output-mapping-engine`
- `registry-create-version`
- `registry-publish`
- `rest-adapter-post-json`
- `yaml-export-portable`
- `mcp-list-tools`
Для каждой такой фичи локальный `DoD` должен быть записан до начала реализации хотя бы в рабочем описании задачи или ветки.
## 16. Практический итог
Правильная последовательность для проекта:
- сначала домен и фундамент;
- потом registry;
- потом один полный REST vertical slice;
- потом admin-ui и MCP слой;
- только после этого расширение на GraphQL и gRPC.
Такой порядок минимизирует архитектурный риск и дает ранний рабочий результат.
+162
View File
@@ -0,0 +1,162 @@
# MCP Interface
## 1. Назначение документа
Этот документ фиксирует, как именно платформа публикует operations в виде MCP tools и какой transport используется в MVP.
Главная цель - убрать неопределенность вокруг вопроса "каким именно будет MCP server" до начала реализации.
## 2. Архитектурное решение
Для MVP `mcp-server` должен публиковать tools через network-oriented MCP transport.
Рекомендуемое решение:
- основной transport: `Streamable HTTP`;
- отдельный `mcp-server` как сервис;
- `stdio` не является обязательной частью MVP.
Причина:
- проект задуман как `MCPaaS`, а не как локальный single-process adapter;
- нужен удаленный доступ к опубликованным tools;
- published tools должны обновляться без пересборки и без локального обертывания каждого клиента.
## 3. Модель публикации tools
Каждая published operation превращается в один MCP tool.
Соответствие:
- одна published version;
- один tool name;
- одна input schema;
- один результат.
Публикация tool основана на:
- `operation.name`
- `tool_description`
- `input_schema`
- `published runtime view`
## 4. Что делает `mcp-server`
`mcp-server` должен:
- загрузить published operations из registry;
- преобразовать их в MCP tool definitions;
- принимать вызовы tools от MCP clients;
- валидировать вход;
- делегировать исполнение в runtime;
- возвращать нормализованный output.
## 5. Что не делает `mcp-server`
`mcp-server` не должен:
- читать draft-конфигурации;
- управлять versioning;
- импортировать YAML;
- выполнять CRUD;
- заниматься protobuf discovery;
- содержать бизнес-логику admin UI.
## 6. Published runtime view
`mcp-server` должен работать не с полной admin-конфигурацией, а с runtime-ready view.
В published runtime view остаются:
- `operation_id`
- `protocol`
- `target`
- `input_schema`
- `output_schema`
- `input_mapping`
- `output_mapping`
- `execution_config`
- `tool_description`
В published runtime view не должны попадать:
- raw uploaded samples;
- generated draft metadata;
- YAML import metadata;
- UI-specific helper fields.
## 7. Transport для MVP
### Поддерживается
- `Streamable HTTP`
### Не обязательно в MVP
- `stdio`
- дополнительные transport adapters
Если позже понадобится локальная интеграция, `stdio` можно добавить как отдельный transport layer поверх того же runtime.
## 8. MCP lifecycle
### Tool listing
При старте и после reload:
1. `mcp-server` читает список published operations.
2. Строит in-memory registry tools.
3. Отдает их через MCP list tools.
### Tool call
1. MCP client вызывает tool.
2. `mcp-server` находит published runtime view.
3. Валидирует input относительно schema.
4. Делегирует вызов в `mcpaas-runtime`.
5. Возвращает результат.
## 9. Обновление tools
После публикации новой версии:
1. `admin-api` фиксирует published version в registry.
2. `registry` обновляет published_operations.
3. `mcp-server` получает reload signal или выполняет controlled refresh.
4. Новый tool contract становится доступен MCP clients.
## 10. Именование tools
Рекомендуется использовать стабильные tool names:
- `crm_create_lead`
- `user_get_profile`
- `inventory_list_items`
Требования:
- имя уникально в пределах платформы;
- имя не зависит от внутреннего numeric version;
- rename operation должен считаться отдельным осознанным изменением.
## 11. Ошибки MCP слоя
На MCP слое нужно различать:
- schema validation error;
- mapping error;
- adapter execution error;
- external service error;
- internal runtime error.
`mcp-server` не должен терять стадию ошибки при трансляции ответа клиенту.
## 12. Практический итог
Для MVP достаточно следующей фиксации:
- `mcp-server` - отдельный сервис;
- transport - `Streamable HTTP`;
- одна published operation = один MCP tool;
- reload published tools без пересборки сервиса;
- никакой draft-логики или admin CRUD в MCP слое.
+709
View File
@@ -0,0 +1,709 @@
# Декомпозиция модулей
## 1. Цель документа
Этот документ фиксирует детальную структуру проекта до начала активной разработки. Его задача - заранее ограничить ответственность каждого компонента, избежать разрастания `mcpaas-core`, не допустить появления "универсальных" структур на все случаи жизни и сохранить понятные границы между доменной логикой, runtime, адаптерами, API и UI.
Основной принцип: каждый crate отвечает за один слой системы. Внутри crate модули должны быть маленькими, тематическими и с минимальным количеством публичных сущностей.
## 2. Общие архитектурные правила
### 2.1. Что считается правильной декомпозицией
- `core` содержит только базовую доменную модель, идентификаторы, типы ошибок и общие контракты.
- `registry` отвечает только за хранение и загрузку конфигурации операций.
- `runtime` исполняет операции, но не знает о способе их хранения.
- адаптеры знают только свой протокол и общий контракт runtime.
- `admin-api` оркестрирует use case для UI, но не содержит протокольной логики.
- `mcp-server` публикует tools и вызывает runtime, но не содержит бизнес-логики конфигурирования.
- `ui` не знает внутреннюю реализацию runtime и работает только через HTTP API.
### 2.2. Что запрещено
- помещать SQL, HTTP-клиенты или gRPC-клиенты в `mcpaas-core`;
- хранить в `core` "общие утилиты", не относящиеся к доменной модели;
- делать `runtime`, который напрямую читает БД;
- писать mapping-логику внутри REST, GraphQL или gRPC адаптеров;
- дублировать доменные типы в `admin-api`, `mcp-server` и адаптерах;
- создавать большие структуры вида `AppState`, в которые складывается все подряд;
- создавать большие enum или config-объекты, содержащие поля всех протоколов одновременно без выделенных вложенных типов.
### 2.3. Предпочтительный стиль
- узкие интерфейсы;
- маленькие DTO;
- отдельные типы для draft, published и runtime-view сущностей;
- отдельные модули для чтения, записи, валидации и исполнения;
- композиция из небольших сервисов вместо одного глобального сервиса.
## 3. Workspace-структура
Рекомендуемая структура:
```text
mcpaas/
apps/
admin-api/
mcp-server/
ui/
crates/
mcpaas-core/
mcpaas-registry/
mcpaas-runtime/
mcpaas-adapter-rest/
mcpaas-adapter-graphql/
mcpaas-adapter-grpc/
mcpaas-mapping/
mcpaas-schema/
mcpaas-proto/
```
Дополнительные crates `mcpaas-mapping`, `mcpaas-schema` и `mcpaas-proto` нужны затем, чтобы не перегружать `mcpaas-core`.
## 4. Детальная декомпозиция по crate
### 4.1. `mcpaas-core`
Назначение:
- базовые доменные типы;
- идентификаторы;
- метаданные операций;
- общие контракты и ошибки верхнего уровня.
Что должно лежать в crate:
```text
mcpaas-core/
src/
lib.rs
ids.rs
protocol.rs
operation/
mod.rs
model.rs
status.rs
target.rs
metadata.rs
auth/
mod.rs
profile.rs
secret_ref.rs
errors/
mod.rs
domain.rs
validation.rs
runtime.rs
```
Описание модулей:
- `ids.rs` - типы `OperationId`, `DescriptorId`, `ToolId` и другие идентификаторы.
- `protocol.rs` - enum протоколов и общие protocol capability flags.
- `operation/model.rs` - основная доменная модель операции без технических деталей хранения.
- `operation/status.rs` - типы состояний операции.
- `operation/target.rs` - базовые protocol-specific target structs.
- `operation/metadata.rs` - описание tool, display name, version, tags.
- `auth/profile.rs` - типы auth-профилей без привязки к конкретному клиенту.
- `auth/secret_ref.rs` - ссылки на секреты, а не сами секреты.
- `errors/*` - типизированные ошибки доменного слоя.
Что не должно лежать в crate:
- JSON Schema реализация;
- mapping engine;
- SQL-модели;
- HTTP DTO;
- protobuf parsing;
- `reqwest`, `sqlx`, `tonic`, `axum`.
Причина:
`mcpaas-core` должен быть максимально стабильным и независимым. Если положить туда все подряд, он станет точкой связности всей системы.
### 4.2. `mcpaas-schema`
Назначение:
- внутренняя модель схем;
- нормализация входа и выхода;
- представление полей для UI и runtime;
- преобразование схем из разных источников в единый вид.
Структура:
```text
mcpaas-schema/
src/
lib.rs
schema/
mod.rs
model.rs
field.rs
scalar.rs
object.rs
collection.rs
oneof.rs
enums.rs
normalize/
mod.rs
json.rs
graphql.rs
protobuf.rs
validate/
mod.rs
input.rs
output.rs
```
Описание:
- `schema/model.rs` - корневая структура схемы.
- `field.rs` - описание поля, nullable, required, description.
- `scalar.rs` - базовые scalar types.
- `object.rs` - вложенные объекты.
- `collection.rs` - массивы и map-подобные структуры.
- `oneof.rs` - представление protobuf `oneof`.
- `enums.rs` - enum-значения и метаданные.
- `normalize/*` - преобразование GraphQL и protobuf моделей в единую схему.
- `normalize/json.rs` - нормализация загруженных JSON-примеров во внутреннюю schema model.
- `validate/*` - проверка JSON относительно внутренней схемы.
Почему отдельный crate:
Схемы будут использоваться почти везде, но это не повод тащить их в `core`. Иначе `core` станет тяжелым и начнет менять версию при каждом изменении схемной логики.
### 4.3. `mcpaas-mapping`
Назначение:
- описание mapping DSL;
- компиляция mappings в runtime-представление;
- применение mappings к входу и выходу;
- автогенерация чернового mapping по загруженным примерам;
- трассировка ошибок маппинга.
Структура:
```text
mcpaas-mapping/
src/
lib.rs
model/
mod.rs
mapping.rs
source.rs
target.rs
transform.rs
parser/
mod.rs
jsonpath.rs
compile/
mod.rs
plan.rs
infer/
mod.rs
from_samples.rs
from_schema.rs
execute/
mod.rs
input.rs
output.rs
errors.rs
```
Описание:
- `model/mapping.rs` - описание одного правила mapping.
- `model/source.rs` - откуда берем данные: `mcp`, `response`, `constant`.
- `model/target.rs` - куда кладем данные: `request.path`, `request.query`, `request.body`, `output`.
- `model/transform.rs` - ограниченный набор допустимых преобразований.
- `parser/jsonpath.rs` - единый parser и validator для `JSONPath` выражений.
- `compile/plan.rs` - предварительно скомпилированный план маппинга.
- `infer/from_samples.rs` - генерация чернового mapping по загруженным примерам JSON.
- `infer/from_schema.rs` - генерация чернового mapping по нормализованной схеме.
- `execute/input.rs` - применение mappings к запросу.
- `execute/output.rs` - применение mappings к ответу.
Антипаттерн, которого нужно избежать:
не помещать mapping-правила в строковые поля, которые потом интерпретируются каждым адаптером по-своему. Mapping должен быть единым движком.
### 4.4. `mcpaas-proto`
Назначение:
- работа с `.proto` и descriptor set;
- извлечение services, methods и message schemas;
- преобразование protobuf metadata во внутренние типы.
Структура:
```text
mcpaas-proto/
src/
lib.rs
descriptor/
mod.rs
loader.rs
source.rs
registry.rs
reflect/
mod.rs
client.rs
model/
mod.rs
service.rs
method.rs
message.rs
field.rs
convert/
mod.rs
to_schema.rs
to_json.rs
from_json.rs
errors.rs
```
Описание:
- `descriptor/loader.rs` - загрузка descriptor set.
- `descriptor/source.rs` - типы источников: upload, file, reflection.
- `descriptor/registry.rs` - индексирование описаний для поиска services/methods.
- `reflect/client.rs` - клиент server reflection, если будет добавлен.
- `model/*` - protobuf-ориентированная промежуточная модель.
- `convert/to_schema.rs` - перевод protobuf message в `mcpaas-schema`.
- `convert/to_json.rs` и `from_json.rs` - преобразование runtime payload.
Почему отдельный crate:
protobuf-логика объемная и быстро начнет загрязнять gRPC adapter, если не отделить ее сразу.
### 4.5. `mcpaas-registry`
Назначение:
- хранение операций, схем, descriptor links и статусов;
- выдача draft/published представлений;
- поиск активных операций для runtime и MCP server.
Структура:
```text
mcpaas-registry/
src/
lib.rs
model/
mod.rs
record.rs
draft.rs
published.rs
repo/
mod.rs
operation_repo.rs
descriptor_repo.rs
service/
mod.rs
create_operation.rs
update_operation.rs
publish_operation.rs
list_operations.rs
get_runtime_view.rs
storage/
mod.rs
sqlite.rs
postgres.rs
cache/
mod.rs
runtime_cache.rs
errors.rs
```
Описание:
- `model/record.rs` - DB-aligned record model.
- `model/draft.rs` - модель черновика.
- `model/published.rs` - модель опубликованной операции.
- `repo/*` - контракты репозиториев.
- `service/*` - use case операции над реестром.
- `storage/*` - реализации репозиториев на `sqlx`.
- `cache/runtime_cache.rs` - кэш активных операций.
Правило:
`registry` не выполняет операции и не знает о `reqwest`/`tonic`. Он только хранит и отдает согласованные представления.
### 4.6. `mcpaas-runtime`
Назначение:
- исполнение операций;
- orchestration между схемой, mapping и адаптерами;
- выдача нормализованного результата.
Структура:
```text
mcpaas-runtime/
src/
lib.rs
executor/
mod.rs
operation_executor.rs
input_prepare.rs
output_finalize.rs
adapter/
mod.rs
traits.rs
dispatch.rs
context/
mod.rs
execution_context.rs
model/
mod.rs
runtime_operation.rs
prepared_request.rs
adapter_response.rs
errors.rs
```
Описание:
- `executor/operation_executor.rs` - основной orchestration use case.
- `executor/input_prepare.rs` - валидация входа и применение input mapping.
- `executor/output_finalize.rs` - обработка adapter response и output mapping.
- `adapter/traits.rs` - общий контракт для протокольных адаптеров.
- `adapter/dispatch.rs` - выбор адаптера по протоколу.
- `context/execution_context.rs` - correlation id, deadlines, tracing data.
- `model/runtime_operation.rs` - runtime-ready представление операции.
Правило:
`runtime` не должен знать, где хранится операция. Он получает уже готовую `runtime_operation`.
### 4.7. `mcpaas-adapter-rest`
Назначение:
- построение и выполнение REST-вызовов.
Структура:
```text
mcpaas-adapter-rest/
src/
lib.rs
client.rs
request/
mod.rs
build.rs
path.rs
query.rs
headers.rs
body.rs
response/
mod.rs
decode.rs
normalize.rs
errors.rs
```
Правило:
REST adapter не валидирует MCP input и не знает о registry. Он получает уже подготовленный request contract.
### 4.8. `mcpaas-adapter-graphql`
Назначение:
- построение и выполнение GraphQL-вызовов.
Структура:
```text
mcpaas-adapter-graphql/
src/
lib.rs
client.rs
request/
mod.rs
build.rs
variables.rs
response/
mod.rs
decode.rs
extract.rs
errors.rs
```
Правило:
GraphQL adapter не занимается introspection по умолчанию и не содержит редактор схем. Он только исполняет подготовленный operation template.
Дополнительное ограничение:
один GraphQL tool соответствует одному заранее определенному `query` или `mutation`. Адаптер не должен принимать от LLM произвольный GraphQL-документ, потому что в MCP-модели операция должна оставаться узкой, предсказуемой и валидируемой по фиксированной схеме.
### 4.9. `mcpaas-adapter-grpc`
Назначение:
- выполнение unary gRPC-вызовов на основе уже выбранного метода и descriptor metadata.
Структура:
```text
mcpaas-adapter-grpc/
src/
lib.rs
channel.rs
invoke/
mod.rs
unary.rs
request/
mod.rs
build.rs
response/
mod.rs
decode.rs
errors.rs
```
Правило:
gRPC adapter не должен сам парсить `.proto`. Этим занимается `mcpaas-proto`. Иначе в адаптере смешаются discovery и execution.
Дополнительное ограничение:
adapter поддерживает только unary RPC. Streaming-вызовы не реализуются, потому что целевая модель MCP tool в проекте соответствует сценарию `запрос -> ответ`, а не долгоживущей сессии обмена сообщениями.
### 4.10. `admin-api`
Назначение:
- HTTP API для UI;
- координация use case создания, редактирования, тестирования и публикации.
Структура:
```text
apps/admin-api/
src/
main.rs
app.rs
state.rs
router.rs
http/
mod.rs
dto/
mod.rs
operation.rs
mapping.rs
test_run.rs
handlers/
mod.rs
create_operation.rs
update_operation.rs
publish_operation.rs
list_operations.rs
test_operation.rs
export_operation_yaml.rs
import_operation_yaml.rs
upload_json_samples.rs
upload_proto.rs
list_grpc_services.rs
response.rs
services/
mod.rs
operation_service.rs
descriptor_service.rs
config_portability_service.rs
test_service.rs
errors.rs
```
Правила:
- HTTP DTO не должны протекать в доменный слой;
- handlers должны быть тонкими;
- orchestration должна жить в `services/*`;
- `state.rs` не должен разрастаться в огромную структуру. Лучше использовать вложенные state-компоненты или отдельные service bundles.
- import/export конфигурации в `YAML` должен быть отдельным use case, а не побочным эффектом обычного CRUD.
### 4.11. `mcp-server`
Назначение:
- публикация MCP tools;
- вызов runtime по имени tool;
- обновление активного списка tools.
Структура:
```text
apps/mcp-server/
src/
main.rs
app.rs
state.rs
tools/
mod.rs
list.rs
call.rs
cache.rs
translate/
mod.rs
to_mcp_tool.rs
from_mcp_input.rs
to_mcp_output.rs
errors.rs
```
Правило:
`mcp-server` не должен реализовывать business rules публикации. Он читает уже опубликованные операции и транслирует их в MCP.
### 4.12. `ui`
Назначение:
- интерфейс оператора.
Предлагаемая frontend-структура:
```text
apps/ui/
src/
main.tsx
app/
router.tsx
providers.tsx
pages/
operation-list/
operation-create/
operation-edit/
operation-test/
grpc-browser/
features/
operation-form/
mapping-editor/
sample-upload/
grpc-method-picker/
schema-viewer/
publish-operation/
entities/
operation/
descriptor/
shared/
api/
lib/
ui/
config/
```
Правило:
UI должен декомпозироваться по пользовательским сценариям, а не по типам файлов уровня "все компоненты в одной папке".
## 5. Правила зависимостей между crate
Целевой граф зависимостей:
```text
mcpaas-core
mcpaas-schema -> mcpaas-core
mcpaas-mapping -> mcpaas-core
mcpaas-proto -> mcpaas-core, mcpaas-schema
mcpaas-registry -> mcpaas-core, mcpaas-schema, mcpaas-mapping
mcpaas-adapter-rest -> mcpaas-core
mcpaas-adapter-graphql -> mcpaas-core
mcpaas-adapter-grpc -> mcpaas-core, mcpaas-proto
mcpaas-runtime -> mcpaas-core, mcpaas-schema, mcpaas-mapping, adapters
admin-api -> mcpaas-core, mcpaas-schema, mcpaas-mapping, mcpaas-proto, mcpaas-registry, mcpaas-runtime
mcp-server -> mcpaas-core, mcpaas-registry, mcpaas-runtime
```
Критические ограничения:
- `mcpaas-core` ни от кого не зависит;
- адаптеры не зависят от `registry`;
- `runtime` не зависит от `admin-api` и `mcp-server`;
- `registry` не зависит от адаптеров;
- `ui` зависит только от HTTP API.
## 6. Границы публичных API модулей
Чтобы структура не разъехалась, нужно заранее ограничить публичность.
Рекомендуемое правило:
- наружу экспортируются только корневые доменные типы, service-интерфейсы и ошибки;
- внутренние DTO, record-модели и промежуточные builder-структуры остаются `pub(crate)`;
- не реэкспортировать целые деревья модулей без необходимости;
- не делать `mod utils`, если можно назвать модуль по смыслу.
Пример плохого решения:
- `pub mod common;`
- `pub mod helpers;`
- `pub struct AppContext { ... 25 полей ... }`
Пример правильного решения:
- `pub struct OperationExecutor`
- `pub trait OperationRepository`
- `pub struct RuntimeOperation`
## 7. Какие большие структуры точно не нужны
Ниже список сущностей, которые легко превращаются в антипаттерн:
- одна гигантская `Operation`, содержащая сразу все REST, GraphQL и gRPC поля;
- один `MappingConfig`, содержащий и input, и output, и transforms, и validation rules без разделения;
- единый `AppState` со всеми репозиториями, клиентами, кэшами и конфигами;
- один `ProtocolAdapter` с ветвлением `match protocol` внутри на сотни строк;
- один `SchemaField` без выделения object/array/enum/oneof вариантов.
Правильный подход:
- отдельные target-типы по протоколам;
- отдельные input/output mapping модели;
- отдельные bounded state-наборы для каждого приложения;
- отдельные adapter crates;
- выделенная иерархия schema types.
## 8. Порядок реализации без архитектурного долга
Рекомендуемый порядок разработки:
1. `mcpaas-core`
2. `mcpaas-schema`
3. `mcpaas-mapping`
4. `mcpaas-registry`
5. `mcpaas-adapter-rest`
6. `mcpaas-runtime`
7. `admin-api`
8. `ui`
9. `mcpaas-proto`
10. `mcpaas-adapter-grpc`
11. `mcpaas-adapter-graphql`
12. `mcp-server`
Причина такого порядка:
- сначала фиксируется доменная модель;
- затем схема и mapping как самые чувствительные части;
- затем реестр и базовое выполнение REST;
- после этого можно собирать UI и только потом наращивать сложные протоколы.
## 9. Практический итог
Если придерживаться этой декомпозиции, то:
- `mcpaas-core` останется маленьким и стабильным;
- schema и mapping не смешаются с transport-логикой;
- protobuf discovery не загрязнит gRPC runtime;
- `admin-api` и `mcp-server` останутся тонкими входными слоями;
- добавление нового протокола не потребует переписывать половину проекта.
Это и есть целевая архитектурная дисциплина проекта: отдельные слои, отдельные модели, минимально необходимая публичность и отсутствие "универсальных" структур, в которые со временем начинает стекаться вся система.
+107
View File
@@ -0,0 +1,107 @@
# GraphQL
## 1. Роль протокола в проекте
GraphQL поддерживается как отдельный тип интеграции, но на слое MCP намеренно ограничивается. Цель платформы не в том, чтобы дать LLM универсальный доступ ко всему GraphQL endpoint, а в том, чтобы превратить конкретный GraphQL-запрос в узкий и предсказуемый MCP tool.
## 2. Что поддерживается в MVP
- `query`
- `mutation`
- один GraphQL endpoint на operation
- фиксированный `query_template`
- фиксированный `selection set`
- загрузка примера выходного `JSON`
- схема переменных
- variables mapping
- response extraction из `data`
- разбор `errors`
- auth и headers
- автогенерация чернового mapping
- ручная донастройка через `JSONPath`
- тестовый вызов перед публикацией
## 3. Что не входит в MVP
- `subscription`
- универсальный GraphQL explorer для LLM
- передача произвольного GraphQL-документа от LLM
- визуальный конструктор сложных selection set
- обязательная зависимость от introspection
- автоматическое построение любого запроса по полной GraphQL schema
## 4. Ключевое архитектурное ограничение
Платформа не должна публиковать в MCP общий GraphQL tool, который умеет получать любые поля и принимать любые параметры в зависимости от намерения LLM.
Правильная модель только одна:
- один tool;
- один конкретный `query` или `mutation`;
- один заранее зафиксированный `selection set`;
- фиксированный набор входных параметров;
- один предсказуемый формат ответа.
Иными словами, на MCP-слое GraphQL сознательно сужается до модели, близкой к RPC или REST operation. Это делается потому, что LLM должен работать с понятным контрактом, а не конструировать произвольный GraphQL-запрос на лету.
## 5. Внутренняя модель GraphQL operation
GraphQL operation должна включать:
- `endpoint`
- `operation_type`
- `operation_name`
- `query_template`
- `variables_schema`
- `input_mapping`
- `response_path`
- `error_policy`
- `headers`
- `auth_profile`
## 6. Как оператор настраивает GraphQL operation
1. Указывает GraphQL endpoint.
2. Выбирает `query` или `mutation`.
3. Задает имя операции.
4. Вставляет готовый шаблон запроса.
5. Описывает переменные, которые разрешено передавать в эту операцию.
6. При необходимости загружает пример JSON-ответа.
7. Система строит черновую схему ответа и стартовый mapping.
8. Настраивает маппинг `MCP input -> GraphQL variables`.
9. Указывает `response_path`, по которому извлекается полезный результат из `data`.
10. При необходимости уточняет mapping через `JSONPath`.
11. Выполняет тест.
12. Публикует operation как MCP tool.
## 7. Поведение runtime
При выполнении GraphQL operation runtime должен:
1. Валидировать вход по фиксированной схеме переменных.
2. Применить input mapping.
3. Собрать GraphQL payload вида `query + variables`.
4. Выполнить HTTP request.
5. Отдельно разобрать `data` и `errors`.
6. Применить output mapping или `response_path`.
7. Вернуть нормализованный результат.
## 8. Критические нюансы
- HTTP `200 OK` не означает успешное выполнение, если в теле присутствует `errors`.
- структура ответа зависит от `selection set`, значит она должна быть фиксирована заранее;
- GraphQL endpoint обычно один, поэтому операция определяется не URL, а телом запроса;
- variables должны быть строго ограничены, иначе один tool станет слишком широким и плохо управляемым;
- `subscription` по смыслу не подходит модели MCP tool, потому что это потоковая, а не request-response интеграция.
- `JSONPath` используется для точечного извлечения вложенных данных из `data` и для управления структурой итогового ответа.
## 9. Почему GraphQL не считается "почти REST"
GraphQL похож на REST только тем, что часто передается по HTTP. Но с точки зрения платформы это другой тип контракта:
- смысл операции задается не endpoint, а запросом;
- ответ зависит от `selection set`;
- ошибки живут в теле ответа, а не только в HTTP status;
- одна и та же точка входа может обслуживать много операций.
Поэтому GraphQL в системе должен иметь отдельный адаптер и отдельную конфигурационную модель.
+107
View File
@@ -0,0 +1,107 @@
# gRPC
## 1. Роль протокола в проекте
gRPC поддерживается как третий основной протокол платформы, но в самой узкой и управляемой форме. Цель состоит не в том, чтобы покрыть все возможности gRPC, а в том, чтобы представить unary RPC-методы как обычные MCP tools с формой входа и формой выхода.
## 2. Что поддерживается в MVP
- только unary RPC
- загрузка `.proto`
- загрузка descriptor set
- загрузка примеров JSON для MCP input/output при необходимости
- извлечение `services`, `methods`, request/response messages
- отображение входных и выходных параметров в UI
- mapping `MCP input -> protobuf request`
- mapping `protobuf response -> MCP output`
- автогенерация чернового mapping
- ручная донастройка через `JSONPath`
- вызов метода по descriptor metadata
- auth/transport settings на уровне соединения
- тестовый вызов перед публикацией
## 3. Что не входит в MVP
- `server streaming`
- `client streaming`
- `bidirectional streaming`
- обязательная поддержка server reflection
- генерация нового Rust-кода под каждый загруженный `.proto`
- сложные сценарии с долгоживущими сессиями вызовов
## 4. Ключевое архитектурное ограничение
В проекте поддерживаются только unary-методы, потому что MCP tool в этой архитектуре соответствует модели `один запрос -> один ответ`.
Это означает:
- один request message;
- один response message;
- один завершенный вызов;
- отсутствие потоковых сообщений;
- отсутствие отдельного жизненного цикла stream-сессии.
Streaming gRPC не нужен для выбранной модели взаимодействия с LLM и только усложнит runtime, UI и хранение состояния.
## 5. Внутренняя модель gRPC operation
gRPC operation должна включать:
- `server_addr`
- `package`
- `service`
- `method`
- `descriptor_ref`
- `input_schema`
- `output_schema`
- `input_mapping`
- `output_mapping`
- `execution_config`
- `tool_description`
## 6. Как оператор настраивает gRPC operation
1. Загружает `.proto` или descriptor set.
2. Система извлекает список services и methods.
3. Оператор выбирает конкретный unary-метод.
4. UI показывает структуру request message и response message.
5. При необходимости загружает примеры JSON для MCP input/output.
6. Система строит черновую схему и стартовый mapping.
7. Оператор задает или уточняет входные MCP-параметры.
8. Настраивает маппинг во входные protobuf fields.
9. Настраивает маппинг из response fields в MCP output.
10. При необходимости уточняет mapping через `JSONPath`.
11. Выполняет тест.
12. Публикует operation как MCP tool.
## 7. Поведение runtime
При выполнении gRPC operation runtime должен:
1. Валидировать MCP input по нормализованной схеме.
2. Применить input mapping.
3. Построить protobuf request message из JSON.
4. Выполнить unary RPC вызов.
5. Преобразовать protobuf response в нормализованный JSON.
6. Применить output mapping.
7. Вернуть итоговый результат.
## 8. Критические нюансы
- `.proto` и descriptor handling должны быть отделены от runtime-вызова;
- protobuf discovery не должен жить внутри gRPC adapter;
- `oneof`, `enum`, `repeated`, `map` и well-known types требуют отдельной нормализации;
- схема сообщения должна быть представлена в UI как обычная форма полей, а не как сырой protobuf descriptor;
- пользователь не должен видеть внутреннюю сложность protobuf-контракта больше, чем это нужно для настройки operation.
- `JSONPath` используется как единый способ точечной адресации вложенных полей при настройке mapping поверх нормализованной JSON-модели.
## 9. Почему gRPC ограничивается unary
Причина не только в сложности реализации. Главное ограничение архитектурное:
- MCP tool моделируется как завершенный вызов;
- LLM работает с запросом и конечным ответом;
- UI платформы построен вокруг формы входа и формы выхода;
- streaming требует отдельной session-модели, buffering, cancellation и состояния.
Поэтому unary gRPC - это не "обрезанная" поддержка, а осознанно выбранная форма, которая действительно совместима с MCP-платформой.
+112
View File
@@ -0,0 +1,112 @@
# REST
## 1. Роль протокола в проекте
REST - базовый и первый по очередности реализации протокол платформы. На нем должна быть обкатана общая модель `Operation`, схема входа и выхода, маппинг, тестовый запуск и публикация MCP tool.
## 2. Что поддерживается в MVP
- HTTP methods: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`
- загрузка примера входного `JSON`
- загрузка примера выходного `JSON`
- path parameters
- query parameters
- headers
- JSON request body
- JSON response body
- auth: `Bearer`, `Basic`, API key
- timeout и базовые transport settings
- request mapping
- response mapping
- автогенерация чернового mapping
- ручная донастройка через `JSONPath`
- тестовый вызов перед публикацией
## 3. Что не входит в MVP
- multipart/form-data
- file upload/download как отдельный сценарий
- XML payload как основной формат
- OpenAPI import с автоматическим созданием mappings
- webhooks
- long polling как специальный режим
- `HEAD` и `OPTIONS` как отдельные пользовательские сценарии
## 4. Внутренняя модель REST operation
REST operation в системе описывается следующими основными частями:
- `base_url`
- `method`
- `path_template`
- `headers`
- `auth_profile`
- `input_schema`
- `input_mapping`
- `output_schema`
- `output_mapping`
- `tool_description`
На слое MCP REST operation всегда выглядит как вызов `запрос -> ответ` с фиксированной схемой входа и выхода.
## 5. Как оператор настраивает REST operation
1. Указывает `base_url`.
2. Выбирает HTTP method.
3. Указывает `path_template`.
4. При необходимости загружает пример входного и выходного `JSON`.
5. Система строит черновую схему и стартовый mapping.
6. Описывает или уточняет входные MCP-параметры.
7. Сопоставляет параметры с `path`, `query`, `headers` и `body`.
8. Указывает, откуда извлекать полезные данные в ответе.
9. При необходимости уточняет mapping через `JSONPath`.
10. Запускает тест.
11. Публикует operation как MCP tool.
## 6. Требования к маппингу
Input mapping должен поддерживать:
- `$.mcp.* -> $.request.path.*`
- `$.mcp.* -> $.request.query.*`
- `$.mcp.* -> $.request.headers.*`
- `$.mcp.* -> $.request.body.*`
- константы
- значения по умолчанию
Output mapping должен поддерживать:
- `$.response.body.* -> $.output.*`
- извлечение вложенных полей
- нормализацию отсутствующих значений
`JSONPath` является основным способом адресации конкретных параметров при работе со вложенными объектами и массивами.
## 7. Поведение runtime
При выполнении REST operation runtime должен:
1. Валидировать вход по нормализованной схеме.
2. Применить input mapping.
3. Собрать HTTP request.
4. Выполнить вызов через `reqwest`.
5. Преобразовать ответ в нормализованный JSON.
6. Применить output mapping.
7. Вернуть итоговый результат MCP server.
## 8. Нюансы и ограничения
- `DELETE` допускается, но body для него не считается обязательным сценарием совместимости.
- `PATCH` требует аккуратной работы с частичными payload, поэтому mapping должен позволять заполнять только выбранные поля.
- Успешный HTTP status сам по себе не гарантирует корректность бизнес-ответа, если response mapping не может извлечь ожидаемые данные.
- REST adapter не должен содержать бизнес-логику маппинга, только transport-логику.
- Загруженные JSON-примеры используются для генерации черновика, но не заменяют явную конфигурацию operation.
## 9. Почему REST остается отдельным протоколом
REST нельзя считать просто частным случаем другого HTTP-based интерфейса, потому что:
- контракт определяется URL, методом и payload;
- semantics HTTP methods важны;
- поведение интеграции часто завязано на headers и auth;
- UX настройки REST operation отличается от GraphQL и gRPC.
+127
View File
@@ -0,0 +1,127 @@
# Runtime Config
## 1. Назначение документа
Этот документ фиксирует конфигурацию окружения, storage и базовые operational assumptions для MVP.
Его задача - убрать неявные решения, которые обычно всплывают уже в процессе написания кода.
## 2. Базовые решения для MVP
- каноническая БД: `PostgreSQL`
- допустимый упрощенный режим разработки: `SQLite`
- artifact storage: локальная файловая система
- MCP transport: `Streamable HTTP`
- admin API и mcp-server запускаются как отдельные приложения
## 3. Artifact storage
В MVP sample JSON, `.proto`, `descriptor set` и YAML import payload должны храниться в локальном файловом storage.
Требования:
- все файлы кладутся в контролируемый базовый каталог;
- в БД хранится только `storage_ref`;
- структура каталогов должна быть детерминированной;
- storage слой должен быть абстрагирован, чтобы потом заменить его на S3-compatible backend.
Рекомендуемая структура:
```text
var/mcpaas/
samples/
descriptors/
yaml-imports/
```
## 4. Секреты и auth profiles
Для MVP:
- operation хранит только `auth_profile_ref`;
- auth profile хранит только `secret_ref`;
- реальные секреты не должны попадать в YAML export;
- секреты не должны логироваться.
Допустимые варианты secret storage:
- env-backed secret store;
- encrypted local secret storage.
Минимальный безопасный вариант для MVP:
- `secret_ref` указывает на env variable alias или key в локальном secret store;
- приложение резолвит его на runtime.
## 5. Переменные окружения
Минимально ожидаются:
- `MCPAAS_DATABASE_URL`
- `MCPAAS_STORAGE_ROOT`
- `MCPAAS_ADMIN_BIND`
- `MCPAAS_MCP_BIND`
- `MCPAAS_LOG_LEVEL`
- `MCPAAS_SECRET_PROVIDER`
Опционально:
- `MCPAAS_ADMIN_TOKEN`
- `MCPAAS_MASTER_KEY`
## 6. Логирование и трассировка
Для MVP нужно использовать:
- structured logging через `tracing`;
- correlation id для test runs и runtime execution;
- раздельные стадии ошибок: schema, mapping, adapter, external service.
## 7. Таймауты и retries
Рекомендуемые стартовые значения:
- default timeout: `10s`
- retry default: `1` attempt, то есть без автоматического повтора
Причина:
- сначала важнее детерминированность и прозрачность;
- aggressive retries могут маскировать реальные ошибки интеграции.
## 8. Режимы запуска
Минимально нужны два режима:
- local development
- demo/deployment
Local development:
- `SQLite` допустим;
- локальный storage;
- упрощенная auth-модель admin-api.
Demo/deployment:
- `PostgreSQL`;
- локальный или сетевой storage;
- включенная auth-защита admin-api;
- стабильный `Streamable HTTP` endpoint для MCP.
## 9. Что важно не допустить
- пути к storage, зашитые в код;
- секреты в `.yaml` exports;
- разные конфигурационные модели для local и production без причины;
- смешивание runtime config и business config operation.
## 10. Практический итог
До старта разработки должны быть приняты как минимум такие решения:
- где лежит БД;
- где лежат artifacts;
- как резолвятся `secret_ref`;
- на каких bind-address запускаются `admin-api` и `mcp-server`;
- какой transport использует MCP server.
+316
View File
@@ -0,0 +1,316 @@
# Rust Code Rules
## 1. Назначение документа
Этот документ фиксирует Rust-specific правила кода для проекта:
- toolchain;
- linting;
- formatting;
- ошибки;
- async;
- ownership;
- visibility;
- dependency hygiene.
Цель документа - убрать плавающие договоренности по стилю и практике командной Rust-разработки.
## 2. Toolchain
### 2.1. Версия Rust
Для проекта должен быть зафиксирован `rust-toolchain.toml`.
В нем должны быть определены:
- стабильный `channel`;
- `edition`;
- при необходимости `components`.
Рекомендуемый состав:
- `rustfmt`
- `clippy`
### 2.2. MSRV
Нужно зафиксировать `MSRV` - минимально поддерживаемую версию Rust.
Правило:
- без необходимости не использовать возможности языка новее зафиксированного `MSRV`;
- обновление `MSRV` - это отдельное осознанное решение.
## 3. Formatting и linting
### 3.1. Formatting
Обязательное правило:
- весь код форматируется через `cargo fmt`.
Ручной стиль форматирования не обсуждается и не поддерживается.
### 3.2. Clippy
Обязательное правило:
- `cargo clippy --all-targets --all-features -- -D warnings`
Предупреждения считаются ошибками, если нет явно зафиксированного исключения.
### 3.3. CI quality gates
Минимально в CI должны запускаться:
- `cargo fmt --check`
- `cargo clippy --all-targets --all-features -- -D warnings`
- `cargo test`
Опционально позже:
- `cargo deny`
- `cargo audit`
## 4. `unsafe`
Для проекта принимается правило:
- `unsafe` запрещен по умолчанию.
Если когда-либо потребуется `unsafe`, то:
- это должно быть отдельное осознанное решение;
- причина должна быть технически обоснована;
- блок должен быть минимальным;
- вокруг него должны быть тесты.
Для MVP можно считать:
- `unsafe_code = deny`
## 5. Panic policy
В production code запрещены:
- `unwrap()`
- `expect()`
- `todo!()`
- `unimplemented!()`
- `dbg!()`
- необоснованные `panic!()`
Допускается:
- в тестах;
- в очень раннем bootstrap-коде, если это действительно аварийное завершение и не часть доменной логики.
Базовое правило:
- ошибки возвращаются через `Result`, а не через panic.
## 6. Правила ошибок
### 6.1. Domain и service errors
В домене и сервисах использовать типизированные ошибки.
Рекомендуемо:
- `thiserror`
### 6.2. Application boundary
На верхних слоях приложений допускается агрегирование ошибок, если это упрощает wiring.
При необходимости:
- `anyhow` только на внешних границах приложения, не в доменной модели.
### 6.3. Error context
Ошибка должна сохранять стадию отказа:
- schema
- mapping
- adapter
- persistence
- external service
- internal runtime
## 7. Visibility rules
Правило:
- по умолчанию все приватное;
- `pub(crate)` предпочтительнее `pub`;
- публичный API должен быть минимальным.
Нельзя:
- открывать модуль наружу "на всякий случай";
- делать `pub` просто ради удобства из соседнего файла;
- реэкспортировать целые деревья модулей без причины.
## 8. Ownership и данные
### 8.1. Клонирование
Правило:
- не клонировать данные без необходимости;
- клон должен быть осознанным, а не способом обойти borrow checker без понимания причины.
### 8.2. Shared mutability
Правило:
- не использовать `Arc<Mutex<_>>` как универсальный контейнер состояния;
- shared mutability допускается только там, где она действительно нужна по архитектуре.
### 8.3. ID types
Идентификаторы должны быть отдельными типами, а не просто `String`.
Примеры:
- `OperationId`
- `DescriptorId`
- `AuthProfileId`
## 9. Async rules
### 9.1. Где допускается `async`
`async` используется только там, где есть:
- I/O;
- network;
- storage;
- async boundary приложения.
### 9.2. Где `async` не нужен
Нельзя превращать:
- schema validation;
- mapping;
- чистую доменную логику;
- небольшие derived methods
в `async fn` без причины.
### 9.3. `tokio`
`tokio` должен находиться:
- в приложениях;
- в I/O слоях;
- в адаптерах и runtime orchestration, если там есть реальный async.
Доменный слой не должен зависеть от `tokio`.
## 10. API design rules
### 10.1. Конструкторы
Использовать:
- `new()` для гарантированно валидного и простого создания;
- `try_new()` там, где есть валидация и возможна ошибка.
### 10.2. Builders
Если структура имеет много параметров и прямой конструктор становится нечитаемым, допускается builder.
Но:
- builder не должен маскировать плохую модель данных;
- builder не должен использоваться как замена нормальной декомпозиции.
### 10.3. DTO отдельно от domain
Если HTTP payload начинает расходиться с доменной моделью, нужно вводить отдельный DTO слой.
Нельзя:
- тащить `serde`-ориентированный API payload прямо в домен только ради удобства.
## 11. Dependency rules
### 11.1. Внешние crates
Правило:
- сначала использовать `std`;
- потом существующие внутренние abstractions;
- только потом тянуть новый внешний crate.
Нельзя:
- добавлять зависимость "на всякий случай";
- дублировать crates с пересекающейся функцией без причины.
### 11.2. Макросы
Правило:
- не злоупотреблять макросами там, где обычный Rust-код читается лучше;
- derive-макросы допустимы;
- сложные процедурные макросы без сильной причины не нужны.
## 12. Serialization rules
### 12.1. JSON/YAML
Правило:
- доменная модель одна;
- `JSON` и `YAML` - только два формата сериализации;
- нельзя допускать, чтобы YAML export стал отдельной несовместимой моделью.
### 12.2. Secrets
Никогда не сериализовать:
- реальные токены;
- пароли;
- API keys
в exports, logs и test snapshots.
## 13. Тестовые практики на уровне Rust-кода
Минимально:
- unit tests рядом с модулем или в `tests`;
- integration tests для crate boundaries;
- фикстуры для schema/mapping/proto/yaml roundtrip.
Полезное правило:
- баг сначала воспроизводится тестом, потом фиксится кодом.
## 14. Что часто запрещают в Rust-командах
Практически всегда под запретом:
- `unwrap()` в production code;
- `unsafe` без review;
- giant modules;
- giant enums со всем подряд;
- giant services, где смешаны orchestration и transport;
- абстракции "на будущее" без второго реального кейса.
## 15. Практический итог
Для этого проекта правильный Rust-профиль такой:
- фиксированный toolchain;
- обязательные `fmt` и `clippy`;
- `unsafe` запрещен по умолчанию;
- panics запрещены в production code;
- ошибки типизированы;
- `pub` минимизируется;
- `async` только на реальных async boundaries;
- код читается за счет имен и декомпозиции, а не за счет комментариев.
+402
View File
@@ -0,0 +1,402 @@
# Rust Design
## 1. Назначение документа
Этот документ фиксирует, как проектировать поведение в Rust-коде:
- какие методы допустимы на `struct` и `enum`;
- что должно жить в `impl`;
- что должно быть вынесено в `trait`;
- что должно быть оформлено как `service` или `use case`.
Главная цель документа - не допустить появления `god-struct`, когда одна сущность одновременно:
- хранит данные;
- валидирует себя целиком;
- ходит в БД;
- дергает HTTP;
- строит mapping;
- управляет publish flow;
- содержит половину бизнес-логики проекта.
## 2. Базовое правило
В Rust нужно разделять:
- `data model`
- `domain behavior`
- `integration contracts`
- `application services`
То есть:
- `struct` и `enum` хранят состояние;
- `impl` на них содержит только локально связанное поведение;
- `trait` задает внешний контракт;
- `service` и `use case` координируют несколько сущностей и внешние зависимости.
## 3. Что допустимо держать в `impl` на структурах
На `impl` допустимы только методы, которые:
- опираются только на внутреннее состояние структуры;
- не требуют инфраструктурных зависимостей;
- не ходят в БД;
- не выполняют сетевые вызовы;
- не меняют чужие aggregate boundaries.
Подходящие примеры:
- `Operation::is_published()`
- `Operation::supports_protocol(protocol)`
- `Operation::tool_name()`
- `MappingRule::is_required()`
- `GeneratedDraft::is_available()`
- `Schema::field(path)`
- `Protocol::as_str()`
Неподходящие примеры:
- `Operation::save(db)`
- `Operation::publish(repo, runtime, cache)`
- `Operation::call_external_api()`
- `Operation::load_descriptor()`
## 4. Какие методы должны жить на ключевых доменных структурах
### 4.1. `Operation`
Допустимые методы:
- `fn tool_name(&self) -> &str`
- `fn is_draft(&self) -> bool`
- `fn is_published(&self) -> bool`
- `fn protocol(&self) -> &Protocol`
- `fn auth_profile_ref(&self) -> Option<&str>`
- `fn can_be_published(&self) -> bool`
Что не должно жить здесь:
- создание новой версии;
- publish;
- YAML import/export;
- DB persistence;
- runtime execution;
- adapter dispatch.
### 4.2. `Target`
Допустимые методы:
- `fn kind(&self) -> Protocol`
- `fn summary(&self) -> String`
Что не должно жить здесь:
- реальный вызов REST/GraphQL/gRPC;
- загрузка descriptor set;
- introspection;
- network logic.
### 4.3. `Schema`
Допустимые методы:
- `fn is_object(&self) -> bool`
- `fn field(&self, name: &str) -> Option<&SchemaField>`
- `fn has_required_fields(&self) -> bool`
- `fn validate_shape(&self, value: &serde_json::Value) -> Result<(), SchemaError>`
Что не должно жить здесь:
- UI rendering logic;
- DB serialization logic;
- adapter-specific request assembly.
### 4.4. `MappingSet` и `MappingRule`
Допустимые методы:
- `fn is_empty(&self) -> bool`
- `fn validate_paths(&self) -> Result<(), MappingError>`
- `fn target_context(&self) -> MappingTargetContext`
Что не должно жить здесь:
- protocol adapter branching;
- network execution;
- persistence;
- доступ к registry.
### 4.5. `ExecutionConfig`
Допустимые методы:
- `fn timeout(&self) -> Duration`
- `fn has_auth(&self) -> bool`
- `fn protocol_options(&self) -> Option<&ProtocolOptions>`
Что не должно жить здесь:
- secret resolution;
- создание HTTP headers из env;
- динамическое чтение конфигов приложения.
## 5. Что нужно выносить в `trait`
`Trait` нужен там, где появляется внешний контракт, который имеет несколько реализаций или зависит от инфраструктуры.
Правильные кандидаты:
- `OperationRepository`
- `PublishedOperationRepository`
- `ProtocolAdapter`
- `SecretResolver`
- `DescriptorStore`
- `ArtifactStore`
- `YamlCodec`
- `DraftGenerator`
### Пример
```rust
pub trait OperationRepository {
async fn get(&self, id: &OperationId) -> Result<OperationRecord, RepoError>;
async fn create_version(&self, cmd: CreateVersion) -> Result<OperationVersionRef, RepoError>;
async fn publish(&self, id: &OperationId, version: u32) -> Result<(), RepoError>;
}
```
Почему это `trait`, а не метод на `Operation`:
- потому что операция сама не должна знать, как она хранится;
- потому что хранение - инфраструктурная зависимость;
- потому что это boundary между доменом и storage.
## 6. Что нужно выносить в service/use-case слой
Если логика:
- координирует несколько сущностей;
- использует `trait`-зависимости;
- меняет состояние нескольких aggregate boundaries;
- имеет бизнес-шаги;
то это `service`, а не `impl` на структуре.
Кандидаты:
- `CreateOperationService`
- `CreateOperationVersionService`
- `PublishOperationService`
- `GenerateDraftService`
- `TestOperationService`
- `ImportOperationYamlService`
- `ExportOperationYamlService`
- `ListPublishedToolsService`
- `OperationExecutor`
## 7. Рекомендуемое распределение поведения
### Domain `impl`
Хранит:
- локальную валидацию;
- derived methods;
- простые status checks;
- инварианты одной сущности.
### `trait`
Хранит:
- внешние контракты;
- infrastructure boundaries;
- replaceable dependencies.
### `service`
Хранит:
- orchestration;
- use case sequence;
- transaction boundaries;
- вызовы нескольких зависимостей.
## 8. Пример правильного разделения
### Плохо
```rust
impl Operation {
pub async fn publish(
&mut self,
repo: &SqlOperationRepository,
cache: &RuntimeCache,
secret_resolver: &EnvSecretResolver,
) -> Result<(), Error> {
self.validate()?;
repo.save(self).await?;
cache.reload().await?;
let _ = secret_resolver.resolve(...)?;
self.status = Status::Published;
Ok(())
}
}
```
Почему плохо:
- доменная сущность знает про SQL;
- знает про кэш;
- знает про secret resolver;
- меняет себя и внешний мир одновременно;
- содержит orchestration.
### Правильно
```rust
impl Operation {
pub fn can_be_published(&self) -> bool {
matches!(self.status, Status::Draft | Status::Testing)
&& !self.input_mapping.rules.is_empty()
&& !self.output_mapping.rules.is_empty()
}
}
pub struct PublishOperationService<R> {
repo: R,
}
impl<R> PublishOperationService<R>
where
R: OperationRepository,
{
pub async fn execute(
&self,
operation_id: &OperationId,
version: u32,
) -> Result<(), PublishError> {
let op = self.repo.get_version(operation_id, version).await?;
if !op.can_be_published() {
return Err(PublishError::InvalidState);
}
self.repo.publish(operation_id, version).await
}
}
```
## 9. Признаки `god-struct`
Если у структуры:
- слишком много полей из разных bounded contexts;
- методы и на schema, и на DB, и на adapters, и на YAML;
- методы с кучей зависимостей в аргументах;
- методы длиннее, чем небольшой локальный инвариант;
- много `match protocol` прямо внутри доменной модели;
то это уже `god-struct`.
Особенно опасные кандидаты:
- `Operation`
- `AppState`
- `OperationExecutor`
- `AdminService`
- `ProtocolAdapter`
## 10. Как не допустить `god-struct`
### 10.1. Для `Operation`
Не добавлять туда:
- repo methods;
- transport methods;
- import/export;
- publish flow;
- sample upload handling.
### 10.2. Для `AppState`
Не складывать все зависимости в один плоский объект на 20 полей.
Лучше:
- `RegistryServices`
- `RuntimeServices`
- `ArtifactServices`
- `AuthServices`
### 10.3. Для `OperationExecutor`
Он может быть orchestration root, но не должен становиться монолитом.
Нужно выделять:
- `InputPrepare`
- `AdapterDispatch`
- `OutputFinalize`
- `ExecutionContextFactory`
## 11. Рекомендуемые `impl`-блоки по проекту
### В `mcpaas-core`
- маленькие `impl` на domain types;
- status helpers;
- derived metadata methods.
### В `mcpaas-schema`
- schema validation;
- field traversal;
- shape helpers.
### В `mcpaas-mapping`
- JSONPath validation;
- mapping rule helpers;
- execution helpers.
### В `mcpaas-proto`
- metadata conversion helpers;
- descriptor lookup helpers.
### В `mcpaas-registry`
- service methods, а не методы на доменных структурах;
- repository implementations.
### В `mcpaas-runtime`
- orchestration services;
- adapter dispatch;
- runtime context management.
## 12. Что лучше описывать не как методы структур
Следующие вещи лучше описывать отдельными сервисами даже если технически их можно записать как `impl`:
- `publish`
- `create_version`
- `import_yaml`
- `export_yaml`
- `generate_draft`
- `test_run`
- `reload_published_tools`
## 13. Практический итог
Для этого проекта хорошее правило такое:
- `struct` знает только себя;
- `trait` знает границу;
- `service` знает сценарий;
- `adapter` знает протокол;
- `repository` знает storage.
Если придерживаться этой схемы, то Rust-код останется модульным, а `Operation` и связанные типы не превратятся в `god-struct` с разнородной логикой.
+131
View File
@@ -0,0 +1,131 @@
# Стратегия тестирования
## 1. Назначение документа
Этот документ фиксирует, как проект должен тестироваться с самого начала разработки, чтобы архитектура не осталась "только на бумаге".
Цель:
- проверять доменную модель отдельно от транспорта;
- ловить регрессии в mapping;
- не дать адаптерам начать вести себя по-разному;
- обеспечить воспроизводимость для дипломной демонстрации.
## 2. Уровни тестов
### 2.1. Unit tests
Покрывают:
- `mcpaas-schema`
- `mcpaas-mapping`
- `mcpaas-proto`
- небольшие части `mcpaas-core`
Что проверять:
- валидацию схем;
- `JSONPath` parsing;
- применение input/output mapping;
- генерацию чернового mapping;
- protobuf -> schema normalization;
- JSON -> protobuf и protobuf -> JSON conversion.
### 2.2. Integration tests
Покрывают:
- `mcpaas-registry` с реальной БД;
- `mcpaas-runtime` с реальными adapter contracts;
- `admin-api` на поднятом приложении;
- publish flow и YAML import/export.
Что проверять:
- создание operation и новой version;
- publish и reload published tools;
- тестовый вызов draft;
- экспорт в YAML и повторный импорт;
- связность БД между `operations`, `operation_versions`, `published_operations`.
### 2.3. Adapter tests
Отдельно для каждого протокола:
- REST adapter;
- GraphQL adapter;
- gRPC unary adapter.
Что проверять:
- сборку request;
- нормализацию response;
- обработку ошибок;
- стабильность mapping context.
### 2.4. End-to-end tests
Минимально нужны сценарии:
- создать REST operation -> протестировать -> опубликовать -> вызвать как MCP tool;
- создать GraphQL operation -> протестировать -> опубликовать -> вызвать как MCP tool;
- загрузить `.proto` или descriptor set -> создать gRPC unary operation -> протестировать -> опубликовать -> вызвать как MCP tool.
## 3. Что должно быть покрыто обязательно
### Обязательно с первого этапа
- schema validation;
- mapping execution;
- YAML import/export roundtrip;
- versioning logic registry;
- publish flow.
### Обязательно до первого демо
- хотя бы один end-to-end сценарий для каждого из трех протоколов;
- negative tests на invalid `JSONPath`;
- negative tests на invalid protobuf descriptor;
- negative tests на GraphQL errors при HTTP `200`.
## 4. Формат тестовых данных
Рекомендуется использовать:
- JSON fixtures для sample input/output;
- YAML golden files для export/import;
- `.proto` и descriptor fixtures для gRPC;
- snapshot tests для generated draft.
## 5. Техническая стратегия
Для Rust-части:
- unit/integration tests через `cargo test`;
- тестовые фикстуры в `tests/fixtures/`;
- golden files для YAML;
- отдельные integration suites для registry и admin-api.
Для frontend:
- unit tests для form helpers и schema rendering;
- integration tests для critical user flows;
- отдельная проверка mapping editor и sample upload flows.
## 6. Что нельзя оставлять без тестов
- version increment logic;
- publish semantics;
- YAML import как `create|upsert`;
- auth profile resolution;
- generated draft application;
- MCP tool execution path.
## 7. Практический итог
Перед активной разработкой проект должен исходить из правила:
- доменная логика тестируется отдельно;
- adapters тестируются отдельно;
- registry и admin-api тестируются на реальной БД;
- минимум один полный end-to-end сценарий должен быть воспроизводим автоматически.
+20
View File
@@ -0,0 +1,20 @@
fmt:
cargo fmt --all
fmt-check:
cargo fmt --all --check
check:
cargo check --workspace
clippy:
cargo clippy --workspace --all-targets --all-features -- -D warnings
test:
cargo test --workspace --all-targets
verify:
just fmt-check
just clippy
just test
+4
View File
@@ -0,0 +1,4 @@
[toolchain]
channel = "stable"
components = ["clippy", "rustfmt"]