headless_lms_mcp/handlers/exercise/
mod.rs1mod get;
6mod list;
7mod patch;
8mod paths;
9mod review_config;
10mod specs;
11
12use chrono::{DateTime, Utc};
13use headless_lms_models::library::agent_changesets::course_state::{CourseState, CourseView};
14use serde::Serialize;
15use uuid::Uuid;
16
17use crate::access::{connection, open_course};
18use crate::state::McpState;
19use crate::tools::{RequestContext, ToolError};
20
21pub use get::GetExercise;
22pub use list::ListExercises;
23pub use patch::PatchExercise;
24
25const REVIEW_INSTRUCTIONS: &str = "review_instructions";
26
27async fn open_course_with_exercise(
29 state: &McpState,
30 ctx: &RequestContext,
31 course_id: Uuid,
32 exercise_id: Uuid,
33) -> Result<CourseView, ToolError> {
34 let mut view = open_course(state, ctx, course_id).await?;
35 let page_ids: Vec<Uuid> = [
36 view.staged.exercise_page_id(exercise_id),
37 view.live.exercise_page_id(exercise_id),
38 ]
39 .into_iter()
40 .flatten()
41 .collect();
42 view.load_exercises_on_pages(&mut *connection(state).await?, &page_ids)
43 .await?;
44 Ok(view)
45}
46
47fn missing_exercise(view: &CourseView, exercise_id: Uuid) -> ToolError {
48 ToolError::NotFound {
49 message: format!(
50 "course {} has no exercise {exercise_id} on its pages; exercise blocks in get_page_content show exercise ids, and exam exercises can't be edited here",
51 view.course.id
52 ),
53 }
54}
55
56#[derive(Serialize)]
58struct ExerciseDeadline {
59 own: Option<DateTime<Utc>>,
60 effective: Option<DateTime<Utc>>,
61}
62
63impl ExerciseDeadline {
64 fn of(state: &CourseState, exercise_id: Uuid) -> Self {
65 Self {
66 own: state.exercise(exercise_id).and_then(|e| e.deadline),
67 effective: state.effective_exercise_deadline(exercise_id),
68 }
69 }
70}