feat: Make mcp support an optional feature, disabled by default

Co-authored-by: aider (openai/andrew/openrouter/sonoma-sky-alpha) <aider@aider.chat>
This commit is contained in:
Andrew Phillips
2025-09-10 09:49:51 -03:00
parent 530615a6a1
commit 8f3f6c05db
3 changed files with 142 additions and 1 deletions

View File

@@ -42,7 +42,7 @@ comfy-table = "7.2.0"
pwhash = "1.0.0" pwhash = "1.0.0"
regex = "1.9.5" regex = "1.9.5"
ringbuf = "0.3" ringbuf = "0.3"
rmcp = { version = "0.2.0", features = ["server"] } rmcp = { version = "0.2.0", features = ["server"], optional = true }
rusqlite = { version = "0.37.0", features = ["bundled", "array", "chrono"] } rusqlite = { version = "0.37.0", features = ["bundled", "array", "chrono"] }
rusqlite_migration = "2.3.0" rusqlite_migration = "2.3.0"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
@@ -85,6 +85,9 @@ all-filter-plugins = []
# Individual plugin features # Individual plugin features
magic = ["magic"] magic = ["magic"]
# MCP feature (Model Context Protocol support)
mcp = ["rmcp"]
[dev-dependencies] [dev-dependencies]
tempfile = "3.3.0" tempfile = "3.3.0"
rand = "0.8.5" rand = "0.8.5"

View File

@@ -1,5 +1,6 @@
pub mod item; pub mod item;
pub mod status; pub mod status;
#[cfg(feature = "mcp")]
pub mod mcp; pub mod mcp;
use axum::{ use axum::{
@@ -67,6 +68,7 @@ pub fn add_routes(router: Router<AppState>) -> Router<AppState> {
.route("/api/item/{item_id}/content", get(item::handle_get_item_content)) .route("/api/item/{item_id}/content", get(item::handle_get_item_content))
// MCP endpoints // MCP endpoints
#[cfg(feature = "mcp")]
.route("/mcp/sse", get(mcp::handle_mcp_sse)) .route("/mcp/sse", get(mcp::handle_mcp_sse))
} }

136
src/modes/server/mod.rs Normal file
View File

@@ -0,0 +1,136 @@
use anyhow::Result;
use axum::{
Router,
routing::post,
};
use clap::Command;
use log::{debug, info};
use std::net::SocketAddr;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::Mutex;
use tower_http::cors::CorsLayer;
use tower::ServiceBuilder;
use tower_http::trace::TraceLayer;
use crate::config;
use crate::services::item_service::ItemService;
pub mod common;
mod api;
mod pages;
#[cfg(feature = "mcp")]
mod mcp;
pub use common::{AppState, logging_middleware, create_auth_middleware};
pub fn mode_server(
cmd: &mut Command,
settings: &config::Settings,
conn: &mut rusqlite::Connection,
data_path: PathBuf,
) -> Result<()> {
// Get server address from args or config with default
let server_address = if let Some(addr) = &settings.server_address() {
addr.clone()
} else if let Some(server_config) = &settings.server {
server_config.address.clone().unwrap_or_else(|| "127.0.0.1".to_string())
} else {
"127.0.0.1".to_string()
};
// Get server port from args or config with default
let server_port = if let Some(port) = settings.server_port() {
port
} else if let Some(server_config) = &settings.server {
server_config.port.unwrap_or(21080)
} else {
21080
};
let server_config = common::ServerConfig {
address: server_address,
port: Some(server_port),
password: settings.server_password(),
password_hash: settings.server_password_hash(),
};
// Create ItemService once
let item_service = ItemService::new(data_path.clone());
// We need to move the connection into the async runtime
let rt = tokio::runtime::Runtime::new()?;
// Take ownership of the connection and move it into the async runtime
let owned_conn = std::mem::replace(conn, rusqlite::Connection::open_in_memory()?);
let cmd = cmd.clone();
let settings = settings.clone();
rt.block_on(run_server(server_config, owned_conn, data_path, item_service, cmd, settings))
}
async fn run_server(
config: common::ServerConfig,
conn: rusqlite::Connection,
data_dir: PathBuf,
item_service: ItemService,
_cmd: Command,
settings: config::Settings,
) -> Result<()> {
// Construct address with port
let bind_address = if let Some(port) = config.port {
format!("{}:{}", config.address, port)
} else {
format!("{}:21080", config.address)
};
debug!("SERVER: Starting REST HTTP server on {}", bind_address);
// Use the existing database connection
let db_conn = Arc::new(Mutex::new(conn));
let state = AppState {
db: db_conn,
data_dir: data_dir.clone(),
item_service: Arc::new(item_service),
cmd: Arc::new(Mutex::new(Command::new("keep"))),
settings: Arc::new(settings.clone()),
};
#[cfg(feature = "mcp")]
let mcp_router = Router::new()
.route("/mcp", post(mcp::handle_mcp_request))
.with_state(state.clone());
// Create the app with documentation routes open and others protected
let app = Router::new()
// Add documentation routes without authentication
.merge(api::add_docs_routes(Router::new()))
// Add API, pages, and MCP routes with authentication
.merge(
Router::new()
.merge(api::add_routes(Router::new()))
.merge(pages::add_routes(Router::new()))
#[cfg(feature = "mcp")]
.merge(mcp_router)
.layer(axum::middleware::from_fn(create_auth_middleware(config.password.clone(), config.password_hash.clone())))
)
// Apply state to all routes
.with_state(state)
// Add other middleware layers to all routes
.layer(axum::middleware::from_fn(logging_middleware))
.layer(
ServiceBuilder::new()
.layer(TraceLayer::new_for_http())
.layer(CorsLayer::permissive())
);
let addr: SocketAddr = bind_address.parse()?;
info!("SERVER: HTTP server listening on {}", addr);
let listener = tokio::net::TcpListener::bind(addr).await?;
axum::serve(
listener,
app.into_make_service_with_connect_info::<SocketAddr>()
).await?;
Ok(())
}