diff --git a/.env.example b/.env.example index ba7672f..2a75b00 100644 --- a/.env.example +++ b/.env.example @@ -1,13 +1,13 @@ -POSTGRES_DB=rmcp -POSTGRES_USER=rmcp +POSTGRES_DB=crank +POSTGRES_USER=crank POSTGRES_PASSWORD=change-me -MCPAAS_DATABASE_URL=postgres://rmcp:change-me@postgres:5432/rmcp -MCPAAS_STORAGE_ROOT=/var/lib/rmcp/storage -MCPAAS_PUBLISH_HOST=127.0.0.1 -MCPAAS_ADMIN_BIND=0.0.0.0:3001 -MCPAAS_MCP_BIND=0.0.0.0:3002 -MCPAAS_MCP_REFRESH_MS=5000 -MCPAAS_LOG_LEVEL=info -MCPAAS_SECRET_PROVIDER=env -MCPAAS_PUBLIC_BASE_URL=https://rmcp.example.com -MCPAAS_MCP_PUBLIC_URL=https://rmcp.example.com/mcp +CRANK_DATABASE_URL=postgres://crank:change-me@postgres:5432/crank +CRANK_STORAGE_ROOT=/var/lib/crank/storage +CRANK_PUBLISH_HOST=127.0.0.1 +CRANK_ADMIN_BIND=0.0.0.0:3001 +CRANK_MCP_BIND=0.0.0.0:3002 +CRANK_MCP_REFRESH_MS=5000 +CRANK_LOG_LEVEL=info +CRANK_SECRET_PROVIDER=env +CRANK_PUBLIC_BASE_URL=https://crank.example.com +CRANK_MCP_PUBLIC_URL=https://crank.example.com/mcp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99152d3..50ed643 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,18 +19,18 @@ jobs: postgres: image: postgres:16-alpine env: - POSTGRES_DB: rmcp_test + POSTGRES_DB: crank_test POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres ports: - 5432:5432 options: >- - --health-cmd "pg_isready -U postgres -d rmcp_test" + --health-cmd "pg_isready -U postgres -d crank_test" --health-interval 10s --health-timeout 5s --health-retries 5 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: - name: Checkout diff --git a/Cargo.lock b/Cargo.lock index 6d49f50..ff46154 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,13 +7,13 @@ name = "admin-api" version = "0.1.0" dependencies = [ "axum", - "mcpaas-adapter-grpc", - "mcpaas-core", - "mcpaas-mapping", - "mcpaas-proto", - "mcpaas-registry", - "mcpaas-runtime", - "mcpaas-schema", + "crank-adapter-grpc", + "crank-core", + "crank-mapping", + "crank-proto", + "crank-registry", + "crank-runtime", + "crank-schema", "reqwest", "serde", "serde_json", @@ -211,6 +211,129 @@ dependencies = [ "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]] name = "crc" version = "3.4.0" @@ -933,12 +1056,12 @@ name = "mcp-server" version = "0.1.0" dependencies = [ "axum", - "mcpaas-adapter-grpc", - "mcpaas-core", - "mcpaas-mapping", - "mcpaas-registry", - "mcpaas-runtime", - "mcpaas-schema", + "crank-adapter-grpc", + "crank-core", + "crank-mapping", + "crank-registry", + "crank-runtime", + "crank-schema", "reqwest", "serde", "serde_json", @@ -949,129 +1072,6 @@ dependencies = [ "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]] name = "md-5" version = "0.10.6" diff --git a/Cargo.toml b/Cargo.toml index 3959bfd..1fc8d14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,15 +2,15 @@ members = [ "apps/admin-api", "apps/mcp-server", - "crates/mcpaas-core", - "crates/mcpaas-schema", - "crates/mcpaas-mapping", - "crates/mcpaas-proto", - "crates/mcpaas-registry", - "crates/mcpaas-runtime", - "crates/mcpaas-adapter-rest", - "crates/mcpaas-adapter-graphql", - "crates/mcpaas-adapter-grpc", + "crates/crank-core", + "crates/crank-schema", + "crates/crank-mapping", + "crates/crank-proto", + "crates/crank-registry", + "crates/crank-runtime", + "crates/crank-adapter-rest", + "crates/crank-adapter-graphql", + "crates/crank-adapter-grpc", ] resolver = "3" diff --git a/Crank.png b/Crank.png new file mode 100644 index 0000000..aa1c5d1 Binary files /dev/null and b/Crank.png differ diff --git a/README.md b/README.md index 789c6b9..74e4dad 100644 --- a/README.md +++ b/README.md @@ -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 и подход к реализации. diff --git a/TASKS.md b/TASKS.md index f56d12d..3c46718 100644 --- a/TASKS.md +++ b/TASKS.md @@ -2,17 +2,17 @@ ## Current -### `feat/ui-concept-alignment` +### `feat/crank-rebrand` Status: completed DoD: -- shell и create flow визуально опираются на утвержденный concept -- sidebar, topbar и page header выглядят как единая продуктовая система -- create page больше не выглядит как длинная сырая форма, а как step-based contract builder -- кодовые JSON и mapping поля имеют отдельную визуальную подачу -- UI build и tests остаются зелеными после выравнивания под concept +- проект переименован в `Crank` в документации, UI и package metadata +- workspace и crate package names согласованы с новым брендом +- `README.md` использует `Crank.png` как главное изображение +- `origin` указывает на `git@github.com:bsodfather/crank.git` +- Rust workspace и UI build остаются зелеными ## Next diff --git a/apps/admin-api/Cargo.toml b/apps/admin-api/Cargo.toml index e14eb08..a6f0074 100644 --- a/apps/admin-api/Cargo.toml +++ b/apps/admin-api/Cargo.toml @@ -7,12 +7,12 @@ version.workspace = true [dependencies] axum.workspace = true -mcpaas-core = { path = "../../crates/mcpaas-core" } -mcpaas-mapping = { path = "../../crates/mcpaas-mapping" } -mcpaas-proto = { path = "../../crates/mcpaas-proto" } -mcpaas-registry = { path = "../../crates/mcpaas-registry" } -mcpaas-runtime = { path = "../../crates/mcpaas-runtime" } -mcpaas-schema = { path = "../../crates/mcpaas-schema" } +crank-core = { path = "../../crates/crank-core" } +crank-mapping = { path = "../../crates/crank-mapping" } +crank-proto = { path = "../../crates/crank-proto" } +crank-registry = { path = "../../crates/crank-registry" } +crank-runtime = { path = "../../crates/crank-runtime" } +crank-schema = { path = "../../crates/crank-schema" } serde.workspace = true serde_json.workspace = true serde_yaml.workspace = true @@ -24,6 +24,6 @@ tracing-subscriber.workspace = true uuid.workspace = true [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 sqlx.workspace = true diff --git a/apps/admin-api/Dockerfile b/apps/admin-api/Dockerfile index 43af89e..ea3c405 100644 --- a/apps/admin-api/Dockerfile +++ b/apps/admin-api/Dockerfile @@ -18,7 +18,7 @@ WORKDIR /app 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 diff --git a/apps/admin-api/src/app.rs b/apps/admin-api/src/app.rs index 83848d0..a6f8ce2 100644 --- a/apps/admin-api/src/app.rs +++ b/apps/admin-api/src/app.rs @@ -80,14 +80,14 @@ mod tests { }; use axum::{Json, Router, routing::post}; - use mcpaas_adapter_grpc::test_support as grpc_test_support; - use mcpaas_core::{ + use crank_adapter_grpc::test_support as grpc_test_support; + use crank_core::{ DescriptorId, ExecutionConfig, GraphqlOperationType, GraphqlTarget, GrpcTarget, HttpMethod, Protocol, RestTarget, Target, ToolDescription, }; - use mcpaas_mapping::{MappingRule, MappingSet}; - use mcpaas_registry::PostgresRegistry; - use mcpaas_schema::{Schema, SchemaKind}; + use crank_mapping::{MappingRule, MappingSet}; + use crank_registry::PostgresRegistry; + use crank_schema::{Schema, SchemaKind}; use serde_json::{Value, json}; use sqlx::{Executor, postgres::PgPoolOptions}; use tokio::net::TcpListener; @@ -621,7 +621,7 @@ mod tests { async fn test_registry() -> PostgresRegistry { 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() .max_connections(1) .connect(&database_url) @@ -648,7 +648,7 @@ mod tests { fn test_storage_root(name: &str) -> std::path::PathBuf { env::temp_dir().join(format!( - "rmcp_admin_api_{name}_{}_{}", + "crank_admin_api_{name}_{}_{}", std::process::id(), SystemTime::now() .duration_since(UNIX_EPOCH) diff --git a/apps/admin-api/src/error.rs b/apps/admin-api/src/error.rs index 6f8778d..fbbdaaa 100644 --- a/apps/admin-api/src/error.rs +++ b/apps/admin-api/src/error.rs @@ -3,10 +3,10 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; -use mcpaas_mapping::MappingError; -use mcpaas_registry::RegistryError; -use mcpaas_runtime::RuntimeError; -use mcpaas_schema::SchemaError; +use crank_mapping::MappingError; +use crank_registry::RegistryError; +use crank_runtime::RuntimeError; +use crank_schema::SchemaError; use serde_json::{Value, json}; use thiserror::Error; use tracing::{error, warn}; diff --git a/apps/admin-api/src/main.rs b/apps/admin-api/src/main.rs index 90e1a63..9d52a44 100644 --- a/apps/admin-api/src/main.rs +++ b/apps/admin-api/src/main.rs @@ -7,7 +7,7 @@ mod storage; use std::{env, net::SocketAddr, path::PathBuf}; -use mcpaas_registry::PostgresRegistry; +use crank_registry::PostgresRegistry; use tokio::net::TcpListener; use tracing::info; @@ -17,16 +17,15 @@ use crate::{app::build_app, service::AdminService, state::AppState}; async fn main() -> Result<(), Box> { tracing_subscriber::fmt() .with_env_filter( - env::var("MCPAAS_LOG_LEVEL") - .unwrap_or_else(|_| "admin_api=info,tower_http=info".into()), + env::var("CRANK_LOG_LEVEL").unwrap_or_else(|_| "admin_api=info,tower_http=info".into()), ) .init(); - let database_url = env::var("MCPAAS_DATABASE_URL")?; + let database_url = env::var("CRANK_DATABASE_URL")?; 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 registry = PostgresRegistry::connect(&database_url).await?; let state = AppState { diff --git a/apps/admin-api/src/routes/operations.rs b/apps/admin-api/src/routes/operations.rs index b101923..3e72808 100644 --- a/apps/admin-api/src/routes/operations.rs +++ b/apps/admin-api/src/routes/operations.rs @@ -96,7 +96,7 @@ pub async fn upload_input_json( .service .save_json_sample( &operation_id.as_str().into(), - mcpaas_registry::SampleKind::InputJson, + crank_registry::SampleKind::InputJson, &payload, ) .await?; @@ -117,7 +117,7 @@ pub async fn upload_output_json( .service .save_json_sample( &operation_id.as_str().into(), - mcpaas_registry::SampleKind::OutputJson, + crank_registry::SampleKind::OutputJson, &payload, ) .await?; diff --git a/apps/admin-api/src/service.rs b/apps/admin-api/src/service.rs index 5024c88..0d2d07a 100644 --- a/apps/admin-api/src/service.rs +++ b/apps/admin-api/src/service.rs @@ -1,18 +1,18 @@ use std::path::PathBuf; -use mcpaas_core::{ +use crank_core::{ AuthConfig, AuthKind, AuthProfile, AuthProfileId, ConfigExport, ExportMode, GeneratedDraft, GeneratedDraftStatus, OperationId, OperationStatus, Protocol, SampleId, Samples, Target, }; -use mcpaas_mapping::{JsonPathRoot, MappingSet, infer_mapping_from_samples}; -use mcpaas_proto::{ProtoService, services_from_descriptor_set_bytes}; -use mcpaas_registry::{ +use crank_mapping::{JsonPathRoot, MappingSet, infer_mapping_from_samples}; +use crank_proto::{ProtoService, services_from_descriptor_set_bytes}; +use crank_registry::{ CreateVersionRequest, OperationSampleMetadata, OperationSummary, OperationVersionRecord, PostgresRegistry, PublishRequest, RegistryOperation, SampleKind, SaveAuthProfileRequest, SaveDescriptorMetadataRequest, SaveSampleMetadataRequest, }; -use mcpaas_runtime::{PreparedRequest, RuntimeError, RuntimeExecutor, RuntimeOperation}; -use mcpaas_schema::Schema; +use crank_runtime::{PreparedRequest, RuntimeError, RuntimeExecutor, RuntimeOperation}; +use crank_schema::Schema; use serde::{Deserialize, Serialize}; use serde_json::{Value, json}; use time::{OffsetDateTime, format_description::well_known::Rfc3339}; @@ -38,8 +38,8 @@ pub struct OperationPayload { pub output_schema: Schema, pub input_mapping: MappingSet, pub output_mapping: MappingSet, - pub execution_config: mcpaas_core::ExecutionConfig, - pub tool_description: mcpaas_core::ToolDescription, + pub execution_config: crank_core::ExecutionConfig, + pub tool_description: crank_core::ToolDescription, } #[derive(Clone, Debug, Deserialize)] @@ -395,13 +395,13 @@ impl AdminService { let services = services_from_descriptor_set_bytes(payload) .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 .storage .write_descriptor( operation_id, summary.current_draft_version, - mcpaas_registry::DescriptorKind::DescriptorSet, + crank_registry::DescriptorKind::DescriptorSet, &descriptor_id, source_name, payload, @@ -410,11 +410,11 @@ impl AdminService { self.registry .save_descriptor_metadata(SaveDescriptorMetadataRequest { - descriptor: &mcpaas_registry::DescriptorMetadata { + descriptor: &crank_registry::DescriptorMetadata { id: descriptor_id.clone(), operation_id: Some(operation_id.clone()), version: Some(summary.current_draft_version), - descriptor_kind: mcpaas_registry::DescriptorKind::DescriptorSet, + descriptor_kind: crank_registry::DescriptorKind::DescriptorSet, storage_ref, source_name: source_name.map(ToOwned::to_owned), 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 .storage .write_descriptor( operation_id, summary.current_draft_version, - mcpaas_registry::DescriptorKind::ProtoUpload, + crank_registry::DescriptorKind::ProtoUpload, &descriptor_id, source_name, payload, @@ -467,11 +467,11 @@ impl AdminService { self.registry .save_descriptor_metadata(SaveDescriptorMetadataRequest { - descriptor: &mcpaas_registry::DescriptorMetadata { + descriptor: &crank_registry::DescriptorMetadata { id: descriptor_id.clone(), operation_id: Some(operation_id.clone()), version: Some(summary.current_draft_version), - descriptor_kind: mcpaas_registry::DescriptorKind::ProtoUpload, + descriptor_kind: crank_registry::DescriptorKind::ProtoUpload, storage_ref, source_name: source_name.map(ToOwned::to_owned), package_index: None, @@ -513,7 +513,7 @@ impl AdminService { .into_iter() .rev() .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"))?; let bytes = self.storage.read_bytes(&descriptor.storage_ref).await?; @@ -809,10 +809,10 @@ impl AdminService { fn build_request_preview( mapping: &MappingSet, input: &Value, -) -> Result { +) -> Result { let mapped = mapping.apply(&json!({ "mcp": input }))?; let prepared = PreparedRequest::from_mapping_output(&mapped).map_err(|error| { - mcpaas_mapping::MappingError::InvalidJsonPath { + crank_mapping::MappingError::InvalidJsonPath { path: error.to_string(), } })?; @@ -890,9 +890,9 @@ fn proto_service_summary(service: ProtoService) -> Result, message: &Value) -> Response { } }, "serverInfo": { - "name": "rmcp-mcp-server", + "name": "crank-mcp-server", "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 { - mcpaas_schema::SchemaKind::Object => { + crank_schema::SchemaKind::Object => { let mut properties = serde_json::Map::new(); let mut required = Vec::new(); @@ -500,7 +500,7 @@ fn schema_to_json_schema(schema: &mcpaas_schema::Schema) -> Value { "required": required }) } - mcpaas_schema::SchemaKind::Array => json!({ + crank_schema::SchemaKind::Array => json!({ "type": "array", "items": schema .items @@ -508,16 +508,16 @@ fn schema_to_json_schema(schema: &mcpaas_schema::Schema) -> Value { .map(schema_to_json_schema) .unwrap_or_else(|| json!({})) }), - mcpaas_schema::SchemaKind::String => json!({ "type": "string" }), - mcpaas_schema::SchemaKind::Integer => json!({ "type": "integer" }), - mcpaas_schema::SchemaKind::Number => json!({ "type": "number" }), - mcpaas_schema::SchemaKind::Boolean => json!({ "type": "boolean" }), - mcpaas_schema::SchemaKind::Enum => json!({ + crank_schema::SchemaKind::String => json!({ "type": "string" }), + crank_schema::SchemaKind::Integer => json!({ "type": "integer" }), + crank_schema::SchemaKind::Number => json!({ "type": "number" }), + crank_schema::SchemaKind::Boolean => json!({ "type": "boolean" }), + crank_schema::SchemaKind::Enum => json!({ "type": "string", "enum": schema.enum_values }), - mcpaas_schema::SchemaKind::Null => json!({ "type": "null" }), - mcpaas_schema::SchemaKind::Oneof => json!({ + crank_schema::SchemaKind::Null => json!({ "type": "null" }), + crank_schema::SchemaKind::Oneof => json!({ "anyOf": schema.variants.iter().map(schema_to_json_schema).collect::>() }), } diff --git a/apps/mcp-server/src/catalog.rs b/apps/mcp-server/src/catalog.rs index 4e2e773..49e19a9 100644 --- a/apps/mcp-server/src/catalog.rs +++ b/apps/mcp-server/src/catalog.rs @@ -4,7 +4,7 @@ use std::{ time::{Duration, Instant}, }; -use mcpaas_registry::{PostgresRegistry, RegistryError, RegistryOperation}; +use crank_registry::{PostgresRegistry, RegistryError, RegistryOperation}; use tokio::sync::RwLock; use tracing::info; diff --git a/apps/mcp-server/src/main.rs b/apps/mcp-server/src/main.rs index f0b95ea..0a40b60 100644 --- a/apps/mcp-server/src/main.rs +++ b/apps/mcp-server/src/main.rs @@ -5,7 +5,7 @@ mod session; use std::{env, net::SocketAddr, time::Duration}; -use mcpaas_registry::PostgresRegistry; +use crank_registry::PostgresRegistry; use tokio::net::TcpListener; use tracing::info; @@ -15,15 +15,15 @@ use crate::app::build_app; async fn main() -> Result<(), Box> { tracing_subscriber::fmt() .with_env_filter( - env::var("MCPAAS_LOG_LEVEL") + env::var("CRANK_LOG_LEVEL") .unwrap_or_else(|_| "mcp_server=info,tower_http=info".into()), ) .init(); - let database_url = env::var("MCPAAS_DATABASE_URL")?; - let bind_addr = env::var("MCPAAS_MCP_BIND").unwrap_or_else(|_| "0.0.0.0:3002".into()); - let public_base_url = env::var("MCPAAS_PUBLIC_BASE_URL").ok(); - let refresh_interval = env::var("MCPAAS_MCP_REFRESH_MS") + let database_url = env::var("CRANK_DATABASE_URL")?; + let bind_addr = env::var("CRANK_MCP_BIND").unwrap_or_else(|_| "0.0.0.0:3002".into()); + let public_base_url = env::var("CRANK_PUBLIC_BASE_URL").ok(); + let refresh_interval = env::var("CRANK_MCP_REFRESH_MS") .ok() .and_then(|value| value.parse::().ok()) .map(Duration::from_millis) @@ -49,14 +49,14 @@ mod tests { }; use axum::{Json, Router, http::header, routing::post}; - use mcpaas_adapter_grpc::test_support as grpc_test_support; - use mcpaas_core::{ + use crank_adapter_grpc::test_support as grpc_test_support; + use crank_core::{ DescriptorId, ExecutionConfig, GraphqlOperationType, GraphqlTarget, GrpcTarget, HttpMethod, Operation, OperationId, OperationStatus, Protocol, RestTarget, Target, ToolDescription, }; - use mcpaas_mapping::{MappingRule, MappingSet}; - use mcpaas_registry::{PostgresRegistry, PublishRequest}; - use mcpaas_schema::{Schema, SchemaKind}; + use crank_mapping::{MappingRule, MappingSet}; + use crank_registry::{PostgresRegistry, PublishRequest}; + use crank_schema::{Schema, SchemaKind}; use serde_json::{Value, json}; use sqlx::{Executor, postgres::PgPoolOptions}; use tokio::net::TcpListener; @@ -86,7 +86,7 @@ mod tests { let base_url = spawn_mcp_server(build_app( registry, Duration::from_millis(0), - Some("https://rmcp.example.com".to_owned()), + Some("https://crank.example.com".to_owned()), )) .await; let client = reqwest::Client::new(); @@ -153,7 +153,7 @@ mod tests { let base_url = spawn_mcp_server(build_app( registry, Duration::from_millis(0), - Some("https://rmcp.example.com".to_owned()), + Some("https://crank.example.com".to_owned()), )) .await; let client = reqwest::Client::new(); @@ -207,7 +207,7 @@ mod tests { let base_url = spawn_mcp_server(build_app( registry, Duration::from_millis(0), - Some("https://rmcp.example.com".to_owned()), + Some("https://crank.example.com".to_owned()), )) .await; let client = reqwest::Client::new(); @@ -244,7 +244,7 @@ mod tests { let base_url = spawn_mcp_server(build_app( registry, Duration::from_millis(0), - Some("https://rmcp.example.com".to_owned()), + Some("https://crank.example.com".to_owned()), )) .await; let client = reqwest::Client::new(); @@ -297,7 +297,7 @@ mod tests { let base_url = spawn_mcp_server(build_app( registry.clone(), Duration::from_millis(0), - Some("https://rmcp.example.com".to_owned()), + Some("https://crank.example.com".to_owned()), )) .await; let client = reqwest::Client::new(); @@ -479,7 +479,7 @@ mod tests { async fn test_registry() -> PostgresRegistry { 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() .max_connections(1) .connect(&database_url) diff --git a/apps/ui/index.html b/apps/ui/index.html index 865fea3..cfa159f 100644 --- a/apps/ui/index.html +++ b/apps/ui/index.html @@ -3,7 +3,7 @@ - RMCP Console + Crank Console
diff --git a/apps/ui/package-lock.json b/apps/ui/package-lock.json index f57125a..bcd80b4 100644 --- a/apps/ui/package-lock.json +++ b/apps/ui/package-lock.json @@ -1,11 +1,11 @@ { - "name": "rmcp-ui", + "name": "crank-ui", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "rmcp-ui", + "name": "crank-ui", "version": "0.1.0", "dependencies": { "@hookform/resolvers": "^5.2.2", diff --git a/apps/ui/package.json b/apps/ui/package.json index 56e897a..8190624 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -1,5 +1,5 @@ { - "name": "rmcp-ui", + "name": "crank-ui", "private": true, "version": "0.1.0", "type": "module", diff --git a/apps/ui/src/features/operation-form/model.test.ts b/apps/ui/src/features/operation-form/model.test.ts index 924c4f9..8326c2d 100644 --- a/apps/ui/src/features/operation-form/model.test.ts +++ b/apps/ui/src/features/operation-form/model.test.ts @@ -8,7 +8,7 @@ describe("buildOperationPayload", () => { ...defaultOperationFormValues, restStaticHeadersText: JSON.stringify( { - "x-app-source": "rmcp", + "x-app-source": "crank", }, null, 2, @@ -26,7 +26,7 @@ describe("buildOperationPayload", () => { expect(payload.target.kind).toBe("rest"); expect(payload.target.method).toBe("POST"); expect(payload.target.static_headers).toEqual({ - "x-app-source": "rmcp", + "x-app-source": "crank", }); expect(payload.execution_config.headers).toEqual({ "x-trace-id": "trace-123", diff --git a/apps/ui/src/features/operation-form/operation-form.tsx b/apps/ui/src/features/operation-form/operation-form.tsx index 3ef4902..9f3ad78 100644 --- a/apps/ui/src/features/operation-form/operation-form.tsx +++ b/apps/ui/src/features/operation-form/operation-form.tsx @@ -2,6 +2,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation } from "@tanstack/react-query"; import { useEffect, useMemo, useRef } from "react"; import { useForm } from "react-hook-form"; +import { createPortal } from "react-dom"; import { useNavigate } from "react-router-dom"; import { createOperation } from "../../entities/operation/api"; @@ -195,7 +196,11 @@ function bytesToBase64(buffer: ArrayBuffer) { return window.btoa(binary); } -export function OperationForm() { +type OperationFormProps = { + actionBarContainerId?: string; +}; + +export function OperationForm({ actionBarContainerId }: OperationFormProps) { const navigate = useNavigate(); const form = useForm({ defaultValues: defaultOperationFormValues, @@ -236,6 +241,9 @@ export function OperationForm() { } }, [activeProtocol]); + const actionBarContainer = + actionBarContainerId === undefined ? null : document.getElementById(actionBarContainerId); + async function handleDescriptorFileChange(file: File | undefined) { if (file === undefined) { return; @@ -339,174 +347,194 @@ export function OperationForm() { } return ( -
{ - creationMutation.mutate(values); - })} - > -
-
-
-

1 — Protocol

-

One tool maps to exactly one upstream method.

-
-
-
-
- {protocolCards.map((card) => { - const isSelected = activeProtocol === card.protocol; - const selectedClassName = isSelected - ? `proto-card selected-${card.protocol === "graphql" ? "gql" : card.protocol}` - : "proto-card"; + <> + { + creationMutation.mutate(values); + })} + > +
+
+
+
+

1 — Protocol

+

One tool maps to exactly one upstream method.

+
+
+
+
+ {protocolCards.map((card) => { + const isSelected = activeProtocol === card.protocol; + const selectedClassName = isSelected + ? `proto-card selected-${card.protocol === "graphql" ? "gql" : card.protocol}` + : "proto-card"; - return ( - - ); - })} +
+

{card.title}

+

{card.description}

+
+ + ); + })} +
+
+ +
+
+
+

2 — Tool identity

+

Name and description visible to the LLM at runtime.

+
+ draft +
+
+
{commonFields.map(renderField)}
+
+
+ +
+
+
+

3 — Upstream target

+

Protocol-specific target configuration exposed through one MCP tool.

+
+ {activeProtocol} +
+
+
+ {protocolFields.map(renderField)} + {activeProtocol === "grpc" ? ( + + ) : 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} +
+ +
+
+

+ Headers and auth stay separate from path and payload mapping. Configure shared + transport headers in the execution section below. +

+
+
+
+ +
+
+
+

4 — Contract schemas

+

Protocol-agnostic schemas for MCP input and output payloads.

+
+
+
+
{schemaFields.map(renderField)}
+
+
+ +
+
+
+

5 — Mapping and execution

+

+ Translate MCP input to request fields, then map upstream output back to tool + output. +

+
+
+
+
+ Input → Request +
+
+
{mappingFields.slice(0, 1).map(renderField)}
+ +
+ Response → Output +
+
+
{mappingFields.slice(1).map(renderField)}
+ +
+ Headers And Runtime +
+
+
{headerFields.map(renderField)}
+
+
+ + {creationMutation.error ? ( +
+ {creationMutation.error instanceof ApiError + ? creationMutation.error.message + : "Failed to create operation"} +
+ ) : null}
-
+
-
-
-
-

2 — Tool identity

-

Name and description visible to the LLM at runtime.

-
- draft -
-
-
{commonFields.map(renderField)}
-
-
- -
-
-
-

3 — Upstream target

-

Protocol-specific target configuration exposed through one MCP tool.

-
- {activeProtocol} -
-
-
- {protocolFields.map(renderField)} - {activeProtocol === "grpc" ? ( - - ) : 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} -
- -
-
-

- Headers and auth stay separate from path and payload mapping. Configure shared - transport headers in the execution section below. -

-
-
-
- -
-
-
-

4 — Contract schemas

-

Protocol-agnostic schemas for MCP input and output payloads.

-
-
-
-
{schemaFields.map(renderField)}
-
-
- -
-
-
-

5 — Mapping and execution

-

Translate MCP input to request fields, then map upstream output back to tool output.

-
-
-
-
- Input → Request -
-
-
{mappingFields.slice(0, 1).map(renderField)}
- -
- Response → Output -
-
-
{mappingFields.slice(1).map(renderField)}
- -
- Headers And Runtime -
-
-
{headerFields.map(renderField)}
-
-
- - {creationMutation.error ? ( -
- {creationMutation.error instanceof ApiError - ? creationMutation.error.message - : "Failed to create operation"} -
- ) : null} - -
-
- ready to publish later -

{protocolSummary(activeProtocol)}

-
- -
- + {actionBarContainer + ? createPortal( +
+
+ ready to publish later +

{protocolSummary(activeProtocol)}

+
+ +
, + actionBarContainer, + ) + : null} + ); } diff --git a/apps/ui/src/pages/operation-create/page.tsx b/apps/ui/src/pages/operation-create/page.tsx index 1539d91..53cf456 100644 --- a/apps/ui/src/pages/operation-create/page.tsx +++ b/apps/ui/src/pages/operation-create/page.tsx @@ -1,9 +1,5 @@ import { OperationForm } from "../../features/operation-form/operation-form"; export function OperationCreatePage() { - return ( -
- -
- ); + return ; } diff --git a/apps/ui/src/shared/ui/app-shell.tsx b/apps/ui/src/shared/ui/app-shell.tsx index 7f46509..0e4a4c3 100644 --- a/apps/ui/src/shared/ui/app-shell.tsx +++ b/apps/ui/src/shared/ui/app-shell.tsx @@ -51,7 +51,7 @@ export function AppShell({ children }: AppShellProps) {
- MCPaaS + Crank Tool Console
@@ -60,6 +60,7 @@ export function AppShell({ children }: AppShellProps) {

Workspace