Initial import
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
# Task 10 — Wire 4 tutor tools into `QuantumBridgeServer`
|
||||
|
||||
> **Index:** [README](README.md). **Spec:** [design](../../specs/2026-04-29-quantum-tutor-design.md).
|
||||
|
||||
## Goal
|
||||
|
||||
Register `get_lesson`, `check_exercise`, `explain_result`, `get_progress` as MCP tools. The wrapper:
|
||||
1. Loads `UserProgress` via `ProgressStore` (sandboxed by env var).
|
||||
2. Routes through `self.backend` (set up in Task 0).
|
||||
3. Maps `protocol_error` to `McpError::invalid_params`; all other errors stay in the structured payload.
|
||||
4. Persists `mark_solved` on `passed: true`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Task 9 merged (all 4 response functions exist in `tutor_tools.rs`).
|
||||
|
||||
## Files
|
||||
|
||||
- Modify: `src/tools/mod.rs`
|
||||
|
||||
## Steps
|
||||
|
||||
- [ ] **Step 1: Add imports + parameter structs at the top of `src/tools/mod.rs`**
|
||||
|
||||
```rust
|
||||
use crate::progress::ProgressStore;
|
||||
use crate::tutor::CurriculumLoader;
|
||||
use crate::tools::tutor_tools;
|
||||
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
pub struct GetLessonParams {
|
||||
/// Module number (1–7).
|
||||
pub module_id: u32,
|
||||
/// Lesson number within the module (optional, defaults to first lesson with pending exercises).
|
||||
pub lesson_id: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
pub struct CheckExerciseParams {
|
||||
/// Exercise identifier, e.g. "1-1-a".
|
||||
pub exercise_id: String,
|
||||
/// OpenQASM 3.0 source of the circuit to verify.
|
||||
pub circuit: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
pub struct ExplainResultParams {
|
||||
/// OpenQASM 3.0 source of the circuit that was executed.
|
||||
pub circuit: String,
|
||||
/// Measurement counts from run_circuit (bitstring → count).
|
||||
pub counts: serde_json::Value,
|
||||
/// Optional statevector from run_circuit with return_statevector=true.
|
||||
pub statevector: Option<serde_json::Value>,
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add helpers on `QuantumBridgeServer` (outside the `#[tool_router]` block)**
|
||||
|
||||
```rust
|
||||
impl QuantumBridgeServer {
|
||||
fn loader(&self) -> CurriculumLoader { CurriculumLoader::default() }
|
||||
|
||||
fn store(&self) -> Result<ProgressStore, McpError> {
|
||||
let path = ProgressStore::default_path()
|
||||
.map_err(|e| McpError::internal_error(e.to_string(), None))?;
|
||||
Ok(ProgressStore::new(path))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Add the 4 tool methods inside `#[tool_router(server_handler)] impl QuantumBridgeServer`**
|
||||
|
||||
```rust
|
||||
#[tool(description = "Get a quantum-computing lesson with concept, example circuit, and the next pending exercise.")]
|
||||
async fn get_lesson(
|
||||
&self,
|
||||
Parameters(GetLessonParams { module_id, lesson_id }): Parameters<GetLessonParams>,
|
||||
) -> Result<CallToolResult, McpError> {
|
||||
let store = self.store()?;
|
||||
let progress = store.load();
|
||||
let json = tutor_tools::get_lesson_response(&self.loader(), &progress, module_id, lesson_id);
|
||||
if let Some(err) = json.get("error").and_then(|v| v.as_str()).map(str::to_owned) {
|
||||
return Err(McpError::invalid_params(err, None));
|
||||
}
|
||||
Ok(CallToolResult::success(vec![Content::text(json.to_string())]))
|
||||
}
|
||||
|
||||
#[tool(description = "Verify a submitted OpenQASM 3.0 circuit against a curriculum exercise. Returns pass/fail with feedback.")]
|
||||
async fn check_exercise(
|
||||
&self,
|
||||
Parameters(CheckExerciseParams { exercise_id, circuit }): Parameters<CheckExerciseParams>,
|
||||
) -> Result<CallToolResult, McpError> {
|
||||
let store = self.store()?;
|
||||
let progress = store.load();
|
||||
let mut json = tutor_tools::check_exercise_response(
|
||||
&self.loader(),
|
||||
self.backend.as_ref(),
|
||||
&progress,
|
||||
&exercise_id,
|
||||
&circuit,
|
||||
);
|
||||
if let Some(err) = json.get("protocol_error").and_then(|v| v.as_str()).map(str::to_owned) {
|
||||
return Err(McpError::invalid_params(err, None));
|
||||
}
|
||||
if json["passed"].as_bool().unwrap_or(false) {
|
||||
store
|
||||
.mark_solved(&exercise_id)
|
||||
.map_err(|e| McpError::internal_error(e.to_string(), None))?;
|
||||
json["progress_updated"] = serde_json::json!(true);
|
||||
}
|
||||
Ok(CallToolResult::success(vec![Content::text(json.to_string())]))
|
||||
}
|
||||
|
||||
#[tool(description = "Analyze a circuit and its measurement results to produce structured pedagogical data (gate breakdown, key concept, outcome stats).")]
|
||||
async fn explain_result(
|
||||
&self,
|
||||
Parameters(ExplainResultParams { circuit, counts, statevector }): Parameters<ExplainResultParams>,
|
||||
) -> Result<CallToolResult, McpError> {
|
||||
let json = tutor_tools::explain_result_response(&self.loader(), &circuit, &counts, statevector.as_ref());
|
||||
if let Some(err) = json.get("error").and_then(|v| v.as_str()).map(str::to_owned) {
|
||||
return Err(McpError::invalid_params(err, None));
|
||||
}
|
||||
Ok(CallToolResult::success(vec![Content::text(json.to_string())]))
|
||||
}
|
||||
|
||||
#[tool(description = "Get the learner's progress through the curriculum (modules status, current lesson, percent complete).")]
|
||||
async fn get_progress(&self) -> Result<CallToolResult, McpError> {
|
||||
let store = self.store()?;
|
||||
let progress = store.load();
|
||||
let json = tutor_tools::get_progress_response(&self.loader(), &progress);
|
||||
Ok(CallToolResult::success(vec![Content::text(json.to_string())]))
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Build and run all unit tests**
|
||||
|
||||
```bash
|
||||
cargo build && cargo test --lib 2>&1 | grep -E "test result|FAILED"
|
||||
```
|
||||
|
||||
Expected: every existing suite green; the 4 new methods compile and the existing tests are untouched.
|
||||
|
||||
- [ ] **Step 5: Verify the MCP `tools/list` exposes 7 tools**
|
||||
|
||||
```bash
|
||||
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"0.0.1"}}}
|
||||
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' | timeout 3 cargo run 2>/dev/null | tail -1 | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d['result']['tools']), 'tools')"
|
||||
```
|
||||
|
||||
Expected: `7 tools`.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add src/tools/mod.rs
|
||||
git commit -m "feat: register 4 tutor tools on QuantumBridgeServer"
|
||||
```
|
||||
Reference in New Issue
Block a user