Engmhabib commited on
Commit
942216e
·
verified ·
1 Parent(s): 48370e3

Upload 30 files

Browse files
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use Python 3.11 image
2
+ FROM python:3.11-slim
3
+
4
+ WORKDIR /app
5
+
6
+ COPY requirements.txt .
7
+ RUN pip install --no-cache-dir -r requirements.txt
8
+
9
+ COPY . .
10
+
11
+ RUN mkdir -p /app/storage/contexts /app/storage/students /app/storage/progress/assessments /app/storage/progress/skills \
12
+ && chmod -R 777 /app/storage
13
+
14
+ RUN mkdir -p /app/static \
15
+ && cp -r /app/src/static/* /app/static/ 2>/dev/null || true \
16
+ && chmod -R 755 /app/static
17
+
18
+ ENV PORT=7860
19
+ ENV PYTHONPATH=/app
20
+
21
+ EXPOSE 7860
22
+
23
+ CMD ["python", "main.py"]
README.md CHANGED
@@ -1,10 +1,25 @@
1
- ---
2
- title: MCP EdTech
3
- emoji: 👁
4
- colorFrom: gray
5
- colorTo: pink
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: MCP EdTech Project
3
+ emoji: 🎓
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: docker
7
+ sdk_version: "latest"
8
+ app_file: main.py
9
+ pinned: false
10
+ ---
11
+
12
+ # MCP EdTech Project
13
+
14
+ A comprehensive implementation of the Model Context Protocol (MCP) for educational technology applications.
15
+
16
+ ## Overview
17
+
18
+ The Model Context Protocol (MCP) is a standardized interface for AI model interactions that allows for consistent handling of context across different models, stateful conversations, and structured input/output formats. This project implements MCP specifically for EdTech applications, providing features such as:
19
+
20
+ - Context management for stateful conversations
21
+ - Student profile management
22
+ - Learning progress tracking
23
+ - Educational content adaptation
24
+ - Assessment and feedback systems
25
+ - Personalized learning path generation
main.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import uvicorn
3
+ from fastapi import FastAPI
4
+ from fastapi.staticfiles import StaticFiles
5
+ from fastapi.responses import RedirectResponse, HTMLResponse, JSONResponse
6
+ from fastapi.openapi.docs import get_swagger_ui_html, get_redoc_html
7
+ from fastapi.openapi.utils import get_openapi
8
+
9
+ from src.api.app import app as api_app
10
+
11
+ app = FastAPI(title="MCP EdTech", description="Model Context Protocol for Educational Technology")
12
+
13
+ app.mount("/static", StaticFiles(directory="static"), name="static")
14
+
15
+ @app.get("/", response_class=HTMLResponse)
16
+ async def serve_root():
17
+ try:
18
+ with open("static/index.html", "r") as f:
19
+ html_content = f.read()
20
+ return HTMLResponse(content=html_content)
21
+ except Exception as e:
22
+ return JSONResponse(content={
23
+ "name": "MCP EdTech API",
24
+ "version": "1.0.0",
25
+ "description": "Model Context Protocol implementation for EdTech applications",
26
+ "demo_ui": "Please visit /demo for the interactive demo"
27
+ })
28
+
29
+ @app.get("/demo", response_class=HTMLResponse)
30
+ async def serve_demo():
31
+ with open("static/index.html", "r") as f:
32
+ html_content = f.read()
33
+ return HTMLResponse(content=html_content)
34
+
35
+ for route in api_app.routes:
36
+ app.routes.append(route)
37
+
38
+ def custom_openapi():
39
+ if app.openapi_schema:
40
+ return app.openapi_schema
41
+ openapi_schema = get_openapi(
42
+ title="MCP EdTech API BY MHA",
43
+ version="1.0.0",
44
+ description="Model Context Protocol implementation for EdTech applications",
45
+ routes=app.routes,
46
+ )
47
+ app.openapi_schema = openapi_schema
48
+ return app.openapi_schema
49
+
50
+ app.openapi = custom_openapi
51
+
52
+ if __name__ == "__main__":
53
+ port = int(os.environ.get("PORT", 7860))
54
+ uvicorn.run(app, host="0.0.0.0", port=port)
project_plan.md ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # MCP EdTech Project Plan
2
+
3
+ ## Project Overview
4
+ This project implements the Model Context Protocol (MCP) for educational technology applications. The implementation will provide a standardized way for EdTech applications to interact with various AI models while maintaining context and state across interactions.
5
+
6
+ ## What is Model Context Protocol (MCP)?
7
+ The Model Context Protocol (MCP) is a standardized interface for AI model interactions that allows for:
8
+ - Consistent handling of context across different models
9
+ - Stateful conversations and interactions
10
+ - Structured input/output formats
11
+ - Model-agnostic implementations
12
+ - Enhanced security and privacy controls
13
+
14
+ ## Project Architecture
15
+
16
+ ### Core Components
17
+ 1. **MCP Core**
18
+ - Protocol specification implementation
19
+ - Context management system
20
+ - State persistence layer
21
+ - Model interface adapters
22
+
23
+ 2. **EdTech-Specific Extensions**
24
+ - Student profile management
25
+ - Learning progress tracking
26
+ - Educational content adaptation
27
+ - Assessment and feedback systems
28
+
29
+ 3. **API Layer**
30
+ - RESTful endpoints
31
+ - WebSocket support for real-time interactions
32
+ - Authentication and authorization
33
+ - Rate limiting and usage monitoring
34
+
35
+ 4. **Demo Applications**
36
+ - Interactive tutoring system
37
+ - Personalized learning path generator
38
+ - Knowledge assessment tool
39
+
40
+ ## Technical Stack
41
+ - **Backend**: FastAPI
42
+ - **Database**: SQLite (development), PostgreSQL (production)
43
+ - **Authentication**: JWT-based auth
44
+ - **Documentation**: OpenAPI/Swagger, ReDoc
45
+ - **Testing**: Pytest
46
+ - **Deployment**: Docker, Hugging Face Spaces, Heroku/Render compatibility
47
+
48
+ ## Implementation Plan
49
+
50
+ ### Phase 1: Core MCP Implementation
51
+ - Define MCP specification for EdTech use cases
52
+ - Implement context management system
53
+ - Create model interface adapters
54
+ - Develop state persistence layer
55
+
56
+ ### Phase 2: EdTech Extensions
57
+ - Implement student profile management
58
+ - Create learning progress tracking
59
+ - Develop educational content adaptation
60
+ - Build assessment and feedback systems
61
+
62
+ ### Phase 3: API Development
63
+ - Design and implement RESTful endpoints
64
+ - Add WebSocket support
65
+ - Implement authentication and authorization
66
+ - Add rate limiting and usage monitoring
67
+
68
+ ### Phase 4: Demo Applications
69
+ - Build interactive tutoring system
70
+ - Create personalized learning path generator
71
+ - Develop knowledge assessment tool
72
+
73
+ ### Phase 5: Documentation and Deployment
74
+ - Write comprehensive documentation
75
+ - Create usage examples
76
+ - Prepare deployment configurations
77
+ - Deploy to Hugging Face and prepare for Heroku/Render
78
+
79
+ ## API Endpoints
80
+
81
+ ### MCP Core Endpoints
82
+ - `POST /api/v1/context/create` - Create a new context
83
+ - `GET /api/v1/context/{context_id}` - Get context information
84
+ - `PUT /api/v1/context/{context_id}` - Update context
85
+ - `DELETE /api/v1/context/{context_id}` - Delete context
86
+ - `POST /api/v1/interact` - Process an interaction within a context
87
+
88
+ ### EdTech-Specific Endpoints
89
+ - `POST /api/v1/students` - Create student profile
90
+ - `GET /api/v1/students/{student_id}` - Get student information
91
+ - `PUT /api/v1/students/{student_id}` - Update student information
92
+ - `GET /api/v1/students/{student_id}/progress` - Get learning progress
93
+ - `POST /api/v1/assessments` - Create assessment
94
+ - `GET /api/v1/learning-paths/{student_id}` - Get personalized learning path
95
+
96
+ ## Deployment Strategy
97
+ 1. **Development Environment**
98
+ - Local development with SQLite
99
+ - Docker containerization for consistent environments
100
+
101
+ 2. **Testing Environment**
102
+ - Automated testing with GitHub Actions
103
+ - Integration testing with test databases
104
+
105
+ 3. **Production Deployment**
106
+ - Hugging Face Spaces for showcase
107
+ - Deployment scripts for Heroku and Render
108
+ - PostgreSQL for production database
109
+
110
+ ## Documentation Plan
111
+ 1. **Technical Documentation**
112
+ - MCP specification
113
+ - API reference
114
+ - Architecture overview
115
+ - Database schema
116
+
117
+ 2. **User Documentation**
118
+ - Getting started guide
119
+ - Integration examples
120
+ - Deployment instructions
121
+ - Customization guide
122
+
123
+ 3. **Demo Documentation**
124
+ - Use case examples
125
+ - Interactive tutorials
126
+ - Sample applications
127
+
128
+ ## Timeline
129
+ - Phase 1: 1-2 weeks
130
+ - Phase 2: 1-2 weeks
131
+ - Phase 3: 1 week
132
+ - Phase 4: 1-2 weeks
133
+ - Phase 5: 1 week
134
+
135
+ Total estimated time: 5-8 weeks for full implementation
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi>=0.95.0
2
+ uvicorn>=0.21.0
3
+ pydantic>=2.0.0
4
+ sqlalchemy>=2.0.0
5
+ python-jose[cryptography]>=3.3.0
6
+ passlib[bcrypt]>=1.7.4
7
+ python-multipart>=0.0.6
8
+ pytest>=7.3.1
9
+ httpx>=0.24.0
10
+ aiosqlite>=0.19.0
11
+ websockets>=11.0.3
src/api/__pycache__/app.cpython-311.pyc ADDED
Binary file (21.4 kB). View file
 
src/api/app.py ADDED
@@ -0,0 +1,429 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ API Layer - FastAPI Application
3
+
4
+ This module implements the FastAPI application for the MCP EdTech project,
5
+ providing RESTful endpoints for interacting with the MCP system.
6
+ """
7
+
8
+ import os
9
+ from typing import Dict, List, Optional, Any, Union
10
+ from datetime import datetime, timedelta
11
+
12
+ from fastapi import FastAPI, Depends, HTTPException, status, BackgroundTasks
13
+ from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
14
+ from fastapi.middleware.cors import CORSMiddleware
15
+ from fastapi.responses import JSONResponse, FileResponse
16
+ from fastapi.staticfiles import StaticFiles
17
+ from pydantic import BaseModel
18
+
19
+ from ..mcp_core.context import Context, ContextManager
20
+ from ..mcp_core.model_adapters import ModelRegistry, MockModelAdapter
21
+ from ..mcp_core.processor import InteractionProcessor
22
+ from ..mcp_core.protocol import (
23
+ MCPRequest, MCPResponse, InteractionType, ContentFormat,
24
+ EducationalLevel, LearningObjective, StudentProfile
25
+ )
26
+ from ..edtech_extensions.student_profile import StudentProfileManager
27
+ from ..edtech_extensions.progress_tracking import ProgressTracker, AssessmentType
28
+ from ..edtech_extensions.content_adaptation import ContentAdapter
29
+
30
+
31
+ # Initialize FastAPI app
32
+ app = FastAPI(
33
+ title="MCP EdTech API",
34
+ description="Model Context Protocol implementation for EdTech applications",
35
+ version="1.0.0"
36
+ )
37
+
38
+ # Add CORS middleware
39
+ app.add_middleware(
40
+ CORSMiddleware,
41
+ allow_origins=["*"], # In production, specify actual origins
42
+ allow_credentials=True,
43
+ allow_methods=["*"],
44
+ allow_headers=["*"],
45
+ )
46
+
47
+ # Initialize core components
48
+ context_manager = ContextManager()
49
+ model_registry = ModelRegistry()
50
+
51
+ # Register mock model adapter for demonstration
52
+ mock_adapter = MockModelAdapter(model_id="mock-edtech-model")
53
+ model_registry.register_adapter("mock", mock_adapter)
54
+
55
+ # Initialize processor
56
+ interaction_processor = InteractionProcessor(context_manager, model_registry)
57
+
58
+ # Initialize EdTech extensions
59
+ os.makedirs("./storage", exist_ok=True)
60
+ student_profile_manager = StudentProfileManager()
61
+ progress_tracker = ProgressTracker()
62
+ content_adapter = ContentAdapter()
63
+
64
+
65
+ # API Models
66
+
67
+ class ContextCreate(BaseModel):
68
+ """Request model for creating a new context."""
69
+ metadata: Dict[str, Any] = {}
70
+
71
+
72
+ class ContextUpdate(BaseModel):
73
+ """Request model for updating a context."""
74
+ metadata: Dict[str, Any] = {}
75
+ state: Dict[str, Any] = {}
76
+
77
+
78
+ class InteractionRequest(BaseModel):
79
+ """Request model for processing an interaction."""
80
+ context_id: Optional[str] = None
81
+ interaction_type: InteractionType
82
+ content: Dict[str, Any]
83
+ format: ContentFormat = ContentFormat.TEXT
84
+ metadata: Dict[str, Any] = {}
85
+ model_name: str = "mock" # Default to mock model
86
+
87
+
88
+ class StudentProfileCreate(BaseModel):
89
+ """Request model for creating a student profile."""
90
+ name: str
91
+ educational_level: EducationalLevel
92
+ learning_style: Optional[str] = None
93
+ interests: List[str] = []
94
+ strengths: List[str] = []
95
+ areas_for_improvement: List[str] = []
96
+
97
+
98
+ class StudentProfileUpdate(BaseModel):
99
+ """Request model for updating a student profile."""
100
+ name: Optional[str] = None
101
+ educational_level: Optional[EducationalLevel] = None
102
+ learning_style: Optional[str] = None
103
+ interests: Optional[List[str]] = None
104
+ strengths: Optional[List[str]] = None
105
+ areas_for_improvement: Optional[List[str]] = None
106
+
107
+
108
+ class AssessmentCreate(BaseModel):
109
+ """Request model for creating an assessment."""
110
+ student_id: str
111
+ assessment_type: AssessmentType
112
+ title: str
113
+ score: float
114
+ max_score: float
115
+ objectives: List[str]
116
+ feedback: str = ""
117
+ details: Dict[str, Any] = {}
118
+
119
+
120
+ class ContentAdaptRequest(BaseModel):
121
+ """Request model for adapting content."""
122
+ content: Dict[str, Any]
123
+ student_id: str
124
+ learning_objectives: List[str]
125
+ content_format: ContentFormat = ContentFormat.TEXT
126
+
127
+
128
+ # API Routes
129
+
130
+ @app.get("/")
131
+ async def root():
132
+ """Root endpoint returning API information."""
133
+ return {
134
+ "name": "MCP EdTech API",
135
+ "version": "1.0.0",
136
+ "description": "Model Context Protocol implementation for EdTech applications"
137
+ }
138
+
139
+
140
+ # Context endpoints
141
+
142
+ @app.post("/api/v1/context/create", response_model=Dict[str, Any])
143
+ async def create_context(request: ContextCreate):
144
+ """Create a new context."""
145
+ context = context_manager.create_context(metadata=request.metadata)
146
+ return context.to_dict()
147
+
148
+
149
+ @app.get("/api/v1/context/{context_id}", response_model=Dict[str, Any])
150
+ async def get_context(context_id: str):
151
+ """Get context information."""
152
+ context = context_manager.get_context(context_id)
153
+ if not context:
154
+ raise HTTPException(status_code=404, detail="Context not found")
155
+ return context.to_dict()
156
+
157
+
158
+ @app.put("/api/v1/context/{context_id}", response_model=Dict[str, Any])
159
+ async def update_context(context_id: str, request: ContextUpdate):
160
+ """Update context."""
161
+ context = context_manager.get_context(context_id)
162
+ if not context:
163
+ raise HTTPException(status_code=404, detail="Context not found")
164
+
165
+ if request.metadata:
166
+ context.metadata.update(request.metadata)
167
+
168
+ if request.state:
169
+ context.update_state(request.state)
170
+
171
+ return context.to_dict()
172
+
173
+
174
+ @app.delete("/api/v1/context/{context_id}", response_model=Dict[str, str])
175
+ async def delete_context(context_id: str):
176
+ """Delete context."""
177
+ success = context_manager.delete_context(context_id)
178
+ if not success:
179
+ raise HTTPException(status_code=404, detail="Context not found")
180
+ return {"status": "deleted", "context_id": context_id}
181
+
182
+
183
+ # Interaction endpoint
184
+
185
+ @app.post("/api/v1/interact", response_model=Dict[str, Any])
186
+ async def process_interaction(request: InteractionRequest):
187
+ """Process an interaction."""
188
+ try:
189
+ mcp_request = MCPRequest(
190
+ context_id=request.context_id,
191
+ interaction_type=request.interaction_type,
192
+ content=request.content,
193
+ format=request.format,
194
+ metadata=request.metadata
195
+ )
196
+
197
+ response = await interaction_processor.process_interaction(
198
+ mcp_request,
199
+ request.model_name
200
+ )
201
+
202
+ return response.dict()
203
+ except ValueError as e:
204
+ raise HTTPException(status_code=400, detail=str(e))
205
+ except Exception as e:
206
+ raise HTTPException(status_code=500, detail=f"Error processing interaction: {str(e)}")
207
+
208
+
209
+ # Student profile endpoints
210
+
211
+ @app.post("/api/v1/students", response_model=Dict[str, Any])
212
+ async def create_student_profile(request: StudentProfileCreate):
213
+ """Create student profile."""
214
+ profile = await student_profile_manager.create_profile(
215
+ name=request.name,
216
+ educational_level=request.educational_level,
217
+ learning_style=request.learning_style,
218
+ interests=request.interests,
219
+ strengths=request.strengths,
220
+ areas_for_improvement=request.areas_for_improvement
221
+ )
222
+ return profile.dict()
223
+
224
+
225
+ @app.get("/api/v1/students/{student_id}", response_model=Dict[str, Any])
226
+ async def get_student_profile(student_id: str):
227
+ """Get student information."""
228
+ profile = await student_profile_manager.get_profile(student_id)
229
+ if not profile:
230
+ raise HTTPException(status_code=404, detail="Student profile not found")
231
+ return profile.dict()
232
+
233
+
234
+ @app.put("/api/v1/students/{student_id}", response_model=Dict[str, Any])
235
+ async def update_student_profile(student_id: str, request: StudentProfileUpdate):
236
+ """Update student information."""
237
+ updates = {k: v for k, v in request.dict().items() if v is not None}
238
+ profile = await student_profile_manager.update_profile(student_id, updates)
239
+ if not profile:
240
+ raise HTTPException(status_code=404, detail="Student profile not found")
241
+ return profile.dict()
242
+
243
+
244
+ # Progress tracking endpoints
245
+
246
+ @app.get("/api/v1/students/{student_id}/progress", response_model=Dict[str, Any])
247
+ async def get_student_progress(student_id: str):
248
+ """Get learning progress."""
249
+ # Check if student exists
250
+ profile = await student_profile_manager.get_profile(student_id)
251
+ if not profile:
252
+ raise HTTPException(status_code=404, detail="Student profile not found")
253
+
254
+ summary = await progress_tracker.get_progress_summary(student_id)
255
+ return summary
256
+
257
+
258
+ @app.post("/api/v1/assessments", response_model=Dict[str, Any])
259
+ async def create_assessment(request: AssessmentCreate):
260
+ """Create assessment."""
261
+ # Check if student exists
262
+ profile = await student_profile_manager.get_profile(request.student_id)
263
+ if not profile:
264
+ raise HTTPException(status_code=404, detail="Student profile not found")
265
+
266
+ result = await progress_tracker.record_assessment(
267
+ student_id=request.student_id,
268
+ assessment_type=request.assessment_type,
269
+ title=request.title,
270
+ score=request.score,
271
+ max_score=request.max_score,
272
+ objectives=request.objectives,
273
+ feedback=request.feedback,
274
+ details=request.details
275
+ )
276
+
277
+ # Update student profile with completed objectives if score is good
278
+ if request.score / request.max_score >= 0.7: # 70% or better
279
+ for objective_id in request.objectives:
280
+ await student_profile_manager.add_completed_objective(
281
+ request.student_id,
282
+ objective_id
283
+ )
284
+
285
+ return result.dict()
286
+
287
+
288
+ @app.get("/api/v1/students/{student_id}/assessments", response_model=List[Dict[str, Any]])
289
+ async def list_student_assessments(student_id: str):
290
+ """List assessments for a student."""
291
+ # Check if student exists
292
+ profile = await student_profile_manager.get_profile(student_id)
293
+ if not profile:
294
+ raise HTTPException(status_code=404, detail="Student profile not found")
295
+
296
+ results = await progress_tracker.list_assessments(student_id)
297
+ return [result.dict() for result in results]
298
+
299
+
300
+ # Content adaptation endpoint
301
+
302
+ @app.post("/api/v1/content/adapt", response_model=Dict[str, Any])
303
+ async def adapt_content(request: ContentAdaptRequest):
304
+ """Adapt content for a student."""
305
+ # Get student profile
306
+ profile = await student_profile_manager.get_profile(request.student_id)
307
+ if not profile:
308
+ raise HTTPException(status_code=404, detail="Student profile not found")
309
+
310
+ # Create learning objectives (in a real app, these would come from a database)
311
+ learning_objectives = [
312
+ LearningObjective(
313
+ id=obj_id,
314
+ description=f"Learning objective {obj_id}",
315
+ taxonomy_level="understand",
316
+ subject_area="general",
317
+ prerequisites=[]
318
+ )
319
+ for obj_id in request.learning_objectives
320
+ ]
321
+
322
+ # Adapt content
323
+ adapted_content = await content_adapter.adapt_content(
324
+ content=request.content,
325
+ student_profile=profile,
326
+ learning_objectives=learning_objectives,
327
+ content_format=request.content_format
328
+ )
329
+
330
+ return adapted_content
331
+
332
+
333
+ # Learning path endpoint
334
+
335
+ @app.get("/api/v1/learning-paths/{student_id}", response_model=Dict[str, Any])
336
+ async def get_learning_path(student_id: str):
337
+ """Get personalized learning path."""
338
+ # Check if student exists
339
+ profile = await student_profile_manager.get_profile(student_id)
340
+ if not profile:
341
+ raise HTTPException(status_code=404, detail="Student profile not found")
342
+
343
+ # In a real implementation, this would generate a personalized learning path
344
+ # based on the student's profile, progress, and available learning materials.
345
+ # For this demo, we'll return a mock learning path.
346
+
347
+ # Get student's completed objectives
348
+ completed_objectives = profile.completed_objectives
349
+ current_objectives = profile.current_objectives
350
+
351
+ # Mock learning path
352
+ return {
353
+ "student_id": student_id,
354
+ "student_name": profile.name,
355
+ "educational_level": profile.educational_level,
356
+ "completed_objectives": completed_objectives,
357
+ "current_objectives": current_objectives,
358
+ "recommended_objectives": [
359
+ {
360
+ "id": f"obj-{i}",
361
+ "title": f"Recommended Objective {i}",
362
+ "description": f"This is a recommended learning objective for {profile.name}",
363
+ "estimated_time": f"{i*2} hours",
364
+ "difficulty": "intermediate"
365
+ }
366
+ for i in range(1, 4)
367
+ ],
368
+ "recommended_resources": [
369
+ {
370
+ "id": f"res-{i}",
371
+ "title": f"Resource {i}",
372
+ "type": "video" if i % 2 == 0 else "article",
373
+ "url": f"https://example.com/resource-{i}",
374
+ "description": f"This is a recommended resource for {profile.name}"
375
+ }
376
+ for i in range(1, 6)
377
+ ],
378
+ "generated_at": datetime.utcnow().isoformat()
379
+ }
380
+
381
+
382
+ # Model information endpoints
383
+
384
+ @app.get("/api/v1/models", response_model=List[str])
385
+ async def list_models():
386
+ """List available models."""
387
+ return model_registry.list_adapters()
388
+
389
+
390
+ @app.get("/api/v1/models/{model_name}/capabilities", response_model=Dict[str, Any])
391
+ async def get_model_capabilities(model_name: str):
392
+ """Get model capabilities."""
393
+ capabilities = model_registry.get_adapter_capabilities(model_name)
394
+ if not capabilities:
395
+ raise HTTPException(status_code=404, detail="Model not found")
396
+ return capabilities
397
+
398
+
399
+ # Health check endpoint
400
+
401
+ @app.get("/health", response_model=Dict[str, str])
402
+ async def health_check():
403
+ """Health check endpoint."""
404
+ return {
405
+ "status": "healthy",
406
+ "timestamp": datetime.utcnow().isoformat()
407
+ }
408
+
409
+
410
+ # Demo UI route
411
+ @app.get("/demo", response_class=FileResponse)
412
+ async def demo_ui():
413
+ """Serve the demo UI."""
414
+ return FileResponse("static/index.html")
415
+
416
+
417
+ # Serve static files for demo UI
418
+ app.mount("/static", StaticFiles(directory="static"), name="static")
419
+
420
+
421
+ # Main function to run the app
422
+ def start():
423
+ """Start the FastAPI application using uvicorn."""
424
+ import uvicorn
425
+ uvicorn.run(app, host="0.0.0.0", port=8000)
426
+
427
+
428
+ if __name__ == "__main__":
429
+ start()
src/demo_apps/demo_ui.py ADDED
@@ -0,0 +1,504 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Demo application for the MCP EdTech project.
3
+
4
+ This module provides a simple web interface for demonstrating the MCP EdTech functionality.
5
+ """
6
+
7
+ import os
8
+ from pathlib import Path
9
+
10
+ # Create static directory for demo UI
11
+ static_dir = Path(__file__).parent.parent / "static"
12
+ static_dir.mkdir(exist_ok=True)
13
+
14
+ # Create index.html for the demo UI
15
+ index_html = """<!DOCTYPE html>
16
+ <html lang="en">
17
+ <head>
18
+ <meta charset="UTF-8">
19
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
20
+ <title>MCP EdTech Demo</title>
21
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
22
+ <style>
23
+ body {
24
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
25
+ padding-top: 20px;
26
+ background-color: #f8f9fa;
27
+ }
28
+ .navbar {
29
+ background-color: #4a6fa5 !important;
30
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
31
+ }
32
+ .navbar-brand {
33
+ font-weight: bold;
34
+ color: white !important;
35
+ }
36
+ .nav-link {
37
+ color: rgba(255,255,255,0.85) !important;
38
+ }
39
+ .nav-link:hover {
40
+ color: white !important;
41
+ }
42
+ .card {
43
+ border-radius: 10px;
44
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
45
+ margin-bottom: 20px;
46
+ transition: transform 0.3s;
47
+ }
48
+ .card:hover {
49
+ transform: translateY(-5px);
50
+ }
51
+ .card-header {
52
+ background-color: #4a6fa5;
53
+ color: white;
54
+ font-weight: bold;
55
+ border-radius: 10px 10px 0 0 !important;
56
+ }
57
+ .btn-primary {
58
+ background-color: #4a6fa5;
59
+ border-color: #4a6fa5;
60
+ }
61
+ .btn-primary:hover {
62
+ background-color: #3a5a8f;
63
+ border-color: #3a5a8f;
64
+ }
65
+ .api-url {
66
+ font-family: monospace;
67
+ background-color: #f1f1f1;
68
+ padding: 5px;
69
+ border-radius: 4px;
70
+ }
71
+ .response-area {
72
+ background-color: #f8f9fa;
73
+ border: 1px solid #dee2e6;
74
+ border-radius: 5px;
75
+ padding: 15px;
76
+ max-height: 300px;
77
+ overflow-y: auto;
78
+ font-family: monospace;
79
+ }
80
+ .footer {
81
+ margin-top: 50px;
82
+ padding: 20px 0;
83
+ background-color: #343a40;
84
+ color: white;
85
+ }
86
+ #loading {
87
+ display: none;
88
+ }
89
+ .spinner-border {
90
+ width: 1rem;
91
+ height: 1rem;
92
+ }
93
+ </style>
94
+ </head>
95
+ <body>
96
+ <nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
97
+ <div class="container">
98
+ <a class="navbar-brand" href="#">MCP EdTech</a>
99
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
100
+ <span class="navbar-toggler-icon"></span>
101
+ </button>
102
+ <div class="collapse navbar-collapse" id="navbarNav">
103
+ <ul class="navbar-nav">
104
+ <li class="nav-item">
105
+ <a class="nav-link active" href="#home">Home</a>
106
+ </li>
107
+ <li class="nav-item">
108
+ <a class="nav-link" href="#api-demo">API Demo</a>
109
+ </li>
110
+ <li class="nav-item">
111
+ <a class="nav-link" href="#documentation">Documentation</a>
112
+ </li>
113
+ </ul>
114
+ </div>
115
+ </div>
116
+ </nav>
117
+
118
+ <div class="container" id="home">
119
+ <div class="row mb-4">
120
+ <div class="col-12">
121
+ <div class="card">
122
+ <div class="card-header">
123
+ Model Context Protocol (MCP) for EdTech
124
+ </div>
125
+ <div class="card-body">
126
+ <h5 class="card-title">Welcome to the MCP EdTech Demo</h5>
127
+ <p class="card-text">
128
+ This demo showcases the Model Context Protocol (MCP) implementation for educational technology applications.
129
+ MCP provides a standardized way for EdTech applications to interact with various AI models while maintaining
130
+ context and state across interactions.
131
+ </p>
132
+ <p>
133
+ Key features of this implementation:
134
+ </p>
135
+ <ul>
136
+ <li>Context management for stateful conversations</li>
137
+ <li>Student profile management</li>
138
+ <li>Learning progress tracking</li>
139
+ <li>Content adaptation based on student profiles</li>
140
+ <li>Assessment and feedback systems</li>
141
+ </ul>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ </div>
146
+
147
+ <div class="row mb-4" id="api-demo">
148
+ <div class="col-12">
149
+ <div class="card">
150
+ <div class="card-header">
151
+ API Demo
152
+ </div>
153
+ <div class="card-body">
154
+ <h5 class="card-title">Try the MCP API</h5>
155
+
156
+ <div class="mb-4">
157
+ <h6>1. Create a Context</h6>
158
+ <p>First, let's create a new context for our interaction:</p>
159
+ <div class="mb-3">
160
+ <button id="createContextBtn" class="btn btn-primary">Create Context</button>
161
+ <span id="loading-context" class="ms-2" style="display: none;">
162
+ <span class="spinner-border spinner-border-sm"></span> Processing...
163
+ </span>
164
+ </div>
165
+ <div class="response-area" id="contextResponse">
166
+ <!-- Context response will appear here -->
167
+ </div>
168
+ </div>
169
+
170
+ <div class="mb-4">
171
+ <h6>2. Process an Interaction</h6>
172
+ <p>Now, let's process an interaction using the context we created:</p>
173
+ <div class="mb-3">
174
+ <label for="interactionText" class="form-label">Enter your question or prompt:</label>
175
+ <input type="text" class="form-control" id="interactionText"
176
+ placeholder="e.g., Explain the concept of photosynthesis">
177
+ </div>
178
+ <div class="mb-3">
179
+ <button id="interactBtn" class="btn btn-primary">Process Interaction</button>
180
+ <span id="loading-interaction" class="ms-2" style="display: none;">
181
+ <span class="spinner-border spinner-border-sm"></span> Processing...
182
+ </span>
183
+ </div>
184
+ <div class="response-area" id="interactionResponse">
185
+ <!-- Interaction response will appear here -->
186
+ </div>
187
+ </div>
188
+
189
+ <div class="mb-4">
190
+ <h6>3. Create a Student Profile</h6>
191
+ <p>Let's create a student profile:</p>
192
+ <div class="mb-3">
193
+ <label for="studentName" class="form-label">Student Name:</label>
194
+ <input type="text" class="form-control" id="studentName" placeholder="John Doe">
195
+ </div>
196
+ <div class="mb-3">
197
+ <label for="educationalLevel" class="form-label">Educational Level:</label>
198
+ <select class="form-select" id="educationalLevel">
199
+ <option value="elementary">Elementary</option>
200
+ <option value="middle_school">Middle School</option>
201
+ <option value="high_school">High School</option>
202
+ <option value="undergraduate">Undergraduate</option>
203
+ <option value="graduate">Graduate</option>
204
+ <option value="professional">Professional</option>
205
+ </select>
206
+ </div>
207
+ <div class="mb-3">
208
+ <button id="createStudentBtn" class="btn btn-primary">Create Student Profile</button>
209
+ <span id="loading-student" class="ms-2" style="display: none;">
210
+ <span class="spinner-border spinner-border-sm"></span> Processing...
211
+ </span>
212
+ </div>
213
+ <div class="response-area" id="studentResponse">
214
+ <!-- Student profile response will appear here -->
215
+ </div>
216
+ </div>
217
+
218
+ <div class="mb-4">
219
+ <h6>4. Adapt Content for Student</h6>
220
+ <p>Now, let's adapt some content for the student:</p>
221
+ <div class="mb-3">
222
+ <label for="contentText" class="form-label">Content to adapt:</label>
223
+ <textarea class="form-control" id="contentText" rows="3"
224
+ placeholder="The process of photosynthesis involves the conversion of light energy into chemical energy that can be used by plants and other organisms."></textarea>
225
+ </div>
226
+ <div class="mb-3">
227
+ <button id="adaptContentBtn" class="btn btn-primary">Adapt Content</button>
228
+ <span id="loading-content" class="ms-2" style="display: none;">
229
+ <span class="spinner-border spinner-border-sm"></span> Processing...
230
+ </span>
231
+ </div>
232
+ <div class="response-area" id="contentResponse">
233
+ <!-- Content adaptation response will appear here -->
234
+ </div>
235
+ </div>
236
+ </div>
237
+ </div>
238
+ </div>
239
+ </div>
240
+
241
+ <div class="row mb-4" id="documentation">
242
+ <div class="col-12">
243
+ <div class="card">
244
+ <div class="card-header">
245
+ API Documentation
246
+ </div>
247
+ <div class="card-body">
248
+ <h5 class="card-title">Available Endpoints</h5>
249
+
250
+ <div class="mb-3">
251
+ <h6>Context Management</h6>
252
+ <ul>
253
+ <li><span class="api-url">POST /api/v1/context/create</span> - Create a new context</li>
254
+ <li><span class="api-url">GET /api/v1/context/{context_id}</span> - Get context information</li>
255
+ <li><span class="api-url">PUT /api/v1/context/{context_id}</span> - Update context</li>
256
+ <li><span class="api-url">DELETE /api/v1/context/{context_id}</span> - Delete context</li>
257
+ </ul>
258
+ </div>
259
+
260
+ <div class="mb-3">
261
+ <h6>Interaction</h6>
262
+ <ul>
263
+ <li><span class="api-url">POST /api/v1/interact</span> - Process an interaction</li>
264
+ </ul>
265
+ </div>
266
+
267
+ <div class="mb-3">
268
+ <h6>Student Profiles</h6>
269
+ <ul>
270
+ <li><span class="api-url">POST /api/v1/students</span> - Create student profile</li>
271
+ <li><span class="api-url">GET /api/v1/students/{student_id}</span> - Get student information</li>
272
+ <li><span class="api-url">PUT /api/v1/students/{student_id}</span> - Update student information</li>
273
+ </ul>
274
+ </div>
275
+
276
+ <div class="mb-3">
277
+ <h6>Progress Tracking</h6>
278
+ <ul>
279
+ <li><span class="api-url">GET /api/v1/students/{student_id}/progress</span> - Get learning progress</li>
280
+ <li><span class="api-url">POST /api/v1/assessments</span> - Create assessment</li>
281
+ <li><span class="api-url">GET /api/v1/students/{student_id}/assessments</span> - List student assessments</li>
282
+ </ul>
283
+ </div>
284
+
285
+ <div class="mb-3">
286
+ <h6>Content Adaptation</h6>
287
+ <ul>
288
+ <li><span class="api-url">POST /api/v1/content/adapt</span> - Adapt content for a student</li>
289
+ </ul>
290
+ </div>
291
+
292
+ <div class="mb-3">
293
+ <h6>Learning Paths</h6>
294
+ <ul>
295
+ <li><span class="api-url">GET /api/v1/learning-paths/{student_id}</span> - Get personalized learning path</li>
296
+ </ul>
297
+ </div>
298
+
299
+ <div class="mb-3">
300
+ <h6>Models</h6>
301
+ <ul>
302
+ <li><span class="api-url">GET /api/v1/models</span> - List available models</li>
303
+ <li><span class="api-url">GET /api/v1/models/{model_name}/capabilities</span> - Get model capabilities</li>
304
+ </ul>
305
+ </div>
306
+
307
+ <p class="mt-4">
308
+ For complete API documentation, visit the <a href="/docs" target="_blank">Swagger UI</a> or
309
+ <a href="/redoc" target="_blank">ReDoc</a> pages.
310
+ </p>
311
+ </div>
312
+ </div>
313
+ </div>
314
+ </div>
315
+ </div>
316
+
317
+ <footer class="footer">
318
+ <div class="container">
319
+ <div class="row">
320
+ <div class="col-md-6">
321
+ <h5>MCP EdTech Project</h5>
322
+ <p>An open-source implementation of the Model Context Protocol for educational technology applications.</p>
323
+ </div>
324
+ <div class="col-md-6 text-md-end">
325
+ <p>© 2025 MCP EdTech Project</p>
326
+ <p>Licensed under MIT License</p>
327
+ </div>
328
+ </div>
329
+ </div>
330
+ </footer>
331
+
332
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
333
+ <script>
334
+ // Store context and student IDs
335
+ let currentContextId = null;
336
+ let currentStudentId = null;
337
+
338
+ // Function to format JSON responses
339
+ function formatJSON(json) {
340
+ return JSON.stringify(json, null, 2);
341
+ }
342
+
343
+ // Create Context
344
+ document.getElementById('createContextBtn').addEventListener('click', async () => {
345
+ const loadingElement = document.getElementById('loading-context');
346
+ loadingElement.style.display = 'inline';
347
+
348
+ try {
349
+ const response = await fetch('/api/v1/context/create', {
350
+ method: 'POST',
351
+ headers: {
352
+ 'Content-Type': 'application/json'
353
+ },
354
+ body: JSON.stringify({
355
+ metadata: {
356
+ source: 'demo-ui',
357
+ created_by: 'demo-user'
358
+ }
359
+ })
360
+ });
361
+
362
+ const data = await response.json();
363
+ currentContextId = data.context_id;
364
+
365
+ document.getElementById('contextResponse').innerText = formatJSON(data);
366
+ } catch (error) {
367
+ document.getElementById('contextResponse').innerText = `Error: ${error.message}`;
368
+ } finally {
369
+ loadingElement.style.display = 'none';
370
+ }
371
+ });
372
+
373
+ // Process Interaction
374
+ document.getElementById('interactBtn').addEventListener('click', async () => {
375
+ if (!currentContextId) {
376
+ alert('Please create a context first!');
377
+ return;
378
+ }
379
+
380
+ const interactionText = document.getElementById('interactionText').value;
381
+ if (!interactionText) {
382
+ alert('Please enter a question or prompt!');
383
+ return;
384
+ }
385
+
386
+ const loadingElement = document.getElementById('loading-interaction');
387
+ loadingElement.style.display = 'inline';
388
+
389
+ try {
390
+ const response = await fetch('/api/v1/interact', {
391
+ method: 'POST',
392
+ headers: {
393
+ 'Content-Type': 'application/json'
394
+ },
395
+ body: JSON.stringify({
396
+ context_id: currentContextId,
397
+ interaction_type: 'text',
398
+ content: {
399
+ text: interactionText
400
+ },
401
+ format: 'text',
402
+ model_name: 'mock'
403
+ })
404
+ });
405
+
406
+ const data = await response.json();
407
+ document.getElementById('interactionResponse').innerText = formatJSON(data);
408
+ } catch (error) {
409
+ document.getElementById('interactionResponse').innerText = `Error: ${error.message}`;
410
+ } finally {
411
+ loadingElement.style.display = 'none';
412
+ }
413
+ });
414
+
415
+ // Create Student Profile
416
+ document.getElementById('createStudentBtn').addEventListener('click', async () => {
417
+ const studentName = document.getElementById('studentName').value;
418
+ if (!studentName) {
419
+ alert('Please enter a student name!');
420
+ return;
421
+ }
422
+
423
+ const educationalLevel = document.getElementById('educationalLevel').value;
424
+
425
+ const loadingElement = document.getElementById('loading-student');
426
+ loadingElement.style.display = 'inline';
427
+
428
+ try {
429
+ const response = await fetch('/api/v1/students', {
430
+ method: 'POST',
431
+ headers: {
432
+ 'Content-Type': 'application/json'
433
+ },
434
+ body: JSON.stringify({
435
+ name: studentName,
436
+ educational_level: educationalLevel,
437
+ learning_style: 'visual',
438
+ interests: ['science', 'technology', 'art'],
439
+ strengths: ['problem-solving', 'creativity'],
440
+ areas_for_improvement: ['time-management']
441
+ })
442
+ });
443
+
444
+ const data = await response.json();
445
+ currentStudentId = data.id;
446
+
447
+ document.getElementById('studentResponse').innerText = formatJSON(data);
448
+ } catch (error) {
449
+ document.getElementById('studentResponse').innerText = `Error: ${error.message}`;
450
+ } finally {
451
+ loadingElement.style.display = 'none';
452
+ }
453
+ });
454
+
455
+ // Adapt Content
456
+ document.getElementById('adaptContentBtn').addEventListener('click', async () => {
457
+ if (!currentStudentId) {
458
+ alert('Please create a student profile first!');
459
+ return;
460
+ }
461
+
462
+ const contentText = document.getElementById('contentText').value;
463
+ if (!contentText) {
464
+ alert('Please enter content to adapt!');
465
+ return;
466
+ }
467
+
468
+ const loadingElement = document.getElementById('loading-content');
469
+ loadingElement.style.display = 'inline';
470
+
471
+ try {
472
+ const response = await fetch('/api/v1/content/adapt', {
473
+ method: 'POST',
474
+ headers: {
475
+ 'Content-Type': 'application/json'
476
+ },
477
+ body: JSON.stringify({
478
+ content: {
479
+ text: contentText
480
+ },
481
+ student_id: currentStudentId,
482
+ learning_objectives: ['obj-1', 'obj-2'],
483
+ content_format: 'text'
484
+ })
485
+ });
486
+
487
+ const data = await response.json();
488
+ document.getElementById('contentResponse').innerText = formatJSON(data);
489
+ } catch (error) {
490
+ document.getElementById('contentResponse').innerText = `Error: ${error.message}`;
491
+ } finally {
492
+ loadingElement.style.display = 'none';
493
+ }
494
+ });
495
+ </script>
496
+ </body>
497
+ </html>
498
+ """
499
+
500
+ # Write the index.html file
501
+ with open(static_dir / "index.html", "w") as f:
502
+ f.write(index_html)
503
+
504
+ print(f"Demo UI created at {static_dir / 'index.html'}")
src/edtech_extensions/__pycache__/content_adaptation.cpython-311.pyc ADDED
Binary file (10.2 kB). View file
 
src/edtech_extensions/__pycache__/progress_tracking.cpython-311.pyc ADDED
Binary file (21.4 kB). View file
 
src/edtech_extensions/__pycache__/student_profile.cpython-311.pyc ADDED
Binary file (11.9 kB). View file
 
src/edtech_extensions/assessment.py ADDED
@@ -0,0 +1,596 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ EdTech Extensions - Assessment and Feedback System
3
+
4
+ This module provides functionality for creating and managing assessments,
5
+ as well as generating personalized feedback for students.
6
+ """
7
+
8
+ from typing import Dict, List, Optional, Any, Union
9
+ import os
10
+ import json
11
+ from datetime import datetime
12
+ from enum import Enum
13
+ from uuid import uuid4
14
+
15
+ from pydantic import BaseModel, Field
16
+
17
+
18
+ class QuestionType(str, Enum):
19
+ """Types of questions supported in assessments."""
20
+ MULTIPLE_CHOICE = "multiple_choice"
21
+ TRUE_FALSE = "true_false"
22
+ SHORT_ANSWER = "short_answer"
23
+ ESSAY = "essay"
24
+ MATCHING = "matching"
25
+ FILL_BLANK = "fill_blank"
26
+
27
+
28
+ class Question(BaseModel):
29
+ """Model representing an assessment question."""
30
+ id: str = Field(..., description="Unique identifier for this question")
31
+ type: QuestionType = Field(..., description="Type of question")
32
+ text: str = Field(..., description="Question text")
33
+ options: List[Dict[str, Any]] = Field(default_factory=list, description="Answer options for multiple choice, etc.")
34
+ correct_answer: Any = Field(..., description="Correct answer or answers")
35
+ points: int = Field(1, description="Points for this question")
36
+ difficulty: str = Field("medium", description="Question difficulty")
37
+ tags: List[str] = Field(default_factory=list, description="Tags for categorizing questions")
38
+ explanation: str = Field("", description="Explanation of the correct answer")
39
+
40
+
41
+ class Assessment(BaseModel):
42
+ """Model representing an assessment."""
43
+ id: str = Field(..., description="Unique identifier for this assessment")
44
+ title: str = Field(..., description="Assessment title")
45
+ description: str = Field("", description="Assessment description")
46
+ questions: List[Question] = Field(..., description="Questions in this assessment")
47
+ time_limit_minutes: Optional[int] = Field(None, description="Time limit in minutes, if any")
48
+ passing_score: float = Field(70.0, description="Passing score percentage")
49
+ tags: List[str] = Field(default_factory=list, description="Tags for categorizing assessments")
50
+ learning_objectives: List[str] = Field(default_factory=list, description="Learning objectives covered")
51
+ created_at: str = Field(..., description="Creation timestamp")
52
+
53
+
54
+ class StudentResponse(BaseModel):
55
+ """Model representing a student's response to a question."""
56
+ question_id: str = Field(..., description="ID of the question")
57
+ student_answer: Any = Field(..., description="Student's answer")
58
+ is_correct: Optional[bool] = Field(None, description="Whether the answer is correct")
59
+ points_earned: Optional[float] = Field(None, description="Points earned for this response")
60
+ feedback: str = Field("", description="Feedback on this response")
61
+
62
+
63
+ class AssessmentSubmission(BaseModel):
64
+ """Model representing a student's assessment submission."""
65
+ id: str = Field(..., description="Unique identifier for this submission")
66
+ assessment_id: str = Field(..., description="ID of the assessment")
67
+ student_id: str = Field(..., description="ID of the student")
68
+ responses: List[StudentResponse] = Field(..., description="Student's responses")
69
+ start_time: str = Field(..., description="Start timestamp")
70
+ end_time: str = Field(..., description="End timestamp")
71
+ score: Optional[float] = Field(None, description="Overall score")
72
+ feedback: str = Field("", description="Overall feedback")
73
+ graded: bool = Field(False, description="Whether the submission has been graded")
74
+
75
+
76
+ class AssessmentSystem:
77
+ """
78
+ Manages assessments and student submissions.
79
+
80
+ This class provides functionality for creating, retrieving, and grading
81
+ assessments, as well as generating personalized feedback.
82
+ """
83
+
84
+ def __init__(self, storage_dir: str = "./storage/assessments"):
85
+ """
86
+ Initialize a new AssessmentSystem.
87
+
88
+ Args:
89
+ storage_dir: Directory to store assessment data in.
90
+ """
91
+ self.storage_dir = storage_dir
92
+ self.assessments_dir = os.path.join(storage_dir, "definitions")
93
+ self.submissions_dir = os.path.join(storage_dir, "submissions")
94
+
95
+ os.makedirs(self.assessments_dir, exist_ok=True)
96
+ os.makedirs(self.submissions_dir, exist_ok=True)
97
+
98
+ def _get_assessment_path(self, assessment_id: str) -> str:
99
+ """
100
+ Get the file path for an assessment.
101
+
102
+ Args:
103
+ assessment_id: The ID of the assessment.
104
+
105
+ Returns:
106
+ The file path for the assessment.
107
+ """
108
+ return os.path.join(self.assessments_dir, f"{assessment_id}.json")
109
+
110
+ def _get_submission_path(self, submission_id: str) -> str:
111
+ """
112
+ Get the file path for a submission.
113
+
114
+ Args:
115
+ submission_id: The ID of the submission.
116
+
117
+ Returns:
118
+ The file path for the submission.
119
+ """
120
+ return os.path.join(self.submissions_dir, f"{submission_id}.json")
121
+
122
+ async def create_assessment(
123
+ self,
124
+ title: str,
125
+ questions: List[Question],
126
+ description: str = "",
127
+ time_limit_minutes: Optional[int] = None,
128
+ passing_score: float = 70.0,
129
+ tags: List[str] = None,
130
+ learning_objectives: List[str] = None
131
+ ) -> Assessment:
132
+ """
133
+ Create a new assessment.
134
+
135
+ Args:
136
+ title: Assessment title.
137
+ questions: List of questions.
138
+ description: Assessment description.
139
+ time_limit_minutes: Time limit in minutes, if any.
140
+ passing_score: Passing score percentage.
141
+ tags: Tags for categorizing the assessment.
142
+ learning_objectives: Learning objectives covered.
143
+
144
+ Returns:
145
+ The newly created Assessment.
146
+ """
147
+ assessment_id = str(uuid4())
148
+ created_at = datetime.utcnow().isoformat()
149
+
150
+ assessment = Assessment(
151
+ id=assessment_id,
152
+ title=title,
153
+ description=description,
154
+ questions=questions,
155
+ time_limit_minutes=time_limit_minutes,
156
+ passing_score=passing_score,
157
+ tags=tags or [],
158
+ learning_objectives=learning_objectives or [],
159
+ created_at=created_at
160
+ )
161
+
162
+ # Save to disk
163
+ await self._save_assessment(assessment)
164
+
165
+ return assessment
166
+
167
+ async def _save_assessment(self, assessment: Assessment) -> bool:
168
+ """
169
+ Save an assessment to disk.
170
+
171
+ Args:
172
+ assessment: The Assessment to save.
173
+
174
+ Returns:
175
+ True if the save was successful, False otherwise.
176
+ """
177
+ try:
178
+ with open(self._get_assessment_path(assessment.id), 'w') as f:
179
+ json.dump(assessment.dict(), f, indent=2)
180
+ return True
181
+ except Exception as e:
182
+ print(f"Error saving assessment: {e}")
183
+ return False
184
+
185
+ async def get_assessment(self, assessment_id: str) -> Optional[Assessment]:
186
+ """
187
+ Get an assessment by ID.
188
+
189
+ Args:
190
+ assessment_id: The ID of the assessment.
191
+
192
+ Returns:
193
+ The Assessment if found, None otherwise.
194
+ """
195
+ try:
196
+ path = self._get_assessment_path(assessment_id)
197
+ if not os.path.exists(path):
198
+ return None
199
+
200
+ with open(path, 'r') as f:
201
+ data = json.load(f)
202
+
203
+ return Assessment(**data)
204
+ except Exception as e:
205
+ print(f"Error loading assessment: {e}")
206
+ return None
207
+
208
+ async def list_assessments(
209
+ self,
210
+ tags: Optional[List[str]] = None,
211
+ learning_objectives: Optional[List[str]] = None
212
+ ) -> List[Assessment]:
213
+ """
214
+ List assessments, optionally filtered.
215
+
216
+ Args:
217
+ tags: Optional filter by tags.
218
+ learning_objectives: Optional filter by learning objectives.
219
+
220
+ Returns:
221
+ List of Assessment objects.
222
+ """
223
+ assessments = []
224
+
225
+ try:
226
+ for filename in os.listdir(self.assessments_dir):
227
+ if filename.endswith('.json'):
228
+ path = os.path.join(self.assessments_dir, filename)
229
+
230
+ with open(path, 'r') as f:
231
+ data = json.load(f)
232
+
233
+ assessment = Assessment(**data)
234
+
235
+ # Apply filters
236
+ include = True
237
+
238
+ if tags:
239
+ # Include if any tag matches
240
+ tag_match = any(tag in assessment.tags for tag in tags)
241
+ if not tag_match:
242
+ include = False
243
+
244
+ if learning_objectives:
245
+ # Include if any learning objective matches
246
+ obj_match = any(obj in assessment.learning_objectives for obj in learning_objectives)
247
+ if not obj_match:
248
+ include = False
249
+
250
+ if include:
251
+ assessments.append(assessment)
252
+ except Exception as e:
253
+ print(f"Error listing assessments: {e}")
254
+
255
+ return assessments
256
+
257
+ async def submit_assessment(
258
+ self,
259
+ assessment_id: str,
260
+ student_id: str,
261
+ responses: List[StudentResponse]
262
+ ) -> AssessmentSubmission:
263
+ """
264
+ Submit an assessment.
265
+
266
+ Args:
267
+ assessment_id: ID of the assessment.
268
+ student_id: ID of the student.
269
+ responses: List of student responses.
270
+
271
+ Returns:
272
+ The AssessmentSubmission.
273
+ """
274
+ submission_id = str(uuid4())
275
+ start_time = datetime.utcnow().isoformat()
276
+ end_time = datetime.utcnow().isoformat()
277
+
278
+ submission = AssessmentSubmission(
279
+ id=submission_id,
280
+ assessment_id=assessment_id,
281
+ student_id=student_id,
282
+ responses=responses,
283
+ start_time=start_time,
284
+ end_time=end_time,
285
+ score=None,
286
+ feedback="",
287
+ graded=False
288
+ )
289
+
290
+ # Save to disk
291
+ await self._save_submission(submission)
292
+
293
+ # Grade the submission
294
+ graded_submission = await self.grade_submission(submission_id)
295
+
296
+ return graded_submission
297
+
298
+ async def _save_submission(self, submission: AssessmentSubmission) -> bool:
299
+ """
300
+ Save a submission to disk.
301
+
302
+ Args:
303
+ submission: The AssessmentSubmission to save.
304
+
305
+ Returns:
306
+ True if the save was successful, False otherwise.
307
+ """
308
+ try:
309
+ with open(self._get_submission_path(submission.id), 'w') as f:
310
+ json.dump(submission.dict(), f, indent=2)
311
+ return True
312
+ except Exception as e:
313
+ print(f"Error saving submission: {e}")
314
+ return False
315
+
316
+ async def get_submission(self, submission_id: str) -> Optional[AssessmentSubmission]:
317
+ """
318
+ Get a submission by ID.
319
+
320
+ Args:
321
+ submission_id: The ID of the submission.
322
+
323
+ Returns:
324
+ The AssessmentSubmission if found, None otherwise.
325
+ """
326
+ try:
327
+ path = self._get_submission_path(submission_id)
328
+ if not os.path.exists(path):
329
+ return None
330
+
331
+ with open(path, 'r') as f:
332
+ data = json.load(f)
333
+
334
+ return AssessmentSubmission(**data)
335
+ except Exception as e:
336
+ print(f"Error loading submission: {e}")
337
+ return None
338
+
339
+ async def grade_submission(self, submission_id: str) -> Optional[AssessmentSubmission]:
340
+ """
341
+ Grade a submission.
342
+
343
+ Args:
344
+ submission_id: The ID of the submission.
345
+
346
+ Returns:
347
+ The graded AssessmentSubmission if found, None otherwise.
348
+ """
349
+ submission = await self.get_submission(submission_id)
350
+ if not submission:
351
+ return None
352
+
353
+ assessment = await self.get_assessment(submission.assessment_id)
354
+ if not assessment:
355
+ return None
356
+
357
+ # Create a map of questions by ID for easy lookup
358
+ questions_map = {q.id: q for q in assessment.questions}
359
+
360
+ total_points = 0
361
+ earned_points = 0
362
+
363
+ # Grade each response
364
+ for response in submission.responses:
365
+ question = questions_map.get(response.question_id)
366
+ if not question:
367
+ continue
368
+
369
+ total_points += question.points
370
+
371
+ # Grade based on question type
372
+ if question.type == QuestionType.MULTIPLE_CHOICE or question.type == QuestionType.TRUE_FALSE:
373
+ # Simple equality check
374
+ is_correct = response.student_answer == question.correct_answer
375
+ points_earned = question.points if is_correct else 0
376
+
377
+ response.is_correct = is_correct
378
+ response.points_earned = points_earned
379
+ response.feedback = question.explanation if question.explanation else (
380
+ "Correct!" if is_correct else f"Incorrect. The correct answer is: {question.correct_answer}"
381
+ )
382
+
383
+ elif question.type == QuestionType.FILL_BLANK:
384
+ # Case-insensitive equality check
385
+ is_correct = str(response.student_answer).lower() == str(question.correct_answer).lower()
386
+ points_earned = question.points if is_correct else 0
387
+
388
+ response.is_correct = is_correct
389
+ response.points_earned = points_earned
390
+ response.feedback = question.explanation if question.explanation else (
391
+ "Correct!" if is_correct else f"Incorrect. The correct answer is: {question.correct_answer}"
392
+ )
393
+
394
+ elif question.type == QuestionType.SHORT_ANSWER:
395
+ # For short answer, we'll use a simple keyword check
396
+ # In a real implementation, this would use more sophisticated NLP techniques
397
+ student_answer = str(response.student_answer).lower()
398
+ correct_keywords = [str(k).lower() for k in question.correct_answer]
399
+
400
+ # Check how many keywords are present
401
+ matches = sum(1 for k in correct_keywords if k in student_answer)
402
+ match_ratio = matches / len(correct_keywords) if correct_keywords else 0
403
+
404
+ # Full points if all keywords present, partial otherwise
405
+ is_correct = match_ratio >= 0.7 # 70% or more keywords
406
+ points_earned = question.points * match_ratio
407
+
408
+ response.is_correct = is_correct
409
+ response.points_earned = points_earned
410
+ response.feedback = question.explanation if question.explanation else (
411
+ "Good answer!" if is_correct else
412
+ f"Your answer could be improved. Important concepts to include: {', '.join(correct_keywords)}"
413
+ )
414
+
415
+ elif question.type == QuestionType.ESSAY:
416
+ # For essays, we'll mark as needing manual grading
417
+ # In a real implementation, this might use AI for initial grading
418
+ response.is_correct = None
419
+ response.points_earned = None
420
+ response.feedback = "This response requires manual grading."
421
+
422
+ elif question.type == QuestionType.MATCHING:
423
+ # Check if all matches are correct
424
+ correct_matches = question.correct_answer
425
+ student_matches = response.student_answer
426
+
427
+ # Count correct matches
428
+ correct_count = sum(1 for k, v in student_matches.items() if k in correct_matches and correct_matches[k] == v)
429
+ total_matches = len(correct_matches)
430
+
431
+ match_ratio = correct_count / total_matches if total_matches else 0
432
+ is_correct = match_ratio >= 0.7 # 70% or more correct
433
+ points_earned = question.points * match_ratio
434
+
435
+ response.is_correct = is_correct
436
+ response.points_earned = points_earned
437
+ response.feedback = question.explanation if question.explanation else (
438
+ "Good matching!" if is_correct else
439
+ f"Some matches were incorrect. Please review the correct answers."
440
+ )
441
+
442
+ # Add to total points
443
+ if response.points_earned is not None:
444
+ earned_points += response.points_earned
445
+
446
+ # Calculate overall score
447
+ score_percentage = (earned_points / total_points * 100) if total_points > 0 else 0
448
+
449
+ # Generate overall feedback
450
+ if score_percentage >= assessment.passing_score:
451
+ overall_feedback = f"Congratulations! You passed with a score of {score_percentage:.1f}%."
452
+ else:
453
+ overall_feedback = f"You scored {score_percentage:.1f}%, which is below the passing score of {assessment.passing_score}%. Please review the material and try again."
454
+
455
+ # Update submission
456
+ submission.score = score_percentage
457
+ submission.feedback = overall_feedback
458
+ submission.graded = True
459
+
460
+ # Save updated submission
461
+ await self._save_submission(submission)
462
+
463
+ return submission
464
+
465
+ async def list_student_submissions(self, student_id: str) -> List[AssessmentSubmission]:
466
+ """
467
+ List submissions for a student.
468
+
469
+ Args:
470
+ student_id: ID of the student.
471
+
472
+ Returns:
473
+ List of AssessmentSubmission objects.
474
+ """
475
+ submissions = []
476
+
477
+ try:
478
+ for filename in os.listdir(self.submissions_dir):
479
+ if filename.endswith('.json'):
480
+ path = os.path.join(self.submissions_dir, filename)
481
+
482
+ with open(path, 'r') as f:
483
+ data = json.load(f)
484
+
485
+ if data.get("student_id") == student_id:
486
+ submissions.append(AssessmentSubmission(**data))
487
+ except Exception as e:
488
+ print(f"Error listing submissions: {e}")
489
+
490
+ # Sort by end time, newest first
491
+ submissions.sort(key=lambda x: x.end_time, reverse=True)
492
+ return submissions
493
+
494
+ async def generate_feedback_report(self, submission_id: str) -> Dict[str, Any]:
495
+ """
496
+ Generate a detailed feedback report for a submission.
497
+
498
+ Args:
499
+ submission_id: ID of the submission.
500
+
501
+ Returns:
502
+ Dictionary containing the feedback report.
503
+ """
504
+ submission = await self.get_submission(submission_id)
505
+ if not submission:
506
+ return {"error": "Submission not found"}
507
+
508
+ assessment = await self.get_assessment(submission.assessment_id)
509
+ if not assessment:
510
+ return {"error": "Assessment not found"}
511
+
512
+ # Create a map of questions by ID for easy lookup
513
+ questions_map = {q.id: q for q in assessment.questions}
514
+
515
+ # Group questions and responses by tags
516
+ tag_groups = {}
517
+ for response in submission.responses:
518
+ question = questions_map.get(response.question_id)
519
+ if not question:
520
+ continue
521
+
522
+ for tag in question.tags:
523
+ if tag not in tag_groups:
524
+ tag_groups[tag] = {
525
+ "total": 0,
526
+ "correct": 0,
527
+ "questions": []
528
+ }
529
+
530
+ tag_groups[tag]["total"] += 1
531
+ if response.is_correct:
532
+ tag_groups[tag]["correct"] += 1
533
+
534
+ tag_groups[tag]["questions"].append({
535
+ "question_text": question.text,
536
+ "student_answer": response.student_answer,
537
+ "correct_answer": question.correct_answer,
538
+ "is_correct": response.is_correct,
539
+ "feedback": response.feedback
540
+ })
541
+
542
+ # Generate strengths and weaknesses
543
+ strengths = []
544
+ weaknesses = []
545
+
546
+ for tag, data in tag_groups.items():
547
+ score = data["correct"] / data["total"] if data["total"] > 0 else 0
548
+
549
+ if score >= 0.8: # 80% or better
550
+ strengths.append({
551
+ "topic": tag,
552
+ "score": score * 100,
553
+ "comment": f"You show strong understanding of {tag}."
554
+ })
555
+ elif score <= 0.6: # 60% or worse
556
+ weaknesses.append({
557
+ "topic": tag,
558
+ "score": score * 100,
559
+ "comment": f"You may need additional practice with {tag}."
560
+ })
561
+
562
+ # Generate recommended resources (in a real app, these would come from a database)
563
+ recommended_resources = []
564
+ for weakness in weaknesses:
565
+ recommended_resources.append({
566
+ "topic": weakness["topic"],
567
+ "title": f"Review material on {weakness['topic']}",
568
+ "type": "article",
569
+ "url": f"https://example.com/resources/{weakness['topic'].lower().replace(' ', '-')}"
570
+ })
571
+
572
+ # Create the report
573
+ report = {
574
+ "submission_id": submission_id,
575
+ "assessment_id": submission.assessment_id,
576
+ "assessment_title": assessment.title,
577
+ "student_id": submission.student_id,
578
+ "score": submission.score,
579
+ "passing_score": assessment.passing_score,
580
+ "passed": submission.score >= assessment.passing_score if submission.score is not None else False,
581
+ "strengths": strengths,
582
+ "weaknesses": weaknesses,
583
+ "topic_breakdown": [
584
+ {
585
+ "topic": tag,
586
+ "score": (data["correct"] / data["total"] * 100) if data["total"] > 0 else 0,
587
+ "questions_count": data["total"]
588
+ }
589
+ for tag, data in tag_groups.items()
590
+ ],
591
+ "recommended_resources": recommended_resources,
592
+ "detailed_feedback": submission.feedback,
593
+ "generated_at": datetime.utcnow().isoformat()
594
+ }
595
+
596
+ return report
src/edtech_extensions/content_adaptation.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ EdTech Extensions - Content Adaptation
3
+
4
+ This module provides functionality for adapting educational content
5
+ based on student profiles and learning objectives.
6
+ """
7
+
8
+ from typing import Dict, List, Optional, Any, Union
9
+ import os
10
+ import json
11
+ from datetime import datetime
12
+
13
+ from ..mcp_core.protocol import StudentProfile, LearningObjective, EducationalLevel, ContentFormat
14
+
15
+
16
+ class ContentAdapter:
17
+ """
18
+ Adapts educational content based on student profiles and learning objectives.
19
+
20
+ This class provides functionality for personalizing educational content
21
+ to match a student's learning style, educational level, and interests.
22
+ """
23
+
24
+ def __init__(self):
25
+ """Initialize a new ContentAdapter."""
26
+ pass
27
+
28
+ async def adapt_content(
29
+ self,
30
+ content: Dict[str, Any],
31
+ student_profile: StudentProfile,
32
+ learning_objectives: List[LearningObjective],
33
+ content_format: ContentFormat = ContentFormat.TEXT
34
+ ) -> Dict[str, Any]:
35
+ """
36
+ Adapt educational content for a specific student.
37
+
38
+ Args:
39
+ content: Original content to adapt.
40
+ student_profile: Student profile to adapt content for.
41
+ learning_objectives: Learning objectives for this content.
42
+ content_format: Format of the content.
43
+
44
+ Returns:
45
+ Dictionary containing adapted content and metadata.
46
+ """
47
+ # In a real implementation, this would use AI models to adapt content
48
+ # based on the student profile and learning objectives.
49
+ # For this demo, we'll implement a simplified version.
50
+
51
+ original_text = content.get("text", "")
52
+
53
+ # Apply adaptations based on educational level
54
+ adapted_text = self._adapt_for_educational_level(
55
+ original_text,
56
+ student_profile.educational_level
57
+ )
58
+
59
+ # Apply adaptations based on learning style
60
+ adapted_text = self._adapt_for_learning_style(
61
+ adapted_text,
62
+ student_profile.learning_style
63
+ )
64
+
65
+ # Apply adaptations based on interests
66
+ adapted_text = self._adapt_for_interests(
67
+ adapted_text,
68
+ student_profile.interests
69
+ )
70
+
71
+ # Generate recommended activities
72
+ recommended_activities = self._generate_recommended_activities(
73
+ student_profile,
74
+ learning_objectives
75
+ )
76
+
77
+ # List adaptations applied
78
+ adaptations_applied = []
79
+ if student_profile.educational_level:
80
+ adaptations_applied.append(f"Adapted for {student_profile.educational_level} educational level")
81
+ if student_profile.learning_style:
82
+ adaptations_applied.append(f"Adapted for {student_profile.learning_style} learning style")
83
+ if student_profile.interests:
84
+ adaptations_applied.append("Incorporated student interests")
85
+
86
+ return {
87
+ "text": adapted_text,
88
+ "original_text": original_text,
89
+ "adaptations_applied": adaptations_applied,
90
+ "recommended_activities": recommended_activities,
91
+ "format": content_format,
92
+ "adapted_at": datetime.utcnow().isoformat()
93
+ }
94
+
95
+ def _adapt_for_educational_level(
96
+ self,
97
+ text: str,
98
+ educational_level: EducationalLevel
99
+ ) -> str:
100
+ """
101
+ Adapt content for a specific educational level.
102
+
103
+ Args:
104
+ text: Original text content.
105
+ educational_level: Target educational level.
106
+
107
+ Returns:
108
+ Adapted text content.
109
+ """
110
+ # In a real implementation, this would use more sophisticated techniques
111
+ # such as vocabulary adjustment, complexity reduction, etc.
112
+
113
+ if educational_level == EducationalLevel.ELEMENTARY:
114
+ # Simplify language for elementary level
115
+ return f"{text}\n\n[Note: This content has been simplified for elementary level students.]"
116
+
117
+ elif educational_level == EducationalLevel.MIDDLE_SCHOOL:
118
+ # Moderate complexity for middle school
119
+ return f"{text}\n\n[Note: This content has been adapted for middle school students.]"
120
+
121
+ elif educational_level == EducationalLevel.HIGH_SCHOOL:
122
+ # Standard complexity for high school
123
+ return f"{text}\n\n[Note: This content has been adapted for high school students.]"
124
+
125
+ elif educational_level == EducationalLevel.UNDERGRADUATE:
126
+ # Higher complexity for undergraduate
127
+ return f"{text}\n\n[Note: This content has been adapted for undergraduate students.]"
128
+
129
+ elif educational_level == EducationalLevel.GRADUATE:
130
+ # Advanced complexity for graduate
131
+ return f"{text}\n\n[Note: This content has been adapted for graduate students with advanced terminology.]"
132
+
133
+ elif educational_level == EducationalLevel.PROFESSIONAL:
134
+ # Professional terminology
135
+ return f"{text}\n\n[Note: This content has been adapted with professional terminology.]"
136
+
137
+ return text
138
+
139
+ def _adapt_for_learning_style(self, text: str, learning_style: str) -> str:
140
+ """
141
+ Adapt content for a specific learning style.
142
+
143
+ Args:
144
+ text: Original text content.
145
+ learning_style: Target learning style.
146
+
147
+ Returns:
148
+ Adapted text content.
149
+ """
150
+ # In a real implementation, this would use more sophisticated techniques
151
+
152
+ if not learning_style:
153
+ return text
154
+
155
+ if learning_style.lower() == "visual":
156
+ # Add visual cues
157
+ return f"{text}\n\n[Note: Visual diagrams and charts would be included here to support visual learners.]"
158
+
159
+ elif learning_style.lower() == "auditory":
160
+ # Add auditory cues
161
+ return f"{text}\n\n[Note: Audio explanations would be available for auditory learners.]"
162
+
163
+ elif learning_style.lower() == "reading":
164
+ # Add reading cues
165
+ return f"{text}\n\n[Note: Additional reading materials and references are provided for reading-oriented learners.]"
166
+
167
+ elif learning_style.lower() == "kinesthetic":
168
+ # Add kinesthetic cues
169
+ return f"{text}\n\n[Note: Interactive exercises would be included here for kinesthetic learners.]"
170
+
171
+ return text
172
+
173
+ def _adapt_for_interests(self, text: str, interests: List[str]) -> str:
174
+ """
175
+ Adapt content to incorporate student interests.
176
+
177
+ Args:
178
+ text: Original text content.
179
+ interests: List of student interests.
180
+
181
+ Returns:
182
+ Adapted text content.
183
+ """
184
+ # In a real implementation, this would use more sophisticated techniques
185
+
186
+ if not interests:
187
+ return text
188
+
189
+ interest_note = f"\n\n[Note: This content would include examples related to your interests in {', '.join(interests)}.]"
190
+ return text + interest_note
191
+
192
+ def _generate_recommended_activities(
193
+ self,
194
+ student_profile: StudentProfile,
195
+ learning_objectives: List[LearningObjective]
196
+ ) -> List[Dict[str, Any]]:
197
+ """
198
+ Generate recommended activities based on student profile and learning objectives.
199
+
200
+ Args:
201
+ student_profile: Student profile.
202
+ learning_objectives: Learning objectives.
203
+
204
+ Returns:
205
+ List of recommended activities.
206
+ """
207
+ # In a real implementation, this would generate personalized activities
208
+ # based on the student's profile and learning objectives.
209
+
210
+ activities = []
211
+
212
+ # Add activities based on learning style
213
+ if student_profile.learning_style:
214
+ if student_profile.learning_style.lower() == "visual":
215
+ activities.append({
216
+ "type": "visualization",
217
+ "title": "Create a visual diagram",
218
+ "description": "Create a diagram that visualizes the key concepts."
219
+ })
220
+ elif student_profile.learning_style.lower() == "auditory":
221
+ activities.append({
222
+ "type": "discussion",
223
+ "title": "Verbal explanation",
224
+ "description": "Record yourself explaining the concept to someone else."
225
+ })
226
+ elif student_profile.learning_style.lower() == "reading":
227
+ activities.append({
228
+ "type": "research",
229
+ "title": "Research and summarize",
230
+ "description": "Find additional resources on this topic and write a summary."
231
+ })
232
+ elif student_profile.learning_style.lower() == "kinesthetic":
233
+ activities.append({
234
+ "type": "hands-on",
235
+ "title": "Practical application",
236
+ "description": "Complete a hands-on project applying these concepts."
237
+ })
238
+
239
+ # Add activities based on learning objectives
240
+ for objective in learning_objectives:
241
+ if "remember" in objective.taxonomy_level.lower():
242
+ activities.append({
243
+ "type": "quiz",
244
+ "title": f"Quiz on {objective.subject_area}",
245
+ "description": f"Test your recall of key facts about {objective.description}"
246
+ })
247
+ elif "understand" in objective.taxonomy_level.lower():
248
+ activities.append({
249
+ "type": "explanation",
250
+ "title": f"Explain {objective.subject_area} concepts",
251
+ "description": f"Write an explanation of {objective.description} in your own words."
252
+ })
253
+ elif "apply" in objective.taxonomy_level.lower():
254
+ activities.append({
255
+ "type": "problem",
256
+ "title": f"Apply {objective.subject_area} knowledge",
257
+ "description": f"Solve problems that require applying {objective.description}."
258
+ })
259
+ elif "analyze" in objective.taxonomy_level.lower():
260
+ activities.append({
261
+ "type": "analysis",
262
+ "title": f"Analyze {objective.subject_area}",
263
+ "description": f"Analyze a case study related to {objective.description}."
264
+ })
265
+
266
+ # Limit to 3 activities
267
+ return activities[:3]
src/edtech_extensions/progress_tracking.py ADDED
@@ -0,0 +1,450 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ EdTech Extensions - Learning Progress Tracking
3
+
4
+ This module provides functionality for tracking student learning progress
5
+ in the MCP EdTech system, including assessment results and skill development.
6
+ """
7
+
8
+ import os
9
+ from datetime import datetime
10
+ from typing import Dict, List, Optional, Any, Set, Tuple
11
+ from uuid import uuid4
12
+ import json
13
+ from enum import Enum
14
+
15
+ from pydantic import BaseModel, Field
16
+
17
+
18
+ class AssessmentType(str, Enum):
19
+ """Types of assessments supported by the system."""
20
+ QUIZ = "quiz"
21
+ TEST = "test"
22
+ ASSIGNMENT = "assignment"
23
+ PROJECT = "project"
24
+ PRACTICE = "practice"
25
+
26
+
27
+ class AssessmentResult(BaseModel):
28
+ """Model representing an assessment result."""
29
+ id: str = Field(..., description="Unique identifier for this assessment result")
30
+ student_id: str = Field(..., description="ID of the student")
31
+ assessment_type: AssessmentType = Field(..., description="Type of assessment")
32
+ title: str = Field(..., description="Title of the assessment")
33
+ score: float = Field(..., description="Score achieved (0-100)")
34
+ max_score: float = Field(..., description="Maximum possible score")
35
+ objectives: List[str] = Field(default_factory=list, description="Learning objectives covered")
36
+ completed_at: str = Field(..., description="Completion timestamp")
37
+ feedback: str = Field("", description="Feedback on the assessment")
38
+ details: Dict[str, Any] = Field(default_factory=dict, description="Detailed assessment data")
39
+
40
+
41
+ class SkillLevel(str, Enum):
42
+ """Skill proficiency levels."""
43
+ NOVICE = "novice"
44
+ BEGINNER = "beginner"
45
+ INTERMEDIATE = "intermediate"
46
+ ADVANCED = "advanced"
47
+ EXPERT = "expert"
48
+
49
+
50
+ class SkillProgress(BaseModel):
51
+ """Model representing progress in a specific skill."""
52
+ skill_id: str = Field(..., description="Unique identifier for this skill")
53
+ name: str = Field(..., description="Name of the skill")
54
+ level: SkillLevel = Field(SkillLevel.NOVICE, description="Current skill level")
55
+ progress_percentage: float = Field(0.0, description="Progress towards next level (0-100)")
56
+ last_updated: str = Field(..., description="Last update timestamp")
57
+ related_objectives: List[str] = Field(default_factory=list, description="Related learning objectives")
58
+
59
+
60
+ class ProgressTracker:
61
+ """
62
+ Tracks student learning progress in the MCP EdTech system.
63
+
64
+ This class provides functionality for recording and analyzing student
65
+ progress, including assessment results and skill development.
66
+ """
67
+
68
+ def __init__(self, storage_dir: str = "./storage/progress"):
69
+ """
70
+ Initialize a new ProgressTracker.
71
+
72
+ Args:
73
+ storage_dir: Directory to store progress data in.
74
+ """
75
+ self.storage_dir = storage_dir
76
+ self.assessments_dir = os.path.join(storage_dir, "assessments")
77
+ self.skills_dir = os.path.join(storage_dir, "skills")
78
+
79
+ os.makedirs(self.assessments_dir, exist_ok=True)
80
+ os.makedirs(self.skills_dir, exist_ok=True)
81
+
82
+ def _get_student_assessments_dir(self, student_id: str) -> str:
83
+ """
84
+ Get the directory for a student's assessment results.
85
+
86
+ Args:
87
+ student_id: The ID of the student.
88
+
89
+ Returns:
90
+ The directory path.
91
+ """
92
+ path = os.path.join(self.assessments_dir, student_id)
93
+ os.makedirs(path, exist_ok=True)
94
+ return path
95
+
96
+ def _get_student_skills_dir(self, student_id: str) -> str:
97
+ """
98
+ Get the directory for a student's skill progress.
99
+
100
+ Args:
101
+ student_id: The ID of the student.
102
+
103
+ Returns:
104
+ The directory path.
105
+ """
106
+ path = os.path.join(self.skills_dir, student_id)
107
+ os.makedirs(path, exist_ok=True)
108
+ return path
109
+
110
+ async def record_assessment(
111
+ self,
112
+ student_id: str,
113
+ assessment_type: AssessmentType,
114
+ title: str,
115
+ score: float,
116
+ max_score: float,
117
+ objectives: List[str],
118
+ feedback: str = "",
119
+ details: Dict[str, Any] = None
120
+ ) -> AssessmentResult:
121
+ """
122
+ Record a new assessment result.
123
+
124
+ Args:
125
+ student_id: ID of the student.
126
+ assessment_type: Type of assessment.
127
+ title: Title of the assessment.
128
+ score: Score achieved.
129
+ max_score: Maximum possible score.
130
+ objectives: Learning objectives covered.
131
+ feedback: Feedback on the assessment.
132
+ details: Detailed assessment data.
133
+
134
+ Returns:
135
+ The newly created AssessmentResult.
136
+ """
137
+ assessment_id = str(uuid4())
138
+ completed_at = datetime.utcnow().isoformat()
139
+
140
+ result = AssessmentResult(
141
+ id=assessment_id,
142
+ student_id=student_id,
143
+ assessment_type=assessment_type,
144
+ title=title,
145
+ score=score,
146
+ max_score=max_score,
147
+ objectives=objectives,
148
+ completed_at=completed_at,
149
+ feedback=feedback,
150
+ details=details or {}
151
+ )
152
+
153
+ # Save to disk
154
+ await self._save_assessment(result)
155
+
156
+ # Update skill progress based on assessment
157
+ for objective_id in objectives:
158
+ await self._update_skills_for_objective(
159
+ student_id,
160
+ objective_id,
161
+ score / max_score
162
+ )
163
+
164
+ return result
165
+
166
+ async def _save_assessment(self, assessment: AssessmentResult) -> bool:
167
+ """
168
+ Save an assessment result to disk.
169
+
170
+ Args:
171
+ assessment: The AssessmentResult to save.
172
+
173
+ Returns:
174
+ True if the save was successful, False otherwise.
175
+ """
176
+ try:
177
+ dir_path = self._get_student_assessments_dir(assessment.student_id)
178
+ file_path = os.path.join(dir_path, f"{assessment.id}.json")
179
+
180
+ with open(file_path, 'w') as f:
181
+ json.dump(assessment.dict(), f, indent=2)
182
+ return True
183
+ except Exception as e:
184
+ print(f"Error saving assessment result: {e}")
185
+ return False
186
+
187
+ async def get_assessment(
188
+ self,
189
+ student_id: str,
190
+ assessment_id: str
191
+ ) -> Optional[AssessmentResult]:
192
+ """
193
+ Get an assessment result by ID.
194
+
195
+ Args:
196
+ student_id: ID of the student.
197
+ assessment_id: ID of the assessment.
198
+
199
+ Returns:
200
+ The AssessmentResult if found, None otherwise.
201
+ """
202
+ try:
203
+ dir_path = self._get_student_assessments_dir(student_id)
204
+ file_path = os.path.join(dir_path, f"{assessment_id}.json")
205
+
206
+ if not os.path.exists(file_path):
207
+ return None
208
+
209
+ with open(file_path, 'r') as f:
210
+ data = json.load(f)
211
+
212
+ return AssessmentResult(**data)
213
+ except Exception as e:
214
+ print(f"Error loading assessment result: {e}")
215
+ return None
216
+
217
+ async def list_assessments(
218
+ self,
219
+ student_id: str,
220
+ assessment_type: Optional[AssessmentType] = None
221
+ ) -> List[AssessmentResult]:
222
+ """
223
+ List assessment results for a student.
224
+
225
+ Args:
226
+ student_id: ID of the student.
227
+ assessment_type: Optional filter by assessment type.
228
+
229
+ Returns:
230
+ List of AssessmentResult objects.
231
+ """
232
+ results = []
233
+
234
+ try:
235
+ dir_path = self._get_student_assessments_dir(student_id)
236
+
237
+ for filename in os.listdir(dir_path):
238
+ if filename.endswith('.json'):
239
+ file_path = os.path.join(dir_path, filename)
240
+
241
+ with open(file_path, 'r') as f:
242
+ data = json.load(f)
243
+
244
+ result = AssessmentResult(**data)
245
+
246
+ # Apply filter if specified
247
+ if assessment_type is None or result.assessment_type == assessment_type:
248
+ results.append(result)
249
+ except Exception as e:
250
+ print(f"Error listing assessment results: {e}")
251
+
252
+ # Sort by completion date, newest first
253
+ results.sort(key=lambda x: x.completed_at, reverse=True)
254
+ return results
255
+
256
+ async def _update_skills_for_objective(
257
+ self,
258
+ student_id: str,
259
+ objective_id: str,
260
+ performance: float
261
+ ) -> None:
262
+ """
263
+ Update skill progress based on performance in an objective.
264
+
265
+ Args:
266
+ student_id: ID of the student.
267
+ objective_id: ID of the learning objective.
268
+ performance: Performance level (0-1).
269
+ """
270
+ # In a real implementation, this would map objectives to skills
271
+ # and update skill progress accordingly.
272
+ # For this demo, we'll create/update a skill with the same ID as the objective.
273
+
274
+ skill = await self.get_skill_progress(student_id, objective_id)
275
+
276
+ if skill:
277
+ # Update existing skill
278
+ new_progress = skill.progress_percentage + (performance * 20) # Increase by up to 20%
279
+
280
+ # Check if we should level up
281
+ if new_progress >= 100:
282
+ # Level up and reset progress
283
+ levels = list(SkillLevel)
284
+ current_index = levels.index(skill.level)
285
+
286
+ if current_index < len(levels) - 1:
287
+ # Move to next level
288
+ skill.level = levels[current_index + 1]
289
+ skill.progress_percentage = new_progress - 100
290
+ else:
291
+ # Already at max level
292
+ skill.progress_percentage = 100
293
+ else:
294
+ skill.progress_percentage = new_progress
295
+
296
+ skill.last_updated = datetime.utcnow().isoformat()
297
+ else:
298
+ # Create new skill
299
+ skill = SkillProgress(
300
+ skill_id=objective_id,
301
+ name=f"Skill for objective {objective_id}", # In a real app, would use actual skill name
302
+ level=SkillLevel.NOVICE,
303
+ progress_percentage=performance * 20, # Initial progress based on performance
304
+ last_updated=datetime.utcnow().isoformat(),
305
+ related_objectives=[objective_id]
306
+ )
307
+
308
+ await self._save_skill_progress(student_id, skill)
309
+
310
+ async def _save_skill_progress(
311
+ self,
312
+ student_id: str,
313
+ skill: SkillProgress
314
+ ) -> bool:
315
+ """
316
+ Save skill progress to disk.
317
+
318
+ Args:
319
+ student_id: ID of the student.
320
+ skill: The SkillProgress to save.
321
+
322
+ Returns:
323
+ True if the save was successful, False otherwise.
324
+ """
325
+ try:
326
+ dir_path = self._get_student_skills_dir(student_id)
327
+ file_path = os.path.join(dir_path, f"{skill.skill_id}.json")
328
+
329
+ with open(file_path, 'w') as f:
330
+ json.dump(skill.dict(), f, indent=2)
331
+ return True
332
+ except Exception as e:
333
+ print(f"Error saving skill progress: {e}")
334
+ return False
335
+
336
+ async def get_skill_progress(
337
+ self,
338
+ student_id: str,
339
+ skill_id: str
340
+ ) -> Optional[SkillProgress]:
341
+ """
342
+ Get skill progress by ID.
343
+
344
+ Args:
345
+ student_id: ID of the student.
346
+ skill_id: ID of the skill.
347
+
348
+ Returns:
349
+ The SkillProgress if found, None otherwise.
350
+ """
351
+ try:
352
+ dir_path = self._get_student_skills_dir(student_id)
353
+ file_path = os.path.join(dir_path, f"{skill_id}.json")
354
+
355
+ if not os.path.exists(file_path):
356
+ return None
357
+
358
+ with open(file_path, 'r') as f:
359
+ data = json.load(f)
360
+
361
+ return SkillProgress(**data)
362
+ except Exception as e:
363
+ print(f"Error loading skill progress: {e}")
364
+ return None
365
+
366
+ async def list_skills(self, student_id: str) -> List[SkillProgress]:
367
+ """
368
+ List skill progress for a student.
369
+
370
+ Args:
371
+ student_id: ID of the student.
372
+
373
+ Returns:
374
+ List of SkillProgress objects.
375
+ """
376
+ skills = []
377
+
378
+ try:
379
+ dir_path = self._get_student_skills_dir(student_id)
380
+
381
+ if os.path.exists(dir_path):
382
+ for filename in os.listdir(dir_path):
383
+ if filename.endswith('.json'):
384
+ file_path = os.path.join(dir_path, filename)
385
+
386
+ with open(file_path, 'r') as f:
387
+ data = json.load(f)
388
+
389
+ skills.append(SkillProgress(**data))
390
+ except Exception as e:
391
+ print(f"Error listing skill progress: {e}")
392
+
393
+ # Sort by level and then by progress percentage
394
+ skills.sort(
395
+ key=lambda x: (list(SkillLevel).index(x.level), x.progress_percentage),
396
+ reverse=True
397
+ )
398
+ return skills
399
+
400
+ async def get_progress_summary(self, student_id: str) -> Dict[str, Any]:
401
+ """
402
+ Get a summary of a student's learning progress.
403
+
404
+ Args:
405
+ student_id: ID of the student.
406
+
407
+ Returns:
408
+ Dictionary containing progress summary.
409
+ """
410
+ assessments = await self.list_assessments(student_id)
411
+ skills = await self.list_skills(student_id)
412
+
413
+ # Calculate assessment statistics
414
+ total_assessments = len(assessments)
415
+ average_score = 0
416
+ assessment_types_count = {}
417
+ recent_assessments = []
418
+
419
+ if total_assessments > 0:
420
+ total_score_percentage = 0
421
+
422
+ for assessment in assessments:
423
+ score_percentage = (assessment.score / assessment.max_score) * 100
424
+ total_score_percentage += score_percentage
425
+
426
+ # Count by type
427
+ assessment_type = assessment.assessment_type
428
+ assessment_types_count[assessment_type] = assessment_types_count.get(assessment_type, 0) + 1
429
+
430
+ average_score = total_score_percentage / total_assessments
431
+ recent_assessments = [a.dict() for a in assessments[:5]] # 5 most recent
432
+
433
+ # Summarize skills
434
+ skill_levels = {}
435
+ for skill in skills:
436
+ level = skill.level
437
+ skill_levels[level] = skill_levels.get(level, 0) + 1
438
+
439
+ # Create summary
440
+ return {
441
+ "student_id": student_id,
442
+ "total_assessments": total_assessments,
443
+ "average_score": round(average_score, 2),
444
+ "assessment_types_count": assessment_types_count,
445
+ "recent_assessments": recent_assessments,
446
+ "total_skills": len(skills),
447
+ "skill_levels": skill_levels,
448
+ "top_skills": [s.dict() for s in skills[:3]], # 3 highest-level skills
449
+ "generated_at": datetime.utcnow().isoformat()
450
+ }
src/edtech_extensions/student_profile.py ADDED
@@ -0,0 +1,289 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ EdTech Extensions - Student Profile Management
3
+
4
+ This module provides functionality for managing student profiles in the MCP EdTech system.
5
+ It includes classes and functions for creating, retrieving, updating, and analyzing student profiles.
6
+ """
7
+
8
+ from datetime import datetime
9
+ from typing import Dict, List, Optional, Any, Set
10
+ from uuid import uuid4
11
+ import json
12
+ import os
13
+ from enum import Enum
14
+
15
+ from pydantic import BaseModel, Field
16
+
17
+ from ..mcp_core.protocol import EducationalLevel, LearningObjective, StudentProfile
18
+
19
+
20
+ class LearningStyle(str, Enum):
21
+ """Learning styles for student profile customization."""
22
+ VISUAL = "visual"
23
+ AUDITORY = "auditory"
24
+ READING = "reading"
25
+ KINESTHETIC = "kinesthetic"
26
+
27
+
28
+ class StudentProfileManager:
29
+ """
30
+ Manages student profiles in the MCP EdTech system.
31
+
32
+ This class provides functionality for creating, retrieving, updating,
33
+ and analyzing student profiles.
34
+ """
35
+
36
+ def __init__(self, storage_dir: str = "./storage/students"):
37
+ """
38
+ Initialize a new StudentProfileManager.
39
+
40
+ Args:
41
+ storage_dir: Directory to store student profiles in.
42
+ """
43
+ self.storage_dir = storage_dir
44
+ os.makedirs(storage_dir, exist_ok=True)
45
+ self.profiles: Dict[str, StudentProfile] = {}
46
+
47
+ def _get_profile_path(self, student_id: str) -> str:
48
+ """
49
+ Get the file path for a student profile.
50
+
51
+ Args:
52
+ student_id: The ID of the student.
53
+
54
+ Returns:
55
+ The file path for the student profile.
56
+ """
57
+ return os.path.join(self.storage_dir, f"{student_id}.json")
58
+
59
+ async def create_profile(
60
+ self,
61
+ name: str,
62
+ educational_level: EducationalLevel,
63
+ learning_style: Optional[str] = None,
64
+ interests: Optional[List[str]] = None,
65
+ strengths: Optional[List[str]] = None,
66
+ areas_for_improvement: Optional[List[str]] = None
67
+ ) -> StudentProfile:
68
+ """
69
+ Create a new student profile.
70
+
71
+ Args:
72
+ name: Student's name.
73
+ educational_level: Student's educational level.
74
+ learning_style: Student's preferred learning style.
75
+ interests: Student's interests.
76
+ strengths: Student's academic strengths.
77
+ areas_for_improvement: Areas where the student needs improvement.
78
+
79
+ Returns:
80
+ The newly created StudentProfile.
81
+ """
82
+ student_id = str(uuid4())
83
+
84
+ profile = StudentProfile(
85
+ id=student_id,
86
+ name=name,
87
+ educational_level=educational_level,
88
+ learning_style=learning_style or "",
89
+ interests=interests or [],
90
+ strengths=strengths or [],
91
+ areas_for_improvement=areas_for_improvement or [],
92
+ completed_objectives=[],
93
+ current_objectives=[]
94
+ )
95
+
96
+ # Save to memory and disk
97
+ self.profiles[student_id] = profile
98
+ await self._save_profile(profile)
99
+
100
+ return profile
101
+
102
+ async def _save_profile(self, profile: StudentProfile) -> bool:
103
+ """
104
+ Save a student profile to disk.
105
+
106
+ Args:
107
+ profile: The StudentProfile to save.
108
+
109
+ Returns:
110
+ True if the save was successful, False otherwise.
111
+ """
112
+ try:
113
+ with open(self._get_profile_path(profile.id), 'w') as f:
114
+ json.dump(profile.dict(), f, indent=2)
115
+ return True
116
+ except Exception as e:
117
+ print(f"Error saving student profile: {e}")
118
+ return False
119
+
120
+ async def get_profile(self, student_id: str) -> Optional[StudentProfile]:
121
+ """
122
+ Get a student profile by ID.
123
+
124
+ Args:
125
+ student_id: The ID of the student.
126
+
127
+ Returns:
128
+ The StudentProfile if found, None otherwise.
129
+ """
130
+ # Check memory cache first
131
+ if student_id in self.profiles:
132
+ return self.profiles[student_id]
133
+
134
+ # Try to load from disk
135
+ try:
136
+ path = self._get_profile_path(student_id)
137
+ if not os.path.exists(path):
138
+ return None
139
+
140
+ with open(path, 'r') as f:
141
+ data = json.load(f)
142
+
143
+ profile = StudentProfile(**data)
144
+ self.profiles[student_id] = profile
145
+ return profile
146
+ except Exception as e:
147
+ print(f"Error loading student profile: {e}")
148
+ return None
149
+
150
+ async def update_profile(
151
+ self,
152
+ student_id: str,
153
+ updates: Dict[str, Any]
154
+ ) -> Optional[StudentProfile]:
155
+ """
156
+ Update a student profile.
157
+
158
+ Args:
159
+ student_id: The ID of the student.
160
+ updates: Dictionary of updates to apply.
161
+
162
+ Returns:
163
+ The updated StudentProfile if found, None otherwise.
164
+ """
165
+ profile = await self.get_profile(student_id)
166
+ if not profile:
167
+ return None
168
+
169
+ # Apply updates
170
+ profile_dict = profile.dict()
171
+ for key, value in updates.items():
172
+ if key in profile_dict:
173
+ setattr(profile, key, value)
174
+
175
+ # Save updated profile
176
+ await self._save_profile(profile)
177
+ self.profiles[student_id] = profile
178
+
179
+ return profile
180
+
181
+ async def delete_profile(self, student_id: str) -> bool:
182
+ """
183
+ Delete a student profile.
184
+
185
+ Args:
186
+ student_id: The ID of the student.
187
+
188
+ Returns:
189
+ True if the deletion was successful, False otherwise.
190
+ """
191
+ try:
192
+ # Remove from memory
193
+ if student_id in self.profiles:
194
+ del self.profiles[student_id]
195
+
196
+ # Remove from disk
197
+ path = self._get_profile_path(student_id)
198
+ if os.path.exists(path):
199
+ os.remove(path)
200
+ return True
201
+ return False
202
+ except Exception as e:
203
+ print(f"Error deleting student profile: {e}")
204
+ return False
205
+
206
+ async def list_profiles(self) -> List[StudentProfile]:
207
+ """
208
+ List all student profiles.
209
+
210
+ Returns:
211
+ List of StudentProfile objects.
212
+ """
213
+ profiles = []
214
+
215
+ # Load all profiles from disk
216
+ try:
217
+ for filename in os.listdir(self.storage_dir):
218
+ if filename.endswith('.json'):
219
+ student_id = filename[:-5] # Remove .json extension
220
+ profile = await self.get_profile(student_id)
221
+ if profile:
222
+ profiles.append(profile)
223
+ except Exception as e:
224
+ print(f"Error listing student profiles: {e}")
225
+
226
+ return profiles
227
+
228
+ async def add_completed_objective(
229
+ self,
230
+ student_id: str,
231
+ objective_id: str
232
+ ) -> Optional[StudentProfile]:
233
+ """
234
+ Add a completed learning objective to a student profile.
235
+
236
+ Args:
237
+ student_id: The ID of the student.
238
+ objective_id: The ID of the completed learning objective.
239
+
240
+ Returns:
241
+ The updated StudentProfile if found, None otherwise.
242
+ """
243
+ profile = await self.get_profile(student_id)
244
+ if not profile:
245
+ return None
246
+
247
+ # Add to completed objectives if not already there
248
+ if objective_id not in profile.completed_objectives:
249
+ profile.completed_objectives.append(objective_id)
250
+
251
+ # Remove from current objectives if present
252
+ if objective_id in profile.current_objectives:
253
+ profile.current_objectives.remove(objective_id)
254
+
255
+ # Save updated profile
256
+ await self._save_profile(profile)
257
+ self.profiles[student_id] = profile
258
+
259
+ return profile
260
+
261
+ async def add_current_objective(
262
+ self,
263
+ student_id: str,
264
+ objective_id: str
265
+ ) -> Optional[StudentProfile]:
266
+ """
267
+ Add a current learning objective to a student profile.
268
+
269
+ Args:
270
+ student_id: The ID of the student.
271
+ objective_id: The ID of the learning objective.
272
+
273
+ Returns:
274
+ The updated StudentProfile if found, None otherwise.
275
+ """
276
+ profile = await self.get_profile(student_id)
277
+ if not profile:
278
+ return None
279
+
280
+ # Add to current objectives if not already there and not completed
281
+ if (objective_id not in profile.current_objectives and
282
+ objective_id not in profile.completed_objectives):
283
+ profile.current_objectives.append(objective_id)
284
+
285
+ # Save updated profile
286
+ await self._save_profile(profile)
287
+ self.profiles[student_id] = profile
288
+
289
+ return profile
src/mcp_core/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ """
2
+ MCP Core - Model Context Protocol implementation for EdTech applications.
3
+ """
src/mcp_core/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (262 Bytes). View file
 
src/mcp_core/__pycache__/context.cpython-311.pyc ADDED
Binary file (11 kB). View file
 
src/mcp_core/__pycache__/model_adapters.cpython-311.pyc ADDED
Binary file (12.6 kB). View file
 
src/mcp_core/__pycache__/processor.cpython-311.pyc ADDED
Binary file (7.31 kB). View file
 
src/mcp_core/__pycache__/protocol.cpython-311.pyc ADDED
Binary file (16.7 kB). View file
 
src/mcp_core/context.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+ from typing import Dict, List, Optional, Any, Union
3
+ from uuid import uuid4
4
+ import json
5
+
6
+
7
+ class Context:
8
+ def __init__(self, context_id: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None, max_history_length: int = 100):
9
+ self.context_id = context_id or str(uuid4())
10
+ self.metadata = metadata or {}
11
+ self.max_history_length = max_history_length
12
+ self.history: List[Dict[str, Any]] = []
13
+ self.state: Dict[str, Any] = {}
14
+ self.created_at = datetime.utcnow()
15
+ self.updated_at = self.created_at
16
+
17
+ def add_interaction(self, input_data: Dict[str, Any], output_data: Dict[str, Any]) -> None:
18
+ interaction = {
19
+ "timestamp": datetime.utcnow().isoformat(),
20
+ "input": input_data,
21
+ "output": output_data
22
+ }
23
+
24
+ self.history.append(interaction)
25
+
26
+ if len(self.history) > self.max_history_length:
27
+ self.history = self.history[-self.max_history_length:]
28
+
29
+ self.updated_at = datetime.utcnow()
30
+
31
+ def update_state(self, new_state: Dict[str, Any], merge: bool = True) -> None:
32
+ if merge:
33
+ self.state.update(new_state)
34
+ else:
35
+ self.state = new_state
36
+
37
+ self.updated_at = datetime.utcnow()
38
+
39
+ def get_history(self, limit: Optional[int] = None) -> List[Dict[str, Any]]:
40
+ if limit is None or limit >= len(self.history):
41
+ return self.history
42
+ return self.history[-limit:]
43
+
44
+ def to_dict(self) -> Dict[str, Any]:
45
+ return {
46
+ "context_id": self.context_id,
47
+ "metadata": self.metadata,
48
+ "state": self.state,
49
+ "history": self.history,
50
+ "created_at": self.created_at.isoformat(),
51
+ "updated_at": self.updated_at.isoformat(),
52
+ }
53
+
54
+ def to_json(self) -> str:
55
+ return json.dumps(self.to_dict())
56
+
57
+ @classmethod
58
+ def from_dict(cls, data: Dict[str, Any]) -> 'Context':
59
+ context = cls(context_id=data.get("context_id"))
60
+ context.metadata = data.get("metadata", {})
61
+ context.state = data.get("state", {})
62
+ context.history = data.get("history", [])
63
+
64
+ if "created_at" in data:
65
+ context.created_at = datetime.fromisoformat(data["created_at"])
66
+ if "updated_at" in data:
67
+ context.updated_at = datetime.fromisoformat(data["updated_at"])
68
+
69
+ return context
70
+
71
+ @classmethod
72
+ def from_json(cls, json_str: str) -> 'Context':
73
+ data = json.loads(json_str)
74
+ return cls.from_dict(data)
75
+
76
+
77
+ class ContextManager:
78
+ def __init__(self):
79
+ self.contexts: Dict[str, Context] = {}
80
+
81
+ def create_context(self, metadata: Optional[Dict[str, Any]] = None, context_id: Optional[str] = None) -> Context:
82
+ context = Context(context_id=context_id, metadata=metadata)
83
+ self.contexts[context.context_id] = context
84
+ return context
85
+
86
+ def get_context(self, context_id: str) -> Optional[Context]:
87
+ return self.contexts.get(context_id)
88
+
89
+ def update_context(self, context_id: str, updates: Dict[str, Any]) -> Optional[Context]:
90
+ context = self.get_context(context_id)
91
+ if not context:
92
+ return None
93
+
94
+ if "metadata" in updates:
95
+ context.metadata.update(updates["metadata"])
96
+
97
+ if "state" in updates:
98
+ context.update_state(updates["state"])
99
+
100
+ return context
101
+
102
+ def delete_context(self, context_id: str) -> bool:
103
+ if context_id in self.contexts:
104
+ del self.contexts[context_id]
105
+ return True
106
+ return False
107
+
108
+ def list_contexts(self) -> List[str]:
109
+ return list(self.contexts.keys())
src/mcp_core/model_adapters.py ADDED
@@ -0,0 +1,313 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ MCP Core - Model Interface Adapters
3
+
4
+ This module provides adapters for interfacing with various AI models,
5
+ allowing the MCP system to work with different model providers and types
6
+ while maintaining a consistent interface.
7
+ """
8
+
9
+ from abc import ABC, abstractmethod
10
+ from typing import Dict, Any, List, Optional, Union
11
+
12
+
13
+ class ModelAdapter(ABC):
14
+ """
15
+ Abstract base class for model adapters.
16
+
17
+ Model adapters provide a consistent interface for interacting with different
18
+ AI models, abstracting away the specifics of each model's API.
19
+ """
20
+
21
+ @abstractmethod
22
+ async def process(self, input_data: Dict[str, Any], context: Dict[str, Any] = None) -> Dict[str, Any]:
23
+ """
24
+ Process an input with the model and return the output.
25
+
26
+ Args:
27
+ input_data: The input data to process.
28
+ context: Optional context information to guide the model.
29
+
30
+ Returns:
31
+ The model's output.
32
+ """
33
+ pass
34
+
35
+ @abstractmethod
36
+ def get_capabilities(self) -> Dict[str, Any]:
37
+ """
38
+ Get the capabilities of this model.
39
+
40
+ Returns:
41
+ Dictionary describing the model's capabilities.
42
+ """
43
+ pass
44
+
45
+
46
+ class MockModelAdapter(ModelAdapter):
47
+ """
48
+ A mock model adapter for testing and demonstration purposes.
49
+
50
+ This adapter simulates responses without requiring an actual AI model.
51
+ """
52
+
53
+ def __init__(self, model_id: str = "mock-model", capabilities: Dict[str, Any] = None):
54
+ """
55
+ Initialize a new MockModelAdapter.
56
+
57
+ Args:
58
+ model_id: Identifier for this mock model.
59
+ capabilities: Dictionary of capabilities this mock model supports.
60
+ """
61
+ self.model_id = model_id
62
+ self._capabilities = capabilities or {
63
+ "text_generation": True,
64
+ "question_answering": True,
65
+ "content_evaluation": True,
66
+ "max_input_length": 1000,
67
+ "supports_streaming": False
68
+ }
69
+
70
+ async def process(self, input_data: Dict[str, Any], context: Dict[str, Any] = None) -> Dict[str, Any]:
71
+ """
72
+ Process an input with the mock model.
73
+
74
+ Args:
75
+ input_data: The input data to process.
76
+ context: Optional context information.
77
+
78
+ Returns:
79
+ A mock response based on the input.
80
+ """
81
+ # Extract the input text or use a default
82
+ input_text = input_data.get("text", "")
83
+
84
+ # Generate a mock response based on the input
85
+ if "question" in input_text.lower():
86
+ response = f"This is a mock answer to your question about {input_text.split('?')[0]}."
87
+ elif "explain" in input_text.lower():
88
+ topic = input_text.lower().split("explain")[1].strip()
89
+ response = f"Here's a mock explanation of {topic}. This would contain educational content in a real implementation."
90
+ else:
91
+ response = f"I've processed your input: '{input_text}'. This is a mock response that would be more relevant in a real implementation."
92
+
93
+ # Include some educational metadata in the response
94
+ return {
95
+ "text": response,
96
+ "model_id": self.model_id,
97
+ "confidence": 0.85,
98
+ "educational_metadata": {
99
+ "complexity_level": "intermediate",
100
+ "topics": ["sample", "mock", "demonstration"],
101
+ "suggested_follow_ups": [
102
+ "Tell me more about this topic",
103
+ "Can you provide an example?",
104
+ "What are the key concepts I should understand?"
105
+ ]
106
+ }
107
+ }
108
+
109
+ def get_capabilities(self) -> Dict[str, Any]:
110
+ """
111
+ Get the capabilities of this mock model.
112
+
113
+ Returns:
114
+ Dictionary describing the mock model's capabilities.
115
+ """
116
+ return self._capabilities
117
+
118
+
119
+ class OpenAIAdapter(ModelAdapter):
120
+ """
121
+ Adapter for OpenAI models.
122
+
123
+ This adapter provides an interface to OpenAI's models through their API.
124
+ Note: This is a placeholder implementation. In a real application, you would
125
+ need to implement the actual API calls to OpenAI.
126
+ """
127
+
128
+ def __init__(self, model_name: str = "gpt-3.5-turbo", api_key: Optional[str] = None):
129
+ """
130
+ Initialize a new OpenAIAdapter.
131
+
132
+ Args:
133
+ model_name: The name of the OpenAI model to use.
134
+ api_key: OpenAI API key. If None, will attempt to use environment variable.
135
+ """
136
+ self.model_name = model_name
137
+ self.api_key = api_key
138
+ # In a real implementation, you would initialize the OpenAI client here
139
+
140
+ async def process(self, input_data: Dict[str, Any], context: Dict[str, Any] = None) -> Dict[str, Any]:
141
+ """
142
+ Process an input with an OpenAI model.
143
+
144
+ Args:
145
+ input_data: The input data to process.
146
+ context: Optional context information.
147
+
148
+ Returns:
149
+ The model's output.
150
+ """
151
+ # This is a placeholder. In a real implementation, you would:
152
+ # 1. Format the input and context for the OpenAI API
153
+ # 2. Make the API call
154
+ # 3. Process and return the response
155
+
156
+ # Placeholder response
157
+ return {
158
+ "text": f"This is a placeholder response from {self.model_name}. In a real implementation, this would be the actual model output.",
159
+ "model": self.model_name,
160
+ "usage": {
161
+ "prompt_tokens": 10,
162
+ "completion_tokens": 20,
163
+ "total_tokens": 30
164
+ }
165
+ }
166
+
167
+ def get_capabilities(self) -> Dict[str, Any]:
168
+ """
169
+ Get the capabilities of this OpenAI model.
170
+
171
+ Returns:
172
+ Dictionary describing the model's capabilities.
173
+ """
174
+ # This would be more accurate in a real implementation
175
+ capabilities = {
176
+ "text_generation": True,
177
+ "question_answering": True,
178
+ "content_evaluation": True,
179
+ "supports_streaming": True
180
+ }
181
+
182
+ # Different capabilities based on model
183
+ if "gpt-4" in self.model_name:
184
+ capabilities.update({
185
+ "max_input_length": 8000,
186
+ "reasoning_ability": "advanced"
187
+ })
188
+ else:
189
+ capabilities.update({
190
+ "max_input_length": 4000,
191
+ "reasoning_ability": "intermediate"
192
+ })
193
+
194
+ return capabilities
195
+
196
+
197
+ class HuggingFaceAdapter(ModelAdapter):
198
+ """
199
+ Adapter for Hugging Face models.
200
+
201
+ This adapter provides an interface to models hosted on Hugging Face's model hub.
202
+ Note: This is a placeholder implementation. In a real application, you would
203
+ need to implement the actual API calls to Hugging Face.
204
+ """
205
+
206
+ def __init__(self, model_id: str, api_key: Optional[str] = None):
207
+ """
208
+ Initialize a new HuggingFaceAdapter.
209
+
210
+ Args:
211
+ model_id: The ID of the Hugging Face model to use.
212
+ api_key: Hugging Face API key. If None, will attempt to use environment variable.
213
+ """
214
+ self.model_id = model_id
215
+ self.api_key = api_key
216
+ # In a real implementation, you would initialize the Hugging Face client here
217
+
218
+ async def process(self, input_data: Dict[str, Any], context: Dict[str, Any] = None) -> Dict[str, Any]:
219
+ """
220
+ Process an input with a Hugging Face model.
221
+
222
+ Args:
223
+ input_data: The input data to process.
224
+ context: Optional context information.
225
+
226
+ Returns:
227
+ The model's output.
228
+ """
229
+ # This is a placeholder. In a real implementation, you would:
230
+ # 1. Format the input and context for the Hugging Face API
231
+ # 2. Make the API call
232
+ # 3. Process and return the response
233
+
234
+ # Placeholder response
235
+ return {
236
+ "text": f"This is a placeholder response from Hugging Face model {self.model_id}. In a real implementation, this would be the actual model output.",
237
+ "model": self.model_id
238
+ }
239
+
240
+ def get_capabilities(self) -> Dict[str, Any]:
241
+ """
242
+ Get the capabilities of this Hugging Face model.
243
+
244
+ Returns:
245
+ Dictionary describing the model's capabilities.
246
+ """
247
+ # This would be determined dynamically in a real implementation
248
+ return {
249
+ "text_generation": True,
250
+ "question_answering": True,
251
+ "content_evaluation": True,
252
+ "max_input_length": 2000,
253
+ "supports_streaming": False
254
+ }
255
+
256
+
257
+ class ModelRegistry:
258
+ """
259
+ Registry for model adapters.
260
+
261
+ This class manages the available model adapters and provides methods
262
+ for registering, retrieving, and listing them.
263
+ """
264
+
265
+ def __init__(self):
266
+ """Initialize a new ModelRegistry."""
267
+ self.adapters: Dict[str, ModelAdapter] = {}
268
+
269
+ def register_adapter(self, name: str, adapter: ModelAdapter) -> None:
270
+ """
271
+ Register a model adapter.
272
+
273
+ Args:
274
+ name: Name to register the adapter under.
275
+ adapter: The ModelAdapter instance to register.
276
+ """
277
+ self.adapters[name] = adapter
278
+
279
+ def get_adapter(self, name: str) -> Optional[ModelAdapter]:
280
+ """
281
+ Get a registered adapter by name.
282
+
283
+ Args:
284
+ name: Name of the adapter to retrieve.
285
+
286
+ Returns:
287
+ The ModelAdapter if found, None otherwise.
288
+ """
289
+ return self.adapters.get(name)
290
+
291
+ def list_adapters(self) -> List[str]:
292
+ """
293
+ List all registered adapter names.
294
+
295
+ Returns:
296
+ List of adapter names.
297
+ """
298
+ return list(self.adapters.keys())
299
+
300
+ def get_adapter_capabilities(self, name: str) -> Optional[Dict[str, Any]]:
301
+ """
302
+ Get the capabilities of a registered adapter.
303
+
304
+ Args:
305
+ name: Name of the adapter.
306
+
307
+ Returns:
308
+ Dictionary of capabilities if the adapter exists, None otherwise.
309
+ """
310
+ adapter = self.get_adapter(name)
311
+ if adapter:
312
+ return adapter.get_capabilities()
313
+ return None
src/mcp_core/persistence.py ADDED
@@ -0,0 +1,505 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ MCP Core - State Persistence Layer
3
+
4
+ This module provides functionality for persisting context state across sessions,
5
+ allowing for long-term storage and retrieval of context data.
6
+ """
7
+
8
+ import json
9
+ import os
10
+ import sqlite3
11
+ from datetime import datetime
12
+ from typing import Dict, List, Optional, Any, Union
13
+ from abc import ABC, abstractmethod
14
+
15
+ from .context import Context
16
+
17
+
18
+ class PersistenceProvider(ABC):
19
+ """
20
+ Abstract base class for persistence providers.
21
+
22
+ Persistence providers handle the storage and retrieval of context data,
23
+ allowing contexts to be persisted across sessions.
24
+ """
25
+
26
+ @abstractmethod
27
+ async def save_context(self, context: Context) -> bool:
28
+ """
29
+ Save a context to persistent storage.
30
+
31
+ Args:
32
+ context: The Context object to save.
33
+
34
+ Returns:
35
+ True if the save was successful, False otherwise.
36
+ """
37
+ pass
38
+
39
+ @abstractmethod
40
+ async def load_context(self, context_id: str) -> Optional[Context]:
41
+ """
42
+ Load a context from persistent storage.
43
+
44
+ Args:
45
+ context_id: The ID of the context to load.
46
+
47
+ Returns:
48
+ The loaded Context object if found, None otherwise.
49
+ """
50
+ pass
51
+
52
+ @abstractmethod
53
+ async def delete_context(self, context_id: str) -> bool:
54
+ """
55
+ Delete a context from persistent storage.
56
+
57
+ Args:
58
+ context_id: The ID of the context to delete.
59
+
60
+ Returns:
61
+ True if the deletion was successful, False otherwise.
62
+ """
63
+ pass
64
+
65
+ @abstractmethod
66
+ async def list_contexts(self, filter_criteria: Optional[Dict[str, Any]] = None) -> List[str]:
67
+ """
68
+ List context IDs in persistent storage, optionally filtered.
69
+
70
+ Args:
71
+ filter_criteria: Optional criteria to filter contexts by.
72
+
73
+ Returns:
74
+ List of context IDs matching the filter criteria.
75
+ """
76
+ pass
77
+
78
+
79
+ class FileSystemProvider(PersistenceProvider):
80
+ """
81
+ File system-based persistence provider.
82
+
83
+ This provider stores contexts as JSON files in the file system.
84
+ """
85
+
86
+ def __init__(self, storage_dir: str = "./storage/contexts"):
87
+ """
88
+ Initialize a new FileSystemProvider.
89
+
90
+ Args:
91
+ storage_dir: Directory to store context files in.
92
+ """
93
+ self.storage_dir = storage_dir
94
+ os.makedirs(storage_dir, exist_ok=True)
95
+
96
+ def _get_context_path(self, context_id: str) -> str:
97
+ """
98
+ Get the file path for a context.
99
+
100
+ Args:
101
+ context_id: The ID of the context.
102
+
103
+ Returns:
104
+ The file path for the context.
105
+ """
106
+ return os.path.join(self.storage_dir, f"{context_id}.json")
107
+
108
+ async def save_context(self, context: Context) -> bool:
109
+ """
110
+ Save a context to a JSON file.
111
+
112
+ Args:
113
+ context: The Context object to save.
114
+
115
+ Returns:
116
+ True if the save was successful, False otherwise.
117
+ """
118
+ try:
119
+ with open(self._get_context_path(context.context_id), 'w') as f:
120
+ json.dump(context.to_dict(), f, indent=2)
121
+ return True
122
+ except Exception as e:
123
+ print(f"Error saving context: {e}")
124
+ return False
125
+
126
+ async def load_context(self, context_id: str) -> Optional[Context]:
127
+ """
128
+ Load a context from a JSON file.
129
+
130
+ Args:
131
+ context_id: The ID of the context to load.
132
+
133
+ Returns:
134
+ The loaded Context object if found, None otherwise.
135
+ """
136
+ try:
137
+ path = self._get_context_path(context_id)
138
+ if not os.path.exists(path):
139
+ return None
140
+
141
+ with open(path, 'r') as f:
142
+ data = json.load(f)
143
+
144
+ return Context.from_dict(data)
145
+ except Exception as e:
146
+ print(f"Error loading context: {e}")
147
+ return None
148
+
149
+ async def delete_context(self, context_id: str) -> bool:
150
+ """
151
+ Delete a context JSON file.
152
+
153
+ Args:
154
+ context_id: The ID of the context to delete.
155
+
156
+ Returns:
157
+ True if the deletion was successful, False otherwise.
158
+ """
159
+ try:
160
+ path = self._get_context_path(context_id)
161
+ if os.path.exists(path):
162
+ os.remove(path)
163
+ return True
164
+ return False
165
+ except Exception as e:
166
+ print(f"Error deleting context: {e}")
167
+ return False
168
+
169
+ async def list_contexts(self, filter_criteria: Optional[Dict[str, Any]] = None) -> List[str]:
170
+ """
171
+ List context IDs in the storage directory, optionally filtered.
172
+
173
+ Args:
174
+ filter_criteria: Optional criteria to filter contexts by.
175
+
176
+ Returns:
177
+ List of context IDs matching the filter criteria.
178
+ """
179
+ try:
180
+ context_ids = []
181
+ for filename in os.listdir(self.storage_dir):
182
+ if filename.endswith('.json'):
183
+ context_id = filename[:-5] # Remove .json extension
184
+
185
+ # Apply filtering if criteria provided
186
+ if filter_criteria:
187
+ try:
188
+ with open(self._get_context_path(context_id), 'r') as f:
189
+ data = json.load(f)
190
+
191
+ # Check if context matches all filter criteria
192
+ matches = True
193
+ for key, value in filter_criteria.items():
194
+ # Handle nested keys with dot notation
195
+ if '.' in key:
196
+ parts = key.split('.')
197
+ current = data
198
+ for part in parts:
199
+ if part not in current:
200
+ matches = False
201
+ break
202
+ current = current[part]
203
+ if matches and current != value:
204
+ matches = False
205
+ elif key not in data or data[key] != value:
206
+ matches = False
207
+
208
+ if matches:
209
+ context_ids.append(context_id)
210
+ except:
211
+ # Skip contexts that can't be loaded or don't match
212
+ continue
213
+ else:
214
+ context_ids.append(context_id)
215
+
216
+ return context_ids
217
+ except Exception as e:
218
+ print(f"Error listing contexts: {e}")
219
+ return []
220
+
221
+
222
+ class SQLiteProvider(PersistenceProvider):
223
+ """
224
+ SQLite-based persistence provider.
225
+
226
+ This provider stores contexts in an SQLite database.
227
+ """
228
+
229
+ def __init__(self, db_path: str = "./storage/contexts.db"):
230
+ """
231
+ Initialize a new SQLiteProvider.
232
+
233
+ Args:
234
+ db_path: Path to the SQLite database file.
235
+ """
236
+ os.makedirs(os.path.dirname(db_path), exist_ok=True)
237
+ self.db_path = db_path
238
+ self._init_db()
239
+
240
+ def _init_db(self):
241
+ """Initialize the database schema if it doesn't exist."""
242
+ conn = sqlite3.connect(self.db_path)
243
+ cursor = conn.cursor()
244
+
245
+ # Create contexts table
246
+ cursor.execute('''
247
+ CREATE TABLE IF NOT EXISTS contexts (
248
+ context_id TEXT PRIMARY KEY,
249
+ metadata TEXT,
250
+ state TEXT,
251
+ history TEXT,
252
+ created_at TEXT,
253
+ updated_at TEXT
254
+ )
255
+ ''')
256
+
257
+ conn.commit()
258
+ conn.close()
259
+
260
+ async def save_context(self, context: Context) -> bool:
261
+ """
262
+ Save a context to the SQLite database.
263
+
264
+ Args:
265
+ context: The Context object to save.
266
+
267
+ Returns:
268
+ True if the save was successful, False otherwise.
269
+ """
270
+ try:
271
+ conn = sqlite3.connect(self.db_path)
272
+ cursor = conn.cursor()
273
+
274
+ # Convert complex objects to JSON strings
275
+ context_data = context.to_dict()
276
+ metadata = json.dumps(context_data.get("metadata", {}))
277
+ state = json.dumps(context_data.get("state", {}))
278
+ history = json.dumps(context_data.get("history", []))
279
+
280
+ # Check if context already exists
281
+ cursor.execute("SELECT 1 FROM contexts WHERE context_id = ?", (context.context_id,))
282
+ exists = cursor.fetchone() is not None
283
+
284
+ if exists:
285
+ # Update existing context
286
+ cursor.execute('''
287
+ UPDATE contexts
288
+ SET metadata = ?, state = ?, history = ?, updated_at = ?
289
+ WHERE context_id = ?
290
+ ''', (metadata, state, history, context.updated_at.isoformat(), context.context_id))
291
+ else:
292
+ # Insert new context
293
+ cursor.execute('''
294
+ INSERT INTO contexts (context_id, metadata, state, history, created_at, updated_at)
295
+ VALUES (?, ?, ?, ?, ?, ?)
296
+ ''', (
297
+ context.context_id,
298
+ metadata,
299
+ state,
300
+ history,
301
+ context.created_at.isoformat(),
302
+ context.updated_at.isoformat()
303
+ ))
304
+
305
+ conn.commit()
306
+ conn.close()
307
+ return True
308
+ except Exception as e:
309
+ print(f"Error saving context to SQLite: {e}")
310
+ return False
311
+
312
+ async def load_context(self, context_id: str) -> Optional[Context]:
313
+ """
314
+ Load a context from the SQLite database.
315
+
316
+ Args:
317
+ context_id: The ID of the context to load.
318
+
319
+ Returns:
320
+ The loaded Context object if found, None otherwise.
321
+ """
322
+ try:
323
+ conn = sqlite3.connect(self.db_path)
324
+ cursor = conn.cursor()
325
+
326
+ cursor.execute('''
327
+ SELECT context_id, metadata, state, history, created_at, updated_at
328
+ FROM contexts
329
+ WHERE context_id = ?
330
+ ''', (context_id,))
331
+
332
+ row = cursor.fetchone()
333
+ conn.close()
334
+
335
+ if not row:
336
+ return None
337
+
338
+ # Reconstruct context from database row
339
+ context_data = {
340
+ "context_id": row[0],
341
+ "metadata": json.loads(row[1]),
342
+ "state": json.loads(row[2]),
343
+ "history": json.loads(row[3]),
344
+ "created_at": row[4],
345
+ "updated_at": row[5]
346
+ }
347
+
348
+ return Context.from_dict(context_data)
349
+ except Exception as e:
350
+ print(f"Error loading context from SQLite: {e}")
351
+ return None
352
+
353
+ async def delete_context(self, context_id: str) -> bool:
354
+ """
355
+ Delete a context from the SQLite database.
356
+
357
+ Args:
358
+ context_id: The ID of the context to delete.
359
+
360
+ Returns:
361
+ True if the deletion was successful, False otherwise.
362
+ """
363
+ try:
364
+ conn = sqlite3.connect(self.db_path)
365
+ cursor = conn.cursor()
366
+
367
+ cursor.execute("DELETE FROM contexts WHERE context_id = ?", (context_id,))
368
+ deleted = cursor.rowcount > 0
369
+
370
+ conn.commit()
371
+ conn.close()
372
+
373
+ return deleted
374
+ except Exception as e:
375
+ print(f"Error deleting context from SQLite: {e}")
376
+ return False
377
+
378
+ async def list_contexts(self, filter_criteria: Optional[Dict[str, Any]] = None) -> List[str]:
379
+ """
380
+ List context IDs in the SQLite database, optionally filtered.
381
+
382
+ Args:
383
+ filter_criteria: Optional criteria to filter contexts by.
384
+
385
+ Returns:
386
+ List of context IDs matching the filter criteria.
387
+ """
388
+ try:
389
+ conn = sqlite3.connect(self.db_path)
390
+ cursor = conn.cursor()
391
+
392
+ if not filter_criteria:
393
+ # Simple case: no filtering
394
+ cursor.execute("SELECT context_id FROM contexts")
395
+ context_ids = [row[0] for row in cursor.fetchall()]
396
+ else:
397
+ # Complex case: need to load and filter each context
398
+ cursor.execute("SELECT context_id, metadata, state FROM contexts")
399
+ rows = cursor.fetchall()
400
+
401
+ context_ids = []
402
+ for row in rows:
403
+ context_id = row[0]
404
+ metadata = json.loads(row[1])
405
+ state = json.loads(row[2])
406
+
407
+ # Check if context matches all filter criteria
408
+ matches = True
409
+ for key, value in filter_criteria.items():
410
+ if key.startswith("metadata."):
411
+ # Filter on metadata field
412
+ field = key[9:] # Remove "metadata." prefix
413
+ if field not in metadata or metadata[field] != value:
414
+ matches = False
415
+ break
416
+ elif key.startswith("state."):
417
+ # Filter on state field
418
+ field = key[6:] # Remove "state." prefix
419
+ if field not in state or state[field] != value:
420
+ matches = False
421
+ break
422
+
423
+ if matches:
424
+ context_ids.append(context_id)
425
+
426
+ conn.close()
427
+ return context_ids
428
+ except Exception as e:
429
+ print(f"Error listing contexts from SQLite: {e}")
430
+ return []
431
+
432
+
433
+ class PersistenceManager:
434
+ """
435
+ Manages context persistence operations.
436
+
437
+ This class provides a unified interface for working with different
438
+ persistence providers.
439
+ """
440
+
441
+ def __init__(self, provider: PersistenceProvider):
442
+ """
443
+ Initialize a new PersistenceManager.
444
+
445
+ Args:
446
+ provider: The persistence provider to use.
447
+ """
448
+ self.provider = provider
449
+
450
+ async def save_context(self, context: Context) -> bool:
451
+ """
452
+ Save a context using the configured provider.
453
+
454
+ Args:
455
+ context: The Context object to save.
456
+
457
+ Returns:
458
+ True if the save was successful, False otherwise.
459
+ """
460
+ return await self.provider.save_context(context)
461
+
462
+ async def load_context(self, context_id: str) -> Optional[Context]:
463
+ """
464
+ Load a context using the configured provider.
465
+
466
+ Args:
467
+ context_id: The ID of the context to load.
468
+
469
+ Returns:
470
+ The loaded Context object if found, None otherwise.
471
+ """
472
+ return await self.provider.load_context(context_id)
473
+
474
+ async def delete_context(self, context_id: str) -> bool:
475
+ """
476
+ Delete a context using the configured provider.
477
+
478
+ Args:
479
+ context_id: The ID of the context to delete.
480
+
481
+ Returns:
482
+ True if the deletion was successful, False otherwise.
483
+ """
484
+ return await self.provider.delete_context(context_id)
485
+
486
+ async def list_contexts(self, filter_criteria: Optional[Dict[str, Any]] = None) -> List[str]:
487
+ """
488
+ List context IDs using the configured provider, optionally filtered.
489
+
490
+ Args:
491
+ filter_criteria: Optional criteria to filter contexts by.
492
+
493
+ Returns:
494
+ List of context IDs matching the filter criteria.
495
+ """
496
+ return await self.provider.list_contexts(filter_criteria)
497
+
498
+ def change_provider(self, provider: PersistenceProvider) -> None:
499
+ """
500
+ Change the persistence provider.
501
+
502
+ Args:
503
+ provider: The new persistence provider to use.
504
+ """
505
+ self.provider = provider
src/mcp_core/processor.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ MCP Core - Interaction Processor
3
+
4
+ This module provides the core functionality for processing interactions
5
+ between users and models through the Model Context Protocol.
6
+ """
7
+
8
+ from typing import Dict, Any, Optional, List, Union
9
+ import asyncio
10
+
11
+ from .context import Context, ContextManager
12
+ from .model_adapters import ModelAdapter, ModelRegistry
13
+ from .protocol import MCPRequest, MCPResponse, InteractionType
14
+
15
+
16
+ class InteractionProcessor:
17
+ """
18
+ Processes interactions between users and models.
19
+
20
+ This class handles the flow of interactions through the MCP system,
21
+ managing context, routing to appropriate models, and formatting responses.
22
+ """
23
+
24
+ def __init__(
25
+ self,
26
+ context_manager: ContextManager,
27
+ model_registry: ModelRegistry
28
+ ):
29
+ """
30
+ Initialize a new InteractionProcessor.
31
+
32
+ Args:
33
+ context_manager: The ContextManager to use for context operations.
34
+ model_registry: The ModelRegistry to use for model operations.
35
+ """
36
+ self.context_manager = context_manager
37
+ self.model_registry = model_registry
38
+
39
+ async def process_interaction(
40
+ self,
41
+ request: MCPRequest,
42
+ model_name: str
43
+ ) -> MCPResponse:
44
+ """
45
+ Process an interaction request.
46
+
47
+ Args:
48
+ request: The MCPRequest to process.
49
+ model_name: Name of the model adapter to use.
50
+
51
+ Returns:
52
+ An MCPResponse containing the model's response.
53
+
54
+ Raises:
55
+ ValueError: If the context ID is invalid or the model is not found.
56
+ """
57
+ # Get or create context
58
+ context_id = request.context_id
59
+ context = None
60
+
61
+ if context_id:
62
+ # Use existing context
63
+ context = self.context_manager.get_context(context_id)
64
+ if not context:
65
+ raise ValueError(f"Context with ID {context_id} not found")
66
+ else:
67
+ # Create new context
68
+ context = self.context_manager.create_context(
69
+ metadata={"interaction_type": request.interaction_type}
70
+ )
71
+ context_id = context.context_id
72
+
73
+ # Get model adapter
74
+ model_adapter = self.model_registry.get_adapter(model_name)
75
+ if not model_adapter:
76
+ raise ValueError(f"Model adapter '{model_name}' not found")
77
+
78
+ # Process the request with the model
79
+ model_input = {
80
+ "text": request.content.get("text", ""),
81
+ "interaction_type": request.interaction_type,
82
+ "format": request.format,
83
+ **request.metadata
84
+ }
85
+
86
+ # Include context state in model input
87
+ model_context = {
88
+ "state": context.state,
89
+ "history": context.get_history(5) # Last 5 interactions
90
+ }
91
+
92
+ # Process with model
93
+ model_output = await model_adapter.process(model_input, model_context)
94
+
95
+ # Update context with this interaction
96
+ context.add_interaction(request.dict(), model_output)
97
+
98
+ # Extract educational metadata if available
99
+ educational_metadata = model_output.get("educational_metadata", {})
100
+ if educational_metadata:
101
+ # Update context state with educational information
102
+ context.update_state({
103
+ "educational": {
104
+ "last_topics": educational_metadata.get("topics", []),
105
+ "complexity_level": educational_metadata.get("complexity_level", ""),
106
+ "suggested_follow_ups": educational_metadata.get("suggested_follow_ups", [])
107
+ }
108
+ })
109
+
110
+ # Create response
111
+ response = MCPResponse(
112
+ context_id=context_id,
113
+ interaction_type=request.interaction_type,
114
+ content={
115
+ "text": model_output.get("text", ""),
116
+ "educational_metadata": educational_metadata
117
+ },
118
+ format=request.format,
119
+ metadata=request.metadata,
120
+ model_info={
121
+ "model_id": model_output.get("model_id", model_name),
122
+ "confidence": model_output.get("confidence", 1.0)
123
+ }
124
+ )
125
+
126
+ return response
127
+
128
+
129
+ class BatchProcessor:
130
+ """
131
+ Processes batches of interactions.
132
+
133
+ This class provides functionality for processing multiple interactions
134
+ in parallel or sequence.
135
+ """
136
+
137
+ def __init__(self, interaction_processor: InteractionProcessor):
138
+ """
139
+ Initialize a new BatchProcessor.
140
+
141
+ Args:
142
+ interaction_processor: The InteractionProcessor to use.
143
+ """
144
+ self.interaction_processor = interaction_processor
145
+
146
+ async def process_batch(
147
+ self,
148
+ requests: List[Dict[str, Any]],
149
+ model_name: str,
150
+ parallel: bool = True
151
+ ) -> List[Dict[str, Any]]:
152
+ """
153
+ Process a batch of interaction requests.
154
+
155
+ Args:
156
+ requests: List of request dictionaries.
157
+ model_name: Name of the model adapter to use.
158
+ parallel: Whether to process requests in parallel.
159
+
160
+ Returns:
161
+ List of response dictionaries.
162
+ """
163
+ if parallel:
164
+ # Process requests in parallel
165
+ tasks = [
166
+ self.interaction_processor.process_interaction(
167
+ MCPRequest(**request),
168
+ model_name
169
+ )
170
+ for request in requests
171
+ ]
172
+ responses = await asyncio.gather(*tasks)
173
+ else:
174
+ # Process requests sequentially
175
+ responses = []
176
+ for request in requests:
177
+ response = await self.interaction_processor.process_interaction(
178
+ MCPRequest(**request),
179
+ model_name
180
+ )
181
+ responses.append(response)
182
+
183
+ # Convert responses to dictionaries
184
+ return [response.dict() for response in responses]
src/mcp_core/protocol.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from enum import Enum
2
+ from typing import Dict, List, Any, Optional, Union, Literal
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class InteractionType(str, Enum):
7
+ TEXT = "text"
8
+ QUESTION = "question"
9
+ ASSESSMENT = "assessment"
10
+ EXPLANATION = "explanation"
11
+ FEEDBACK = "feedback"
12
+ SUMMARY = "summary"
13
+
14
+
15
+ class ContentFormat(str, Enum):
16
+ TEXT = "text"
17
+ MARKDOWN = "markdown"
18
+ HTML = "html"
19
+ JSON = "json"
20
+
21
+
22
+ class EducationalLevel(str, Enum):
23
+ ELEMENTARY = "elementary"
24
+ MIDDLE_SCHOOL = "middle_school"
25
+ HIGH_SCHOOL = "high_school"
26
+ UNDERGRADUATE = "undergraduate"
27
+ GRADUATE = "graduate"
28
+ PROFESSIONAL = "professional"
29
+
30
+
31
+ class MCPRequest(BaseModel):
32
+ context_id: Optional[str] = Field(None)
33
+ interaction_type: InteractionType = Field(...)
34
+ content: Dict[str, Any] = Field(...)
35
+ format: ContentFormat = Field(ContentFormat.TEXT)
36
+ metadata: Dict[str, Any] = Field(default_factory=dict)
37
+
38
+
39
+ class MCPResponse(BaseModel):
40
+ context_id: str = Field(...)
41
+ interaction_type: InteractionType = Field(...)
42
+ content: Dict[str, Any] = Field(...)
43
+ format: ContentFormat = Field(...)
44
+ metadata: Dict[str, Any] = Field(default_factory=dict)
45
+ model_info: Dict[str, Any] = Field(default_factory=dict)
46
+
47
+
48
+ class TextRequest(MCPRequest):
49
+ interaction_type: Literal[InteractionType.TEXT] = Field(InteractionType.TEXT)
50
+ content: Dict[str, Any] = Field(...)
51
+
52
+
53
+ class TextResponse(MCPResponse):
54
+ interaction_type: Literal[InteractionType.TEXT] = Field(InteractionType.TEXT)
55
+ content: Dict[str, Any] = Field(...)
56
+
57
+
58
+ class QuestionRequest(MCPRequest):
59
+ interaction_type: Literal[InteractionType.QUESTION] = Field(InteractionType.QUESTION)
60
+ content: Dict[str, Any] = Field(...)
61
+
62
+
63
+ class QuestionResponse(MCPResponse):
64
+ interaction_type: Literal[InteractionType.QUESTION] = Field(InteractionType.QUESTION)
65
+ content: Dict[str, Any] = Field(...)
66
+
67
+
68
+ class AssessmentRequest(MCPRequest):
69
+ interaction_type: Literal[InteractionType.ASSESSMENT] = Field(InteractionType.ASSESSMENT)
70
+ content: Dict[str, Any] = Field(...)
71
+
72
+
73
+ class AssessmentResponse(MCPResponse):
74
+ interaction_type: Literal[InteractionType.ASSESSMENT] = Field(InteractionType.ASSESSMENT)
75
+ content: Dict[str, Any] = Field(...)
76
+
77
+
78
+ class ExplanationRequest(MCPRequest):
79
+ interaction_type: Literal[InteractionType.EXPLANATION] = Field(InteractionType.EXPLANATION)
80
+ content: Dict[str, Any] = Field(...)
81
+
82
+
83
+ class ExplanationResponse(MCPResponse):
84
+ interaction_type: Literal[InteractionType.EXPLANATION] = Field(InteractionType.EXPLANATION)
85
+ content: Dict[str, Any] = Field(...)
86
+
87
+
88
+ class FeedbackRequest(MCPRequest):
89
+ interaction_type: Literal[InteractionType.FEEDBACK] = Field(InteractionType.FEEDBACK)
90
+ content: Dict[str, Any] = Field(...)
91
+
92
+
93
+ class FeedbackResponse(MCPResponse):
94
+ interaction_type: Literal[InteractionType.FEEDBACK] = Field(InteractionType.FEEDBACK)
95
+ content: Dict[str, Any] = Field(...)
96
+
97
+
98
+ class SummaryRequest(MCPRequest):
99
+ interaction_type: Literal[InteractionType.SUMMARY] = Field(InteractionType.SUMMARY)
100
+ content: Dict[str, Any] = Field(...)
101
+
102
+
103
+ class SummaryResponse(MCPResponse):
104
+ interaction_type: Literal[InteractionType.SUMMARY] = Field(InteractionType.SUMMARY)
105
+ content: Dict[str, Any] = Field(...)
106
+
107
+
108
+ class LearningObjective(BaseModel):
109
+ id: str = Field(...)
110
+ description: str = Field(...)
111
+ taxonomy_level: str = Field(...)
112
+ subject_area: str = Field(...)
113
+ prerequisites: List[str] = Field(default_factory=list)
114
+
115
+
116
+ class StudentProfile(BaseModel):
117
+ id: str = Field(...)
118
+ name: str = Field(...)
119
+ educational_level: EducationalLevel = Field(...)
120
+ learning_style: str = Field(None)
121
+ interests: List[str] = Field(default_factory=list)
122
+ strengths: List[str] = Field(default_factory=list)
123
+ areas_for_improvement: List[str] = Field(default_factory=list)
124
+ completed_objectives: List[str] = Field(default_factory=list)
125
+ current_objectives: List[str] = Field(default_factory=list)
126
+
127
+
128
+ class ContentAdaptationRequest(MCPRequest):
129
+ interaction_type: Literal[InteractionType.TEXT] = Field(InteractionType.TEXT)
130
+ content: Dict[str, Any] = Field(...)
131
+ student_profile: StudentProfile = Field(...)
132
+ learning_objectives: List[LearningObjective] = Field(...)
133
+
134
+
135
+ class ContentAdaptationResponse(MCPResponse):
136
+ interaction_type: Literal[InteractionType.TEXT] = Field(InteractionType.TEXT)
137
+ content: Dict[str, Any] = Field(...)
138
+ adaptations_applied: List[str] = Field(...)
139
+ recommended_activities: List[Dict[str, Any]] = Field(default_factory=list)
140
+
141
+
142
+ MCP_VERSION = "1.0.0"
143
+ MCP_SPEC_URL = "https://example.com/mcp-specification"
144
+
145
+ MCP_CAPABILITIES = {
146
+ "version": MCP_VERSION,
147
+ "supported_interaction_types": [e.value for e in InteractionType],
148
+ "supported_content_formats": [e.value for e in ContentFormat],
149
+ "supported_educational_levels": [e.value for e in EducationalLevel],
150
+ "features": {
151
+ "context_management": True,
152
+ "state_persistence": True,
153
+ "content_adaptation": True,
154
+ "assessment": True,
155
+ "feedback": True,
156
+ "learning_path_generation": True
157
+ }
158
+ }
src/static/index.html ADDED
@@ -0,0 +1,483 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>MCP EdTech Demo</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
8
+ <style>
9
+ body {
10
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
11
+ padding-top: 20px;
12
+ background-color: #f8f9fa;
13
+ }
14
+ .navbar {
15
+ background-color: #4a6fa5 !important;
16
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
17
+ }
18
+ .navbar-brand {
19
+ font-weight: bold;
20
+ color: white !important;
21
+ }
22
+ .nav-link {
23
+ color: rgba(255,255,255,0.85) !important;
24
+ }
25
+ .nav-link:hover {
26
+ color: white !important;
27
+ }
28
+ .card {
29
+ border-radius: 10px;
30
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
31
+ margin-bottom: 20px;
32
+ transition: transform 0.3s;
33
+ }
34
+ .card:hover {
35
+ transform: translateY(-5px);
36
+ }
37
+ .card-header {
38
+ background-color: #4a6fa5;
39
+ color: white;
40
+ font-weight: bold;
41
+ border-radius: 10px 10px 0 0 !important;
42
+ }
43
+ .btn-primary {
44
+ background-color: #4a6fa5;
45
+ border-color: #4a6fa5;
46
+ }
47
+ .btn-primary:hover {
48
+ background-color: #3a5a8f;
49
+ border-color: #3a5a8f;
50
+ }
51
+ .api-url {
52
+ font-family: monospace;
53
+ background-color: #f1f1f1;
54
+ padding: 5px;
55
+ border-radius: 4px;
56
+ }
57
+ .response-area {
58
+ background-color: #f8f9fa;
59
+ border: 1px solid #dee2e6;
60
+ border-radius: 5px;
61
+ padding: 15px;
62
+ max-height: 300px;
63
+ overflow-y: auto;
64
+ font-family: monospace;
65
+ }
66
+ .footer {
67
+ margin-top: 50px;
68
+ padding: 20px 0;
69
+ background-color: #343a40;
70
+ color: white;
71
+ }
72
+ #loading {
73
+ display: none;
74
+ }
75
+ .spinner-border {
76
+ width: 1rem;
77
+ height: 1rem;
78
+ }
79
+ </style>
80
+ </head>
81
+ <body>
82
+ <nav class="navbar navbar-expand-lg navbar-dark bg-dark mb-4">
83
+ <div class="container">
84
+ <a class="navbar-brand" href="#">MCP EdTech</a>
85
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
86
+ <span class="navbar-toggler-icon"></span>
87
+ </button>
88
+ <div class="collapse navbar-collapse" id="navbarNav">
89
+ <ul class="navbar-nav">
90
+ <li class="nav-item">
91
+ <a class="nav-link active" href="#home">Home</a>
92
+ </li>
93
+ <li class="nav-item">
94
+ <a class="nav-link" href="#api-demo">API Demo</a>
95
+ </li>
96
+ <li class="nav-item">
97
+ <a class="nav-link" href="#documentation">Documentation</a>
98
+ </li>
99
+ </ul>
100
+ </div>
101
+ </div>
102
+ </nav>
103
+
104
+ <div class="container" id="home">
105
+ <div class="row mb-4">
106
+ <div class="col-12">
107
+ <div class="card">
108
+ <div class="card-header">
109
+ Model Context Protocol (MCP) for EdTech
110
+ </div>
111
+ <div class="card-body">
112
+ <h5 class="card-title">Welcome to the MCP EdTech Demo</h5>
113
+ <p class="card-text">
114
+ This demo showcases the Model Context Protocol (MCP) implementation for educational technology applications.
115
+ MCP provides a standardized way for EdTech applications to interact with various AI models while maintaining
116
+ context and state across interactions.
117
+ </p>
118
+ <p>
119
+ Key features of this implementation:
120
+ </p>
121
+ <ul>
122
+ <li>Context management for stateful conversations</li>
123
+ <li>Student profile management</li>
124
+ <li>Learning progress tracking</li>
125
+ <li>Content adaptation based on student profiles</li>
126
+ <li>Assessment and feedback systems</li>
127
+ </ul>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ </div>
132
+
133
+ <div class="row mb-4" id="api-demo">
134
+ <div class="col-12">
135
+ <div class="card">
136
+ <div class="card-header">
137
+ API Demo
138
+ </div>
139
+ <div class="card-body">
140
+ <h5 class="card-title">Try the MCP API</h5>
141
+
142
+ <div class="mb-4">
143
+ <h6>1. Create a Context</h6>
144
+ <p>First, let's create a new context for our interaction:</p>
145
+ <div class="mb-3">
146
+ <button id="createContextBtn" class="btn btn-primary">Create Context</button>
147
+ <span id="loading-context" class="ms-2" style="display: none;">
148
+ <span class="spinner-border spinner-border-sm"></span> Processing...
149
+ </span>
150
+ </div>
151
+ <div class="response-area" id="contextResponse">
152
+ <!-- Context response will appear here -->
153
+ </div>
154
+ </div>
155
+
156
+ <div class="mb-4">
157
+ <h6>2. Process an Interaction</h6>
158
+ <p>Now, let's process an interaction using the context we created:</p>
159
+ <div class="mb-3">
160
+ <label for="interactionText" class="form-label">Enter your question or prompt:</label>
161
+ <input type="text" class="form-control" id="interactionText"
162
+ placeholder="e.g., Explain the concept of photosynthesis">
163
+ </div>
164
+ <div class="mb-3">
165
+ <button id="interactBtn" class="btn btn-primary">Process Interaction</button>
166
+ <span id="loading-interaction" class="ms-2" style="display: none;">
167
+ <span class="spinner-border spinner-border-sm"></span> Processing...
168
+ </span>
169
+ </div>
170
+ <div class="response-area" id="interactionResponse">
171
+ <!-- Interaction response will appear here -->
172
+ </div>
173
+ </div>
174
+
175
+ <div class="mb-4">
176
+ <h6>3. Create a Student Profile</h6>
177
+ <p>Let's create a student profile:</p>
178
+ <div class="mb-3">
179
+ <label for="studentName" class="form-label">Student Name:</label>
180
+ <input type="text" class="form-control" id="studentName" placeholder="John Doe">
181
+ </div>
182
+ <div class="mb-3">
183
+ <label for="educationalLevel" class="form-label">Educational Level:</label>
184
+ <select class="form-select" id="educationalLevel">
185
+ <option value="elementary">Elementary</option>
186
+ <option value="middle_school">Middle School</option>
187
+ <option value="high_school">High School</option>
188
+ <option value="undergraduate">Undergraduate</option>
189
+ <option value="graduate">Graduate</option>
190
+ <option value="professional">Professional</option>
191
+ </select>
192
+ </div>
193
+ <div class="mb-3">
194
+ <button id="createStudentBtn" class="btn btn-primary">Create Student Profile</button>
195
+ <span id="loading-student" class="ms-2" style="display: none;">
196
+ <span class="spinner-border spinner-border-sm"></span> Processing...
197
+ </span>
198
+ </div>
199
+ <div class="response-area" id="studentResponse">
200
+ <!-- Student profile response will appear here -->
201
+ </div>
202
+ </div>
203
+
204
+ <div class="mb-4">
205
+ <h6>4. Adapt Content for Student</h6>
206
+ <p>Now, let's adapt some content for the student:</p>
207
+ <div class="mb-3">
208
+ <label for="contentText" class="form-label">Content to adapt:</label>
209
+ <textarea class="form-control" id="contentText" rows="3"
210
+ placeholder="The process of photosynthesis involves the conversion of light energy into chemical energy that can be used by plants and other organisms."></textarea>
211
+ </div>
212
+ <div class="mb-3">
213
+ <button id="adaptContentBtn" class="btn btn-primary">Adapt Content</button>
214
+ <span id="loading-content" class="ms-2" style="display: none;">
215
+ <span class="spinner-border spinner-border-sm"></span> Processing...
216
+ </span>
217
+ </div>
218
+ <div class="response-area" id="contentResponse">
219
+ <!-- Content adaptation response will appear here -->
220
+ </div>
221
+ </div>
222
+ </div>
223
+ </div>
224
+ </div>
225
+ </div>
226
+
227
+ <div class="row mb-4" id="documentation">
228
+ <div class="col-12">
229
+ <div class="card">
230
+ <div class="card-header">
231
+ API Documentation
232
+ </div>
233
+ <div class="card-body">
234
+ <h5 class="card-title">Available Endpoints</h5>
235
+
236
+ <div class="mb-3">
237
+ <h6>Context Management</h6>
238
+ <ul>
239
+ <li><span class="api-url">POST /api/v1/context/create</span> - Create a new context</li>
240
+ <li><span class="api-url">GET /api/v1/context/{context_id}</span> - Get context information</li>
241
+ <li><span class="api-url">PUT /api/v1/context/{context_id}</span> - Update context</li>
242
+ <li><span class="api-url">DELETE /api/v1/context/{context_id}</span> - Delete context</li>
243
+ </ul>
244
+ </div>
245
+
246
+ <div class="mb-3">
247
+ <h6>Interaction</h6>
248
+ <ul>
249
+ <li><span class="api-url">POST /api/v1/interact</span> - Process an interaction</li>
250
+ </ul>
251
+ </div>
252
+
253
+ <div class="mb-3">
254
+ <h6>Student Profiles</h6>
255
+ <ul>
256
+ <li><span class="api-url">POST /api/v1/students</span> - Create student profile</li>
257
+ <li><span class="api-url">GET /api/v1/students/{student_id}</span> - Get student information</li>
258
+ <li><span class="api-url">PUT /api/v1/students/{student_id}</span> - Update student information</li>
259
+ </ul>
260
+ </div>
261
+
262
+ <div class="mb-3">
263
+ <h6>Progress Tracking</h6>
264
+ <ul>
265
+ <li><span class="api-url">GET /api/v1/students/{student_id}/progress</span> - Get learning progress</li>
266
+ <li><span class="api-url">POST /api/v1/assessments</span> - Create assessment</li>
267
+ <li><span class="api-url">GET /api/v1/students/{student_id}/assessments</span> - List student assessments</li>
268
+ </ul>
269
+ </div>
270
+
271
+ <div class="mb-3">
272
+ <h6>Content Adaptation</h6>
273
+ <ul>
274
+ <li><span class="api-url">POST /api/v1/content/adapt</span> - Adapt content for a student</li>
275
+ </ul>
276
+ </div>
277
+
278
+ <div class="mb-3">
279
+ <h6>Learning Paths</h6>
280
+ <ul>
281
+ <li><span class="api-url">GET /api/v1/learning-paths/{student_id}</span> - Get personalized learning path</li>
282
+ </ul>
283
+ </div>
284
+
285
+ <div class="mb-3">
286
+ <h6>Models</h6>
287
+ <ul>
288
+ <li><span class="api-url">GET /api/v1/models</span> - List available models</li>
289
+ <li><span class="api-url">GET /api/v1/models/{model_name}/capabilities</span> - Get model capabilities</li>
290
+ </ul>
291
+ </div>
292
+
293
+ <p class="mt-4">
294
+ For complete API documentation, visit the <a href="/docs" target="_blank">Swagger UI</a> or
295
+ <a href="/redoc" target="_blank">ReDoc</a> pages.
296
+ </p>
297
+ </div>
298
+ </div>
299
+ </div>
300
+ </div>
301
+ </div>
302
+
303
+ <footer class="footer">
304
+ <div class="container">
305
+ <div class="row">
306
+ <div class="col-md-6">
307
+ <h5>MCP EdTech Project</h5>
308
+ <p>An open-source implementation of the Model Context Protocol for educational technology applications.</p>
309
+ </div>
310
+ <div class="col-md-6 text-md-end">
311
+ <p>© 2025 MCP EdTech Project</p>
312
+ <p>Licensed under MIT License</p>
313
+ </div>
314
+ </div>
315
+ </div>
316
+ </footer>
317
+
318
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
319
+ <script>
320
+ // Store context and student IDs
321
+ let currentContextId = null;
322
+ let currentStudentId = null;
323
+
324
+ // Function to format JSON responses
325
+ function formatJSON(json) {
326
+ return JSON.stringify(json, null, 2);
327
+ }
328
+
329
+ // Create Context
330
+ document.getElementById('createContextBtn').addEventListener('click', async () => {
331
+ const loadingElement = document.getElementById('loading-context');
332
+ loadingElement.style.display = 'inline';
333
+
334
+ try {
335
+ const response = await fetch('/api/v1/context/create', {
336
+ method: 'POST',
337
+ headers: {
338
+ 'Content-Type': 'application/json'
339
+ },
340
+ body: JSON.stringify({
341
+ metadata: {
342
+ source: 'demo-ui',
343
+ created_by: 'demo-user'
344
+ }
345
+ })
346
+ });
347
+
348
+ const data = await response.json();
349
+ currentContextId = data.context_id;
350
+
351
+ document.getElementById('contextResponse').innerText = formatJSON(data);
352
+ } catch (error) {
353
+ document.getElementById('contextResponse').innerText = `Error: ${error.message}`;
354
+ } finally {
355
+ loadingElement.style.display = 'none';
356
+ }
357
+ });
358
+
359
+ // Process Interaction
360
+ document.getElementById('interactBtn').addEventListener('click', async () => {
361
+ if (!currentContextId) {
362
+ alert('Please create a context first!');
363
+ return;
364
+ }
365
+
366
+ const interactionText = document.getElementById('interactionText').value;
367
+ if (!interactionText) {
368
+ alert('Please enter a question or prompt!');
369
+ return;
370
+ }
371
+
372
+ const loadingElement = document.getElementById('loading-interaction');
373
+ loadingElement.style.display = 'inline';
374
+
375
+ try {
376
+ const response = await fetch('/api/v1/interact', {
377
+ method: 'POST',
378
+ headers: {
379
+ 'Content-Type': 'application/json'
380
+ },
381
+ body: JSON.stringify({
382
+ context_id: currentContextId,
383
+ interaction_type: 'text',
384
+ content: {
385
+ text: interactionText
386
+ },
387
+ format: 'text',
388
+ model_name: 'mock'
389
+ })
390
+ });
391
+
392
+ const data = await response.json();
393
+ document.getElementById('interactionResponse').innerText = formatJSON(data);
394
+ } catch (error) {
395
+ document.getElementById('interactionResponse').innerText = `Error: ${error.message}`;
396
+ } finally {
397
+ loadingElement.style.display = 'none';
398
+ }
399
+ });
400
+
401
+ // Create Student Profile
402
+ document.getElementById('createStudentBtn').addEventListener('click', async () => {
403
+ const studentName = document.getElementById('studentName').value;
404
+ if (!studentName) {
405
+ alert('Please enter a student name!');
406
+ return;
407
+ }
408
+
409
+ const educationalLevel = document.getElementById('educationalLevel').value;
410
+
411
+ const loadingElement = document.getElementById('loading-student');
412
+ loadingElement.style.display = 'inline';
413
+
414
+ try {
415
+ const response = await fetch('/api/v1/students', {
416
+ method: 'POST',
417
+ headers: {
418
+ 'Content-Type': 'application/json'
419
+ },
420
+ body: JSON.stringify({
421
+ name: studentName,
422
+ educational_level: educationalLevel,
423
+ learning_style: 'visual',
424
+ interests: ['science', 'technology', 'art'],
425
+ strengths: ['problem-solving', 'creativity'],
426
+ areas_for_improvement: ['time-management']
427
+ })
428
+ });
429
+
430
+ const data = await response.json();
431
+ currentStudentId = data.id;
432
+
433
+ document.getElementById('studentResponse').innerText = formatJSON(data);
434
+ } catch (error) {
435
+ document.getElementById('studentResponse').innerText = `Error: ${error.message}`;
436
+ } finally {
437
+ loadingElement.style.display = 'none';
438
+ }
439
+ });
440
+
441
+ // Adapt Content
442
+ document.getElementById('adaptContentBtn').addEventListener('click', async () => {
443
+ if (!currentStudentId) {
444
+ alert('Please create a student profile first!');
445
+ return;
446
+ }
447
+
448
+ const contentText = document.getElementById('contentText').value;
449
+ if (!contentText) {
450
+ alert('Please enter content to adapt!');
451
+ return;
452
+ }
453
+
454
+ const loadingElement = document.getElementById('loading-content');
455
+ loadingElement.style.display = 'inline';
456
+
457
+ try {
458
+ const response = await fetch('/api/v1/content/adapt', {
459
+ method: 'POST',
460
+ headers: {
461
+ 'Content-Type': 'application/json'
462
+ },
463
+ body: JSON.stringify({
464
+ content: {
465
+ text: contentText
466
+ },
467
+ student_id: currentStudentId,
468
+ learning_objectives: ['obj-1', 'obj-2'],
469
+ content_format: 'text'
470
+ })
471
+ });
472
+
473
+ const data = await response.json();
474
+ document.getElementById('contentResponse').innerText = formatJSON(data);
475
+ } catch (error) {
476
+ document.getElementById('contentResponse').innerText = `Error: ${error.message}`;
477
+ } finally {
478
+ loadingElement.style.display = 'none';
479
+ }
480
+ });
481
+ </script>
482
+ </body>
483
+ </html>
static/index.html ADDED
@@ -0,0 +1,411 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>MCP EdTech Demo</title>
7
+ <style>
8
+ :root {
9
+ --primary-color: #4a6fa5;
10
+ --secondary-color: #6c757d;
11
+ --accent-color: #28a745;
12
+ --light-bg: #f8f9fa;
13
+ --dark-bg: #343a40;
14
+ --text-color: #333;
15
+ }
16
+
17
+ body {
18
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
19
+ line-height: 1.6;
20
+ color: var(--text-color);
21
+ margin: 0;
22
+ padding: 0;
23
+ background-color: #f5f7fa;
24
+ }
25
+
26
+ .navbar {
27
+ background-color: var(--primary-color);
28
+ color: white;
29
+ padding: 1rem;
30
+ display: flex;
31
+ justify-content: space-between;
32
+ align-items: center;
33
+ }
34
+
35
+ .navbar-brand {
36
+ font-size: 1.5rem;
37
+ font-weight: bold;
38
+ color: white;
39
+ text-decoration: none;
40
+ }
41
+
42
+ .navbar-nav {
43
+ display: flex;
44
+ list-style: none;
45
+ margin: 0;
46
+ padding: 0;
47
+ }
48
+
49
+ .nav-item {
50
+ margin-left: 1.5rem;
51
+ }
52
+
53
+ .nav-link {
54
+ color: white;
55
+ text-decoration: none;
56
+ transition: color 0.3s;
57
+ }
58
+
59
+ .nav-link:hover {
60
+ color: #e0e0e0;
61
+ }
62
+
63
+ .container {
64
+ max-width: 1200px;
65
+ margin: 0 auto;
66
+ padding: 2rem;
67
+ }
68
+
69
+ .card {
70
+ background-color: white;
71
+ border-radius: 8px;
72
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
73
+ padding: 1.5rem;
74
+ margin-bottom: 2rem;
75
+ }
76
+
77
+ .card-header {
78
+ background-color: var(--primary-color);
79
+ color: white;
80
+ padding: 1rem;
81
+ border-radius: 8px 8px 0 0;
82
+ margin: -1.5rem -1.5rem 1.5rem -1.5rem;
83
+ }
84
+
85
+ h1, h2, h3, h4 {
86
+ color: var(--primary-color);
87
+ }
88
+
89
+ .btn {
90
+ display: inline-block;
91
+ background-color: var(--primary-color);
92
+ color: white;
93
+ padding: 0.5rem 1rem;
94
+ border: none;
95
+ border-radius: 4px;
96
+ cursor: pointer;
97
+ font-size: 1rem;
98
+ transition: background-color 0.3s;
99
+ }
100
+
101
+ .btn:hover {
102
+ background-color: #3a5a8a;
103
+ }
104
+
105
+ .btn-success {
106
+ background-color: var(--accent-color);
107
+ }
108
+
109
+ .btn-success:hover {
110
+ background-color: #218838;
111
+ }
112
+
113
+ .form-group {
114
+ margin-bottom: 1rem;
115
+ }
116
+
117
+ label {
118
+ display: block;
119
+ margin-bottom: 0.5rem;
120
+ font-weight: 500;
121
+ }
122
+
123
+ input, textarea {
124
+ width: 100%;
125
+ padding: 0.5rem;
126
+ border: 1px solid #ddd;
127
+ border-radius: 4px;
128
+ font-size: 1rem;
129
+ }
130
+
131
+ .response-area {
132
+ background-color: var(--light-bg);
133
+ border: 1px solid #ddd;
134
+ border-radius: 4px;
135
+ padding: 1rem;
136
+ margin-top: 1rem;
137
+ min-height: 100px;
138
+ white-space: pre-wrap;
139
+ }
140
+
141
+ .endpoints-list {
142
+ list-style-type: none;
143
+ padding: 0;
144
+ }
145
+
146
+ .endpoints-list li {
147
+ padding: 0.5rem 0;
148
+ border-bottom: 1px solid #eee;
149
+ }
150
+
151
+ .context-id {
152
+ font-family: monospace;
153
+ background-color: #f1f1f1;
154
+ padding: 0.2rem 0.4rem;
155
+ border-radius: 4px;
156
+ }
157
+
158
+ .loading {
159
+ display: none;
160
+ text-align: center;
161
+ margin: 1rem 0;
162
+ }
163
+
164
+ .loading::after {
165
+ content: "...";
166
+ animation: dots 1.5s steps(5, end) infinite;
167
+ }
168
+
169
+ @keyframes dots {
170
+ 0%, 20% { content: "."; }
171
+ 40% { content: ".."; }
172
+ 60%, 100% { content: "..."; }
173
+ }
174
+
175
+ .error-message {
176
+ color: #dc3545;
177
+ margin-top: 0.5rem;
178
+ display: none;
179
+ }
180
+
181
+ .success-message {
182
+ color: var(--accent-color);
183
+ margin-top: 0.5rem;
184
+ display: none;
185
+ }
186
+
187
+ code {
188
+ font-family: 'Courier New', Courier, monospace;
189
+ background-color: #f5f5f5;
190
+ padding: 0.2rem 0.4rem;
191
+ border-radius: 3px;
192
+ font-size: 0.9em;
193
+ }
194
+
195
+ @media (max-width: 768px) {
196
+ .container {
197
+ padding: 1rem;
198
+ }
199
+
200
+ .navbar {
201
+ flex-direction: column;
202
+ align-items: flex-start;
203
+ }
204
+
205
+ .navbar-nav {
206
+ margin-top: 1rem;
207
+ flex-direction: column;
208
+ }
209
+
210
+ .nav-item {
211
+ margin-left: 0;
212
+ margin-top: 0.5rem;
213
+ }
214
+ }
215
+ </style>
216
+ </head>
217
+ <body>
218
+ <nav class="navbar">
219
+ <a href="#" class="navbar-brand">MCP EdTech</a>
220
+ <ul class="navbar-nav">
221
+ <li class="nav-item"><a href="#" class="nav-link">Home</a></li>
222
+ <li class="nav-item"><a href="#api-demo" class="nav-link">API Demo</a></li>
223
+ <li class="nav-item"><a href="/docs" class="nav-link">Documentation</a></li>
224
+ <li class="nav-item"><a href="#" class="nav-link">Developed by M.Habib Agrebi</a></li>
225
+ </ul>
226
+ </nav>
227
+
228
+ <div class="container">
229
+ <div class="card">
230
+ <h1>Model Context Protocol (MCP) for EdTech</h1>
231
+
232
+ <h2>Welcome to the MCP EdTech Demo</h2>
233
+ <p>This demo showcases the Model Context Protocol (MCP) implementation for educational technology applications. MCP provides a standardized way for EdTech applications to interact with various AI models while maintaining context and state across interactions.</p>
234
+
235
+ <h3>Key features of this implementation:</h3>
236
+ <ul>
237
+ <li>Context management for stateful conversations</li>
238
+ <li>Student profile management</li>
239
+ <li>Learning progress tracking</li>
240
+ <li>Content adaptation based on student profiles</li>
241
+ <li>Assessment and feedback systems</li>
242
+ </ul>
243
+ </div>
244
+
245
+ <div id="api-demo" class="card">
246
+ <div class="card-header">
247
+ <h2>API Demo</h2>
248
+ </div>
249
+
250
+ <h3>Try the MCP API</h3>
251
+ <p>1. Create a Context</p>
252
+ <p>First, let's create a new context for our interaction:</p>
253
+ <button id="createContextBtn" class="btn">Create Context</button>
254
+ <div id="contextLoading" class="loading">Creating context</div>
255
+ <div id="contextError" class="error-message"></div>
256
+ <div id="contextSuccess" class="success-message"></div>
257
+ <div id="contextResponse" class="response-area" style="display: none;"></div>
258
+
259
+ <p>2. Process an Interaction</p>
260
+ <p>Now, let's process an interaction using the context we created:</p>
261
+ <div class="form-group">
262
+ <label for="interactionInput">Enter your question or prompt:</label>
263
+ <input type="text" id="interactionInput" placeholder="e.g., Explain the concept of photosynthesis">
264
+ </div>
265
+ <button id="processInteractionBtn" class="btn">Process Interaction</button>
266
+ <div id="interactionLoading" class="loading">Processing interaction</div>
267
+ <div id="interactionError" class="error-message"></div>
268
+ <div id="interactionResponse" class="response-area" style="display: none;"></div>
269
+ </div>
270
+
271
+ <div class="card">
272
+ <h3>Available Endpoints</h3>
273
+ <ul class="endpoints-list">
274
+ <li><strong>Interaction</strong>
275
+ <ul>
276
+ <li>POST /interact - Process an interaction</li>
277
+ </ul>
278
+ </li>
279
+ <li><strong>Student Profiles</strong>
280
+ <ul>
281
+ <li>POST /students - Create student profile</li>
282
+ <li>GET /students/{student_id} - Get student information</li>
283
+ <li>PUT /students/{student_id} - Update student information</li>
284
+ </ul>
285
+ </li>
286
+ <li><strong>Content Adaptation</strong>
287
+ <ul>
288
+ <li>POST /content/adapt - Adapt content for a student</li>
289
+ </ul>
290
+ </li>
291
+ <li><strong>Models</strong>
292
+ <ul>
293
+ <li>GET /models - List available models</li>
294
+ <li>GET /models/{model_name}/capabilities - Get model capabilities</li>
295
+ </ul>
296
+ </li>
297
+ </ul>
298
+ <p>For complete API documentation, visit the <a href="/docs">Swagger UI</a> or <a href="/redoc">ReDoc</a> pages.</p>
299
+ </div>
300
+ </div>
301
+
302
+ <footer style="background-color: var(--primary-color); color: white; text-align: center; padding: 1rem; margin-top: 2rem;">
303
+ <p>Developed by M.Habib Agrebi</p>
304
+ </footer>
305
+
306
+ <script>
307
+ let currentContextId = null;
308
+
309
+ document.getElementById('createContextBtn').addEventListener('click', createContext);
310
+ document.getElementById('processInteractionBtn').addEventListener('click', processInteraction);
311
+
312
+ async function createContext() {
313
+ const contextLoading = document.getElementById('contextLoading');
314
+ const contextError = document.getElementById('contextError');
315
+ const contextSuccess = document.getElementById('contextSuccess');
316
+ const contextResponse = document.getElementById('contextResponse');
317
+
318
+ contextLoading.style.display = 'block';
319
+ contextError.style.display = 'none';
320
+ contextSuccess.style.display = 'none';
321
+ contextResponse.style.display = 'none';
322
+
323
+ try {
324
+ const response = await fetch('/context/create', {
325
+ method: 'POST',
326
+ headers: {
327
+ 'Content-Type': 'application/json'
328
+ },
329
+ body: JSON.stringify({
330
+ metadata: {
331
+ source: 'demo-ui',
332
+ created_by: 'demo-user'
333
+ }
334
+ })
335
+ });
336
+
337
+ const data = await response.json();
338
+
339
+ if (!response.ok) {
340
+ throw new Error(data.detail || 'Failed to create context');
341
+ }
342
+
343
+ currentContextId = data.context_id;
344
+
345
+ contextResponse.textContent = JSON.stringify(data, null, 2);
346
+ contextResponse.style.display = 'block';
347
+ contextSuccess.textContent = `Context created successfully! Context ID: ${currentContextId}`;
348
+ contextSuccess.style.display = 'block';
349
+ } catch (error) {
350
+ contextError.textContent = `Error: ${error.message}`;
351
+ contextError.style.display = 'block';
352
+ } finally {
353
+ contextLoading.style.display = 'none';
354
+ }
355
+ }
356
+
357
+ async function processInteraction() {
358
+ if (!currentContextId) {
359
+ alert('Please create a context first!');
360
+ return;
361
+ }
362
+
363
+ const interactionInput = document.getElementById('interactionInput');
364
+ const interactionLoading = document.getElementById('interactionLoading');
365
+ const interactionError = document.getElementById('interactionError');
366
+ const interactionResponse = document.getElementById('interactionResponse');
367
+
368
+ if (!interactionInput.value.trim()) {
369
+ alert('Please enter a question or prompt!');
370
+ return;
371
+ }
372
+
373
+ interactionLoading.style.display = 'block';
374
+ interactionError.style.display = 'none';
375
+ interactionResponse.style.display = 'none';
376
+
377
+ try {
378
+ const response = await fetch('/interact', {
379
+ method: 'POST',
380
+ headers: {
381
+ 'Content-Type': 'application/json'
382
+ },
383
+ body: JSON.stringify({
384
+ context_id: currentContextId,
385
+ interaction_type: 'text',
386
+ content: {
387
+ text: interactionInput.value.trim()
388
+ },
389
+ format: 'text',
390
+ model_name: 'mock'
391
+ })
392
+ });
393
+
394
+ const data = await response.json();
395
+
396
+ if (!response.ok) {
397
+ throw new Error(data.detail || 'Failed to process interaction');
398
+ }
399
+
400
+ interactionResponse.textContent = data.content.text || JSON.stringify(data, null, 2);
401
+ interactionResponse.style.display = 'block';
402
+ } catch (error) {
403
+ interactionError.textContent = `Error: ${error.message}`;
404
+ interactionError.style.display = 'block';
405
+ } finally {
406
+ interactionLoading.style.display = 'none';
407
+ }
408
+ }
409
+ </script>
410
+ </body>
411
+ </html>
storage/students/293fc89c-8969-43ed-9a8f-f3e2131d97cd.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "id": "293fc89c-8969-43ed-9a8f-f3e2131d97cd",
3
+ "name": "habib",
4
+ "educational_level": "graduate",
5
+ "learning_style": "visual",
6
+ "interests": [
7
+ "science",
8
+ "technology",
9
+ "art"
10
+ ],
11
+ "strengths": [
12
+ "problem-solving",
13
+ "creativity"
14
+ ],
15
+ "areas_for_improvement": [
16
+ "time-management"
17
+ ],
18
+ "completed_objectives": [],
19
+ "current_objectives": []
20
+ }