Merge pull request #1 from bsodfather/feat/crank-rebrand

Feat/crank rebrand
This commit is contained in:
bsodfather
2026-03-28 01:41:47 +03:00
committed by GitHub
103 changed files with 790 additions and 704 deletions
+12 -12
View File
@@ -1,13 +1,13 @@
POSTGRES_DB=rmcp POSTGRES_DB=crank
POSTGRES_USER=rmcp POSTGRES_USER=crank
POSTGRES_PASSWORD=change-me POSTGRES_PASSWORD=change-me
MCPAAS_DATABASE_URL=postgres://rmcp:change-me@postgres:5432/rmcp CRANK_DATABASE_URL=postgres://crank:change-me@postgres:5432/crank
MCPAAS_STORAGE_ROOT=/var/lib/rmcp/storage CRANK_STORAGE_ROOT=/var/lib/crank/storage
MCPAAS_PUBLISH_HOST=127.0.0.1 CRANK_PUBLISH_HOST=127.0.0.1
MCPAAS_ADMIN_BIND=0.0.0.0:3001 CRANK_ADMIN_BIND=0.0.0.0:3001
MCPAAS_MCP_BIND=0.0.0.0:3002 CRANK_MCP_BIND=0.0.0.0:3002
MCPAAS_MCP_REFRESH_MS=5000 CRANK_MCP_REFRESH_MS=5000
MCPAAS_LOG_LEVEL=info CRANK_LOG_LEVEL=info
MCPAAS_SECRET_PROVIDER=env CRANK_SECRET_PROVIDER=env
MCPAAS_PUBLIC_BASE_URL=https://rmcp.example.com CRANK_PUBLIC_BASE_URL=https://crank.example.com
MCPAAS_MCP_PUBLIC_URL=https://rmcp.example.com/mcp CRANK_MCP_PUBLIC_URL=https://crank.example.com/mcp
+3 -3
View File
@@ -19,18 +19,18 @@ jobs:
postgres: postgres:
image: postgres:16-alpine image: postgres:16-alpine
env: env:
POSTGRES_DB: rmcp_test POSTGRES_DB: crank_test
POSTGRES_USER: postgres POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
ports: ports:
- 5432:5432 - 5432:5432
options: >- options: >-
--health-cmd "pg_isready -U postgres -d rmcp_test" --health-cmd "pg_isready -U postgres -d crank_test"
--health-interval 10s --health-interval 10s
--health-timeout 5s --health-timeout 5s
--health-retries 5 --health-retries 5
env: env:
TEST_DATABASE_URL: postgres://postgres:postgres@127.0.0.1:5432/rmcp_test TEST_DATABASE_URL: postgres://postgres:postgres@127.0.0.1:5432/crank_test
steps: steps:
- name: Checkout - name: Checkout
Generated
+136 -136
View File
@@ -7,13 +7,13 @@ name = "admin-api"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"axum", "axum",
"mcpaas-adapter-grpc", "crank-adapter-grpc",
"mcpaas-core", "crank-core",
"mcpaas-mapping", "crank-mapping",
"mcpaas-proto", "crank-proto",
"mcpaas-registry", "crank-registry",
"mcpaas-runtime", "crank-runtime",
"mcpaas-schema", "crank-schema",
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
@@ -211,6 +211,129 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crank-adapter-graphql"
version = "0.1.0"
dependencies = [
"axum",
"crank-core",
"crank-mapping",
"reqwest",
"serde",
"serde_json",
"thiserror",
"tokio",
]
[[package]]
name = "crank-adapter-grpc"
version = "0.1.0"
dependencies = [
"base64",
"crank-core",
"crank-proto",
"prost",
"prost-reflect",
"prost-types",
"protoc-bin-vendored",
"serde",
"serde_json",
"thiserror",
"tokio",
"tonic",
"tonic-prost",
"tonic-prost-build",
]
[[package]]
name = "crank-adapter-rest"
version = "0.1.0"
dependencies = [
"axum",
"crank-core",
"reqwest",
"serde",
"serde_json",
"thiserror",
"tokio",
]
[[package]]
name = "crank-core"
version = "0.1.0"
dependencies = [
"serde",
"serde_json",
"serde_yaml",
"thiserror",
]
[[package]]
name = "crank-mapping"
version = "0.1.0"
dependencies = [
"crank-core",
"serde",
"serde_json",
"serde_yaml",
"thiserror",
]
[[package]]
name = "crank-proto"
version = "0.1.0"
dependencies = [
"crank-core",
"crank-schema",
"prost",
"prost-reflect",
"prost-types",
"serde",
"thiserror",
]
[[package]]
name = "crank-registry"
version = "0.1.0"
dependencies = [
"crank-core",
"crank-mapping",
"crank-schema",
"serde",
"serde_json",
"sqlx",
"thiserror",
"tokio",
]
[[package]]
name = "crank-runtime"
version = "0.1.0"
dependencies = [
"axum",
"crank-adapter-graphql",
"crank-adapter-grpc",
"crank-adapter-rest",
"crank-core",
"crank-mapping",
"crank-schema",
"serde",
"serde_json",
"thiserror",
"tokio",
]
[[package]]
name = "crank-schema"
version = "0.1.0"
dependencies = [
"crank-core",
"serde",
"serde_json",
"serde_yaml",
"thiserror",
]
[[package]] [[package]]
name = "crc" name = "crc"
version = "3.4.0" version = "3.4.0"
@@ -933,12 +1056,12 @@ name = "mcp-server"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"axum", "axum",
"mcpaas-adapter-grpc", "crank-adapter-grpc",
"mcpaas-core", "crank-core",
"mcpaas-mapping", "crank-mapping",
"mcpaas-registry", "crank-registry",
"mcpaas-runtime", "crank-runtime",
"mcpaas-schema", "crank-schema",
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
@@ -949,129 +1072,6 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "mcpaas-adapter-graphql"
version = "0.1.0"
dependencies = [
"axum",
"mcpaas-core",
"mcpaas-mapping",
"reqwest",
"serde",
"serde_json",
"thiserror",
"tokio",
]
[[package]]
name = "mcpaas-adapter-grpc"
version = "0.1.0"
dependencies = [
"base64",
"mcpaas-core",
"mcpaas-proto",
"prost",
"prost-reflect",
"prost-types",
"protoc-bin-vendored",
"serde",
"serde_json",
"thiserror",
"tokio",
"tonic",
"tonic-prost",
"tonic-prost-build",
]
[[package]]
name = "mcpaas-adapter-rest"
version = "0.1.0"
dependencies = [
"axum",
"mcpaas-core",
"reqwest",
"serde",
"serde_json",
"thiserror",
"tokio",
]
[[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",
"prost",
"prost-reflect",
"prost-types",
"serde",
"thiserror",
]
[[package]]
name = "mcpaas-registry"
version = "0.1.0"
dependencies = [
"mcpaas-core",
"mcpaas-mapping",
"mcpaas-schema",
"serde",
"serde_json",
"sqlx",
"thiserror",
"tokio",
]
[[package]]
name = "mcpaas-runtime"
version = "0.1.0"
dependencies = [
"axum",
"mcpaas-adapter-graphql",
"mcpaas-adapter-grpc",
"mcpaas-adapter-rest",
"mcpaas-core",
"mcpaas-mapping",
"mcpaas-schema",
"serde",
"serde_json",
"thiserror",
"tokio",
]
[[package]]
name = "mcpaas-schema"
version = "0.1.0"
dependencies = [
"mcpaas-core",
"serde",
"serde_json",
"serde_yaml",
"thiserror",
]
[[package]] [[package]]
name = "md-5" name = "md-5"
version = "0.10.6" version = "0.10.6"
+9 -9
View File
@@ -2,15 +2,15 @@
members = [ members = [
"apps/admin-api", "apps/admin-api",
"apps/mcp-server", "apps/mcp-server",
"crates/mcpaas-core", "crates/crank-core",
"crates/mcpaas-schema", "crates/crank-schema",
"crates/mcpaas-mapping", "crates/crank-mapping",
"crates/mcpaas-proto", "crates/crank-proto",
"crates/mcpaas-registry", "crates/crank-registry",
"crates/mcpaas-runtime", "crates/crank-runtime",
"crates/mcpaas-adapter-rest", "crates/crank-adapter-rest",
"crates/mcpaas-adapter-graphql", "crates/crank-adapter-graphql",
"crates/mcpaas-adapter-grpc", "crates/crank-adapter-grpc",
] ]
resolver = "3" resolver = "3"
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

+4 -2
View File
@@ -1,6 +1,8 @@
# MCPaaS # Crank
MCPaaS - это low-code платформа для публикации внешних API в виде MCP tools без написания нового backend-обработчика под каждую интеграцию. Система предоставляет единый административный UI, в котором оператор может подключать REST, GraphQL и gRPC операции, настраивать маппинг входных и выходных данных, выполнять тестовый вызов и публиковать результат как MCP tool. ![Crank](./Crank.png)
Crank - это low-code платформа для публикации внешних API в виде MCP tools без написания нового backend-обработчика под каждую интеграцию. Система предоставляет единый административный UI, в котором оператор может подключать REST, GraphQL и gRPC операции, настраивать маппинг входных и выходных данных, выполнять тестовый вызов и публиковать результат как MCP tool.
На текущем этапе репозиторий содержит проектную документацию и архитектурные решения, которые задают границы MVP и подход к реализации. На текущем этапе репозиторий содержит проектную документацию и архитектурные решения, которые задают границы MVP и подход к реализации.
+6 -6
View File
@@ -2,17 +2,17 @@
## Current ## Current
### `feat/ui-concept-alignment` ### `feat/crank-rebrand`
Status: completed Status: completed
DoD: DoD:
- shell и create flow визуально опираются на утвержденный concept - проект переименован в `Crank` в документации, UI и package metadata
- sidebar, topbar и page header выглядят как единая продуктовая система - workspace и crate package names согласованы с новым брендом
- create page больше не выглядит как длинная сырая форма, а как step-based contract builder - `README.md` использует `Crank.png` как главное изображение
- кодовые JSON и mapping поля имеют отдельную визуальную подачу - `origin` указывает на `git@github.com:bsodfather/crank.git`
- UI build и tests остаются зелеными после выравнивания под concept - Rust workspace и UI build остаются зелеными
## Next ## Next
+7 -7
View File
@@ -7,12 +7,12 @@ version.workspace = true
[dependencies] [dependencies]
axum.workspace = true axum.workspace = true
mcpaas-core = { path = "../../crates/mcpaas-core" } crank-core = { path = "../../crates/crank-core" }
mcpaas-mapping = { path = "../../crates/mcpaas-mapping" } crank-mapping = { path = "../../crates/crank-mapping" }
mcpaas-proto = { path = "../../crates/mcpaas-proto" } crank-proto = { path = "../../crates/crank-proto" }
mcpaas-registry = { path = "../../crates/mcpaas-registry" } crank-registry = { path = "../../crates/crank-registry" }
mcpaas-runtime = { path = "../../crates/mcpaas-runtime" } crank-runtime = { path = "../../crates/crank-runtime" }
mcpaas-schema = { path = "../../crates/mcpaas-schema" } crank-schema = { path = "../../crates/crank-schema" }
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
serde_yaml.workspace = true serde_yaml.workspace = true
@@ -24,6 +24,6 @@ tracing-subscriber.workspace = true
uuid.workspace = true uuid.workspace = true
[dev-dependencies] [dev-dependencies]
mcpaas-adapter-grpc = { path = "../../crates/mcpaas-adapter-grpc", features = ["test-support"] } crank-adapter-grpc = { path = "../../crates/crank-adapter-grpc", features = ["test-support"] }
reqwest.workspace = true reqwest.workspace = true
sqlx.workspace = true sqlx.workspace = true
+1 -1
View File
@@ -18,7 +18,7 @@ WORKDIR /app
COPY --from=builder /app/target/release/admin-api /usr/local/bin/admin-api COPY --from=builder /app/target/release/admin-api /usr/local/bin/admin-api
ENV MCPAAS_ADMIN_BIND=0.0.0.0:3001 ENV CRANK_ADMIN_BIND=0.0.0.0:3001
EXPOSE 3001 EXPOSE 3001
+7 -7
View File
@@ -80,14 +80,14 @@ mod tests {
}; };
use axum::{Json, Router, routing::post}; use axum::{Json, Router, routing::post};
use mcpaas_adapter_grpc::test_support as grpc_test_support; use crank_adapter_grpc::test_support as grpc_test_support;
use mcpaas_core::{ use crank_core::{
DescriptorId, ExecutionConfig, GraphqlOperationType, GraphqlTarget, GrpcTarget, HttpMethod, DescriptorId, ExecutionConfig, GraphqlOperationType, GraphqlTarget, GrpcTarget, HttpMethod,
Protocol, RestTarget, Target, ToolDescription, Protocol, RestTarget, Target, ToolDescription,
}; };
use mcpaas_mapping::{MappingRule, MappingSet}; use crank_mapping::{MappingRule, MappingSet};
use mcpaas_registry::PostgresRegistry; use crank_registry::PostgresRegistry;
use mcpaas_schema::{Schema, SchemaKind}; use crank_schema::{Schema, SchemaKind};
use serde_json::{Value, json}; use serde_json::{Value, json};
use sqlx::{Executor, postgres::PgPoolOptions}; use sqlx::{Executor, postgres::PgPoolOptions};
use tokio::net::TcpListener; use tokio::net::TcpListener;
@@ -621,7 +621,7 @@ mod tests {
async fn test_registry() -> PostgresRegistry { async fn test_registry() -> PostgresRegistry {
let database_url = env::var("TEST_DATABASE_URL") let database_url = env::var("TEST_DATABASE_URL")
.unwrap_or_else(|_| "postgres://rmcp:rmcp@127.0.0.1:5432/rmcp".to_owned()); .unwrap_or_else(|_| "postgres://crank:crank@127.0.0.1:5432/crank".to_owned());
let admin_pool = PgPoolOptions::new() let admin_pool = PgPoolOptions::new()
.max_connections(1) .max_connections(1)
.connect(&database_url) .connect(&database_url)
@@ -648,7 +648,7 @@ mod tests {
fn test_storage_root(name: &str) -> std::path::PathBuf { fn test_storage_root(name: &str) -> std::path::PathBuf {
env::temp_dir().join(format!( env::temp_dir().join(format!(
"rmcp_admin_api_{name}_{}_{}", "crank_admin_api_{name}_{}_{}",
std::process::id(), std::process::id(),
SystemTime::now() SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
+4 -4
View File
@@ -3,10 +3,10 @@ use axum::{
http::StatusCode, http::StatusCode,
response::{IntoResponse, Response}, response::{IntoResponse, Response},
}; };
use mcpaas_mapping::MappingError; use crank_mapping::MappingError;
use mcpaas_registry::RegistryError; use crank_registry::RegistryError;
use mcpaas_runtime::RuntimeError; use crank_runtime::RuntimeError;
use mcpaas_schema::SchemaError; use crank_schema::SchemaError;
use serde_json::{Value, json}; use serde_json::{Value, json};
use thiserror::Error; use thiserror::Error;
use tracing::{error, warn}; use tracing::{error, warn};
+5 -6
View File
@@ -7,7 +7,7 @@ mod storage;
use std::{env, net::SocketAddr, path::PathBuf}; use std::{env, net::SocketAddr, path::PathBuf};
use mcpaas_registry::PostgresRegistry; use crank_registry::PostgresRegistry;
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tracing::info; use tracing::info;
@@ -17,16 +17,15 @@ use crate::{app::build_app, service::AdminService, state::AppState};
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt() tracing_subscriber::fmt()
.with_env_filter( .with_env_filter(
env::var("MCPAAS_LOG_LEVEL") env::var("CRANK_LOG_LEVEL").unwrap_or_else(|_| "admin_api=info,tower_http=info".into()),
.unwrap_or_else(|_| "admin_api=info,tower_http=info".into()),
) )
.init(); .init();
let database_url = env::var("MCPAAS_DATABASE_URL")?; let database_url = env::var("CRANK_DATABASE_URL")?;
let storage_root = PathBuf::from( let storage_root = PathBuf::from(
env::var("MCPAAS_STORAGE_ROOT").unwrap_or_else(|_| "/var/lib/rmcp/storage".into()), env::var("CRANK_STORAGE_ROOT").unwrap_or_else(|_| "/var/lib/crank/storage".into()),
); );
let bind_addr = env::var("MCPAAS_ADMIN_BIND").unwrap_or_else(|_| "0.0.0.0:3001".into()); let bind_addr = env::var("CRANK_ADMIN_BIND").unwrap_or_else(|_| "0.0.0.0:3001".into());
let socket_addr: SocketAddr = bind_addr.parse()?; let socket_addr: SocketAddr = bind_addr.parse()?;
let registry = PostgresRegistry::connect(&database_url).await?; let registry = PostgresRegistry::connect(&database_url).await?;
let state = AppState { let state = AppState {
+2 -2
View File
@@ -96,7 +96,7 @@ pub async fn upload_input_json(
.service .service
.save_json_sample( .save_json_sample(
&operation_id.as_str().into(), &operation_id.as_str().into(),
mcpaas_registry::SampleKind::InputJson, crank_registry::SampleKind::InputJson,
&payload, &payload,
) )
.await?; .await?;
@@ -117,7 +117,7 @@ pub async fn upload_output_json(
.service .service
.save_json_sample( .save_json_sample(
&operation_id.as_str().into(), &operation_id.as_str().into(),
mcpaas_registry::SampleKind::OutputJson, crank_registry::SampleKind::OutputJson,
&payload, &payload,
) )
.await?; .await?;
+21 -21
View File
@@ -1,18 +1,18 @@
use std::path::PathBuf; use std::path::PathBuf;
use mcpaas_core::{ use crank_core::{
AuthConfig, AuthKind, AuthProfile, AuthProfileId, ConfigExport, ExportMode, GeneratedDraft, AuthConfig, AuthKind, AuthProfile, AuthProfileId, ConfigExport, ExportMode, GeneratedDraft,
GeneratedDraftStatus, OperationId, OperationStatus, Protocol, SampleId, Samples, Target, GeneratedDraftStatus, OperationId, OperationStatus, Protocol, SampleId, Samples, Target,
}; };
use mcpaas_mapping::{JsonPathRoot, MappingSet, infer_mapping_from_samples}; use crank_mapping::{JsonPathRoot, MappingSet, infer_mapping_from_samples};
use mcpaas_proto::{ProtoService, services_from_descriptor_set_bytes}; use crank_proto::{ProtoService, services_from_descriptor_set_bytes};
use mcpaas_registry::{ use crank_registry::{
CreateVersionRequest, OperationSampleMetadata, OperationSummary, OperationVersionRecord, CreateVersionRequest, OperationSampleMetadata, OperationSummary, OperationVersionRecord,
PostgresRegistry, PublishRequest, RegistryOperation, SampleKind, SaveAuthProfileRequest, PostgresRegistry, PublishRequest, RegistryOperation, SampleKind, SaveAuthProfileRequest,
SaveDescriptorMetadataRequest, SaveSampleMetadataRequest, SaveDescriptorMetadataRequest, SaveSampleMetadataRequest,
}; };
use mcpaas_runtime::{PreparedRequest, RuntimeError, RuntimeExecutor, RuntimeOperation}; use crank_runtime::{PreparedRequest, RuntimeError, RuntimeExecutor, RuntimeOperation};
use mcpaas_schema::Schema; use crank_schema::Schema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{Value, json}; use serde_json::{Value, json};
use time::{OffsetDateTime, format_description::well_known::Rfc3339}; use time::{OffsetDateTime, format_description::well_known::Rfc3339};
@@ -38,8 +38,8 @@ pub struct OperationPayload {
pub output_schema: Schema, pub output_schema: Schema,
pub input_mapping: MappingSet, pub input_mapping: MappingSet,
pub output_mapping: MappingSet, pub output_mapping: MappingSet,
pub execution_config: mcpaas_core::ExecutionConfig, pub execution_config: crank_core::ExecutionConfig,
pub tool_description: mcpaas_core::ToolDescription, pub tool_description: crank_core::ToolDescription,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
@@ -395,13 +395,13 @@ impl AdminService {
let services = services_from_descriptor_set_bytes(payload) let services = services_from_descriptor_set_bytes(payload)
.map_err(|error| ApiError::validation(error.to_string()))?; .map_err(|error| ApiError::validation(error.to_string()))?;
let descriptor_id = mcpaas_core::DescriptorId::new(new_prefixed_id("desc")); let descriptor_id = crank_core::DescriptorId::new(new_prefixed_id("desc"));
let storage_ref = self let storage_ref = self
.storage .storage
.write_descriptor( .write_descriptor(
operation_id, operation_id,
summary.current_draft_version, summary.current_draft_version,
mcpaas_registry::DescriptorKind::DescriptorSet, crank_registry::DescriptorKind::DescriptorSet,
&descriptor_id, &descriptor_id,
source_name, source_name,
payload, payload,
@@ -410,11 +410,11 @@ impl AdminService {
self.registry self.registry
.save_descriptor_metadata(SaveDescriptorMetadataRequest { .save_descriptor_metadata(SaveDescriptorMetadataRequest {
descriptor: &mcpaas_registry::DescriptorMetadata { descriptor: &crank_registry::DescriptorMetadata {
id: descriptor_id.clone(), id: descriptor_id.clone(),
operation_id: Some(operation_id.clone()), operation_id: Some(operation_id.clone()),
version: Some(summary.current_draft_version), version: Some(summary.current_draft_version),
descriptor_kind: mcpaas_registry::DescriptorKind::DescriptorSet, descriptor_kind: crank_registry::DescriptorKind::DescriptorSet,
storage_ref, storage_ref,
source_name: source_name.map(ToOwned::to_owned), source_name: source_name.map(ToOwned::to_owned),
package_index: Some( package_index: Some(
@@ -452,13 +452,13 @@ impl AdminService {
)); ));
} }
let descriptor_id = mcpaas_core::DescriptorId::new(new_prefixed_id("desc")); let descriptor_id = crank_core::DescriptorId::new(new_prefixed_id("desc"));
let storage_ref = self let storage_ref = self
.storage .storage
.write_descriptor( .write_descriptor(
operation_id, operation_id,
summary.current_draft_version, summary.current_draft_version,
mcpaas_registry::DescriptorKind::ProtoUpload, crank_registry::DescriptorKind::ProtoUpload,
&descriptor_id, &descriptor_id,
source_name, source_name,
payload, payload,
@@ -467,11 +467,11 @@ impl AdminService {
self.registry self.registry
.save_descriptor_metadata(SaveDescriptorMetadataRequest { .save_descriptor_metadata(SaveDescriptorMetadataRequest {
descriptor: &mcpaas_registry::DescriptorMetadata { descriptor: &crank_registry::DescriptorMetadata {
id: descriptor_id.clone(), id: descriptor_id.clone(),
operation_id: Some(operation_id.clone()), operation_id: Some(operation_id.clone()),
version: Some(summary.current_draft_version), version: Some(summary.current_draft_version),
descriptor_kind: mcpaas_registry::DescriptorKind::ProtoUpload, descriptor_kind: crank_registry::DescriptorKind::ProtoUpload,
storage_ref, storage_ref,
source_name: source_name.map(ToOwned::to_owned), source_name: source_name.map(ToOwned::to_owned),
package_index: None, package_index: None,
@@ -513,7 +513,7 @@ impl AdminService {
.into_iter() .into_iter()
.rev() .rev()
.find(|descriptor| { .find(|descriptor| {
descriptor.descriptor_kind == mcpaas_registry::DescriptorKind::DescriptorSet descriptor.descriptor_kind == crank_registry::DescriptorKind::DescriptorSet
}) })
.ok_or_else(|| ApiError::not_found("descriptor set was not uploaded"))?; .ok_or_else(|| ApiError::not_found("descriptor set was not uploaded"))?;
let bytes = self.storage.read_bytes(&descriptor.storage_ref).await?; let bytes = self.storage.read_bytes(&descriptor.storage_ref).await?;
@@ -809,10 +809,10 @@ impl AdminService {
fn build_request_preview( fn build_request_preview(
mapping: &MappingSet, mapping: &MappingSet,
input: &Value, input: &Value,
) -> Result<Value, mcpaas_mapping::MappingError> { ) -> Result<Value, crank_mapping::MappingError> {
let mapped = mapping.apply(&json!({ "mcp": input }))?; let mapped = mapping.apply(&json!({ "mcp": input }))?;
let prepared = PreparedRequest::from_mapping_output(&mapped).map_err(|error| { let prepared = PreparedRequest::from_mapping_output(&mapped).map_err(|error| {
mcpaas_mapping::MappingError::InvalidJsonPath { crank_mapping::MappingError::InvalidJsonPath {
path: error.to_string(), path: error.to_string(),
} }
})?; })?;
@@ -890,9 +890,9 @@ fn proto_service_summary(service: ProtoService) -> Result<GrpcServiceSummary, Ap
Ok(GrpcMethodSummary { Ok(GrpcMethodSummary {
name: method.name.clone(), name: method.name.clone(),
kind: "unary".to_owned(), kind: "unary".to_owned(),
input_schema: mcpaas_proto::message_to_schema(&method.input) input_schema: crank_proto::message_to_schema(&method.input)
.map_err(|error| ApiError::validation(error.to_string()))?, .map_err(|error| ApiError::validation(error.to_string()))?,
output_schema: mcpaas_proto::message_to_schema(&method.output) output_schema: crank_proto::message_to_schema(&method.output)
.map_err(|error| ApiError::validation(error.to_string()))?, .map_err(|error| ApiError::validation(error.to_string()))?,
}) })
}) })
+2 -2
View File
@@ -1,7 +1,7 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use mcpaas_core::{DescriptorId, OperationId, SampleId}; use crank_core::{DescriptorId, OperationId, SampleId};
use mcpaas_registry::{DescriptorKind, SampleKind}; use crank_registry::{DescriptorKind, SampleKind};
use serde_json::Value; use serde_json::Value;
use thiserror::Error; use thiserror::Error;
use tokio::fs; use tokio::fs;
+7 -7
View File
@@ -7,9 +7,9 @@ version.workspace = true
[dependencies] [dependencies]
axum.workspace = true axum.workspace = true
mcpaas-registry = { path = "../../crates/mcpaas-registry" } crank-registry = { path = "../../crates/crank-registry" }
mcpaas-runtime = { path = "../../crates/mcpaas-runtime" } crank-runtime = { path = "../../crates/crank-runtime" }
mcpaas-schema = { path = "../../crates/mcpaas-schema" } crank-schema = { path = "../../crates/crank-schema" }
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
tokio = { workspace = true, features = ["sync"] } tokio = { workspace = true, features = ["sync"] }
@@ -18,9 +18,9 @@ tracing-subscriber.workspace = true
uuid.workspace = true uuid.workspace = true
[dev-dependencies] [dev-dependencies]
mcpaas-adapter-grpc = { path = "../../crates/mcpaas-adapter-grpc", features = ["test-support"] } crank-adapter-grpc = { path = "../../crates/crank-adapter-grpc", features = ["test-support"] }
mcpaas-core = { path = "../../crates/mcpaas-core" } crank-core = { path = "../../crates/crank-core" }
mcpaas-mapping = { path = "../../crates/mcpaas-mapping" } crank-mapping = { path = "../../crates/crank-mapping" }
mcpaas-schema = { path = "../../crates/mcpaas-schema" } crank-schema = { path = "../../crates/crank-schema" }
reqwest.workspace = true reqwest.workspace = true
sqlx.workspace = true sqlx.workspace = true
+1 -1
View File
@@ -18,7 +18,7 @@ WORKDIR /app
COPY --from=builder /app/target/release/mcp-server /usr/local/bin/mcp-server COPY --from=builder /app/target/release/mcp-server /usr/local/bin/mcp-server
ENV MCPAAS_MCP_BIND=0.0.0.0:3002 ENV CRANK_MCP_BIND=0.0.0.0:3002
EXPOSE 3002 EXPOSE 3002
+13 -13
View File
@@ -10,8 +10,8 @@ use axum::{
response::{IntoResponse, Response}, response::{IntoResponse, Response},
routing::get, routing::get,
}; };
use mcpaas_registry::{PostgresRegistry, RegistryOperation}; use crank_registry::{PostgresRegistry, RegistryOperation};
use mcpaas_runtime::{RuntimeError, RuntimeExecutor, RuntimeOperation}; use crank_runtime::{RuntimeError, RuntimeExecutor, RuntimeOperation};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::{Value, json}; use serde_json::{Value, json};
@@ -312,7 +312,7 @@ async fn handle_initialize(state: Arc<AppState>, message: &Value) -> Response {
} }
}, },
"serverInfo": { "serverInfo": {
"name": "rmcp-mcp-server", "name": "crank-mcp-server",
"version": env!("CARGO_PKG_VERSION") "version": env!("CARGO_PKG_VERSION")
} }
}), }),
@@ -480,9 +480,9 @@ fn tool_definition(operation: &RegistryOperation) -> Value {
}) })
} }
fn schema_to_json_schema(schema: &mcpaas_schema::Schema) -> Value { fn schema_to_json_schema(schema: &crank_schema::Schema) -> Value {
match schema.kind { match schema.kind {
mcpaas_schema::SchemaKind::Object => { crank_schema::SchemaKind::Object => {
let mut properties = serde_json::Map::new(); let mut properties = serde_json::Map::new();
let mut required = Vec::new(); let mut required = Vec::new();
@@ -500,7 +500,7 @@ fn schema_to_json_schema(schema: &mcpaas_schema::Schema) -> Value {
"required": required "required": required
}) })
} }
mcpaas_schema::SchemaKind::Array => json!({ crank_schema::SchemaKind::Array => json!({
"type": "array", "type": "array",
"items": schema "items": schema
.items .items
@@ -508,16 +508,16 @@ fn schema_to_json_schema(schema: &mcpaas_schema::Schema) -> Value {
.map(schema_to_json_schema) .map(schema_to_json_schema)
.unwrap_or_else(|| json!({})) .unwrap_or_else(|| json!({}))
}), }),
mcpaas_schema::SchemaKind::String => json!({ "type": "string" }), crank_schema::SchemaKind::String => json!({ "type": "string" }),
mcpaas_schema::SchemaKind::Integer => json!({ "type": "integer" }), crank_schema::SchemaKind::Integer => json!({ "type": "integer" }),
mcpaas_schema::SchemaKind::Number => json!({ "type": "number" }), crank_schema::SchemaKind::Number => json!({ "type": "number" }),
mcpaas_schema::SchemaKind::Boolean => json!({ "type": "boolean" }), crank_schema::SchemaKind::Boolean => json!({ "type": "boolean" }),
mcpaas_schema::SchemaKind::Enum => json!({ crank_schema::SchemaKind::Enum => json!({
"type": "string", "type": "string",
"enum": schema.enum_values "enum": schema.enum_values
}), }),
mcpaas_schema::SchemaKind::Null => json!({ "type": "null" }), crank_schema::SchemaKind::Null => json!({ "type": "null" }),
mcpaas_schema::SchemaKind::Oneof => json!({ crank_schema::SchemaKind::Oneof => json!({
"anyOf": schema.variants.iter().map(schema_to_json_schema).collect::<Vec<_>>() "anyOf": schema.variants.iter().map(schema_to_json_schema).collect::<Vec<_>>()
}), }),
} }
+1 -1
View File
@@ -4,7 +4,7 @@ use std::{
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use mcpaas_registry::{PostgresRegistry, RegistryError, RegistryOperation}; use crank_registry::{PostgresRegistry, RegistryError, RegistryOperation};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use tracing::info; use tracing::info;
+17 -17
View File
@@ -5,7 +5,7 @@ mod session;
use std::{env, net::SocketAddr, time::Duration}; use std::{env, net::SocketAddr, time::Duration};
use mcpaas_registry::PostgresRegistry; use crank_registry::PostgresRegistry;
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tracing::info; use tracing::info;
@@ -15,15 +15,15 @@ use crate::app::build_app;
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
tracing_subscriber::fmt() tracing_subscriber::fmt()
.with_env_filter( .with_env_filter(
env::var("MCPAAS_LOG_LEVEL") env::var("CRANK_LOG_LEVEL")
.unwrap_or_else(|_| "mcp_server=info,tower_http=info".into()), .unwrap_or_else(|_| "mcp_server=info,tower_http=info".into()),
) )
.init(); .init();
let database_url = env::var("MCPAAS_DATABASE_URL")?; let database_url = env::var("CRANK_DATABASE_URL")?;
let bind_addr = env::var("MCPAAS_MCP_BIND").unwrap_or_else(|_| "0.0.0.0:3002".into()); let bind_addr = env::var("CRANK_MCP_BIND").unwrap_or_else(|_| "0.0.0.0:3002".into());
let public_base_url = env::var("MCPAAS_PUBLIC_BASE_URL").ok(); let public_base_url = env::var("CRANK_PUBLIC_BASE_URL").ok();
let refresh_interval = env::var("MCPAAS_MCP_REFRESH_MS") let refresh_interval = env::var("CRANK_MCP_REFRESH_MS")
.ok() .ok()
.and_then(|value| value.parse::<u64>().ok()) .and_then(|value| value.parse::<u64>().ok())
.map(Duration::from_millis) .map(Duration::from_millis)
@@ -49,14 +49,14 @@ mod tests {
}; };
use axum::{Json, Router, http::header, routing::post}; use axum::{Json, Router, http::header, routing::post};
use mcpaas_adapter_grpc::test_support as grpc_test_support; use crank_adapter_grpc::test_support as grpc_test_support;
use mcpaas_core::{ use crank_core::{
DescriptorId, ExecutionConfig, GraphqlOperationType, GraphqlTarget, GrpcTarget, HttpMethod, DescriptorId, ExecutionConfig, GraphqlOperationType, GraphqlTarget, GrpcTarget, HttpMethod,
Operation, OperationId, OperationStatus, Protocol, RestTarget, Target, ToolDescription, Operation, OperationId, OperationStatus, Protocol, RestTarget, Target, ToolDescription,
}; };
use mcpaas_mapping::{MappingRule, MappingSet}; use crank_mapping::{MappingRule, MappingSet};
use mcpaas_registry::{PostgresRegistry, PublishRequest}; use crank_registry::{PostgresRegistry, PublishRequest};
use mcpaas_schema::{Schema, SchemaKind}; use crank_schema::{Schema, SchemaKind};
use serde_json::{Value, json}; use serde_json::{Value, json};
use sqlx::{Executor, postgres::PgPoolOptions}; use sqlx::{Executor, postgres::PgPoolOptions};
use tokio::net::TcpListener; use tokio::net::TcpListener;
@@ -86,7 +86,7 @@ mod tests {
let base_url = spawn_mcp_server(build_app( let base_url = spawn_mcp_server(build_app(
registry, registry,
Duration::from_millis(0), Duration::from_millis(0),
Some("https://rmcp.example.com".to_owned()), Some("https://crank.example.com".to_owned()),
)) ))
.await; .await;
let client = reqwest::Client::new(); let client = reqwest::Client::new();
@@ -153,7 +153,7 @@ mod tests {
let base_url = spawn_mcp_server(build_app( let base_url = spawn_mcp_server(build_app(
registry, registry,
Duration::from_millis(0), Duration::from_millis(0),
Some("https://rmcp.example.com".to_owned()), Some("https://crank.example.com".to_owned()),
)) ))
.await; .await;
let client = reqwest::Client::new(); let client = reqwest::Client::new();
@@ -207,7 +207,7 @@ mod tests {
let base_url = spawn_mcp_server(build_app( let base_url = spawn_mcp_server(build_app(
registry, registry,
Duration::from_millis(0), Duration::from_millis(0),
Some("https://rmcp.example.com".to_owned()), Some("https://crank.example.com".to_owned()),
)) ))
.await; .await;
let client = reqwest::Client::new(); let client = reqwest::Client::new();
@@ -244,7 +244,7 @@ mod tests {
let base_url = spawn_mcp_server(build_app( let base_url = spawn_mcp_server(build_app(
registry, registry,
Duration::from_millis(0), Duration::from_millis(0),
Some("https://rmcp.example.com".to_owned()), Some("https://crank.example.com".to_owned()),
)) ))
.await; .await;
let client = reqwest::Client::new(); let client = reqwest::Client::new();
@@ -297,7 +297,7 @@ mod tests {
let base_url = spawn_mcp_server(build_app( let base_url = spawn_mcp_server(build_app(
registry.clone(), registry.clone(),
Duration::from_millis(0), Duration::from_millis(0),
Some("https://rmcp.example.com".to_owned()), Some("https://crank.example.com".to_owned()),
)) ))
.await; .await;
let client = reqwest::Client::new(); let client = reqwest::Client::new();
@@ -479,7 +479,7 @@ mod tests {
async fn test_registry() -> PostgresRegistry { async fn test_registry() -> PostgresRegistry {
let database_url = env::var("TEST_DATABASE_URL") let database_url = env::var("TEST_DATABASE_URL")
.unwrap_or_else(|_| "postgres://rmcp:rmcp@127.0.0.1:5432/rmcp".to_owned()); .unwrap_or_else(|_| "postgres://crank:crank@127.0.0.1:5432/crank".to_owned());
let admin_pool = PgPoolOptions::new() let admin_pool = PgPoolOptions::new()
.max_connections(1) .max_connections(1)
.connect(&database_url) .connect(&database_url)
+1 -1
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>RMCP Console</title> <title>Crank Console</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+2 -2
View File
@@ -1,11 +1,11 @@
{ {
"name": "rmcp-ui", "name": "crank-ui",
"version": "0.1.0", "version": "0.1.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "rmcp-ui", "name": "crank-ui",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.2.2", "@hookform/resolvers": "^5.2.2",
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"name": "rmcp-ui", "name": "crank-ui",
"private": true, "private": true,
"version": "0.1.0", "version": "0.1.0",
"type": "module", "type": "module",
@@ -8,7 +8,7 @@ describe("buildOperationPayload", () => {
...defaultOperationFormValues, ...defaultOperationFormValues,
restStaticHeadersText: JSON.stringify( restStaticHeadersText: JSON.stringify(
{ {
"x-app-source": "rmcp", "x-app-source": "crank",
}, },
null, null,
2, 2,
@@ -26,7 +26,7 @@ describe("buildOperationPayload", () => {
expect(payload.target.kind).toBe("rest"); expect(payload.target.kind).toBe("rest");
expect(payload.target.method).toBe("POST"); expect(payload.target.method).toBe("POST");
expect(payload.target.static_headers).toEqual({ expect(payload.target.static_headers).toEqual({
"x-app-source": "rmcp", "x-app-source": "crank",
}); });
expect(payload.execution_config.headers).toEqual({ expect(payload.execution_config.headers).toEqual({
"x-trace-id": "trace-123", "x-trace-id": "trace-123",
@@ -2,6 +2,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import { useEffect, useMemo, useRef } from "react"; import { useEffect, useMemo, useRef } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { createPortal } from "react-dom";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { createOperation } from "../../entities/operation/api"; import { createOperation } from "../../entities/operation/api";
@@ -195,7 +196,11 @@ function bytesToBase64(buffer: ArrayBuffer) {
return window.btoa(binary); return window.btoa(binary);
} }
export function OperationForm() { type OperationFormProps = {
actionBarContainerId?: string;
};
export function OperationForm({ actionBarContainerId }: OperationFormProps) {
const navigate = useNavigate(); const navigate = useNavigate();
const form = useForm<OperationFormValues>({ const form = useForm<OperationFormValues>({
defaultValues: defaultOperationFormValues, defaultValues: defaultOperationFormValues,
@@ -236,6 +241,9 @@ export function OperationForm() {
} }
}, [activeProtocol]); }, [activeProtocol]);
const actionBarContainer =
actionBarContainerId === undefined ? null : document.getElementById(actionBarContainerId);
async function handleDescriptorFileChange(file: File | undefined) { async function handleDescriptorFileChange(file: File | undefined) {
if (file === undefined) { if (file === undefined) {
return; return;
@@ -339,174 +347,194 @@ export function OperationForm() {
} }
return ( return (
<form <>
className="builder-stack" <form
onSubmit={form.handleSubmit((values) => { className="builder-stack"
creationMutation.mutate(values); id="operation-create-form"
})} onSubmit={form.handleSubmit((values) => {
> creationMutation.mutate(values);
<section className="builder-card"> })}
<header className="builder-card-header"> >
<div> <div className="builder-content">
<h2>1 Protocol</h2> <section className="builder-card">
<p>One tool maps to exactly one upstream method.</p> <header className="builder-card-header">
</div> <div>
</header> <h2>1 Protocol</h2>
<div className="builder-card-body"> <p>One tool maps to exactly one upstream method.</p>
<div className="proto-grid"> </div>
{protocolCards.map((card) => { </header>
const isSelected = activeProtocol === card.protocol; <div className="builder-card-body">
const selectedClassName = isSelected <div className="proto-grid">
? `proto-card selected-${card.protocol === "graphql" ? "gql" : card.protocol}` {protocolCards.map((card) => {
: "proto-card"; const isSelected = activeProtocol === card.protocol;
const selectedClassName = isSelected
? `proto-card selected-${card.protocol === "graphql" ? "gql" : card.protocol}`
: "proto-card";
return ( return (
<button <button
className={selectedClassName} className={selectedClassName}
key={card.protocol} key={card.protocol}
onClick={() => { onClick={() => {
form.setValue("protocol", card.protocol, { form.setValue("protocol", card.protocol, {
shouldDirty: true, shouldDirty: true,
shouldValidate: true, shouldValidate: true,
}); });
}} }}
type="button" type="button"
> >
<div className="proto-card-top"> <div className="proto-card-top">
<div className={`proto-icon pi-${card.protocol === "graphql" ? "gql" : card.protocol}`}> <div className={`proto-icon pi-${card.protocol === "graphql" ? "gql" : card.protocol}`}>
{card.protocol === "graphql" ? "GQL" : card.protocol.toUpperCase()} {card.protocol === "graphql" ? "GQL" : card.protocol.toUpperCase()}
</div>
<div className="proto-radio" />
</div> </div>
<div className="proto-radio" /> <div className="proto-card-copy">
</div> <h3>{card.title}</h3>
<div className="proto-card-copy"> <p>{card.description}</p>
<h3>{card.title}</h3> </div>
<p>{card.description}</p> </button>
</div> );
</button> })}
); </div>
})}
</div> </div>
</section>
<section className="builder-card">
<header className="builder-card-header">
<div>
<h2>2 Tool identity</h2>
<p>Name and description visible to the LLM at runtime.</p>
</div>
<span className="status-pill status-draft">draft</span>
</header>
<div className="builder-card-body">
<div className="form-grid">{commonFields.map(renderField)}</div>
</div>
</section>
<section className="builder-card">
<header className="builder-card-header">
<div>
<h2>3 Upstream target</h2>
<p>Protocol-specific target configuration exposed through one MCP tool.</p>
</div>
<span className={`protocol-pill protocol-${activeProtocol}`}>{activeProtocol}</span>
</header>
<div className="builder-card-body">
<div className="form-grid">
{protocolFields.map(renderField)}
{activeProtocol === "grpc" ? (
<label className="field-block field-block-wide">
<span>Descriptor set file</span>
<small className="field-hint">
Upload a compiled descriptor-set file to autofill the base64 payload.
</small>
<input
className="input"
type="file"
accept=".bin,.pb,.desc"
onChange={(event) => {
void handleDescriptorFileChange(event.target.files?.[0]);
}}
/>
</label>
) : null}
{activeProtocol === "rest"
? renderField({
name: "restStaticHeadersText",
label: "Static headers",
description:
"Always sent for this REST target before dynamic request headers are merged.",
rows: 8,
wide: true,
code: true,
})
: null}
</div>
<div className="info-banner">
<div className="info-dot" />
<p>
Headers and auth stay separate from path and payload mapping. Configure shared
transport headers in the execution section below.
</p>
</div>
</div>
</section>
<section className="builder-card">
<header className="builder-card-header">
<div>
<h2>4 Contract schemas</h2>
<p>Protocol-agnostic schemas for MCP input and output payloads.</p>
</div>
</header>
<div className="builder-card-body">
<div className="form-grid">{schemaFields.map(renderField)}</div>
</div>
</section>
<section className="builder-card">
<header className="builder-card-header">
<div>
<h2>5 Mapping and execution</h2>
<p>
Translate MCP input to request fields, then map upstream output back to tool
output.
</p>
</div>
</header>
<div className="builder-card-body">
<div className="section-divider">
<span className="section-divider-label">Input Request</span>
<div className="section-divider-line" />
</div>
<div className="form-grid">{mappingFields.slice(0, 1).map(renderField)}</div>
<div className="section-divider">
<span className="section-divider-label">Response Output</span>
<div className="section-divider-line" />
</div>
<div className="form-grid">{mappingFields.slice(1).map(renderField)}</div>
<div className="section-divider">
<span className="section-divider-label">Headers And Runtime</span>
<div className="section-divider-line" />
</div>
<div className="form-grid">{headerFields.map(renderField)}</div>
</div>
</section>
{creationMutation.error ? (
<div className="feedback-card feedback-error">
{creationMutation.error instanceof ApiError
? creationMutation.error.message
: "Failed to create operation"}
</div>
) : null}
</div> </div>
</section> </form>
<section className="builder-card"> {actionBarContainer
<header className="builder-card-header"> ? createPortal(
<div> <div className="sticky-action-bar">
<h2>2 Tool identity</h2> <div className="sticky-action-copy">
<p>Name and description visible to the LLM at runtime.</p> <span className="status-pill status-testing">ready to publish later</span>
</div> <p>{protocolSummary(activeProtocol)}</p>
<span className="status-pill status-draft">draft</span> </div>
</header> <button
<div className="builder-card-body"> className="button-primary button-primary-strong"
<div className="form-grid">{commonFields.map(renderField)}</div> type="submit"
</div> form="operation-create-form"
</section> disabled={creationMutation.isPending}
>
<section className="builder-card"> {creationMutation.isPending ? "Creating..." : "Create operation"}
<header className="builder-card-header"> </button>
<div> </div>,
<h2>3 Upstream target</h2> actionBarContainer,
<p>Protocol-specific target configuration exposed through one MCP tool.</p> )
</div> : null}
<span className={`protocol-pill protocol-${activeProtocol}`}>{activeProtocol}</span> </>
</header>
<div className="builder-card-body">
<div className="form-grid">
{protocolFields.map(renderField)}
{activeProtocol === "grpc" ? (
<label className="field-block field-block-wide">
<span>Descriptor set file</span>
<small className="field-hint">
Upload a compiled descriptor-set file to autofill the base64 payload.
</small>
<input
className="input"
type="file"
accept=".bin,.pb,.desc"
onChange={(event) => {
void handleDescriptorFileChange(event.target.files?.[0]);
}}
/>
</label>
) : null}
{activeProtocol === "rest" ? renderField({
name: "restStaticHeadersText",
label: "Static headers",
description:
"Always sent for this REST target before dynamic request headers are merged.",
rows: 8,
wide: true,
code: true,
}) : null}
</div>
<div className="info-banner">
<div className="info-dot" />
<p>
Headers and auth stay separate from path and payload mapping. Configure shared
transport headers in the execution section below.
</p>
</div>
</div>
</section>
<section className="builder-card">
<header className="builder-card-header">
<div>
<h2>4 Contract schemas</h2>
<p>Protocol-agnostic schemas for MCP input and output payloads.</p>
</div>
</header>
<div className="builder-card-body">
<div className="form-grid">{schemaFields.map(renderField)}</div>
</div>
</section>
<section className="builder-card">
<header className="builder-card-header">
<div>
<h2>5 Mapping and execution</h2>
<p>Translate MCP input to request fields, then map upstream output back to tool output.</p>
</div>
</header>
<div className="builder-card-body">
<div className="section-divider">
<span className="section-divider-label">Input Request</span>
<div className="section-divider-line" />
</div>
<div className="form-grid">{mappingFields.slice(0, 1).map(renderField)}</div>
<div className="section-divider">
<span className="section-divider-label">Response Output</span>
<div className="section-divider-line" />
</div>
<div className="form-grid">{mappingFields.slice(1).map(renderField)}</div>
<div className="section-divider">
<span className="section-divider-label">Headers And Runtime</span>
<div className="section-divider-line" />
</div>
<div className="form-grid">{headerFields.map(renderField)}</div>
</div>
</section>
{creationMutation.error ? (
<div className="feedback-card feedback-error">
{creationMutation.error instanceof ApiError
? creationMutation.error.message
: "Failed to create operation"}
</div>
) : null}
<div className="sticky-action-bar">
<div className="sticky-action-copy">
<span className="status-pill status-testing">ready to publish later</span>
<p>{protocolSummary(activeProtocol)}</p>
</div>
<button className="button-primary button-primary-strong" type="submit" disabled={creationMutation.isPending}>
{creationMutation.isPending ? "Creating..." : "Create operation"}
</button>
</div>
</form>
); );
} }
+1 -5
View File
@@ -1,9 +1,5 @@
import { OperationForm } from "../../features/operation-form/operation-form"; import { OperationForm } from "../../features/operation-form/operation-form";
export function OperationCreatePage() { export function OperationCreatePage() {
return ( return <OperationForm actionBarContainerId="app-layout-main" />;
<div className="page-stack">
<OperationForm />
</div>
);
} }
+10 -7
View File
@@ -51,7 +51,7 @@ export function AppShell({ children }: AppShellProps) {
</svg> </svg>
</div> </div>
<div className="logo-text"> <div className="logo-text">
<strong>MCPaaS</strong> <strong>Crank</strong>
<span>Tool Console</span> <span>Tool Console</span>
</div> </div>
</div> </div>
@@ -60,6 +60,7 @@ export function AppShell({ children }: AppShellProps) {
<p className="sidebar-label">Workspace</p> <p className="sidebar-label">Workspace</p>
<nav className="sidebar-nav"> <nav className="sidebar-nav">
<NavLink <NavLink
end
to="/operations" to="/operations"
className={({ isActive }) => className={({ isActive }) =>
isActive ? "nav-link nav-link-active" : "nav-link" isActive ? "nav-link nav-link-active" : "nav-link"
@@ -106,7 +107,7 @@ export function AppShell({ children }: AppShellProps) {
</div> </div>
</aside> </aside>
<div className="layout-main"> <div className="layout-main" id="app-layout-main">
<header className="topbar"> <header className="topbar">
<div className="breadcrumb"> <div className="breadcrumb">
<span>{meta.section}</span> <span>{meta.section}</span>
@@ -120,12 +121,14 @@ export function AppShell({ children }: AppShellProps) {
</div> </div>
</header> </header>
<main className="main-content"> <main className="main-content" id="app-main-content">
<div className="page-header"> <div className="main-content-inner">
<h1>{meta.heading}</h1> <div className="page-header">
<p>{meta.subtitle}</p> <h1>{meta.heading}</h1>
<p>{meta.subtitle}</p>
</div>
{children}
</div> </div>
{children}
</main> </main>
</div> </div>
</div> </div>
+64 -5
View File
@@ -316,12 +316,22 @@ button {
} }
.main-content { .main-content {
padding: 28px; width: 100%;
max-width: 1480px;
margin: 0 auto;
flex: 1; flex: 1;
display: flex;
flex-direction: column;
}
.main-content-inner {
flex: 1;
padding: 28px;
} }
.page-header { .page-header {
margin-bottom: 24px; max-width: 1240px;
margin: 0 auto 24px;
} }
.page-header h1 { .page-header h1 {
@@ -400,6 +410,18 @@ button {
gap: 16px; gap: 16px;
} }
.builder-stack {
width: 100%;
}
.builder-content {
width: 100%;
max-width: 1240px;
margin: 0 auto;
display: grid;
gap: 16px;
}
.page-section, .page-section,
.builder-card, .builder-card,
.operation-card, .operation-card,
@@ -425,9 +447,16 @@ button {
align-items: flex-start; align-items: flex-start;
justify-content: space-between; justify-content: space-between;
gap: 12px; gap: 12px;
}
.section-header {
margin-bottom: 16px; margin-bottom: 16px;
} }
.builder-card-header {
padding: 20px 24px 0;
}
.section-header h2, .section-header h2,
.builder-card-header h2 { .builder-card-header h2 {
margin: 0 0 2px; margin: 0 0 2px;
@@ -452,6 +481,12 @@ button {
.builder-card-body { .builder-card-body {
display: grid; display: grid;
gap: 16px; gap: 16px;
padding: 0 24px 24px;
}
.builder-content .form-grid {
grid-template-columns: repeat(2, minmax(280px, 420px));
justify-content: space-between;
} }
.proto-grid { .proto-grid {
@@ -843,8 +878,8 @@ button {
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 16px; gap: 16px;
padding: 12px 20px; padding: 12px 28px;
border-radius: var(--radius-xl); border-radius: 0;
background: rgba(255, 255, 255, 0.9); background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(16px); backdrop-filter: blur(16px);
border: 1px solid var(--border); border: 1px solid var(--border);
@@ -920,6 +955,7 @@ button {
} }
.field-inline { .field-inline {
width: 100%;
max-width: 420px; max-width: 420px;
} }
@@ -1093,9 +1129,17 @@ pre {
padding: 0 20px; padding: 0 20px;
} }
.main-content { .main-content-inner {
padding: 20px; padding: 20px;
} }
.sticky-action-bar {
padding-inline: 20px;
}
.builder-content {
max-width: none;
}
} }
@media (max-width: 820px) { @media (max-width: 820px) {
@@ -1116,4 +1160,19 @@ pre {
.hero-metrics { .hero-metrics {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.main-content-inner {
padding: 16px;
}
.sticky-action-bar {
padding-inline: 16px;
}
.builder-card-header,
.builder-card-body,
.page-section {
padding-left: 16px;
padding-right: 16px;
}
} }
@@ -1,13 +1,13 @@
[package] [package]
name = "mcpaas-adapter-graphql" name = "crank-adapter-graphql"
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
rust-version.workspace = true rust-version.workspace = true
version.workspace = true version.workspace = true
[dependencies] [dependencies]
mcpaas-core = { path = "../mcpaas-core" } crank-core = { path = "../crank-core" }
mcpaas-mapping = { path = "../mcpaas-mapping" } crank-mapping = { path = "../crank-mapping" }
reqwest.workspace = true reqwest.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
@@ -1,7 +1,7 @@
use std::{collections::BTreeMap, time::Duration}; use std::{collections::BTreeMap, time::Duration};
use mcpaas_core::GraphqlTarget; use crank_core::GraphqlTarget;
use mcpaas_mapping::JsonPath; use crank_mapping::JsonPath;
use reqwest::{ use reqwest::{
Client, Client,
header::{CONTENT_TYPE, HeaderMap, HeaderName, HeaderValue}, header::{CONTENT_TYPE, HeaderMap, HeaderName, HeaderValue},
@@ -161,7 +161,7 @@ mod tests {
use std::collections::BTreeMap; use std::collections::BTreeMap;
use axum::{Json, Router, routing::post}; use axum::{Json, Router, routing::post};
use mcpaas_core::{GraphqlOperationType, GraphqlTarget}; use crank_core::{GraphqlOperationType, GraphqlTarget};
use serde_json::{Value, json}; use serde_json::{Value, json};
use tokio::net::TcpListener; use tokio::net::TcpListener;
@@ -1,5 +1,5 @@
[package] [package]
name = "mcpaas-adapter-grpc" name = "crank-adapter-grpc"
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
rust-version.workspace = true rust-version.workspace = true
@@ -10,8 +10,8 @@ test-support = []
[dependencies] [dependencies]
base64.workspace = true base64.workspace = true
mcpaas-core = { path = "../mcpaas-core" } crank-core = { path = "../crank-core" }
mcpaas-proto = { path = "../mcpaas-proto" } crank-proto = { path = "../crank-proto" }
prost.workspace = true prost.workspace = true
prost-reflect.workspace = true prost-reflect.workspace = true
serde.workspace = true serde.workspace = true
@@ -1,7 +1,7 @@
use std::{collections::BTreeMap, str::FromStr, time::Duration}; use std::{collections::BTreeMap, str::FromStr, time::Duration};
use base64::{Engine as _, engine::general_purpose::STANDARD}; use base64::{Engine as _, engine::general_purpose::STANDARD};
use mcpaas_core::GrpcTarget; use crank_core::GrpcTarget;
use prost::Message; use prost::Message;
use prost_reflect::{DescriptorPool, MethodDescriptor, prost_types::FileDescriptorSet}; use prost_reflect::{DescriptorPool, MethodDescriptor, prost_types::FileDescriptorSet};
use tonic::{ use tonic::{
@@ -138,7 +138,7 @@ fn normalize_headers(metadata: &tonic::metadata::MetadataMap) -> BTreeMap<String
mod tests { mod tests {
use std::collections::BTreeMap; use std::collections::BTreeMap;
use mcpaas_core::{DescriptorId, GrpcTarget}; use crank_core::{DescriptorId, GrpcTarget};
use serde_json::json; use serde_json::json;
use crate::{GrpcAdapter, GrpcRequest, test_support}; use crate::{GrpcAdapter, GrpcRequest, test_support};
@@ -1,12 +1,12 @@
[package] [package]
name = "mcpaas-adapter-rest" name = "crank-adapter-rest"
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
rust-version.workspace = true rust-version.workspace = true
version.workspace = true version.workspace = true
[dependencies] [dependencies]
mcpaas-core = { path = "../mcpaas-core" } crank-core = { path = "../crank-core" }
reqwest.workspace = true reqwest.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
@@ -1,6 +1,6 @@
use std::{collections::BTreeMap, time::Duration}; use std::{collections::BTreeMap, time::Duration};
use mcpaas_core::{HttpMethod, RestTarget}; use crank_core::{HttpMethod, RestTarget};
use reqwest::{ use reqwest::{
Client, Client,
header::{HeaderMap, HeaderName, HeaderValue}, header::{HeaderMap, HeaderName, HeaderValue},
@@ -174,7 +174,7 @@ mod tests {
http::HeaderMap, http::HeaderMap,
routing::{get, post}, routing::{get, post},
}; };
use mcpaas_core::{HttpMethod, RestTarget}; use crank_core::{HttpMethod, RestTarget};
use serde_json::{Value, json}; use serde_json::{Value, json};
use tokio::net::TcpListener; use tokio::net::TcpListener;
@@ -1,5 +1,5 @@
[package] [package]
name = "mcpaas-core" name = "crank-core"
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
rust-version.workspace = true rust-version.workspace = true
@@ -321,7 +321,7 @@ mod tests {
base_url: "https://api.example.com".to_owned(), base_url: "https://api.example.com".to_owned(),
method: HttpMethod::Post, method: HttpMethod::Post,
path_template: "/v1/leads".to_owned(), path_template: "/v1/leads".to_owned(),
static_headers: BTreeMap::from([("X-App-Source".to_owned(), "mcpaas".to_owned())]), static_headers: BTreeMap::from([("X-App-Source".to_owned(), "crank".to_owned())]),
}), }),
input_schema: json!({"type":"object","fields":{"email":{"type":"string","required":true}}}), input_schema: json!({"type":"object","fields":{"email":{"type":"string","required":true}}}),
output_schema: json!({"type":"object","fields":{"id":{"type":"string","required":true}}}), output_schema: json!({"type":"object","fields":{"id":{"type":"string","required":true}}}),
@@ -1,12 +1,12 @@
[package] [package]
name = "mcpaas-schema" name = "crank-mapping"
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
rust-version.workspace = true rust-version.workspace = true
version.workspace = true version.workspace = true
[dependencies] [dependencies]
mcpaas-core = { path = "../mcpaas-core" } crank-core = { path = "../crank-core" }
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
thiserror.workspace = true thiserror.workspace = true
@@ -1,13 +1,13 @@
[package] [package]
name = "mcpaas-proto" name = "crank-proto"
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
rust-version.workspace = true rust-version.workspace = true
version.workspace = true version.workspace = true
[dependencies] [dependencies]
mcpaas-core = { path = "../mcpaas-core" } crank-core = { path = "../crank-core" }
mcpaas-schema = { path = "../mcpaas-schema" } crank-schema = { path = "../crank-schema" }
prost.workspace = true prost.workspace = true
prost-reflect.workspace = true prost-reflect.workspace = true
prost-types.workspace = true prost-types.workspace = true
@@ -1,6 +1,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use mcpaas_schema::{Schema, SchemaKind}; use crank_schema::{Schema, SchemaKind};
use crate::{ use crate::{
errors::ProtoError, errors::ProtoError,
@@ -161,7 +161,7 @@ fn scalar_schema(kind: &ProtoScalarKind) -> Schema {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use mcpaas_schema::SchemaKind; use crank_schema::SchemaKind;
use crate::{ use crate::{
ProtoEnum, ProtoField, ProtoFieldCardinality, ProtoFieldType, ProtoMapField, ProtoMessage, ProtoEnum, ProtoField, ProtoFieldCardinality, ProtoFieldType, ProtoMapField, ProtoMessage,
@@ -1,14 +1,14 @@
[package] [package]
name = "mcpaas-registry" name = "crank-registry"
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
rust-version.workspace = true rust-version.workspace = true
version.workspace = true version.workspace = true
[dependencies] [dependencies]
mcpaas-core = { path = "../mcpaas-core" } crank-core = { path = "../crank-core" }
mcpaas-mapping = { path = "../mcpaas-mapping" } crank-mapping = { path = "../crank-mapping" }
mcpaas-schema = { path = "../mcpaas-schema" } crank-schema = { path = "../crank-schema" }
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
sqlx.workspace = true sqlx.workspace = true
@@ -1,9 +1,9 @@
use mcpaas_core::{ use crank_core::{
AuthProfile, DescriptorId, ExportMode, Operation, OperationId, OperationStatus, Protocol, AuthProfile, DescriptorId, ExportMode, Operation, OperationId, OperationStatus, Protocol,
SampleId, SampleId,
}; };
use mcpaas_mapping::MappingSet; use crank_mapping::MappingSet;
use mcpaas_schema::Schema; use crank_schema::Schema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
@@ -1,4 +1,4 @@
use mcpaas_core::{AuthProfile, OperationId, OperationStatus}; use crank_core::{AuthProfile, OperationId, OperationStatus};
use serde::{Serialize, de::DeserializeOwned}; use serde::{Serialize, de::DeserializeOwned};
use serde_json::Value; use serde_json::Value;
use sqlx::{ use sqlx::{
@@ -481,7 +481,7 @@ impl PostgresRegistry {
pub async fn get_auth_profile( pub async fn get_auth_profile(
&self, &self,
auth_profile_id: &mcpaas_core::AuthProfileId, auth_profile_id: &crank_core::AuthProfileId,
) -> Result<Option<AuthProfile>, RegistryError> { ) -> Result<Option<AuthProfile>, RegistryError> {
let row = sqlx::query( let row = sqlx::query(
"select "select
@@ -917,7 +917,7 @@ fn map_operation_version_record(row: &PgRow) -> Result<OperationVersionRecord, R
fn map_auth_profile(row: &PgRow) -> Result<AuthProfile, RegistryError> { fn map_auth_profile(row: &PgRow) -> Result<AuthProfile, RegistryError> {
Ok(AuthProfile { Ok(AuthProfile {
id: mcpaas_core::AuthProfileId::new(row.try_get::<String, _>("id")?), id: crank_core::AuthProfileId::new(row.try_get::<String, _>("id")?),
name: row.try_get("name")?, name: row.try_get("name")?,
kind: deserialize_enum_text(&row.try_get::<String, _>("kind")?, "kind")?, kind: deserialize_enum_text(&row.try_get::<String, _>("kind")?, "kind")?,
config: deserialize_json_value(row.try_get::<Json<Value>, _>("config_json")?.0)?, config: deserialize_json_value(row.try_get::<Json<Value>, _>("config_json")?.0)?,
@@ -928,7 +928,7 @@ fn map_auth_profile(row: &PgRow) -> Result<AuthProfile, RegistryError> {
fn map_sample_metadata(row: &PgRow) -> Result<OperationSampleMetadata, RegistryError> { fn map_sample_metadata(row: &PgRow) -> Result<OperationSampleMetadata, RegistryError> {
Ok(OperationSampleMetadata { Ok(OperationSampleMetadata {
id: mcpaas_core::SampleId::new(row.try_get::<String, _>("id")?), id: crank_core::SampleId::new(row.try_get::<String, _>("id")?),
operation_id: OperationId::new(row.try_get::<String, _>("operation_id")?), operation_id: OperationId::new(row.try_get::<String, _>("operation_id")?),
version: from_db_version(row.try_get("version")?, "version")?, version: from_db_version(row.try_get("version")?, "version")?,
sample_kind: deserialize_enum_text( sample_kind: deserialize_enum_text(
@@ -944,7 +944,7 @@ fn map_sample_metadata(row: &PgRow) -> Result<OperationSampleMetadata, RegistryE
fn map_descriptor_metadata(row: &PgRow) -> Result<DescriptorMetadata, RegistryError> { fn map_descriptor_metadata(row: &PgRow) -> Result<DescriptorMetadata, RegistryError> {
Ok(DescriptorMetadata { Ok(DescriptorMetadata {
id: mcpaas_core::DescriptorId::new(row.try_get::<String, _>("id")?), id: crank_core::DescriptorId::new(row.try_get::<String, _>("id")?),
operation_id: row operation_id: row
.try_get::<Option<String>, _>("operation_id")? .try_get::<Option<String>, _>("operation_id")?
.map(OperationId::new), .map(OperationId::new),
@@ -970,7 +970,7 @@ fn map_yaml_import_job(row: &PgRow) -> Result<YamlImportJob, RegistryError> {
id: YamlImportJobId::new(row.try_get::<String, _>("id")?), id: YamlImportJobId::new(row.try_get::<String, _>("id")?),
source_sample_id: row source_sample_id: row
.try_get::<Option<String>, _>("source_sample_id")? .try_get::<Option<String>, _>("source_sample_id")?
.map(mcpaas_core::SampleId::new), .map(crank_core::SampleId::new),
status: deserialize_enum_text(&row.try_get::<String, _>("status")?, "status")?, status: deserialize_enum_text(&row.try_get::<String, _>("status")?, "status")?,
format_version: row.try_get("format_version")?, format_version: row.try_get("format_version")?,
mode: deserialize_enum_text(&row.try_get::<String, _>("mode")?, "mode")?, mode: deserialize_enum_text(&row.try_get::<String, _>("mode")?, "mode")?,
@@ -1038,14 +1038,14 @@ mod tests {
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };
use mcpaas_core::{ use crank_core::{
ApiKeyHeaderAuthConfig, AuthConfig, AuthKind, AuthProfile, ConfigExport, ExecutionConfig, ApiKeyHeaderAuthConfig, AuthConfig, AuthKind, AuthProfile, ConfigExport, ExecutionConfig,
ExportMode, GeneratedDraft, GeneratedDraftStatus, HttpMethod, OperationId, OperationStatus, ExportMode, GeneratedDraft, GeneratedDraftStatus, HttpMethod, OperationId, OperationStatus,
Protocol, RestTarget, RetryPolicy, Samples, SecretRef, Target, ToolDescription, Protocol, RestTarget, RetryPolicy, Samples, SecretRef, Target, ToolDescription,
ToolExample, ToolExample,
}; };
use mcpaas_mapping::{MappingRule, MappingSet}; use crank_mapping::{MappingRule, MappingSet};
use mcpaas_schema::{Schema, SchemaKind}; use crank_schema::{Schema, SchemaKind};
use serde_json::json; use serde_json::json;
use sqlx::{Executor, PgPool, postgres::PgPoolOptions}; use sqlx::{Executor, PgPool, postgres::PgPoolOptions};
@@ -1159,12 +1159,12 @@ mod tests {
registry.create_operation(&operation, None).await.unwrap(); registry.create_operation(&operation, None).await.unwrap();
let auth_profile = AuthProfile { let auth_profile = AuthProfile {
id: "auth_rmcp".into(), id: "auth_crank".into(),
name: "RMCP API key".to_owned(), name: "Crank API key".to_owned(),
kind: AuthKind::ApiKeyHeader, kind: AuthKind::ApiKeyHeader,
config: AuthConfig::ApiKeyHeader(ApiKeyHeaderAuthConfig { config: AuthConfig::ApiKeyHeader(ApiKeyHeaderAuthConfig {
header_name: "X-Api-Key".to_owned(), header_name: "X-Api-Key".to_owned(),
secret_ref: SecretRef::new("vault://rmcp/api-key"), secret_ref: SecretRef::new("vault://crank/api-key"),
}), }),
created_at: "2026-03-25T12:00:00Z".to_owned(), created_at: "2026-03-25T12:00:00Z".to_owned(),
updated_at: "2026-03-25T12:00:00Z".to_owned(), updated_at: "2026-03-25T12:00:00Z".to_owned(),
@@ -1415,7 +1415,7 @@ mod tests {
execution_config: ExecutionConfig { execution_config: ExecutionConfig {
timeout_ms: 10_000, timeout_ms: 10_000,
retry_policy: Some(RetryPolicy { max_attempts: 3 }), retry_policy: Some(RetryPolicy { max_attempts: 3 }),
auth_profile_ref: Some("auth_rmcp".into()), auth_profile_ref: Some("auth_crank".into()),
headers: BTreeMap::from([("X-Request-Id".to_owned(), "static".to_owned())]), headers: BTreeMap::from([("X-Request-Id".to_owned(), "static".to_owned())]),
protocol_options: None, protocol_options: None,
}, },
+22
View File
@@ -0,0 +1,22 @@
[package]
name = "crank-runtime"
edition.workspace = true
license.workspace = true
rust-version.workspace = true
version.workspace = true
[dependencies]
crank-adapter-graphql = { path = "../crank-adapter-graphql" }
crank-adapter-grpc = { path = "../crank-adapter-grpc" }
crank-adapter-rest = { path = "../crank-adapter-rest" }
crank-core = { path = "../crank-core" }
crank-mapping = { path = "../crank-mapping" }
crank-schema = { path = "../crank-schema" }
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
[dev-dependencies]
axum.workspace = true
crank-adapter-grpc = { path = "../crank-adapter-grpc", features = ["test-support"] }
tokio.workspace = true
@@ -1,9 +1,9 @@
use mcpaas_adapter_graphql::GraphqlAdapterError; use crank_adapter_graphql::GraphqlAdapterError;
use mcpaas_adapter_grpc::GrpcAdapterError; use crank_adapter_grpc::GrpcAdapterError;
use mcpaas_adapter_rest::RestAdapterError; use crank_adapter_rest::RestAdapterError;
use mcpaas_core::Protocol; use crank_core::Protocol;
use mcpaas_mapping::MappingError; use crank_mapping::MappingError;
use mcpaas_schema::SchemaError; use crank_schema::SchemaError;
use thiserror::Error; use thiserror::Error;
#[derive(Debug, Error)] #[derive(Debug, Error)]
@@ -1,9 +1,9 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use mcpaas_adapter_graphql::{GraphqlAdapter, GraphqlRequest}; use crank_adapter_graphql::{GraphqlAdapter, GraphqlRequest};
use mcpaas_adapter_grpc::{GrpcAdapter, GrpcRequest}; use crank_adapter_grpc::{GrpcAdapter, GrpcRequest};
use mcpaas_adapter_rest::{RestAdapter, RestRequest}; use crank_adapter_rest::{RestAdapter, RestRequest};
use mcpaas_core::Target; use crank_core::Target;
use serde_json::{Map, Value, json}; use serde_json::{Map, Value, json};
use crate::{AdapterResponse, PreparedRequest, RuntimeError, RuntimeOperation}; use crate::{AdapterResponse, PreparedRequest, RuntimeError, RuntimeOperation};
@@ -217,14 +217,14 @@ mod tests {
use std::collections::BTreeMap; use std::collections::BTreeMap;
use axum::{Json, Router, routing::post}; use axum::{Json, Router, routing::post};
use mcpaas_adapter_grpc::test_support as grpc_test_support; use crank_adapter_grpc::test_support as grpc_test_support;
use mcpaas_core::{ use crank_core::{
DescriptorId, ExecutionConfig, GeneratedDraft, GeneratedDraftStatus, GraphqlOperationType, DescriptorId, ExecutionConfig, GeneratedDraft, GeneratedDraftStatus, GraphqlOperationType,
GraphqlTarget, GrpcTarget, HttpMethod, Operation, OperationId, OperationStatus, Protocol, GraphqlTarget, GrpcTarget, HttpMethod, Operation, OperationId, OperationStatus, Protocol,
RestTarget, Samples, Target, ToolDescription, ToolExample, RestTarget, Samples, Target, ToolDescription, ToolExample,
}; };
use mcpaas_mapping::{MappingRule, MappingSet}; use crank_mapping::{MappingRule, MappingSet};
use mcpaas_schema::{Schema, SchemaKind}; use crank_schema::{Schema, SchemaKind};
use serde_json::{Value, json}; use serde_json::{Value, json};
use tokio::net::TcpListener; use tokio::net::TcpListener;
@@ -1,8 +1,8 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use mcpaas_core::{ExecutionConfig, Operation, OperationId, Protocol, Target, ToolDescription}; use crank_core::{ExecutionConfig, Operation, OperationId, Protocol, Target, ToolDescription};
use mcpaas_mapping::MappingSet; use crank_mapping::MappingSet;
use mcpaas_schema::Schema; use crank_schema::Schema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
@@ -1,12 +1,12 @@
[package] [package]
name = "mcpaas-mapping" name = "crank-schema"
edition.workspace = true edition.workspace = true
license.workspace = true license.workspace = true
rust-version.workspace = true rust-version.workspace = true
version.workspace = true version.workspace = true
[dependencies] [dependencies]
mcpaas-core = { path = "../mcpaas-core" } crank-core = { path = "../crank-core" }
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
thiserror.workspace = true thiserror.workspace = true
-22
View File
@@ -1,22 +0,0 @@
[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
[dev-dependencies]
axum.workspace = true
mcpaas-adapter-grpc = { path = "../mcpaas-adapter-grpc", features = ["test-support"] }
tokio.workspace = true
+19 -19
View File
@@ -3,15 +3,15 @@ services:
image: postgres:16-alpine image: postgres:16-alpine
restart: unless-stopped restart: unless-stopped
environment: environment:
POSTGRES_DB: ${POSTGRES_DB:-rmcp} POSTGRES_DB: ${POSTGRES_DB:-crank}
POSTGRES_USER: ${POSTGRES_USER:-rmcp} POSTGRES_USER: ${POSTGRES_USER:-crank}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-rmcp} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-crank}
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - postgres_data:/var/lib/postgresql/data
ports: ports:
- "127.0.0.1:5432:5432" - "127.0.0.1:5432:5432"
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-rmcp} -d ${POSTGRES_DB:-rmcp}"] test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-crank} -d ${POSTGRES_DB:-crank}"]
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5
@@ -22,18 +22,18 @@ services:
dockerfile: apps/admin-api/Dockerfile dockerfile: apps/admin-api/Dockerfile
restart: unless-stopped restart: unless-stopped
environment: environment:
MCPAAS_DATABASE_URL: ${MCPAAS_DATABASE_URL:-postgres://rmcp:rmcp@postgres:5432/rmcp} CRANK_DATABASE_URL: ${CRANK_DATABASE_URL:-postgres://crank:crank@postgres:5432/crank}
MCPAAS_STORAGE_ROOT: ${MCPAAS_STORAGE_ROOT:-/var/lib/rmcp/storage} CRANK_STORAGE_ROOT: ${CRANK_STORAGE_ROOT:-/var/lib/crank/storage}
MCPAAS_ADMIN_BIND: ${MCPAAS_ADMIN_BIND:-0.0.0.0:3001} CRANK_ADMIN_BIND: ${CRANK_ADMIN_BIND:-0.0.0.0:3001}
MCPAAS_LOG_LEVEL: ${MCPAAS_LOG_LEVEL:-info} CRANK_LOG_LEVEL: ${CRANK_LOG_LEVEL:-info}
MCPAAS_SECRET_PROVIDER: ${MCPAAS_SECRET_PROVIDER:-env} CRANK_SECRET_PROVIDER: ${CRANK_SECRET_PROVIDER:-env}
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
volumes: volumes:
- artifact_storage:${MCPAAS_STORAGE_ROOT:-/var/lib/rmcp/storage} - artifact_storage:${CRANK_STORAGE_ROOT:-/var/lib/crank/storage}
ports: ports:
- "${MCPAAS_PUBLISH_HOST:-127.0.0.1}:3001:3001" - "${CRANK_PUBLISH_HOST:-127.0.0.1}:3001:3001"
healthcheck: healthcheck:
test: ["CMD", "curl", "--fail", "http://127.0.0.1:3001/health"] test: ["CMD", "curl", "--fail", "http://127.0.0.1:3001/health"]
interval: 15s interval: 15s
@@ -46,18 +46,18 @@ services:
dockerfile: apps/mcp-server/Dockerfile dockerfile: apps/mcp-server/Dockerfile
restart: unless-stopped restart: unless-stopped
environment: environment:
MCPAAS_DATABASE_URL: ${MCPAAS_DATABASE_URL:-postgres://rmcp:rmcp@postgres:5432/rmcp} CRANK_DATABASE_URL: ${CRANK_DATABASE_URL:-postgres://crank:crank@postgres:5432/crank}
MCPAAS_STORAGE_ROOT: ${MCPAAS_STORAGE_ROOT:-/var/lib/rmcp/storage} CRANK_STORAGE_ROOT: ${CRANK_STORAGE_ROOT:-/var/lib/crank/storage}
MCPAAS_MCP_BIND: ${MCPAAS_MCP_BIND:-0.0.0.0:3002} CRANK_MCP_BIND: ${CRANK_MCP_BIND:-0.0.0.0:3002}
MCPAAS_LOG_LEVEL: ${MCPAAS_LOG_LEVEL:-info} CRANK_LOG_LEVEL: ${CRANK_LOG_LEVEL:-info}
MCPAAS_SECRET_PROVIDER: ${MCPAAS_SECRET_PROVIDER:-env} CRANK_SECRET_PROVIDER: ${CRANK_SECRET_PROVIDER:-env}
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
volumes: volumes:
- artifact_storage:${MCPAAS_STORAGE_ROOT:-/var/lib/rmcp/storage} - artifact_storage:${CRANK_STORAGE_ROOT:-/var/lib/crank/storage}
ports: ports:
- "${MCPAAS_PUBLISH_HOST:-127.0.0.1}:3002:3002" - "${CRANK_PUBLISH_HOST:-127.0.0.1}:3002:3002"
healthcheck: healthcheck:
test: ["CMD", "curl", "--fail", "http://127.0.0.1:3002/health"] test: ["CMD", "curl", "--fail", "http://127.0.0.1:3002/health"]
interval: 15s interval: 15s
@@ -70,7 +70,7 @@ services:
dockerfile: apps/ui/Dockerfile dockerfile: apps/ui/Dockerfile
restart: unless-stopped restart: unless-stopped
ports: ports:
- "${MCPAAS_PUBLISH_HOST:-127.0.0.1}:3000:3000" - "${CRANK_PUBLISH_HOST:-127.0.0.1}:3000:3000"
healthcheck: healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/"] test: ["CMD", "wget", "-qO-", "http://127.0.0.1:3000/"]
interval: 15s interval: 15s
+22 -22
View File
@@ -279,7 +279,7 @@ gRPC target:
## 9. Основные компоненты ## 9. Основные компоненты
### `mcpaas-core` ### `crank-core`
Ответственность: Ответственность:
@@ -288,7 +288,7 @@ gRPC target:
- статусы и базовые protocol-specific target types, - статусы и базовые protocol-specific target types,
- общие ошибки. - общие ошибки.
### `mcpaas-schema` ### `crank-schema`
Ответственность: Ответственность:
@@ -296,7 +296,7 @@ gRPC target:
- представление типов и полей для UI и runtime, - представление типов и полей для UI и runtime,
- валидация JSON относительно внутренней схемы. - валидация JSON относительно внутренней схемы.
### `mcpaas-mapping` ### `crank-mapping`
Ответственность: Ответственность:
@@ -305,7 +305,7 @@ gRPC target:
- применение input/output mapping, - применение input/output mapping,
- генерация чернового mapping по sample-данным и схемам. - генерация чернового mapping по sample-данным и схемам.
### `mcpaas-proto` ### `crank-proto`
Ответственность: Ответственность:
@@ -314,7 +314,7 @@ gRPC target:
- извлечение services, methods и message schemas, - извлечение services, methods и message schemas,
- преобразование protobuf metadata в нормализованные схемы. - преобразование protobuf metadata в нормализованные схемы.
### `mcpaas-registry` ### `crank-registry`
Ответственность: Ответственность:
@@ -323,7 +323,7 @@ gRPC target:
- выдача списка активных tools, - выдача списка активных tools,
- инвалидация кэша и сигналы на reload. - инвалидация кэша и сигналы на reload.
### `mcpaas-runtime` ### `crank-runtime`
Ответственность: Ответственность:
@@ -333,7 +333,7 @@ gRPC target:
- применение output mapping, - применение output mapping,
- единообразные runtime-ошибки. - единообразные runtime-ошибки.
### `mcpaas-adapter-rest` ### `crank-adapter-rest`
Ответственность: Ответственность:
@@ -341,7 +341,7 @@ gRPC target:
- отправка запроса через `reqwest`, - отправка запроса через `reqwest`,
- нормализация HTTP-ответа в JSON. - нормализация HTTP-ответа в JSON.
### `mcpaas-adapter-graphql` ### `crank-adapter-graphql`
Ответственность: Ответственность:
@@ -350,7 +350,7 @@ gRPC target:
- отправка запроса, - отправка запроса,
- извлечение `data` и ошибок из GraphQL-ответа. - извлечение `data` и ошибок из GraphQL-ответа.
### `mcpaas-adapter-grpc` ### `crank-adapter-grpc`
Ответственность: Ответственность:
@@ -358,7 +358,7 @@ gRPC target:
- вызов unary RPC метода, - вызов unary RPC метода,
- преобразование protobuf response обратно в нормализованный JSON. - преобразование protobuf response обратно в нормализованный JSON.
### `mcpaas-admin-api` ### `crank-admin-api`
Ответственность: Ответственность:
@@ -370,7 +370,7 @@ gRPC target:
- endpoints для тестового выполнения операций, - endpoints для тестового выполнения операций,
- discovery endpoints для gRPC metadata. - discovery endpoints для gRPC metadata.
### `mcpaas-mcp-server` ### `crank-mcp-server`
Ответственность: Ответственность:
@@ -379,7 +379,7 @@ gRPC target:
- делегирование выполнения в runtime, - делегирование выполнения в runtime,
- возврат нормализованного результата MCP-клиенту. - возврат нормализованного результата MCP-клиенту.
### `mcpaas-ui` ### `crank-ui`
Ответственность: Ответственность:
@@ -407,21 +407,21 @@ gRPC target:
Для реализации рекомендуется workspace-структура: Для реализации рекомендуется workspace-структура:
```text ```text
mcpaas/ crank/
apps/ apps/
admin-api/ admin-api/
mcp-server/ mcp-server/
ui/ ui/
crates/ crates/
mcpaas-core/ crank-core/
mcpaas-schema/ crank-schema/
mcpaas-mapping/ crank-mapping/
mcpaas-proto/ crank-proto/
mcpaas-registry/ crank-registry/
mcpaas-runtime/ crank-runtime/
mcpaas-adapter-rest/ crank-adapter-rest/
mcpaas-adapter-graphql/ crank-adapter-graphql/
mcpaas-adapter-grpc/ crank-adapter-grpc/
docs/ docs/
``` ```
+4 -4
View File
@@ -169,7 +169,7 @@
"method": "PATCH", "method": "PATCH",
"path_template": "/v1/users/{userId}", "path_template": "/v1/users/{userId}",
"static_headers": { "static_headers": {
"X-App-Source": "mcpaas" "X-App-Source": "crank"
} }
} }
``` ```
@@ -437,7 +437,7 @@
}, },
"auth_profile_ref": "auth_01hr7x8rj2d8nq8v0c4m4t1r9e", "auth_profile_ref": "auth_01hr7x8rj2d8nq8v0c4m4t1r9e",
"headers": { "headers": {
"X-Client": "mcpaas" "X-Client": "crank"
}, },
"protocol_options": { "protocol_options": {
"rest": null, "rest": null,
@@ -717,10 +717,10 @@ operation:
Эта модель задает основу для: Эта модель задает основу для:
- Rust structs в `mcpaas-core`, `mcpaas-schema`, `mcpaas-mapping`; - Rust structs в `crank-core`, `crank-schema`, `crank-mapping`;
- DTO для `admin-api`; - DTO для `admin-api`;
- таблиц `operations`, `operation_versions`, `operation_samples`, `operation_descriptors`; - таблиц `operations`, `operation_versions`, `operation_samples`, `operation_descriptors`;
- import/export layer для `YAML` конфигураций; - import/export layer для `YAML` конфигураций;
- runtime view, который будет передаваться в `mcpaas-runtime`. - runtime view, который будет передаваться в `crank-runtime`.
Следующим логическим шагом после этого документа должна стать схема БД, в которой эти сущности будут разложены по таблицам и связям. Следующим логическим шагом после этого документа должна стать схема БД, в которой эти сущности будут разложены по таблицам и связям.
+1 -1
View File
@@ -2,7 +2,7 @@
## 1. Назначение документа ## 1. Назначение документа
Этот документ фиксирует структуру хранения конфигураций, версий операций, загруженных артефактов и published runtime-view. Его цель - дать основу для SQL-миграций и для реализации `mcpaas-registry`. Этот документ фиксирует структуру хранения конфигураций, версий операций, загруженных артефактов и published runtime-view. Его цель - дать основу для SQL-миграций и для реализации `crank-registry`.
В документе предполагается реляционная модель, ориентированная на `PostgreSQL`. Канонической считается схема, совместимая с `PostgreSQL`. В документе предполагается реляционная модель, ориентированная на `PostgreSQL`. Канонической считается схема, совместимая с `PostgreSQL`.
+4 -4
View File
@@ -2,14 +2,14 @@
## 1. Цель документа ## 1. Цель документа
Этот документ фиксирует минимальный воспроизводимый сценарий запуска и демонстрации MCPaaS без знания внутренней структуры кода. Этот документ фиксирует минимальный воспроизводимый сценарий запуска и демонстрации Crank без знания внутренней структуры кода.
## 2. Предусловия ## 2. Предусловия
- доступен `PostgreSQL`; - доступен `PostgreSQL`;
- задан `MCPAAS_DATABASE_URL`; - задан `CRANK_DATABASE_URL`;
- задан `MCPAAS_STORAGE_ROOT`; - задан `CRANK_STORAGE_ROOT`;
- доступны `MCPAAS_ADMIN_BIND` и `MCPAAS_MCP_BIND`; - доступны `CRANK_ADMIN_BIND` и `CRANK_MCP_BIND`;
- для UI установлен `Node.js`; - для UI установлен `Node.js`;
- для Rust-части установлен toolchain из `rust-toolchain.toml`. - для Rust-части установлен toolchain из `rust-toolchain.toml`.
+1 -1
View File
@@ -169,7 +169,7 @@ services:
- не публиковать внутренние сервисы наружу без необходимости; - не публиковать внутренние сервисы наружу без необходимости;
- содержать healthchecks. - содержать healthchecks.
Практически это должно быть управляемо через env вроде `MCPAAS_PUBLISH_HOST`: Практически это должно быть управляемо через env вроде `CRANK_PUBLISH_HOST`:
- `127.0.0.1` для local reverse proxy на том же Linux host; - `127.0.0.1` для local reverse proxy на том же Linux host;
- конкретный внутренний IP или `0.0.0.0` для отдельного центрального reverse proxy в LAN. - конкретный внутренний IP или `0.0.0.0` для отдельного центрального reverse proxy в LAN.
+5 -5
View File
@@ -55,10 +55,10 @@
### 5.1. Что пишется через TDD обязательно ### 5.1. Что пишется через TDD обязательно
- `mcpaas-schema` - `crank-schema`
- `mcpaas-mapping` - `crank-mapping`
- `mcpaas-registry` - `crank-registry`
- `mcpaas-runtime` - `crank-runtime`
- YAML import/export - YAML import/export
- versioning logic - versioning logic
@@ -171,7 +171,7 @@
Удаленный репозиторий проекта: Удаленный репозиторий проекта:
```text ```text
git@github.com:bsodfather/rmcp.git git@github.com:bsodfather/crank.git
``` ```
### 8.2. Ветки ### 8.2. Ветки
+16 -16
View File
@@ -15,15 +15,15 @@
```mermaid ```mermaid
flowchart LR flowchart LR
UI[mcpaas-ui] UI[crank-ui]
ADMIN[admin-api] ADMIN[admin-api]
MCP[mcp-server] MCP[mcp-server]
REG[mcpaas-registry] REG[crank-registry]
RUN[mcpaas-runtime] RUN[crank-runtime]
CORE[mcpaas-core] CORE[crank-core]
SCHEMA[mcpaas-schema] SCHEMA[crank-schema]
MAP[mcpaas-mapping] MAP[crank-mapping]
PROTO[mcpaas-proto] PROTO[crank-proto]
REST[adapter-rest] REST[adapter-rest]
GQL[adapter-graphql] GQL[adapter-graphql]
GRPC[adapter-grpc] GRPC[adapter-grpc]
@@ -58,15 +58,15 @@ flowchart LR
```mermaid ```mermaid
flowchart TD flowchart TD
CORE[mcpaas-core] CORE[crank-core]
SCHEMA[mcpaas-schema] SCHEMA[crank-schema]
MAP[mcpaas-mapping] MAP[crank-mapping]
PROTO[mcpaas-proto] PROTO[crank-proto]
REG[mcpaas-registry] REG[crank-registry]
RUN[mcpaas-runtime] RUN[crank-runtime]
REST[mcpaas-adapter-rest] REST[crank-adapter-rest]
GQL[mcpaas-adapter-graphql] GQL[crank-adapter-graphql]
GRPC[mcpaas-adapter-grpc] GRPC[crank-adapter-grpc]
ADMIN[apps/admin-api] ADMIN[apps/admin-api]
MCP[apps/mcp-server] MCP[apps/mcp-server]
+17 -17
View File
@@ -25,15 +25,15 @@
- `apps/admin-api` - `apps/admin-api`
- `apps/mcp-server` - `apps/mcp-server`
- `apps/ui` - `apps/ui`
- `crates/mcpaas-core` - `crates/crank-core`
- `crates/mcpaas-schema` - `crates/crank-schema`
- `crates/mcpaas-mapping` - `crates/crank-mapping`
- `crates/mcpaas-proto` - `crates/crank-proto`
- `crates/mcpaas-registry` - `crates/crank-registry`
- `crates/mcpaas-runtime` - `crates/crank-runtime`
- `crates/mcpaas-adapter-rest` - `crates/crank-adapter-rest`
- `crates/mcpaas-adapter-graphql` - `crates/crank-adapter-graphql`
- `crates/mcpaas-adapter-grpc` - `crates/crank-adapter-grpc`
Результат: Результат:
@@ -87,7 +87,7 @@ DoD:
Цель: Цель:
- реализовать `mcpaas-schema`. - реализовать `crank-schema`.
Фичи: Фичи:
@@ -113,7 +113,7 @@ DoD:
Цель: Цель:
- реализовать `mcpaas-mapping`. - реализовать `crank-mapping`.
Фичи: Фичи:
@@ -139,7 +139,7 @@ DoD:
Цель: Цель:
- реализовать `mcpaas-registry` и миграции. - реализовать `crank-registry` и миграции.
Фичи: Фичи:
@@ -171,8 +171,8 @@ DoD:
Фичи: Фичи:
- `mcpaas-adapter-rest` - `crank-adapter-rest`
- `mcpaas-runtime` для REST - `crank-runtime` для REST
- REST test run - REST test run
- создание REST operation - создание REST operation
- publish REST operation - publish REST operation
@@ -282,7 +282,7 @@ DoD:
Фичи: Фичи:
- `mcpaas-adapter-graphql` - `crank-adapter-graphql`
- GraphQL target support; - GraphQL target support;
- variables mapping; - variables mapping;
- `response_path`; - `response_path`;
@@ -309,11 +309,11 @@ DoD:
Фичи: Фичи:
- `mcpaas-proto` - `crank-proto`
- descriptor loading; - descriptor loading;
- service/method discovery; - service/method discovery;
- protobuf normalization; - protobuf normalization;
- `mcpaas-adapter-grpc` - `crank-adapter-grpc`
- unary test runs; - unary test runs;
- publish и вызов через MCP. - publish и вызов через MCP.
+2 -2
View File
@@ -18,7 +18,7 @@
Причина: Причина:
- проект задуман как `MCPaaS`, а не как локальный single-process adapter; - проект задуман как `Crank`, а не как локальный single-process adapter;
- нужен удаленный доступ к опубликованным tools; - нужен удаленный доступ к опубликованным tools;
- published tools должны обновляться без пересборки и без локального обертывания каждого клиента. - published tools должны обновляться без пересборки и без локального обертывания каждого клиента.
@@ -127,7 +127,7 @@ MVP-контракт `mcp-server` строится вокруг JSON-RPC мет
1. MCP client вызывает tool. 1. MCP client вызывает tool.
2. `mcp-server` находит published runtime view. 2. `mcp-server` находит published runtime view.
3. Валидирует input относительно schema. 3. Валидирует input относительно schema.
4. Делегирует вызов в `mcpaas-runtime`. 4. Делегирует вызов в `crank-runtime`.
5. Возвращает результат. 5. Возвращает результат.
## 9. Обновление tools ## 9. Обновление tools
+56 -56
View File
@@ -2,7 +2,7 @@
## 1. Цель документа ## 1. Цель документа
Этот документ фиксирует детальную структуру проекта до начала активной разработки. Его задача - заранее ограничить ответственность каждого компонента, избежать разрастания `mcpaas-core`, не допустить появления "универсальных" структур на все случаи жизни и сохранить понятные границы между доменной логикой, runtime, адаптерами, API и UI. Этот документ фиксирует детальную структуру проекта до начала активной разработки. Его задача - заранее ограничить ответственность каждого компонента, избежать разрастания `crank-core`, не допустить появления "универсальных" структур на все случаи жизни и сохранить понятные границы между доменной логикой, runtime, адаптерами, API и UI.
Основной принцип: каждый crate отвечает за один слой системы. Внутри crate модули должны быть маленькими, тематическими и с минимальным количеством публичных сущностей. Основной принцип: каждый crate отвечает за один слой системы. Внутри crate модули должны быть маленькими, тематическими и с минимальным количеством публичных сущностей.
@@ -20,7 +20,7 @@
### 2.2. Что запрещено ### 2.2. Что запрещено
- помещать SQL, HTTP-клиенты или gRPC-клиенты в `mcpaas-core`; - помещать SQL, HTTP-клиенты или gRPC-клиенты в `crank-core`;
- хранить в `core` "общие утилиты", не относящиеся к доменной модели; - хранить в `core` "общие утилиты", не относящиеся к доменной модели;
- делать `runtime`, который напрямую читает БД; - делать `runtime`, который напрямую читает БД;
- писать mapping-логику внутри REST, GraphQL или gRPC адаптеров; - писать mapping-логику внутри REST, GraphQL или gRPC адаптеров;
@@ -41,28 +41,28 @@
Рекомендуемая структура: Рекомендуемая структура:
```text ```text
mcpaas/ crank/
apps/ apps/
admin-api/ admin-api/
mcp-server/ mcp-server/
ui/ ui/
crates/ crates/
mcpaas-core/ crank-core/
mcpaas-registry/ crank-registry/
mcpaas-runtime/ crank-runtime/
mcpaas-adapter-rest/ crank-adapter-rest/
mcpaas-adapter-graphql/ crank-adapter-graphql/
mcpaas-adapter-grpc/ crank-adapter-grpc/
mcpaas-mapping/ crank-mapping/
mcpaas-schema/ crank-schema/
mcpaas-proto/ crank-proto/
``` ```
Дополнительные crates `mcpaas-mapping`, `mcpaas-schema` и `mcpaas-proto` нужны затем, чтобы не перегружать `mcpaas-core`. Дополнительные crates `crank-mapping`, `crank-schema` и `crank-proto` нужны затем, чтобы не перегружать `crank-core`.
## 4. Детальная декомпозиция по crate ## 4. Детальная декомпозиция по crate
### 4.1. `mcpaas-core` ### 4.1. `crank-core`
Назначение: Назначение:
@@ -74,7 +74,7 @@ mcpaas/
Что должно лежать в crate: Что должно лежать в crate:
```text ```text
mcpaas-core/ crank-core/
src/ src/
lib.rs lib.rs
ids.rs ids.rs
@@ -119,9 +119,9 @@ mcpaas-core/
Причина: Причина:
`mcpaas-core` должен быть максимально стабильным и независимым. Если положить туда все подряд, он станет точкой связности всей системы. `crank-core` должен быть максимально стабильным и независимым. Если положить туда все подряд, он станет точкой связности всей системы.
### 4.2. `mcpaas-schema` ### 4.2. `crank-schema`
Назначение: Назначение:
@@ -133,7 +133,7 @@ mcpaas-core/
Структура: Структура:
```text ```text
mcpaas-schema/ crank-schema/
src/ src/
lib.rs lib.rs
schema/ schema/
@@ -173,7 +173,7 @@ mcpaas-schema/
Схемы будут использоваться почти везде, но это не повод тащить их в `core`. Иначе `core` станет тяжелым и начнет менять версию при каждом изменении схемной логики. Схемы будут использоваться почти везде, но это не повод тащить их в `core`. Иначе `core` станет тяжелым и начнет менять версию при каждом изменении схемной логики.
### 4.3. `mcpaas-mapping` ### 4.3. `crank-mapping`
Назначение: Назначение:
@@ -186,7 +186,7 @@ mcpaas-schema/
Структура: Структура:
```text ```text
mcpaas-mapping/ crank-mapping/
src/ src/
lib.rs lib.rs
model/ model/
@@ -229,7 +229,7 @@ mcpaas-mapping/
не помещать mapping-правила в строковые поля, которые потом интерпретируются каждым адаптером по-своему. Mapping должен быть единым движком. не помещать mapping-правила в строковые поля, которые потом интерпретируются каждым адаптером по-своему. Mapping должен быть единым движком.
### 4.4. `mcpaas-proto` ### 4.4. `crank-proto`
Назначение: Назначение:
@@ -240,7 +240,7 @@ mcpaas-mapping/
Структура: Структура:
```text ```text
mcpaas-proto/ crank-proto/
src/ src/
lib.rs lib.rs
descriptor/ descriptor/
@@ -272,14 +272,14 @@ mcpaas-proto/
- `descriptor/registry.rs` - индексирование описаний для поиска services/methods. - `descriptor/registry.rs` - индексирование описаний для поиска services/methods.
- `reflect/client.rs` - клиент server reflection, если будет добавлен. - `reflect/client.rs` - клиент server reflection, если будет добавлен.
- `model/*` - protobuf-ориентированная промежуточная модель. - `model/*` - protobuf-ориентированная промежуточная модель.
- `convert/to_schema.rs` - перевод protobuf message в `mcpaas-schema`. - `convert/to_schema.rs` - перевод protobuf message в `crank-schema`.
- `convert/to_json.rs` и `from_json.rs` - преобразование runtime payload. - `convert/to_json.rs` и `from_json.rs` - преобразование runtime payload.
Почему отдельный crate: Почему отдельный crate:
protobuf-логика объемная и быстро начнет загрязнять gRPC adapter, если не отделить ее сразу. protobuf-логика объемная и быстро начнет загрязнять gRPC adapter, если не отделить ее сразу.
### 4.5. `mcpaas-registry` ### 4.5. `crank-registry`
Назначение: Назначение:
@@ -290,7 +290,7 @@ protobuf-логика объемная и быстро начнет загряз
Структура: Структура:
```text ```text
mcpaas-registry/ crank-registry/
src/ src/
lib.rs lib.rs
model/ model/
@@ -332,7 +332,7 @@ mcpaas-registry/
`registry` не выполняет операции и не знает о `reqwest`/`tonic`. Он только хранит и отдает согласованные представления. `registry` не выполняет операции и не знает о `reqwest`/`tonic`. Он только хранит и отдает согласованные представления.
### 4.6. `mcpaas-runtime` ### 4.6. `crank-runtime`
Назначение: Назначение:
@@ -343,7 +343,7 @@ mcpaas-registry/
Структура: Структура:
```text ```text
mcpaas-runtime/ crank-runtime/
src/ src/
lib.rs lib.rs
executor/ executor/
@@ -380,7 +380,7 @@ mcpaas-runtime/
`runtime` не должен знать, где хранится операция. Он получает уже готовую `runtime_operation`. `runtime` не должен знать, где хранится операция. Он получает уже готовую `runtime_operation`.
### 4.7. `mcpaas-adapter-rest` ### 4.7. `crank-adapter-rest`
Назначение: Назначение:
@@ -389,7 +389,7 @@ mcpaas-runtime/
Структура: Структура:
```text ```text
mcpaas-adapter-rest/ crank-adapter-rest/
src/ src/
lib.rs lib.rs
client.rs client.rs
@@ -411,7 +411,7 @@ mcpaas-adapter-rest/
REST adapter не валидирует MCP input и не знает о registry. Он получает уже подготовленный request contract. REST adapter не валидирует MCP input и не знает о registry. Он получает уже подготовленный request contract.
### 4.8. `mcpaas-adapter-graphql` ### 4.8. `crank-adapter-graphql`
Назначение: Назначение:
@@ -420,7 +420,7 @@ REST adapter не валидирует MCP input и не знает о registry.
Структура: Структура:
```text ```text
mcpaas-adapter-graphql/ crank-adapter-graphql/
src/ src/
lib.rs lib.rs
client.rs client.rs
@@ -443,7 +443,7 @@ GraphQL adapter не занимается introspection по умолчанию
один GraphQL tool соответствует одному заранее определенному `query` или `mutation`. Адаптер не должен принимать от LLM произвольный GraphQL-документ, потому что в MCP-модели операция должна оставаться узкой, предсказуемой и валидируемой по фиксированной схеме. один GraphQL tool соответствует одному заранее определенному `query` или `mutation`. Адаптер не должен принимать от LLM произвольный GraphQL-документ, потому что в MCP-модели операция должна оставаться узкой, предсказуемой и валидируемой по фиксированной схеме.
### 4.9. `mcpaas-adapter-grpc` ### 4.9. `crank-adapter-grpc`
Назначение: Назначение:
@@ -452,7 +452,7 @@ GraphQL adapter не занимается introspection по умолчанию
Структура: Структура:
```text ```text
mcpaas-adapter-grpc/ crank-adapter-grpc/
src/ src/
lib.rs lib.rs
channel.rs channel.rs
@@ -470,7 +470,7 @@ mcpaas-adapter-grpc/
Правило: Правило:
gRPC adapter не должен сам парсить `.proto`. Этим занимается `mcpaas-proto`. Иначе в адаптере смешаются discovery и execution. gRPC adapter не должен сам парсить `.proto`. Этим занимается `crank-proto`. Иначе в адаптере смешаются discovery и execution.
Дополнительное ограничение: Дополнительное ограничение:
@@ -609,22 +609,22 @@ UI должен декомпозироваться по пользователь
Целевой граф зависимостей: Целевой граф зависимостей:
```text ```text
mcpaas-core crank-core
mcpaas-schema -> mcpaas-core crank-schema -> crank-core
mcpaas-mapping -> mcpaas-core crank-mapping -> crank-core
mcpaas-proto -> mcpaas-core, mcpaas-schema crank-proto -> crank-core, crank-schema
mcpaas-registry -> mcpaas-core, mcpaas-schema, mcpaas-mapping crank-registry -> crank-core, crank-schema, crank-mapping
mcpaas-adapter-rest -> mcpaas-core crank-adapter-rest -> crank-core
mcpaas-adapter-graphql -> mcpaas-core crank-adapter-graphql -> crank-core
mcpaas-adapter-grpc -> mcpaas-core, mcpaas-proto crank-adapter-grpc -> crank-core, crank-proto
mcpaas-runtime -> mcpaas-core, mcpaas-schema, mcpaas-mapping, adapters crank-runtime -> crank-core, crank-schema, crank-mapping, adapters
admin-api -> mcpaas-core, mcpaas-schema, mcpaas-mapping, mcpaas-proto, mcpaas-registry, mcpaas-runtime admin-api -> crank-core, crank-schema, crank-mapping, crank-proto, crank-registry, crank-runtime
mcp-server -> mcpaas-core, mcpaas-registry, mcpaas-runtime mcp-server -> crank-core, crank-registry, crank-runtime
``` ```
Критические ограничения: Критические ограничения:
- `mcpaas-core` ни от кого не зависит; - `crank-core` ни от кого не зависит;
- адаптеры не зависят от `registry`; - адаптеры не зависят от `registry`;
- `runtime` не зависит от `admin-api` и `mcp-server`; - `runtime` не зависит от `admin-api` и `mcp-server`;
- `registry` не зависит от адаптеров; - `registry` не зависит от адаптеров;
@@ -675,17 +675,17 @@ mcp-server -> mcpaas-core, mcpaas-registry, mcpaas-runtime
Рекомендуемый порядок разработки: Рекомендуемый порядок разработки:
1. `mcpaas-core` 1. `crank-core`
2. `mcpaas-schema` 2. `crank-schema`
3. `mcpaas-mapping` 3. `crank-mapping`
4. `mcpaas-registry` 4. `crank-registry`
5. `mcpaas-adapter-rest` 5. `crank-adapter-rest`
6. `mcpaas-runtime` 6. `crank-runtime`
7. `admin-api` 7. `admin-api`
8. `ui` 8. `ui`
9. `mcpaas-proto` 9. `crank-proto`
10. `mcpaas-adapter-grpc` 10. `crank-adapter-grpc`
11. `mcpaas-adapter-graphql` 11. `crank-adapter-graphql`
12. `mcp-server` 12. `mcp-server`
Причина такого порядка: Причина такого порядка:
@@ -699,7 +699,7 @@ mcp-server -> mcpaas-core, mcpaas-registry, mcpaas-runtime
Если придерживаться этой декомпозиции, то: Если придерживаться этой декомпозиции, то:
- `mcpaas-core` останется маленьким и стабильным; - `crank-core` останется маленьким и стабильным;
- schema и mapping не смешаются с transport-логикой; - schema и mapping не смешаются с transport-логикой;
- protobuf discovery не загрязнит gRPC runtime; - protobuf discovery не загрязнит gRPC runtime;
- `admin-api` и `mcp-server` останутся тонкими входными слоями; - `admin-api` и `mcp-server` останутся тонкими входными слоями;
+13 -13
View File
@@ -28,7 +28,7 @@
Рекомендуемая структура: Рекомендуемая структура:
```text ```text
var/mcpaas/ var/crank/
samples/ samples/
descriptors/ descriptors/
yaml-imports/ yaml-imports/
@@ -57,24 +57,24 @@ var/mcpaas/
Минимально ожидаются: Минимально ожидаются:
- `MCPAAS_DATABASE_URL` - `CRANK_DATABASE_URL`
- `MCPAAS_STORAGE_ROOT` - `CRANK_STORAGE_ROOT`
- `MCPAAS_ADMIN_BIND` - `CRANK_ADMIN_BIND`
- `MCPAAS_MCP_BIND` - `CRANK_MCP_BIND`
- `MCPAAS_MCP_REFRESH_MS` - `CRANK_MCP_REFRESH_MS`
- `MCPAAS_LOG_LEVEL` - `CRANK_LOG_LEVEL`
- `MCPAAS_SECRET_PROVIDER` - `CRANK_SECRET_PROVIDER`
- `MCPAAS_PUBLIC_BASE_URL` - `CRANK_PUBLIC_BASE_URL`
- `MCPAAS_MCP_PUBLIC_URL` - `CRANK_MCP_PUBLIC_URL`
Опционально: Опционально:
- `MCPAAS_ADMIN_TOKEN` - `CRANK_ADMIN_TOKEN`
- `MCPAAS_MASTER_KEY` - `CRANK_MASTER_KEY`
Стартовое значение для refresh published tools: Стартовое значение для refresh published tools:
- `MCPAAS_MCP_REFRESH_MS=5000` - `CRANK_MCP_REFRESH_MS=5000`
## 6. Логирование и трассировка ## 6. Логирование и трассировка

Some files were not shown because too many files have changed in this diff Show More