headless_lms_server/domain/
mcp_endpoint.rs1use std::sync::Arc;
4
5use headless_lms_base::config::{ApplicationConfiguration, McpConfiguration};
6use headless_lms_mcp::{
7 http::McpEndpoint,
8 state::{ExerciseServiceClients, McpState},
9 validator::{BlockValidator, CmsValidatorClient, UnconfiguredValidator},
10};
11use headless_lms_utils::file_store::FileStore;
12use sqlx::PgPool;
13use uuid::Uuid;
14
15use crate::domain::models_requests::{self, JwtKey};
16
17pub fn build_mcp_endpoint(
20 db_pool: PgPool,
21 app_conf: &ApplicationConfiguration,
22 jwt_key: Arc<JwtKey>,
23 file_store: Arc<dyn FileStore + Send + Sync>,
24) -> McpEndpoint {
25 let validator = block_validator(app_conf.mcp_configuration.as_ref());
26 let base_url = app_conf.base_url.clone();
27 let spec_jwt_key = jwt_key.clone();
28 let exercise_services = ExerciseServiceClients {
29 spec_fetcher: Arc::new(move || {
30 Box::new(models_requests::make_spec_fetcher(
31 base_url.clone(),
32 Uuid::new_v4(),
33 spec_jwt_key.clone(),
34 ))
35 }),
36 fetch_service_info: Arc::new(models_requests::fetch_service_info),
37 };
38 let state = McpState::new(
39 db_pool,
40 app_conf.clone(),
41 jwt_key,
42 file_store,
43 validator,
44 exercise_services,
45 );
46 McpEndpoint::new(state)
47}
48
49fn block_validator(mcp_configuration: Option<&McpConfiguration>) -> Arc<dyn BlockValidator> {
50 let Some(mcp_configuration) = mcp_configuration else {
51 return unconfigured("CMS_INTERNAL_URL and AGENT_VALIDATOR_SECRET must both be set".into());
52 };
53 match CmsValidatorClient::new(
54 mcp_configuration.cms_internal_url.clone(),
55 mcp_configuration.agent_validator_secret.clone(),
56 ) {
57 Ok(client) => Arc::new(client),
58 Err(err) => unconfigured(format!("couldn't build the validator's HTTP client: {err}")),
59 }
60}
61
62fn unconfigured(reason: String) -> Arc<dyn BlockValidator> {
63 warn!("Agent writes through MCP are disabled: {reason}");
64 Arc::new(UnconfiguredValidator { reason })
65}