mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-11 03:40:05 +00:00
feat(server): mcp-servers HTTP API — handlers, type reuse, TS client
Implement the server-managed MCP server catalog HTTP API on top of the OpenAPI spec and the `fabro-mcp-store` foundation, mirroring the automations feature end to end. - fabro-api: reuse the domain model as the wire contract via `with_replacement` instead of parallel DTOs — `McpServer` → `McpServerDefinition`, `CreateMcpServerRequest` → `McpServerDraft`, `ReplaceMcpServerRequest` → `McpServerReplace`. The transport types (`McpTransport`, `McpHttpProtocol`) are already mapped to the domain `u64`/`u16` widths, so the spec's signed integer formats never leak. Add identity + JSON-parity tests. - fabro-types: derive serde on `McpServerDefinition` so it can serve as the public response type. - fabro-server: wire the `McpServerStore` into `AppState` (load from the active config's `mcps/` directory) and add the `list/create/get/replace/delete` handlers with ETag-on-201, required `If-Match` on PUT/DELETE, and the same store-error → status mapping automations uses (404/409/422/500). Register the routes. - Regenerate the TypeScript Axios client against the merged spec. Credential-literal validation (warn vs hard-reject for credential-looking literal env/header values) is left intentionally unenforced behind a TODO; that decision is still open. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
164d9dcfbc
commit
20cde16b59
29 changed files with 1740 additions and 27 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2928,6 +2928,7 @@ dependencies = [
|
|||
"fabro-llm",
|
||||
"fabro-macros",
|
||||
"fabro-manifest",
|
||||
"fabro-mcp-store",
|
||||
"fabro-model",
|
||||
"fabro-proc",
|
||||
"fabro-redact",
|
||||
|
|
|
|||
|
|
@ -645,6 +645,18 @@ fn main() {
|
|||
"fabro_automation::AutomationReplace",
|
||||
&[],
|
||||
),
|
||||
// MCP server catalog wire types reuse the domain model rather than
|
||||
// generating parallel DTOs. The integer formats in the spec generate as
|
||||
// `i64`/`i32`, but the domain types use `u64`/`u16`; the replacements
|
||||
// ensure the signed widths never leak into the public client.
|
||||
// (`McpTransport`/`McpHttpProtocol` are already mapped above.)
|
||||
("McpServer", "fabro_types::McpServerDefinition", &[]),
|
||||
("CreateMcpServerRequest", "fabro_types::McpServerDraft", &[]),
|
||||
(
|
||||
"ReplaceMcpServerRequest",
|
||||
"fabro_types::McpServerReplace",
|
||||
&[],
|
||||
),
|
||||
("Environment", "fabro_environment::Environment", &[]),
|
||||
("SessionId", "fabro_types::SessionId", &[]),
|
||||
("TurnId", "fabro_types::TurnId", &[]),
|
||||
|
|
|
|||
|
|
@ -24,8 +24,7 @@ pub mod types {
|
|||
ModelTestMode, Provider, ReasoningEffort, ReasoningEffortFeature, Speed as BillingSpeed,
|
||||
};
|
||||
pub use fabro_types::run_event::AgentSessionActivatedProps;
|
||||
pub use fabro_types::settings::ServerNamespace;
|
||||
pub use fabro_types::settings::run::{McpHttpProtocol, McpTransport};
|
||||
pub use fabro_types::settings::run::McpHttpProtocol;
|
||||
pub use fabro_types::settings::server::{
|
||||
GithubIntegrationSettings, GithubIntegrationStrategy, IntegrationWebhooksSettings,
|
||||
LogDestination, ObjectStoreSettings, ServerApiSettings, ServerArtifactsSettings,
|
||||
|
|
@ -35,6 +34,7 @@ pub mod types {
|
|||
ServerSlateDbSettings, ServerStorageSettings, ServerWebSettings, SlackIntegrationSettings,
|
||||
WebhookStrategy,
|
||||
};
|
||||
pub use fabro_types::settings::{McpTransport, ServerNamespace};
|
||||
pub use fabro_types::status::{
|
||||
BlockedReason, FailureReason, PendingReason, RunControlAction, RunStatus, SuccessReason,
|
||||
};
|
||||
|
|
@ -46,28 +46,29 @@ pub mod types {
|
|||
FailureCategory, FailureDetail, FailureSignature, GitContext, IdpIdentity,
|
||||
IntegrationConnectionKind, IntegrationConnectionState, IntegrationConnectionStatus,
|
||||
IntegrationProvider, IntegrationStatus, InterviewOption, InterviewQuestionRecord,
|
||||
McpServerProjection, McpServerStatus, Message, PairId, PairMessageId, PairMessageRecord,
|
||||
PairMessageRequest, PairRecord, PairStartRequest, PairStatus, PairTarget,
|
||||
PairTranscriptEntry, PairTranscriptResponse, PendingInterviewRecord, PermissionLevel,
|
||||
PreRunPushOutcome, Principal, PullRequest, PullRequestDetails, PullRequestDetailsStatus,
|
||||
PullRequestDetailsUnavailableReason, PullRequestLink, PullRequestMeta, PullRequestResponse,
|
||||
QuestionType, RepositoryRef, Role, Run, RunApproval, RunApprovalState, RunClientProvenance,
|
||||
RunEvent, RunEventDetailContentKind, RunEventDetailResponse, RunFailure,
|
||||
RunPairStatusResponse, RunProjection, RunProvenance, RunRunnableSource, RunSandbox,
|
||||
RunSandboxFailure, RunSandboxInstance, RunSandboxKind, RunSandboxPlan, RunSandboxRuntime,
|
||||
RunServerProvenance, RunSize, SandboxDetails, SandboxInfo, SandboxListMeta,
|
||||
SandboxListResponse, SandboxNetwork, SandboxNetworkPolicy, SandboxNetworkPolicyMode,
|
||||
SandboxProviderKind, SandboxProviderLookupError, SandboxResources, SandboxService,
|
||||
SandboxServiceListResponse, SandboxState, SandboxTimestamps, SecretMetadata, SecretType,
|
||||
ServerSettings, SessionDetail, SessionId, SessionMessage, SessionRecord, SessionStatus,
|
||||
SessionSummary, SessionTurn, SkillsProjection, StageCompletion, StageContextWindow,
|
||||
StageContextWindowBreakdownItem, StageContextWindowCategory, StageContextWindowCountMethod,
|
||||
StageContextWindowProjection, StageContextWindowStaleness,
|
||||
StageContextWindowUnavailableReason, StageContextWindowWarning, StageHandler,
|
||||
StageModelUsage, StageOutcome, StageProjection, StageState, SubAgentProjection,
|
||||
SubAgentStatus, SystemActorKind, SystemIntegrationStatus, SystemIntegrationsResponse,
|
||||
TodoListProjection, TurnId, UpdateVariableRequest, UserPrincipal, Variable,
|
||||
VariableListResponse, WorkflowSettings,
|
||||
McpServerDefinition as McpServer, McpServerDraft as CreateMcpServerRequest,
|
||||
McpServerProjection, McpServerReplace as ReplaceMcpServerRequest, McpServerStatus, Message,
|
||||
PairId, PairMessageId, PairMessageRecord, PairMessageRequest, PairRecord, PairStartRequest,
|
||||
PairStatus, PairTarget, PairTranscriptEntry, PairTranscriptResponse,
|
||||
PendingInterviewRecord, PermissionLevel, PreRunPushOutcome, Principal, PullRequest,
|
||||
PullRequestDetails, PullRequestDetailsStatus, PullRequestDetailsUnavailableReason,
|
||||
PullRequestLink, PullRequestMeta, PullRequestResponse, QuestionType, RepositoryRef, Role,
|
||||
Run, RunApproval, RunApprovalState, RunClientProvenance, RunEvent,
|
||||
RunEventDetailContentKind, RunEventDetailResponse, RunFailure, RunPairStatusResponse,
|
||||
RunProjection, RunProvenance, RunRunnableSource, RunSandbox, RunSandboxFailure,
|
||||
RunSandboxInstance, RunSandboxKind, RunSandboxPlan, RunSandboxRuntime, RunServerProvenance,
|
||||
RunSize, SandboxDetails, SandboxInfo, SandboxListMeta, SandboxListResponse, SandboxNetwork,
|
||||
SandboxNetworkPolicy, SandboxNetworkPolicyMode, SandboxProviderKind,
|
||||
SandboxProviderLookupError, SandboxResources, SandboxService, SandboxServiceListResponse,
|
||||
SandboxState, SandboxTimestamps, SecretMetadata, SecretType, ServerSettings, SessionDetail,
|
||||
SessionId, SessionMessage, SessionRecord, SessionStatus, SessionSummary, SessionTurn,
|
||||
SkillsProjection, StageCompletion, StageContextWindow, StageContextWindowBreakdownItem,
|
||||
StageContextWindowCategory, StageContextWindowCountMethod, StageContextWindowProjection,
|
||||
StageContextWindowStaleness, StageContextWindowUnavailableReason,
|
||||
StageContextWindowWarning, StageHandler, StageModelUsage, StageOutcome, StageProjection,
|
||||
StageState, SubAgentProjection, SubAgentStatus, SystemActorKind, SystemIntegrationStatus,
|
||||
SystemIntegrationsResponse, TodoListProjection, TurnId, UpdateVariableRequest,
|
||||
UserPrincipal, Variable, VariableListResponse, WorkflowSettings,
|
||||
};
|
||||
|
||||
pub use crate::generated::types::*;
|
||||
|
|
|
|||
108
lib/crates/fabro-api/tests/mcp_server_round_trip.rs
Normal file
108
lib/crates/fabro-api/tests/mcp_server_round_trip.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
use fabro_api::types::{
|
||||
CreateMcpServerRequest as ApiCreateMcpServerRequest, McpHttpProtocol as ApiMcpHttpProtocol,
|
||||
McpServer as ApiMcpServer, McpTransport as ApiMcpTransport,
|
||||
ReplaceMcpServerRequest as ApiReplaceMcpServerRequest,
|
||||
};
|
||||
use fabro_types::settings::McpTransport;
|
||||
use fabro_types::settings::run::McpHttpProtocol;
|
||||
use fabro_types::{McpServerDefinition, McpServerDraft, McpServerReplace};
|
||||
use serde_json::json;
|
||||
|
||||
// Compile-time witnesses that the generated API types resolve to the same types
|
||||
// as the `fabro-types` domain types via `with_replacement(...)`. If progenitor
|
||||
// stops reusing the domain type, these functions stop type-checking and the
|
||||
// build fails. This is what keeps the spec's signed integer formats (`i64` for
|
||||
// the `u64` timeouts, `i32` for the `u16` sandbox port) from leaking into the
|
||||
// public client.
|
||||
const _: fn(ApiMcpServer) -> McpServerDefinition = |value| value;
|
||||
const _: fn(ApiCreateMcpServerRequest) -> McpServerDraft = |value| value;
|
||||
const _: fn(ApiReplaceMcpServerRequest) -> McpServerReplace = |value| value;
|
||||
const _: fn(ApiMcpTransport) -> McpTransport = |value| value;
|
||||
const _: fn(ApiMcpHttpProtocol) -> McpHttpProtocol = |value| value;
|
||||
|
||||
#[test]
|
||||
fn mcp_server_response_round_trips_http_transport_json_shape() {
|
||||
let value = json!({
|
||||
"id": "sentry",
|
||||
"revision": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
"name": "Sentry",
|
||||
"description": "Production Sentry MCP server.",
|
||||
"transport": {
|
||||
"type": "http",
|
||||
"protocol": "streamable_http",
|
||||
"url": "https://sentry.example.com/mcp",
|
||||
"headers": {
|
||||
"X-Org": "fabro"
|
||||
}
|
||||
},
|
||||
"startup_timeout_secs": 10,
|
||||
"tool_timeout_secs": 60
|
||||
});
|
||||
|
||||
let api: ApiMcpServer = serde_json::from_value(value.clone()).unwrap();
|
||||
assert_eq!(serde_json::to_value(api).unwrap(), value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mcp_server_response_round_trips_null_description() {
|
||||
let value = json!({
|
||||
"id": "local",
|
||||
"revision": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
"name": "Local",
|
||||
"description": null,
|
||||
"transport": {
|
||||
"type": "stdio",
|
||||
"command": ["npx", "@modelcontextprotocol/server-filesystem"],
|
||||
"env": {}
|
||||
},
|
||||
"startup_timeout_secs": 10,
|
||||
"tool_timeout_secs": 60
|
||||
});
|
||||
|
||||
let api: ApiMcpServer = serde_json::from_value(value.clone()).unwrap();
|
||||
assert_eq!(serde_json::to_value(api).unwrap(), value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_mcp_server_request_round_trips_sandbox_transport_json_shape() {
|
||||
// The sandbox transport carries a `port`, whose spec format is `int32`.
|
||||
// Reusing the domain type pins it to `u16`, so this round-trip also guards
|
||||
// against the signed-width leak.
|
||||
let value = json!({
|
||||
"id": "sandbox-mcp",
|
||||
"name": "Sandbox MCP",
|
||||
"transport": {
|
||||
"type": "sandbox",
|
||||
"protocol": "sse",
|
||||
"command": ["./serve"],
|
||||
"port": 8080,
|
||||
"env": {
|
||||
"NODE_ENV": "production"
|
||||
}
|
||||
},
|
||||
"startup_timeout_secs": 15,
|
||||
"tool_timeout_secs": 90
|
||||
});
|
||||
|
||||
let api: ApiCreateMcpServerRequest = serde_json::from_value(value.clone()).unwrap();
|
||||
assert_eq!(serde_json::to_value(api).unwrap(), value);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replace_mcp_server_request_round_trips_http_transport_json_shape() {
|
||||
let value = json!({
|
||||
"name": "Sentry v2",
|
||||
"description": "Updated.",
|
||||
"transport": {
|
||||
"type": "http",
|
||||
"protocol": "streamable_http",
|
||||
"url": "https://sentry.example.com/mcp/v2",
|
||||
"headers": {}
|
||||
},
|
||||
"startup_timeout_secs": 20,
|
||||
"tool_timeout_secs": 120
|
||||
});
|
||||
|
||||
let api: ApiReplaceMcpServerRequest = serde_json::from_value(value.clone()).unwrap();
|
||||
assert_eq!(serde_json::to_value(api).unwrap(), value);
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@ fabro-github = { path = "../fabro-github" }
|
|||
fabro-agent = { path = "../fabro-agent" }
|
||||
fabro-llm = { path = "../fabro-llm" }
|
||||
fabro-manifest = { path = "../fabro-manifest" }
|
||||
fabro-mcp-store = { path = "../fabro-mcp-store" }
|
||||
fabro-model = { path = "../fabro-model" }
|
||||
fabro-proc = { path = "../fabro-proc" }
|
||||
fabro-tool = { path = "../fabro-tool" }
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ use fabro_llm::model_test::run_model_test;
|
|||
use fabro_llm::types::{
|
||||
FinishReason, Message as LlmMessage, Request as LlmRequest, ToolChoice, ToolDefinition,
|
||||
};
|
||||
use fabro_mcp_store::McpServerStore;
|
||||
use fabro_model::catalog::LlmCatalogSettings;
|
||||
use fabro_model::{BilledTokenCounts, Catalog, ModelRef, ModelTestMode, ProviderId};
|
||||
use fabro_redact::redact_jsonl_line;
|
||||
|
|
@ -1107,6 +1108,7 @@ pub(crate) struct AppStores {
|
|||
pub(crate) runs: Arc<Database>,
|
||||
pub(crate) automations: Arc<AutomationStore>,
|
||||
pub(crate) environments: Arc<EnvironmentStore>,
|
||||
pub(crate) mcp_servers: Arc<McpServerStore>,
|
||||
pub(crate) vault: Arc<AsyncRwLock<Vault>>,
|
||||
pub(crate) variables: Arc<VariableStore>,
|
||||
}
|
||||
|
|
@ -1122,6 +1124,10 @@ impl AppState {
|
|||
&self.stores.environments
|
||||
}
|
||||
|
||||
pub(crate) fn mcp_server_store(&self) -> &McpServerStore {
|
||||
&self.stores.mcp_servers
|
||||
}
|
||||
|
||||
pub(crate) async fn materialize_automation_run(
|
||||
&self,
|
||||
input: AutomationRunMaterializeInput,
|
||||
|
|
@ -2285,6 +2291,13 @@ fn environment_dir_for_active_config(active_config_path: &std::path::Path) -> Pa
|
|||
.join("environments")
|
||||
}
|
||||
|
||||
fn mcp_server_dir_for_active_config(active_config_path: &std::path::Path) -> PathBuf {
|
||||
active_config_path
|
||||
.parent()
|
||||
.unwrap_or_else(|| std::path::Path::new("."))
|
||||
.join("mcps")
|
||||
}
|
||||
|
||||
pub(crate) fn build_app_state(config: AppStateConfig) -> anyhow::Result<Arc<AppState>> {
|
||||
let AppStateConfig {
|
||||
resolved_settings,
|
||||
|
|
@ -2329,6 +2342,12 @@ pub(crate) fn build_app_state(config: AppStateConfig) -> anyhow::Result<Arc<AppS
|
|||
.map_err(anyhow::Error::new)
|
||||
.context("load environments")?,
|
||||
);
|
||||
let mcp_server_dir = mcp_server_dir_for_active_config(&active_config_path);
|
||||
let mcp_server_store = Arc::new(
|
||||
McpServerStore::load(mcp_server_dir)
|
||||
.map_err(anyhow::Error::new)
|
||||
.context("load mcp servers")?,
|
||||
);
|
||||
let variables = Arc::new(VariableStore::new(db_pool));
|
||||
let vault = match preloaded_vault {
|
||||
Some(vault) => vault,
|
||||
|
|
@ -2439,6 +2458,7 @@ pub(crate) fn build_app_state(config: AppStateConfig) -> anyhow::Result<Arc<AppS
|
|||
runs: store,
|
||||
automations: automation_store,
|
||||
environments: environment_store,
|
||||
mcp_servers: mcp_server_store,
|
||||
vault,
|
||||
variables,
|
||||
},
|
||||
|
|
|
|||
150
lib/crates/fabro-server/src/server/handler/mcp_servers.rs
Normal file
150
lib/crates/fabro-server/src/server/handler/mcp_servers.rs
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use axum::http::HeaderMap;
|
||||
use fabro_mcp_store::McpServerStoreError;
|
||||
use fabro_types::{McpServerDefinition, McpServerDraft, McpServerId, McpServerReplace};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::super::{
|
||||
ApiError, AppState, IntoResponse, Json, Path, RequiredUser, Response, Router, State,
|
||||
StatusCode, get,
|
||||
};
|
||||
use super::{json_with_etag_response, parse_required_if_match};
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct McpServerListResponse {
|
||||
data: Vec<McpServerDefinition>,
|
||||
meta: McpServerListMeta,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct McpServerListMeta {
|
||||
total: usize,
|
||||
}
|
||||
|
||||
pub(super) fn routes() -> Router<Arc<AppState>> {
|
||||
Router::new()
|
||||
.route(
|
||||
"/mcp-servers",
|
||||
get(list_mcp_servers).post(create_mcp_server),
|
||||
)
|
||||
.route(
|
||||
"/mcp-servers/{id}",
|
||||
get(get_mcp_server)
|
||||
.put(replace_mcp_server)
|
||||
.delete(delete_mcp_server),
|
||||
)
|
||||
}
|
||||
|
||||
async fn list_mcp_servers(_auth: RequiredUser, State(state): State<Arc<AppState>>) -> Response {
|
||||
let data = state.mcp_server_store().list().await;
|
||||
let total = data.len();
|
||||
(
|
||||
StatusCode::OK,
|
||||
Json(McpServerListResponse {
|
||||
data,
|
||||
meta: McpServerListMeta { total },
|
||||
}),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
|
||||
async fn create_mcp_server(
|
||||
_auth: RequiredUser,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(draft): Json<McpServerDraft>,
|
||||
) -> Result<Response, ApiError> {
|
||||
// TODO(mcp): credential-literal validation — warn vs hard-reject still open.
|
||||
// The store keeps validation structural (id/name/transport); whether the API
|
||||
// should reject credential-looking literal values in env/header fields and
|
||||
// point the user at `{{ secrets.NAME }}` is undecided. Until then literals
|
||||
// are accepted verbatim and secret references are stored as written.
|
||||
let definition = state.mcp_server_store().create(draft).await?;
|
||||
Ok(mcp_server_with_etag_response(
|
||||
StatusCode::CREATED,
|
||||
definition,
|
||||
))
|
||||
}
|
||||
|
||||
async fn get_mcp_server(
|
||||
_auth: RequiredUser,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(id): Path<String>,
|
||||
) -> Result<Response, ApiError> {
|
||||
let id = parse_path_id(id)?;
|
||||
match state.mcp_server_store().get(&id).await {
|
||||
Some(definition) => Ok(mcp_server_with_etag_response(StatusCode::OK, definition)),
|
||||
None => Err(ApiError::not_found(format!("mcp server not found: {id}"))),
|
||||
}
|
||||
}
|
||||
|
||||
async fn replace_mcp_server(
|
||||
_auth: RequiredUser,
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
Path(id): Path<String>,
|
||||
Json(replacement): Json<McpServerReplace>,
|
||||
) -> Result<Response, ApiError> {
|
||||
let id = parse_path_id(id)?;
|
||||
let expected = parse_required_if_match(&headers, "mcp server", &id)?;
|
||||
// TODO(mcp): credential-literal validation — warn vs hard-reject still open.
|
||||
// See `create_mcp_server`; validation stays structural here too.
|
||||
let definition = state
|
||||
.mcp_server_store()
|
||||
.replace(&id, &expected, replacement)
|
||||
.await?;
|
||||
Ok(mcp_server_with_etag_response(StatusCode::OK, definition))
|
||||
}
|
||||
|
||||
async fn delete_mcp_server(
|
||||
_auth: RequiredUser,
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
Path(id): Path<String>,
|
||||
) -> Result<Response, ApiError> {
|
||||
let id = parse_path_id(id)?;
|
||||
let expected = parse_required_if_match(&headers, "mcp server", &id)?;
|
||||
state.mcp_server_store().delete(&id, &expected).await?;
|
||||
Ok(StatusCode::NO_CONTENT.into_response())
|
||||
}
|
||||
|
||||
fn parse_path_id(id: String) -> Result<McpServerId, ApiError> {
|
||||
McpServerId::new(id)
|
||||
.map_err(|err| ApiError::bad_request(format!("invalid mcp server id: {err}")))
|
||||
}
|
||||
|
||||
fn mcp_server_with_etag_response(status: StatusCode, definition: McpServerDefinition) -> Response {
|
||||
let revision = definition.revision.clone();
|
||||
json_with_etag_response(status, "mcp server", &revision, definition)
|
||||
}
|
||||
|
||||
impl From<McpServerStoreError> for ApiError {
|
||||
fn from(err: McpServerStoreError) -> Self {
|
||||
match err {
|
||||
McpServerStoreError::NotFound { id } => {
|
||||
Self::not_found(format!("mcp server not found: {id}"))
|
||||
}
|
||||
McpServerStoreError::AlreadyExists { id } => Self::new(
|
||||
StatusCode::CONFLICT,
|
||||
format!("mcp server already exists: {id}"),
|
||||
),
|
||||
McpServerStoreError::StaleRevision { id, .. } => Self::new(
|
||||
StatusCode::CONFLICT,
|
||||
format!("mcp server revision is stale: {id}"),
|
||||
),
|
||||
McpServerStoreError::Validation { source } => {
|
||||
Self::new(StatusCode::UNPROCESSABLE_ENTITY, source.to_string())
|
||||
}
|
||||
// Remaining variants are persistence/parse faults that indicate an
|
||||
// internal problem rather than a client one.
|
||||
McpServerStoreError::InvalidFilename { .. }
|
||||
| McpServerStoreError::Parse { .. }
|
||||
| McpServerStoreError::InvalidUtf8 { .. }
|
||||
| McpServerStoreError::Serialize { .. }
|
||||
| McpServerStoreError::Io { .. } => Self::new(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"mcp server store operation failed",
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ pub(in crate::server) mod events;
|
|||
pub(in crate::server) mod graph;
|
||||
pub(in crate::server) mod lifecycle;
|
||||
mod llm_sse;
|
||||
mod mcp_servers;
|
||||
mod models;
|
||||
mod pair;
|
||||
mod playground;
|
||||
|
|
@ -211,6 +212,7 @@ pub(super) fn real_routes() -> Router<Arc<AppState>> {
|
|||
.merge(pull_requests::routes())
|
||||
.merge(artifacts::routes())
|
||||
.merge(automations::routes())
|
||||
.merge(mcp_servers::routes())
|
||||
.merge(environments::routes())
|
||||
.merge(sandbox::routes())
|
||||
.merge(sandboxes::routes())
|
||||
|
|
|
|||
569
lib/crates/fabro-server/tests/it/api/mcp_servers.rs
Normal file
569
lib/crates/fabro-server/tests/it/api/mcp_servers.rs
Normal file
|
|
@ -0,0 +1,569 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use axum::body::Body;
|
||||
use axum::http::{Method, Request, StatusCode, header};
|
||||
use fabro_server::server::build_router;
|
||||
use fabro_server::test_support::{TestAppStateBuilder, build_test_router, test_auth_mode};
|
||||
use serde_json::{Value, json};
|
||||
use tower::ServiceExt;
|
||||
|
||||
use crate::helpers::{checked_response, response_json, response_status};
|
||||
|
||||
fn mcp_server_body(id: &str, name: &str) -> Value {
|
||||
json!({
|
||||
"id": id,
|
||||
"name": name,
|
||||
"description": "Production MCP server.",
|
||||
"transport": {
|
||||
"type": "http",
|
||||
"protocol": "streamable_http",
|
||||
"url": "https://example.com/mcp",
|
||||
"headers": {
|
||||
"X-Org": "fabro"
|
||||
}
|
||||
},
|
||||
"startup_timeout_secs": 10,
|
||||
"tool_timeout_secs": 60
|
||||
})
|
||||
}
|
||||
|
||||
fn replacement_body(name: &str) -> Value {
|
||||
json!({
|
||||
"name": name,
|
||||
"description": null,
|
||||
"transport": {
|
||||
"type": "http",
|
||||
"protocol": "sse",
|
||||
"url": "https://example.com/mcp/v2",
|
||||
"headers": {}
|
||||
},
|
||||
"startup_timeout_secs": 15,
|
||||
"tool_timeout_secs": 90
|
||||
})
|
||||
}
|
||||
|
||||
fn mcp_server_app() -> (axum::Router, tempfile::TempDir, PathBuf) {
|
||||
let temp_dir = tempfile::tempdir().expect("mcp server test tempdir should be created");
|
||||
let active_config_path = temp_dir.path().join("settings.toml");
|
||||
let mcp_dir = temp_dir.path().join("mcps");
|
||||
let state = TestAppStateBuilder::new()
|
||||
.active_config_path(active_config_path)
|
||||
.build();
|
||||
(build_test_router(state), temp_dir, mcp_dir)
|
||||
}
|
||||
|
||||
fn json_request(method: Method, path: &str, body: &Value) -> Request<Body> {
|
||||
Request::builder()
|
||||
.method(method)
|
||||
.uri(crate::helpers::api(path))
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.body(Body::from(
|
||||
serde_json::to_vec(&body).expect("mcp server fixture should serialize"),
|
||||
))
|
||||
.expect("mcp server JSON request should build")
|
||||
}
|
||||
|
||||
fn empty_request(method: Method, path: &str) -> Request<Body> {
|
||||
Request::builder()
|
||||
.method(method)
|
||||
.uri(crate::helpers::api(path))
|
||||
.body(Body::empty())
|
||||
.expect("mcp server request should build")
|
||||
}
|
||||
|
||||
fn request_with_if_match(
|
||||
method: Method,
|
||||
path: &str,
|
||||
revision: &str,
|
||||
body: Option<Value>,
|
||||
) -> Request<Body> {
|
||||
let mut builder = Request::builder()
|
||||
.method(method)
|
||||
.uri(crate::helpers::api(path))
|
||||
.header(header::IF_MATCH, revision);
|
||||
let body = match body {
|
||||
Some(value) => {
|
||||
builder = builder.header(header::CONTENT_TYPE, "application/json");
|
||||
Body::from(serde_json::to_vec(&value).expect("mcp server fixture should serialize"))
|
||||
}
|
||||
None => Body::empty(),
|
||||
};
|
||||
builder
|
||||
.body(body)
|
||||
.expect("mcp server If-Match request should build")
|
||||
}
|
||||
|
||||
async fn create_mcp_server(app: &axum::Router, id: &str, name: &str) -> Value {
|
||||
create_mcp_server_with_body(app, &mcp_server_body(id, name)).await
|
||||
}
|
||||
|
||||
async fn create_mcp_server_with_body(app: &axum::Router, body: &Value) -> Value {
|
||||
let response = app
|
||||
.clone()
|
||||
.oneshot(json_request(Method::POST, "/mcp-servers", body))
|
||||
.await
|
||||
.expect("create mcp server should respond");
|
||||
response_json(response, StatusCode::CREATED, "POST /api/v1/mcp-servers").await
|
||||
}
|
||||
|
||||
fn revision_from(body: &Value) -> &str {
|
||||
body["revision"]
|
||||
.as_str()
|
||||
.expect("mcp server response should include a revision")
|
||||
}
|
||||
|
||||
async fn persisted_mcp_server_toml(mcp_dir: &Path, id: &str) -> toml::Value {
|
||||
let persisted = tokio::fs::read_to_string(mcp_dir.join(format!("{id}.toml")))
|
||||
.await
|
||||
.expect("persisted mcp server TOML should be readable");
|
||||
toml::from_str(&persisted).expect("persisted mcp server TOML should parse")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn empty_mcp_server_list_returns_total_zero() {
|
||||
let (app, _temp_dir, _mcp_dir) = mcp_server_app();
|
||||
|
||||
let response = app
|
||||
.oneshot(empty_request(Method::GET, "/mcp-servers"))
|
||||
.await
|
||||
.expect("list mcp servers should respond");
|
||||
let body = response_json(response, StatusCode::OK, "GET /api/v1/mcp-servers").await;
|
||||
|
||||
assert_eq!(
|
||||
body,
|
||||
json!({
|
||||
"data": [],
|
||||
"meta": { "total": 0 }
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn create_mcp_server_returns_etag_and_persists_sibling_toml_file() {
|
||||
let (app, _temp_dir, mcp_dir) = mcp_server_app();
|
||||
|
||||
let response = app
|
||||
.clone()
|
||||
.oneshot(json_request(
|
||||
Method::POST,
|
||||
"/mcp-servers",
|
||||
&mcp_server_body("sentry", "Sentry"),
|
||||
))
|
||||
.await
|
||||
.expect("create mcp server should respond");
|
||||
let response =
|
||||
checked_response(response, StatusCode::CREATED, "POST /api/v1/mcp-servers").await;
|
||||
let etag = response
|
||||
.headers()
|
||||
.get(header::ETAG)
|
||||
.expect("create mcp server should include ETag")
|
||||
.to_str()
|
||||
.expect("ETag should be ASCII")
|
||||
.to_string();
|
||||
let body = crate::helpers::body_json(response.into_body()).await;
|
||||
|
||||
assert_eq!(body["id"], "sentry");
|
||||
assert_eq!(body["name"], "Sentry");
|
||||
assert_eq!(etag, format!("\"{}\"", revision_from(&body)));
|
||||
assert!(mcp_dir.join("sentry.toml").exists());
|
||||
|
||||
let persisted = persisted_mcp_server_toml(&mcp_dir, "sentry").await;
|
||||
assert_eq!(
|
||||
persisted.get("name").and_then(toml::Value::as_str),
|
||||
Some("Sentry")
|
||||
);
|
||||
assert!(persisted.get("id").is_none());
|
||||
assert!(persisted.get("revision").is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mcp_server_round_trips_through_create_get_and_toml() {
|
||||
let (app, _temp_dir, mcp_dir) = mcp_server_app();
|
||||
|
||||
let created = create_mcp_server(&app, "sentry", "Sentry").await;
|
||||
assert_eq!(created["transport"]["type"], "http");
|
||||
assert_eq!(created["transport"]["url"], "https://example.com/mcp");
|
||||
|
||||
let response = app
|
||||
.clone()
|
||||
.oneshot(empty_request(Method::GET, "/mcp-servers/sentry"))
|
||||
.await
|
||||
.expect("get mcp server should respond");
|
||||
let retrieved = response_json(response, StatusCode::OK, "GET /api/v1/mcp-servers/sentry").await;
|
||||
assert_eq!(retrieved, created);
|
||||
|
||||
let persisted = persisted_mcp_server_toml(&mcp_dir, "sentry").await;
|
||||
assert_eq!(
|
||||
persisted
|
||||
.get("transport")
|
||||
.and_then(|transport| transport.get("type"))
|
||||
.and_then(toml::Value::as_str),
|
||||
Some("http")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_mcp_servers_returns_items_sorted_by_id() {
|
||||
let (app, _temp_dir, _mcp_dir) = mcp_server_app();
|
||||
create_mcp_server(&app, "zulu", "Zulu").await;
|
||||
create_mcp_server(&app, "alpha", "Alpha").await;
|
||||
|
||||
let response = app
|
||||
.oneshot(empty_request(Method::GET, "/mcp-servers"))
|
||||
.await
|
||||
.expect("list mcp servers should respond");
|
||||
let body = response_json(response, StatusCode::OK, "GET /api/v1/mcp-servers").await;
|
||||
|
||||
assert_eq!(body["meta"]["total"], 2);
|
||||
assert_eq!(body["data"][0]["id"], "alpha");
|
||||
assert_eq!(body["data"][1]["id"], "zulu");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn duplicate_mcp_server_create_returns_conflict() {
|
||||
let (app, _temp_dir, _mcp_dir) = mcp_server_app();
|
||||
create_mcp_server(&app, "sentry", "Sentry").await;
|
||||
|
||||
let response = app
|
||||
.oneshot(json_request(
|
||||
Method::POST,
|
||||
"/mcp-servers",
|
||||
&mcp_server_body("sentry", "Duplicate"),
|
||||
))
|
||||
.await
|
||||
.expect("duplicate create should respond");
|
||||
|
||||
response_status(
|
||||
response,
|
||||
StatusCode::CONFLICT,
|
||||
"POST /api/v1/mcp-servers duplicate",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_mcp_server_returns_current_etag() {
|
||||
let (app, _temp_dir, _mcp_dir) = mcp_server_app();
|
||||
let created = create_mcp_server(&app, "sentry", "Sentry").await;
|
||||
let revision = revision_from(&created);
|
||||
|
||||
let response = app
|
||||
.oneshot(empty_request(Method::GET, "/mcp-servers/sentry"))
|
||||
.await
|
||||
.expect("get mcp server should respond");
|
||||
let response =
|
||||
checked_response(response, StatusCode::OK, "GET /api/v1/mcp-servers/sentry").await;
|
||||
|
||||
assert_eq!(
|
||||
response
|
||||
.headers()
|
||||
.get(header::ETAG)
|
||||
.expect("GET mcp server should include ETag"),
|
||||
&format!("\"{revision}\"")
|
||||
);
|
||||
let body = crate::helpers::body_json(response.into_body()).await;
|
||||
assert_eq!(body["revision"], revision);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_missing_mcp_server_returns_not_found() {
|
||||
let (app, _temp_dir, _mcp_dir) = mcp_server_app();
|
||||
|
||||
let response = app
|
||||
.oneshot(empty_request(Method::GET, "/mcp-servers/missing"))
|
||||
.await
|
||||
.expect("get missing mcp server should respond");
|
||||
|
||||
response_status(
|
||||
response,
|
||||
StatusCode::NOT_FOUND,
|
||||
"GET /api/v1/mcp-servers/missing",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn replace_mcp_server_accepts_unquoted_if_match_and_returns_new_etag() {
|
||||
let (app, _temp_dir, _mcp_dir) = mcp_server_app();
|
||||
let created = create_mcp_server(&app, "sentry", "Sentry").await;
|
||||
let revision = revision_from(&created);
|
||||
|
||||
let response = app
|
||||
.oneshot(request_with_if_match(
|
||||
Method::PUT,
|
||||
"/mcp-servers/sentry",
|
||||
revision,
|
||||
Some(replacement_body("Sentry v2")),
|
||||
))
|
||||
.await
|
||||
.expect("replace mcp server should respond");
|
||||
let response =
|
||||
checked_response(response, StatusCode::OK, "PUT /api/v1/mcp-servers/sentry").await;
|
||||
let etag = response
|
||||
.headers()
|
||||
.get(header::ETAG)
|
||||
.expect("PUT mcp server should include ETag")
|
||||
.to_str()
|
||||
.expect("ETag should be ASCII")
|
||||
.to_string();
|
||||
let body = crate::helpers::body_json(response.into_body()).await;
|
||||
|
||||
assert_eq!(body["name"], "Sentry v2");
|
||||
assert_eq!(body["transport"]["protocol"], "sse");
|
||||
assert_ne!(body["revision"], revision);
|
||||
assert_eq!(etag, format!("\"{}\"", revision_from(&body)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn replace_mcp_server_accepts_quoted_if_match() {
|
||||
let (app, _temp_dir, _mcp_dir) = mcp_server_app();
|
||||
let created = create_mcp_server(&app, "sentry", "Sentry").await;
|
||||
let revision = revision_from(&created);
|
||||
|
||||
let response = app
|
||||
.oneshot(request_with_if_match(
|
||||
Method::PUT,
|
||||
"/mcp-servers/sentry",
|
||||
&format!("\"{revision}\""),
|
||||
Some(replacement_body("Sentry v2")),
|
||||
))
|
||||
.await
|
||||
.expect("replace mcp server with quoted If-Match should respond");
|
||||
|
||||
response_status(
|
||||
response,
|
||||
StatusCode::OK,
|
||||
"PUT /api/v1/mcp-servers/sentry quoted If-Match",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stale_mcp_server_replace_returns_conflict() {
|
||||
let (app, _temp_dir, _mcp_dir) = mcp_server_app();
|
||||
let created = create_mcp_server(&app, "sentry", "Sentry").await;
|
||||
let stale_revision = revision_from(&created).to_string();
|
||||
|
||||
let replaced = app
|
||||
.clone()
|
||||
.oneshot(request_with_if_match(
|
||||
Method::PUT,
|
||||
"/mcp-servers/sentry",
|
||||
&stale_revision,
|
||||
Some(replacement_body("Updated")),
|
||||
))
|
||||
.await
|
||||
.expect("first replace should respond");
|
||||
response_status(
|
||||
replaced,
|
||||
StatusCode::OK,
|
||||
"PUT /api/v1/mcp-servers/sentry first replace",
|
||||
)
|
||||
.await;
|
||||
|
||||
let response = app
|
||||
.oneshot(request_with_if_match(
|
||||
Method::PUT,
|
||||
"/mcp-servers/sentry",
|
||||
&stale_revision,
|
||||
Some(replacement_body("Stale")),
|
||||
))
|
||||
.await
|
||||
.expect("stale replace should respond");
|
||||
|
||||
response_status(
|
||||
response,
|
||||
StatusCode::CONFLICT,
|
||||
"PUT /api/v1/mcp-servers/sentry stale",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn replace_and_delete_mcp_server_require_if_match() {
|
||||
let (app, _temp_dir, _mcp_dir) = mcp_server_app();
|
||||
create_mcp_server(&app, "sentry", "Sentry").await;
|
||||
|
||||
let replace_response = app
|
||||
.clone()
|
||||
.oneshot(json_request(
|
||||
Method::PUT,
|
||||
"/mcp-servers/sentry",
|
||||
&replacement_body("Updated"),
|
||||
))
|
||||
.await
|
||||
.expect("replace without If-Match should respond");
|
||||
response_status(
|
||||
replace_response,
|
||||
StatusCode::PRECONDITION_REQUIRED,
|
||||
"PUT /api/v1/mcp-servers/sentry without If-Match",
|
||||
)
|
||||
.await;
|
||||
|
||||
let delete_response = app
|
||||
.oneshot(empty_request(Method::DELETE, "/mcp-servers/sentry"))
|
||||
.await
|
||||
.expect("delete without If-Match should respond");
|
||||
response_status(
|
||||
delete_response,
|
||||
StatusCode::PRECONDITION_REQUIRED,
|
||||
"DELETE /api/v1/mcp-servers/sentry without If-Match",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_mcp_server_removes_file_and_resource() {
|
||||
let (app, _temp_dir, mcp_dir) = mcp_server_app();
|
||||
let created = create_mcp_server(&app, "sentry", "Sentry").await;
|
||||
let revision = revision_from(&created);
|
||||
|
||||
let response = app
|
||||
.clone()
|
||||
.oneshot(request_with_if_match(
|
||||
Method::DELETE,
|
||||
"/mcp-servers/sentry",
|
||||
&format!("\"{revision}\""),
|
||||
None,
|
||||
))
|
||||
.await
|
||||
.expect("delete mcp server should respond");
|
||||
response_status(
|
||||
response,
|
||||
StatusCode::NO_CONTENT,
|
||||
"DELETE /api/v1/mcp-servers/sentry",
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(!mcp_dir.join("sentry.toml").exists());
|
||||
let response = app
|
||||
.oneshot(empty_request(Method::GET, "/mcp-servers/sentry"))
|
||||
.await
|
||||
.expect("get deleted mcp server should respond");
|
||||
response_status(
|
||||
response,
|
||||
StatusCode::NOT_FOUND,
|
||||
"GET /api/v1/mcp-servers/sentry after delete",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn empty_mcp_server_name_is_unprocessable() {
|
||||
let (app, _temp_dir, _mcp_dir) = mcp_server_app();
|
||||
let mut body = mcp_server_body("sentry", "Sentry");
|
||||
body["name"] = json!(" ");
|
||||
|
||||
let response = app
|
||||
.oneshot(json_request(Method::POST, "/mcp-servers", &body))
|
||||
.await
|
||||
.expect("empty mcp server name create should respond");
|
||||
|
||||
response_status(
|
||||
response,
|
||||
StatusCode::UNPROCESSABLE_ENTITY,
|
||||
"POST /api/v1/mcp-servers empty name",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn empty_transport_command_is_unprocessable() {
|
||||
let (app, _temp_dir, _mcp_dir) = mcp_server_app();
|
||||
let mut body = mcp_server_body("local", "Local");
|
||||
body["transport"] = json!({
|
||||
"type": "stdio",
|
||||
"command": [],
|
||||
"env": {}
|
||||
});
|
||||
|
||||
let response = app
|
||||
.oneshot(json_request(Method::POST, "/mcp-servers", &body))
|
||||
.await
|
||||
.expect("empty transport command create should respond");
|
||||
|
||||
response_status(
|
||||
response,
|
||||
StatusCode::UNPROCESSABLE_ENTITY,
|
||||
"POST /api/v1/mcp-servers empty transport command",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn unknown_transport_type_is_unprocessable() {
|
||||
let (app, _temp_dir, _mcp_dir) = mcp_server_app();
|
||||
let mut body = mcp_server_body("sentry", "Sentry");
|
||||
body["transport"] = json!({
|
||||
"type": "carrier-pigeon",
|
||||
"url": "https://example.com/mcp"
|
||||
});
|
||||
|
||||
let response = app
|
||||
.oneshot(json_request(Method::POST, "/mcp-servers", &body))
|
||||
.await
|
||||
.expect("unknown transport type create should respond");
|
||||
|
||||
response_status(
|
||||
response,
|
||||
StatusCode::UNPROCESSABLE_ENTITY,
|
||||
"POST /api/v1/mcp-servers unknown transport type",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn invalid_mcp_server_id_is_bad_request() {
|
||||
let (app, _temp_dir, _mcp_dir) = mcp_server_app();
|
||||
|
||||
let response = app
|
||||
.oneshot(empty_request(Method::GET, "/mcp-servers/Bad_Id"))
|
||||
.await
|
||||
.expect("invalid id get should respond");
|
||||
|
||||
response_status(
|
||||
response,
|
||||
StatusCode::BAD_REQUEST,
|
||||
"GET /api/v1/mcp-servers/Bad_Id",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mcp_server_store_malformed_persisted_toml_fails_startup() {
|
||||
let temp_dir = tempfile::tempdir().expect("mcp server test tempdir should be created");
|
||||
let mcp_dir = temp_dir.path().join("mcps");
|
||||
tokio::fs::create_dir_all(&mcp_dir)
|
||||
.await
|
||||
.expect("mcp server dir should be created");
|
||||
tokio::fs::write(mcp_dir.join("broken.toml"), "not valid toml =")
|
||||
.await
|
||||
.expect("broken mcp server fixture should be written");
|
||||
|
||||
let result = TestAppStateBuilder::new()
|
||||
.active_config_path(temp_dir.path().join("settings.toml"))
|
||||
.try_build();
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mcp_servers_routes_require_authenticated_user() {
|
||||
let temp_dir = tempfile::tempdir().expect("mcp server test tempdir should be created");
|
||||
let state = TestAppStateBuilder::new()
|
||||
.active_config_path(temp_dir.path().join("settings.toml"))
|
||||
.build();
|
||||
let app = build_router(state, test_auth_mode());
|
||||
|
||||
let response = app
|
||||
.oneshot(empty_request(Method::GET, "/mcp-servers"))
|
||||
.await
|
||||
.expect("unauthenticated mcp server list should respond");
|
||||
|
||||
response_status(
|
||||
response,
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"GET /api/v1/mcp-servers without auth",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ mod environments;
|
|||
mod events;
|
||||
mod install;
|
||||
mod install_openai_compatible;
|
||||
mod mcp_servers;
|
||||
mod routing;
|
||||
mod run_files;
|
||||
mod runs;
|
||||
|
|
|
|||
|
|
@ -22,8 +22,13 @@ use crate::settings::McpTransport;
|
|||
/// A server-managed MCP server definition.
|
||||
///
|
||||
/// `id` and `revision` are derived (filename stem + content hash of the
|
||||
/// persisted TOML bytes) and are not stored in the file body.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
/// persisted TOML bytes) and are not stored in the persisted TOML body. It
|
||||
/// derives serde because it is the public wire type for `GET`/`POST`/`PUT` MCP
|
||||
/// server responses (reused by `fabro-api` via `with_replacement` rather than a
|
||||
/// parallel DTO). The serialized shape is the struct's own fields — `id` and
|
||||
/// `revision` are present on the wire but not in the persisted TOML body.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct McpServerDefinition {
|
||||
pub id: McpServerId,
|
||||
pub revision: McpServerRevision,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ api/human-in-the-loop-api.ts
|
|||
api/insights-api.ts
|
||||
api/install-api.ts
|
||||
api/integrations-api.ts
|
||||
api/mcpservers-api.ts
|
||||
api/models-api.ts
|
||||
api/playground-api.ts
|
||||
api/repos-api.ts
|
||||
|
|
@ -98,6 +99,7 @@ models/cost-source.ts
|
|||
models/create-automation-request.ts
|
||||
models/create-completion-request.ts
|
||||
models/create-environment-request.ts
|
||||
models/create-mcp-server-request.ts
|
||||
models/create-playground-chat-request.ts
|
||||
models/create-run-pull-request-request.ts
|
||||
models/create-run-session-request.ts
|
||||
|
|
@ -203,14 +205,22 @@ models/manifest-goal.ts
|
|||
models/manifest-target.ts
|
||||
models/manifest-workflow-config.ts
|
||||
models/manifest-workflow.ts
|
||||
models/mcp-http-protocol.ts
|
||||
models/mcp-server-list-meta.ts
|
||||
models/mcp-server-list-response.ts
|
||||
models/mcp-server-projection.ts
|
||||
models/mcp-server-settings.ts
|
||||
models/mcp-server-status-failed.ts
|
||||
models/mcp-server-status-ready.ts
|
||||
models/mcp-server-status.ts
|
||||
models/mcp-server.ts
|
||||
models/mcp-transport-http.ts
|
||||
models/mcp-transport-sandbox.ts
|
||||
models/mcp-transport-stdio.ts
|
||||
models/mcp-transport-view-http.ts
|
||||
models/mcp-transport-view-sandbox.ts
|
||||
models/mcp-transport-view-stdio.ts
|
||||
models/mcp-transport-view.ts
|
||||
models/mcp-transport.ts
|
||||
models/merge-method.ts
|
||||
models/merge-run-pull-request-request.ts
|
||||
|
|
@ -309,6 +319,7 @@ models/render-workflow-graph-format.ts
|
|||
models/render-workflow-graph-request.ts
|
||||
models/replace-automation-request.ts
|
||||
models/replace-environment-request.ts
|
||||
models/replace-mcp-server-request.ts
|
||||
models/repo-check-response-permissions.ts
|
||||
models/repo-check-response.ts
|
||||
models/repository-ref.ts
|
||||
|
|
|
|||
1
lib/packages/fabro-api-client/src/api.ts
generated
1
lib/packages/fabro-api-client/src/api.ts
generated
|
|
@ -24,6 +24,7 @@ export * from './api/human-in-the-loop-api';
|
|||
export * from './api/insights-api';
|
||||
export * from './api/install-api';
|
||||
export * from './api/integrations-api';
|
||||
export * from './api/mcpservers-api';
|
||||
export * from './api/models-api';
|
||||
export * from './api/playground-api';
|
||||
export * from './api/repos-api';
|
||||
|
|
|
|||
453
lib/packages/fabro-api-client/src/api/mcpservers-api.ts
generated
Normal file
453
lib/packages/fabro-api-client/src/api/mcpservers-api.ts
generated
Normal file
|
|
@ -0,0 +1,453 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
import type { Configuration } from '../configuration';
|
||||
import type { AxiosPromise, AxiosInstance, RawAxiosRequestConfig } from 'axios';
|
||||
import globalAxios from 'axios';
|
||||
// Some imports not used depending on template conditions
|
||||
// @ts-ignore
|
||||
import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction, replaceWithSerializableTypeIfNeeded } from '../common';
|
||||
// @ts-ignore
|
||||
import { BASE_PATH, COLLECTION_FORMATS, type RequestArgs, BaseAPI, RequiredError, operationServerMap } from '../base';
|
||||
// @ts-ignore
|
||||
import type { CreateMcpServerRequest } from '../models';
|
||||
// @ts-ignore
|
||||
import type { ErrorResponse } from '../models';
|
||||
// @ts-ignore
|
||||
import type { McpServer } from '../models';
|
||||
// @ts-ignore
|
||||
import type { McpServerListResponse } from '../models';
|
||||
// @ts-ignore
|
||||
import type { ReplaceMcpServerRequest } from '../models';
|
||||
/**
|
||||
* MCPServersApi - axios parameter creator
|
||||
*/
|
||||
export const MCPServersApiAxiosParamCreator = function (configuration?: Configuration) {
|
||||
return {
|
||||
/**
|
||||
* Creates a new MCP server definition. The id is the runtime MCP server name used in qualified tool names.
|
||||
* @summary Create MCP server
|
||||
* @param {CreateMcpServerRequest} createMcpServerRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
createMcpServer: async (createMcpServerRequest: CreateMcpServerRequest, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'createMcpServerRequest' is not null or undefined
|
||||
assertParamExists('createMcpServer', 'createMcpServerRequest', createMcpServerRequest)
|
||||
const localVarPath = `/api/v1/mcp-servers`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication SessionCookie required
|
||||
|
||||
// authentication BearerAuth required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
localVarHeaderParameter['Accept'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(createMcpServerRequest, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Deletes an MCP server definition when `If-Match` matches the current MCP server revision.
|
||||
* @summary Delete MCP server
|
||||
* @param {string} id Stable MCP server identifier, used as the runtime MCP server name in qualified tool names.
|
||||
* @param {string} ifMatch Current resource revision used for optimistic concurrency, as returned in the `ETag` response header.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteMcpServer: async (id: string, ifMatch: string, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('deleteMcpServer', 'id', id)
|
||||
// verify required parameter 'ifMatch' is not null or undefined
|
||||
assertParamExists('deleteMcpServer', 'ifMatch', ifMatch)
|
||||
const localVarPath = `/api/v1/mcp-servers/{id}`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication SessionCookie required
|
||||
|
||||
// authentication BearerAuth required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
localVarHeaderParameter['Accept'] = 'application/json';
|
||||
|
||||
if (ifMatch != null) {
|
||||
localVarHeaderParameter['If-Match'] = String(ifMatch);
|
||||
}
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Returns all server-managed MCP server definitions with transport env/header values omitted.
|
||||
* @summary List MCP servers
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listMcpServers: async (options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
const localVarPath = `/api/v1/mcp-servers`;
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication SessionCookie required
|
||||
|
||||
// authentication BearerAuth required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
localVarHeaderParameter['Accept'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Replaces an MCP server definition when `If-Match` matches the current MCP server revision.
|
||||
* @summary Replace MCP server
|
||||
* @param {string} id Stable MCP server identifier, used as the runtime MCP server name in qualified tool names.
|
||||
* @param {string} ifMatch Current resource revision used for optimistic concurrency, as returned in the `ETag` response header.
|
||||
* @param {ReplaceMcpServerRequest} replaceMcpServerRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
replaceMcpServer: async (id: string, ifMatch: string, replaceMcpServerRequest: ReplaceMcpServerRequest, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('replaceMcpServer', 'id', id)
|
||||
// verify required parameter 'ifMatch' is not null or undefined
|
||||
assertParamExists('replaceMcpServer', 'ifMatch', ifMatch)
|
||||
// verify required parameter 'replaceMcpServerRequest' is not null or undefined
|
||||
assertParamExists('replaceMcpServer', 'replaceMcpServerRequest', replaceMcpServerRequest)
|
||||
const localVarPath = `/api/v1/mcp-servers/{id}`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication SessionCookie required
|
||||
|
||||
// authentication BearerAuth required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||
localVarHeaderParameter['Accept'] = 'application/json';
|
||||
|
||||
if (ifMatch != null) {
|
||||
localVarHeaderParameter['If-Match'] = String(ifMatch);
|
||||
}
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
localVarRequestOptions.data = serializeDataIfNeeded(replaceMcpServerRequest, localVarRequestOptions, configuration)
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Returns one MCP server definition by id with transport env/header values omitted.
|
||||
* @summary Retrieve MCP server
|
||||
* @param {string} id Stable MCP server identifier, used as the runtime MCP server name in qualified tool names.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
retrieveMcpServer: async (id: string, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||
// verify required parameter 'id' is not null or undefined
|
||||
assertParamExists('retrieveMcpServer', 'id', id)
|
||||
const localVarPath = `/api/v1/mcp-servers/{id}`
|
||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||
let baseOptions;
|
||||
if (configuration) {
|
||||
baseOptions = configuration.baseOptions;
|
||||
}
|
||||
|
||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||
const localVarHeaderParameter = {} as any;
|
||||
const localVarQueryParameter = {} as any;
|
||||
|
||||
// authentication SessionCookie required
|
||||
|
||||
// authentication BearerAuth required
|
||||
// http bearer authentication required
|
||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||
|
||||
localVarHeaderParameter['Accept'] = 'application/json';
|
||||
|
||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||
|
||||
return {
|
||||
url: toPathString(localVarUrlObj),
|
||||
options: localVarRequestOptions,
|
||||
};
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* MCPServersApi - functional programming interface
|
||||
*/
|
||||
export const MCPServersApiFp = function(configuration?: Configuration) {
|
||||
const localVarAxiosParamCreator = MCPServersApiAxiosParamCreator(configuration)
|
||||
return {
|
||||
/**
|
||||
* Creates a new MCP server definition. The id is the runtime MCP server name used in qualified tool names.
|
||||
* @summary Create MCP server
|
||||
* @param {CreateMcpServerRequest} createMcpServerRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async createMcpServer(createMcpServerRequest: CreateMcpServerRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<McpServer>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.createMcpServer(createMcpServerRequest, options);
|
||||
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||
const localVarOperationServerBasePath = operationServerMap['MCPServersApi.createMcpServer']?.[localVarOperationServerIndex]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
* Deletes an MCP server definition when `If-Match` matches the current MCP server revision.
|
||||
* @summary Delete MCP server
|
||||
* @param {string} id Stable MCP server identifier, used as the runtime MCP server name in qualified tool names.
|
||||
* @param {string} ifMatch Current resource revision used for optimistic concurrency, as returned in the `ETag` response header.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async deleteMcpServer(id: string, ifMatch: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteMcpServer(id, ifMatch, options);
|
||||
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||
const localVarOperationServerBasePath = operationServerMap['MCPServersApi.deleteMcpServer']?.[localVarOperationServerIndex]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
* Returns all server-managed MCP server definitions with transport env/header values omitted.
|
||||
* @summary List MCP servers
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async listMcpServers(options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<McpServerListResponse>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.listMcpServers(options);
|
||||
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||
const localVarOperationServerBasePath = operationServerMap['MCPServersApi.listMcpServers']?.[localVarOperationServerIndex]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
* Replaces an MCP server definition when `If-Match` matches the current MCP server revision.
|
||||
* @summary Replace MCP server
|
||||
* @param {string} id Stable MCP server identifier, used as the runtime MCP server name in qualified tool names.
|
||||
* @param {string} ifMatch Current resource revision used for optimistic concurrency, as returned in the `ETag` response header.
|
||||
* @param {ReplaceMcpServerRequest} replaceMcpServerRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async replaceMcpServer(id: string, ifMatch: string, replaceMcpServerRequest: ReplaceMcpServerRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<McpServer>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.replaceMcpServer(id, ifMatch, replaceMcpServerRequest, options);
|
||||
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||
const localVarOperationServerBasePath = operationServerMap['MCPServersApi.replaceMcpServer']?.[localVarOperationServerIndex]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||
},
|
||||
/**
|
||||
* Returns one MCP server definition by id with transport env/header values omitted.
|
||||
* @summary Retrieve MCP server
|
||||
* @param {string} id Stable MCP server identifier, used as the runtime MCP server name in qualified tool names.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
async retrieveMcpServer(id: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<McpServer>> {
|
||||
const localVarAxiosArgs = await localVarAxiosParamCreator.retrieveMcpServer(id, options);
|
||||
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||
const localVarOperationServerBasePath = operationServerMap['MCPServersApi.retrieveMcpServer']?.[localVarOperationServerIndex]?.url;
|
||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* MCPServersApi - factory interface
|
||||
*/
|
||||
export const MCPServersApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
||||
const localVarFp = MCPServersApiFp(configuration)
|
||||
return {
|
||||
/**
|
||||
* Creates a new MCP server definition. The id is the runtime MCP server name used in qualified tool names.
|
||||
* @summary Create MCP server
|
||||
* @param {CreateMcpServerRequest} createMcpServerRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
createMcpServer(createMcpServerRequest: CreateMcpServerRequest, options?: RawAxiosRequestConfig): AxiosPromise<McpServer> {
|
||||
return localVarFp.createMcpServer(createMcpServerRequest, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* Deletes an MCP server definition when `If-Match` matches the current MCP server revision.
|
||||
* @summary Delete MCP server
|
||||
* @param {string} id Stable MCP server identifier, used as the runtime MCP server name in qualified tool names.
|
||||
* @param {string} ifMatch Current resource revision used for optimistic concurrency, as returned in the `ETag` response header.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
deleteMcpServer(id: string, ifMatch: string, options?: RawAxiosRequestConfig): AxiosPromise<void> {
|
||||
return localVarFp.deleteMcpServer(id, ifMatch, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* Returns all server-managed MCP server definitions with transport env/header values omitted.
|
||||
* @summary List MCP servers
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
listMcpServers(options?: RawAxiosRequestConfig): AxiosPromise<McpServerListResponse> {
|
||||
return localVarFp.listMcpServers(options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* Replaces an MCP server definition when `If-Match` matches the current MCP server revision.
|
||||
* @summary Replace MCP server
|
||||
* @param {string} id Stable MCP server identifier, used as the runtime MCP server name in qualified tool names.
|
||||
* @param {string} ifMatch Current resource revision used for optimistic concurrency, as returned in the `ETag` response header.
|
||||
* @param {ReplaceMcpServerRequest} replaceMcpServerRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
replaceMcpServer(id: string, ifMatch: string, replaceMcpServerRequest: ReplaceMcpServerRequest, options?: RawAxiosRequestConfig): AxiosPromise<McpServer> {
|
||||
return localVarFp.replaceMcpServer(id, ifMatch, replaceMcpServerRequest, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
/**
|
||||
* Returns one MCP server definition by id with transport env/header values omitted.
|
||||
* @summary Retrieve MCP server
|
||||
* @param {string} id Stable MCP server identifier, used as the runtime MCP server name in qualified tool names.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
retrieveMcpServer(id: string, options?: RawAxiosRequestConfig): AxiosPromise<McpServer> {
|
||||
return localVarFp.retrieveMcpServer(id, options).then((request) => request(axios, basePath));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* MCPServersApi - object-oriented interface
|
||||
*/
|
||||
export class MCPServersApi extends BaseAPI {
|
||||
/**
|
||||
* Creates a new MCP server definition. The id is the runtime MCP server name used in qualified tool names.
|
||||
* @summary Create MCP server
|
||||
* @param {CreateMcpServerRequest} createMcpServerRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
public createMcpServer(createMcpServerRequest: CreateMcpServerRequest, options?: RawAxiosRequestConfig) {
|
||||
return MCPServersApiFp(this.configuration).createMcpServer(createMcpServerRequest, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an MCP server definition when `If-Match` matches the current MCP server revision.
|
||||
* @summary Delete MCP server
|
||||
* @param {string} id Stable MCP server identifier, used as the runtime MCP server name in qualified tool names.
|
||||
* @param {string} ifMatch Current resource revision used for optimistic concurrency, as returned in the `ETag` response header.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
public deleteMcpServer(id: string, ifMatch: string, options?: RawAxiosRequestConfig) {
|
||||
return MCPServersApiFp(this.configuration).deleteMcpServer(id, ifMatch, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all server-managed MCP server definitions with transport env/header values omitted.
|
||||
* @summary List MCP servers
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
public listMcpServers(options?: RawAxiosRequestConfig) {
|
||||
return MCPServersApiFp(this.configuration).listMcpServers(options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces an MCP server definition when `If-Match` matches the current MCP server revision.
|
||||
* @summary Replace MCP server
|
||||
* @param {string} id Stable MCP server identifier, used as the runtime MCP server name in qualified tool names.
|
||||
* @param {string} ifMatch Current resource revision used for optimistic concurrency, as returned in the `ETag` response header.
|
||||
* @param {ReplaceMcpServerRequest} replaceMcpServerRequest
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
public replaceMcpServer(id: string, ifMatch: string, replaceMcpServerRequest: ReplaceMcpServerRequest, options?: RawAxiosRequestConfig) {
|
||||
return MCPServersApiFp(this.configuration).replaceMcpServer(id, ifMatch, replaceMcpServerRequest, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns one MCP server definition by id with transport env/header values omitted.
|
||||
* @summary Retrieve MCP server
|
||||
* @param {string} id Stable MCP server identifier, used as the runtime MCP server name in qualified tool names.
|
||||
* @param {*} [options] Override http request option.
|
||||
* @throws {RequiredError}
|
||||
*/
|
||||
public retrieveMcpServer(id: string, options?: RawAxiosRequestConfig) {
|
||||
return MCPServersApiFp(this.configuration).retrieveMcpServer(id, options).then((request) => request(this.axios, this.basePath));
|
||||
}
|
||||
}
|
||||
36
lib/packages/fabro-api-client/src/models/create-mcp-server-request.ts
generated
Normal file
36
lib/packages/fabro-api-client/src/models/create-mcp-server-request.ts
generated
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpTransport } from './mcp-transport';
|
||||
|
||||
/**
|
||||
* Request body for creating an MCP server definition.
|
||||
*/
|
||||
export interface CreateMcpServerRequest {
|
||||
/**
|
||||
* Stable MCP server identifier, used as the runtime MCP server name in qualified tool names.
|
||||
*/
|
||||
'id': string;
|
||||
/**
|
||||
* Human-readable label for display in management UIs.
|
||||
*/
|
||||
'display_name': string;
|
||||
'description'?: string | null;
|
||||
'transport': McpTransport;
|
||||
'startup_timeout_secs': number;
|
||||
'tool_timeout_secs': number;
|
||||
}
|
||||
10
lib/packages/fabro-api-client/src/models/index.ts
generated
10
lib/packages/fabro-api-client/src/models/index.ts
generated
|
|
@ -70,6 +70,7 @@ export * from './cost-source';
|
|||
export * from './create-automation-request';
|
||||
export * from './create-completion-request';
|
||||
export * from './create-environment-request';
|
||||
export * from './create-mcp-server-request';
|
||||
export * from './create-playground-chat-request';
|
||||
export * from './create-run-pull-request-request';
|
||||
export * from './create-run-session-request';
|
||||
|
|
@ -174,6 +175,10 @@ export * from './manifest-goal';
|
|||
export * from './manifest-target';
|
||||
export * from './manifest-workflow';
|
||||
export * from './manifest-workflow-config';
|
||||
export * from './mcp-http-protocol';
|
||||
export * from './mcp-server';
|
||||
export * from './mcp-server-list-meta';
|
||||
export * from './mcp-server-list-response';
|
||||
export * from './mcp-server-projection';
|
||||
export * from './mcp-server-settings';
|
||||
export * from './mcp-server-status';
|
||||
|
|
@ -183,6 +188,10 @@ export * from './mcp-transport';
|
|||
export * from './mcp-transport-http';
|
||||
export * from './mcp-transport-sandbox';
|
||||
export * from './mcp-transport-stdio';
|
||||
export * from './mcp-transport-view';
|
||||
export * from './mcp-transport-view-http';
|
||||
export * from './mcp-transport-view-sandbox';
|
||||
export * from './mcp-transport-view-stdio';
|
||||
export * from './merge-method';
|
||||
export * from './merge-run-pull-request-request';
|
||||
export * from './merge-run-pull-request-response';
|
||||
|
|
@ -280,6 +289,7 @@ export * from './render-workflow-graph-format';
|
|||
export * from './render-workflow-graph-request';
|
||||
export * from './replace-automation-request';
|
||||
export * from './replace-environment-request';
|
||||
export * from './replace-mcp-server-request';
|
||||
export * from './repo-check-response';
|
||||
export * from './repo-check-response-permissions';
|
||||
export * from './repository-ref';
|
||||
|
|
|
|||
26
lib/packages/fabro-api-client/src/models/mcp-http-protocol.ts
generated
Normal file
26
lib/packages/fabro-api-client/src/models/mcp-http-protocol.ts
generated
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Wire protocol used by HTTP and sandbox MCP transports.
|
||||
*/
|
||||
|
||||
export const McpHttpProtocol = {
|
||||
STREAMABLE_HTTP: 'streamable_http',
|
||||
SSE: 'sse'
|
||||
} as const;
|
||||
|
||||
export type McpHttpProtocol = typeof McpHttpProtocol[keyof typeof McpHttpProtocol];
|
||||
25
lib/packages/fabro-api-client/src/models/mcp-server-list-meta.ts
generated
Normal file
25
lib/packages/fabro-api-client/src/models/mcp-server-list-meta.ts
generated
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Metadata for MCP server list responses.
|
||||
*/
|
||||
export interface McpServerListMeta {
|
||||
/**
|
||||
* Total number of server-managed MCP server definitions.
|
||||
*/
|
||||
'total': number;
|
||||
}
|
||||
29
lib/packages/fabro-api-client/src/models/mcp-server-list-response.ts
generated
Normal file
29
lib/packages/fabro-api-client/src/models/mcp-server-list-response.ts
generated
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpServer } from './mcp-server';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpServerListMeta } from './mcp-server-list-meta';
|
||||
|
||||
/**
|
||||
* List envelope for MCP server definitions.
|
||||
*/
|
||||
export interface McpServerListResponse {
|
||||
'data': Array<McpServer>;
|
||||
'meta': McpServerListMeta;
|
||||
}
|
||||
46
lib/packages/fabro-api-client/src/models/mcp-server.ts
generated
Normal file
46
lib/packages/fabro-api-client/src/models/mcp-server.ts
generated
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpTransportView } from './mcp-transport-view';
|
||||
|
||||
/**
|
||||
* Public server-managed MCP server definition. Transport env/header values are never returned.
|
||||
*/
|
||||
export interface McpServer {
|
||||
/**
|
||||
* Stable MCP server identifier, used as the runtime MCP server name in qualified tool names.
|
||||
*/
|
||||
'id': string;
|
||||
/**
|
||||
* Stable revision used with `If-Match` for optimistic concurrency.
|
||||
*/
|
||||
'revision': string;
|
||||
/**
|
||||
* Human-readable label for display in management UIs.
|
||||
*/
|
||||
'display_name': string;
|
||||
'description': string | null;
|
||||
'transport': McpTransportView;
|
||||
/**
|
||||
* Seconds to wait for the MCP server to become ready at connect time.
|
||||
*/
|
||||
'startup_timeout_secs': number;
|
||||
/**
|
||||
* Seconds to allow each MCP tool call before timing out.
|
||||
*/
|
||||
'tool_timeout_secs': number;
|
||||
}
|
||||
|
|
@ -13,9 +13,16 @@
|
|||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpHttpProtocol } from './mcp-http-protocol';
|
||||
|
||||
/**
|
||||
* HTTP transport that connects to a remote MCP server URL.
|
||||
*/
|
||||
export interface McpTransportHttp {
|
||||
'type': McpTransportHttpTypeEnum;
|
||||
'protocol'?: McpHttpProtocol;
|
||||
'url': string;
|
||||
'headers': { [key: string]: string; };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,9 +13,19 @@
|
|||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpHttpProtocol } from './mcp-http-protocol';
|
||||
|
||||
/**
|
||||
* Sandbox transport that launches the MCP server inside the run sandbox and connects over HTTP.
|
||||
*/
|
||||
export interface McpTransportSandbox {
|
||||
'type': McpTransportSandboxTypeEnum;
|
||||
'protocol'?: McpHttpProtocol;
|
||||
/**
|
||||
* Command and arguments used to launch the in-sandbox MCP server.
|
||||
*/
|
||||
'command': Array<string>;
|
||||
'port': number;
|
||||
'env': { [key: string]: string; };
|
||||
|
|
|
|||
|
|
@ -14,8 +14,14 @@
|
|||
|
||||
|
||||
|
||||
/**
|
||||
* Stdio transport that launches a local MCP server subprocess.
|
||||
*/
|
||||
export interface McpTransportStdio {
|
||||
'type': McpTransportStdioTypeEnum;
|
||||
/**
|
||||
* Command and arguments used to launch the MCP server.
|
||||
*/
|
||||
'command': Array<string>;
|
||||
'env': { [key: string]: string; };
|
||||
}
|
||||
|
|
|
|||
37
lib/packages/fabro-api-client/src/models/mcp-transport-view-http.ts
generated
Normal file
37
lib/packages/fabro-api-client/src/models/mcp-transport-view-http.ts
generated
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpHttpProtocol } from './mcp-http-protocol';
|
||||
|
||||
/**
|
||||
* HTTP transport view. Header values are omitted.
|
||||
*/
|
||||
export interface McpTransportViewHttp {
|
||||
'type': McpTransportViewHttpTypeEnum;
|
||||
'protocol'?: McpHttpProtocol;
|
||||
'url': string;
|
||||
/**
|
||||
* HTTP header names configured for this transport.
|
||||
*/
|
||||
'header_keys': Array<string>;
|
||||
}
|
||||
|
||||
export const McpTransportViewHttpTypeEnum = {
|
||||
HTTP: 'http'
|
||||
} as const;
|
||||
|
||||
export type McpTransportViewHttpTypeEnum = typeof McpTransportViewHttpTypeEnum[keyof typeof McpTransportViewHttpTypeEnum];
|
||||
41
lib/packages/fabro-api-client/src/models/mcp-transport-view-sandbox.ts
generated
Normal file
41
lib/packages/fabro-api-client/src/models/mcp-transport-view-sandbox.ts
generated
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpHttpProtocol } from './mcp-http-protocol';
|
||||
|
||||
/**
|
||||
* Sandbox transport view. Environment variable values are omitted.
|
||||
*/
|
||||
export interface McpTransportViewSandbox {
|
||||
'type': McpTransportViewSandboxTypeEnum;
|
||||
'protocol'?: McpHttpProtocol;
|
||||
/**
|
||||
* Command and arguments used to launch the in-sandbox MCP server.
|
||||
*/
|
||||
'command': Array<string>;
|
||||
'port': number;
|
||||
/**
|
||||
* Environment variable names configured for this transport.
|
||||
*/
|
||||
'env_keys': Array<string>;
|
||||
}
|
||||
|
||||
export const McpTransportViewSandboxTypeEnum = {
|
||||
SANDBOX: 'sandbox'
|
||||
} as const;
|
||||
|
||||
export type McpTransportViewSandboxTypeEnum = typeof McpTransportViewSandboxTypeEnum[keyof typeof McpTransportViewSandboxTypeEnum];
|
||||
36
lib/packages/fabro-api-client/src/models/mcp-transport-view-stdio.ts
generated
Normal file
36
lib/packages/fabro-api-client/src/models/mcp-transport-view-stdio.ts
generated
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Stdio transport view. Environment variable values are omitted.
|
||||
*/
|
||||
export interface McpTransportViewStdio {
|
||||
'type': McpTransportViewStdioTypeEnum;
|
||||
/**
|
||||
* Command and arguments used to launch the MCP server.
|
||||
*/
|
||||
'command': Array<string>;
|
||||
/**
|
||||
* Environment variable names configured for this transport.
|
||||
*/
|
||||
'env_keys': Array<string>;
|
||||
}
|
||||
|
||||
export const McpTransportViewStdioTypeEnum = {
|
||||
STDIO: 'stdio'
|
||||
} as const;
|
||||
|
||||
export type McpTransportViewStdioTypeEnum = typeof McpTransportViewStdioTypeEnum[keyof typeof McpTransportViewStdioTypeEnum];
|
||||
33
lib/packages/fabro-api-client/src/models/mcp-transport-view.ts
generated
Normal file
33
lib/packages/fabro-api-client/src/models/mcp-transport-view.ts
generated
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpHttpProtocol } from './mcp-http-protocol';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpTransportViewHttp } from './mcp-transport-view-http';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpTransportViewSandbox } from './mcp-transport-view-sandbox';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpTransportViewStdio } from './mcp-transport-view-stdio';
|
||||
|
||||
/**
|
||||
* @type McpTransportView
|
||||
* MCP server transport configuration returned by catalog read APIs.
|
||||
*/
|
||||
export type McpTransportView = { type: 'http' } & McpTransportViewHttp | { type: 'sandbox' } & McpTransportViewSandbox | { type: 'stdio' } & McpTransportViewStdio;
|
||||
|
|
@ -13,6 +13,9 @@
|
|||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpHttpProtocol } from './mcp-http-protocol';
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpTransportHttp } from './mcp-transport-http';
|
||||
|
|
@ -25,5 +28,6 @@ import type { McpTransportStdio } from './mcp-transport-stdio';
|
|||
|
||||
/**
|
||||
* @type McpTransport
|
||||
* MCP server transport configuration. The `type` field selects stdio, HTTP, or sandbox transport. Unknown `type` discriminator values are reported as domain validation errors with HTTP 422.
|
||||
*/
|
||||
export type McpTransport = McpTransportHttp | McpTransportSandbox | McpTransportStdio;
|
||||
export type McpTransport = { type: 'http' } & McpTransportHttp | { type: 'sandbox' } & McpTransportSandbox | { type: 'stdio' } & McpTransportStdio;
|
||||
|
|
|
|||
32
lib/packages/fabro-api-client/src/models/replace-mcp-server-request.ts
generated
Normal file
32
lib/packages/fabro-api-client/src/models/replace-mcp-server-request.ts
generated
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* Fabro Run API
|
||||
* HTTP API for managing Fabro workflow run executions.
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
// May contain unused imports in some cases
|
||||
// @ts-ignore
|
||||
import type { McpTransport } from './mcp-transport';
|
||||
|
||||
/**
|
||||
* Request body for replacing an MCP server definition. The path id is authoritative.
|
||||
*/
|
||||
export interface ReplaceMcpServerRequest {
|
||||
/**
|
||||
* Human-readable label for display in management UIs.
|
||||
*/
|
||||
'display_name': string;
|
||||
'description'?: string | null;
|
||||
'transport': McpTransport;
|
||||
'startup_timeout_secs': number;
|
||||
'tool_timeout_secs': number;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue