headless_lms_mcp/
state.rs1use std::sync::Arc;
4use std::time::{Duration, Instant};
5
6use futures::future::BoxFuture;
7use headless_lms_base::{config::ApplicationConfiguration, jwt::JwtKey};
8use headless_lms_models::{
9 ModelResult, SpecFetcher, exercise_service_info::ExerciseServiceInfoApi,
10};
11use headless_lms_utils::file_store::FileStore;
12use sqlx::PgPool;
13use tokio::sync::Mutex;
14use url::Url;
15use uuid::Uuid;
16
17use crate::validator::{BlockSchemas, BlockValidator, ValidatorError};
18
19const SCHEMA_CACHE_TTL: Duration = Duration::from_secs(300);
20
21pub type ServiceInfoFetcher =
23 Arc<dyn Fn(Url) -> BoxFuture<'static, ModelResult<ExerciseServiceInfoApi>> + Send + Sync>;
24
25#[derive(Clone)]
27pub struct ExerciseServiceClients {
28 pub spec_fetcher: Arc<dyn Fn() -> Box<dyn SpecFetcher + Send + Sync> + Send + Sync>,
30 pub fetch_service_info: ServiceInfoFetcher,
31}
32
33pub struct McpState {
35 pub pool: PgPool,
36 pub app_conf: ApplicationConfiguration,
37 pub jwt_key: Arc<JwtKey>,
38 pub file_store: Arc<dyn FileStore + Send + Sync>,
39 pub validator: Arc<dyn BlockValidator>,
40 pub exercise_services: ExerciseServiceClients,
41 block_schemas: Mutex<Option<(Instant, Arc<BlockSchemas>)>>,
43}
44
45impl McpState {
46 pub fn new(
48 pool: PgPool,
49 app_conf: ApplicationConfiguration,
50 jwt_key: Arc<JwtKey>,
51 file_store: Arc<dyn FileStore + Send + Sync>,
52 validator: Arc<dyn BlockValidator>,
53 exercise_services: ExerciseServiceClients,
54 ) -> Self {
55 Self {
56 pool,
57 app_conf,
58 jwt_key,
59 file_store,
60 validator,
61 exercise_services,
62 block_schemas: Mutex::new(None),
63 }
64 }
65
66 pub fn review_url(&self, changeset_id: Uuid) -> String {
68 self.app_conf
69 .public_url(&format!("/manage/agent-changesets/{changeset_id}"))
70 }
71
72 pub async fn block_schemas(&self) -> Result<Arc<BlockSchemas>, ValidatorError> {
74 let mut cache = self.block_schemas.lock().await;
75 if let Some((fetched_at, schemas)) = &*cache
76 && fetched_at.elapsed() < SCHEMA_CACHE_TTL
77 {
78 return Ok(schemas.clone());
79 }
80 let schemas = Arc::new(self.validator.block_schemas().await?);
81 *cache = Some((Instant::now(), schemas.clone()));
82 Ok(schemas)
83 }
84}