mcp_server_collection now also a library

own_assist now bundles the server collection by default
This commit is contained in:
2026-05-14 17:39:10 +02:00
parent 18221b7b56
commit 5f16db06f5
18 changed files with 717 additions and 141 deletions
+2 -1
View File
@@ -1109,12 +1109,13 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "own_assist_common"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"log",
"serde",
"thiserror",
"toml",
"tracing-subscriber",
]
[[package]]
+1 -1
View File
@@ -11,8 +11,8 @@ use rmcp::ErrorData;
use rmcp::model::ErrorCode;
use thiserror::Error;
use tower_http::auth::AddAuthorization;
use tracing::event;
use tracing::Level;
use tracing::event;
use uuid::Uuid;
pub(crate) type AuthorizedCaldavClient =
+8 -8
View File
@@ -5,18 +5,18 @@ use serde::{Deserialize, Serialize};
use toml::map::Map;
#[derive(Debug, Deserialize, Serialize)]
pub(crate) struct Config {
pub struct Config {
#[serde(rename = "bind-address")]
pub(crate) bind_address: Option<url::Url>,
pub(crate) servers: Vec<ServerConfig>,
pub bind_address: Option<url::Url>,
pub servers: Vec<ServerConfig>,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub(crate) struct ServerConfig {
pub(crate) path: String,
pub(crate) r#type: McpServiceType,
#[serde(rename = "additional-properties")]
pub(crate) additional_properties: Map<String, toml::Value>,
pub struct ServerConfig {
pub path: String,
pub r#type: McpServiceType,
#[serde(rename = "additional-properties", default = "Map::new")]
pub additional_properties: Map<String, toml::Value>,
}
impl Config {
+2 -1
View File
@@ -62,7 +62,8 @@ impl DateTimeHandler {
Fri => "Friday",
Sat => "Saturday",
Sun => "Sunday",
}.to_string()
}
.to_string()
}
}
+95
View File
@@ -0,0 +1,95 @@
pub mod config;
#[cfg(feature = "datetime")]
pub mod datetime;
#[cfg(feature = "caldav")]
pub mod caldav;
mod server_handler;
#[cfg(feature = "caldav")]
use crate::caldav::CalDavHandler;
use crate::config::{Config, ServerConfig};
#[cfg(feature = "datetime")]
use crate::datetime::DateTimeHandler;
use crate::server_handler::{McpServerHandler, McpServerHandlerError};
use axum::Router;
use axum::response::Json;
use own_assist_common::exit_msg;
use serde::{Deserialize, Serialize};
use tracing::{Level, event};
#[derive(Debug, Clone, Deserialize, Serialize)]
#[non_exhaustive]
pub enum McpServiceType {
#[cfg(feature = "datetime")]
DateTime,
#[cfg(feature = "caldav")]
CalDav,
}
pub(crate) async fn mcp_router(
server_configs: &Vec<ServerConfig>,
) -> Result<Router, McpServerHandlerError> {
let mut router = Router::new();
for config in server_configs {
router = match config.r#type {
#[cfg(feature = "datetime")]
McpServiceType::DateTime => router.route_service(
config.path.as_str(),
DateTimeHandler::mcp_service(config.additional_properties.clone()).await?,
),
#[cfg(feature = "caldav")]
McpServiceType::CalDav => router.route_service(
config.path.as_str(),
CalDavHandler::mcp_service(config.additional_properties.clone()).await?,
),
}
}
Ok(router)
}
fn bind_address_format(url: url::Url) -> String {
format!(
"{}:{}",
url.host_str().expect("No host specified"),
url.port().unwrap_or(8000)
)
}
pub async fn serve(config: Config) {
let routes = Json(
config
.servers
.clone()
.iter()
.map(|server| server.path.clone())
.collect::<Vec<_>>(),
);
let router = mcp_router(&config.servers)
.await
.inspect_err(exit_msg!("Error handling server: {e}"))
.unwrap()
.route("/", axum::routing::get(|| async { routes }));
let bind_address = config
.bind_address
.unwrap_or(url::Url::parse("http://localhost:8000").unwrap());
event!(Level::INFO, "binding address at {bind_address}");
let tcp_listener = tokio::net::TcpListener::bind(bind_address_format(bind_address))
.await
.inspect_err(exit_msg!("Error bind tcp listener: {e:#?}"))
.unwrap();
let ct = tokio_util::sync::CancellationToken::new();
let _ = axum::serve(tcp_listener, router)
.with_graceful_shutdown(async move {
tokio::signal::ctrl_c().await.unwrap();
ct.cancel();
})
.await;
}
+6 -102
View File
@@ -1,111 +1,15 @@
mod config;
#[cfg(feature = "datetime")]
pub mod datetime;
#[cfg(feature = "caldav")]
pub mod caldav;
mod server_handler;
#[cfg(feature = "caldav")]
use crate::caldav::CalDavHandler;
use crate::config::{Config, ServerConfig};
#[cfg(feature = "datetime")]
use crate::datetime::DateTimeHandler;
use crate::server_handler::{McpServerHandler, McpServerHandlerError};
use axum::Router;
use axum::response::Json;
use serde::{Deserialize, Serialize};
use mcp_server_collection::config::Config;
use mcp_server_collection::serve;
use own_assist_common::{exit_msg, init_tracing_subscriber};
use tokio::main;
use tracing::{event, Level};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use own_assist_common::exit_msg;
#[derive(Debug, Clone, Deserialize, Serialize)]
#[non_exhaustive]
pub enum McpServiceType {
#[cfg(feature = "datetime")]
DateTime,
#[cfg(feature = "caldav")]
CalDav,
}
pub(crate) async fn mcp_router(
server_configs: &Vec<ServerConfig>,
) -> Result<Router, McpServerHandlerError> {
let mut router = Router::new();
for config in server_configs {
router = match config.r#type {
#[cfg(feature = "datetime")]
McpServiceType::DateTime => router.route_service(
config.path.as_str(),
DateTimeHandler::mcp_service(config.additional_properties.clone()).await?,
),
#[cfg(feature = "caldav")]
McpServiceType::CalDav => router.route_service(
config.path.as_str(),
CalDavHandler::mcp_service(config.additional_properties.clone()).await?,
),
}
}
Ok(router)
}
fn bind_address_format(url: url::Url) -> String {
format!(
"{}:{}",
url.host_str().expect("No host specified"),
url.port().unwrap_or(8000)
)
}
#[main]
async fn main() {
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "debug".to_string().into()),
)
.with(tracing_subscriber::fmt::layer())
.init();
init_tracing_subscriber();
let config = Config::from_file()
.inspect_err(exit_msg!("Error loading config: {e}"))
.inspect_err(exit_msg!("Error loading config"))
.unwrap();
let routes = Json(
config
.servers
.clone()
.iter()
.map(|server| server.path.clone())
.collect::<Vec<_>>(),
);
let router = mcp_router(&config.servers)
.await
.inspect_err(exit_msg!("Error handling server: {e}"))
.unwrap()
.route("/", axum::routing::get(|| async { routes }));
let bind_address = config
.bind_address
.unwrap_or(url::Url::parse("http://localhost:8000").unwrap());
event!(Level::INFO,"binding address at {bind_address}");
let tcp_listener = tokio::net::TcpListener::bind(bind_address_format(bind_address))
.await
.inspect_err(exit_msg!("Error bind tcp listener: {e:#?}"))
.unwrap();
let ct = tokio_util::sync::CancellationToken::new();
let _ = axum::serve(tcp_listener, router)
.with_graceful_shutdown(async move {
tokio::signal::ctrl_c().await.unwrap();
ct.cancel();
})
.await;
serve(config).await;
}