Spaces:
Sleeping
Sleeping
Meet Patel
commited on
Commit
·
d4df2a7
1
Parent(s):
def69a7
Enhance TutorX MCP server with SSE support, update default port to 8001, and implement asynchronous API requests in Gradio interface. Added error handling for API interactions and improved server configuration.
Browse files
app.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
"""
|
2 |
-
Gradio web interface for the TutorX MCP Server
|
3 |
"""
|
4 |
|
5 |
import gradio as gr
|
@@ -10,10 +10,16 @@ from io import BytesIO
|
|
10 |
from PIL import Image
|
11 |
from datetime import datetime
|
12 |
import asyncio
|
|
|
|
|
|
|
13 |
|
14 |
# Import MCP client to communicate with the MCP server
|
15 |
from client import client
|
16 |
|
|
|
|
|
|
|
17 |
# Utility functions
|
18 |
def image_to_base64(img):
|
19 |
"""Convert a PIL image or numpy array to base64 string"""
|
@@ -25,13 +31,37 @@ def image_to_base64(img):
|
|
25 |
img_str = base64.b64encode(buffered.getvalue()).decode()
|
26 |
return img_str
|
27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
28 |
# Create Gradio interface
|
29 |
with gr.Blocks(title="TutorX Educational AI", theme=gr.themes.Soft()) as demo:
|
30 |
gr.Markdown("# 📚 TutorX Educational AI Platform")
|
31 |
gr.Markdown("""
|
32 |
An adaptive, multi-modal, and collaborative AI tutoring platform built with MCP.
|
33 |
|
34 |
-
This interface demonstrates the functionality of the TutorX MCP server.
|
35 |
""")
|
36 |
|
37 |
# Set a default student ID for the demo
|
@@ -53,8 +83,12 @@ with gr.Blocks(title="TutorX Educational AI", theme=gr.themes.Soft()) as demo:
|
|
53 |
|
54 |
with gr.Column():
|
55 |
assessment_output = gr.JSON(label="Skill Assessment")
|
|
|
|
|
|
|
|
|
56 |
assess_btn.click(
|
57 |
-
fn=
|
58 |
inputs=[concept_id_input],
|
59 |
outputs=[assessment_output]
|
60 |
)
|
@@ -63,8 +97,12 @@ with gr.Blocks(title="TutorX Educational AI", theme=gr.themes.Soft()) as demo:
|
|
63 |
concept_graph_btn = gr.Button("Show Concept Graph")
|
64 |
concept_graph_output = gr.JSON(label="Concept Graph")
|
65 |
|
|
|
|
|
|
|
|
|
66 |
concept_graph_btn.click(
|
67 |
-
fn=
|
68 |
inputs=[],
|
69 |
outputs=[concept_graph_output]
|
70 |
)
|
@@ -83,8 +121,16 @@ with gr.Blocks(title="TutorX Educational AI", theme=gr.themes.Soft()) as demo:
|
|
83 |
with gr.Column():
|
84 |
quiz_output = gr.JSON(label="Generated Quiz")
|
85 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
gen_quiz_btn.click(
|
87 |
-
fn=
|
88 |
inputs=[concepts_input, diff_input],
|
89 |
outputs=[quiz_output]
|
90 |
)
|
@@ -216,4 +262,4 @@ with gr.Blocks(title="TutorX Educational AI", theme=gr.themes.Soft()) as demo:
|
|
216 |
|
217 |
# Launch the interface
|
218 |
if __name__ == "__main__":
|
219 |
-
demo.launch(server_name="0.0.0.0", server_port=7860)
|
|
|
1 |
"""
|
2 |
+
Gradio web interface for the TutorX MCP Server with SSE support
|
3 |
"""
|
4 |
|
5 |
import gradio as gr
|
|
|
10 |
from PIL import Image
|
11 |
from datetime import datetime
|
12 |
import asyncio
|
13 |
+
import aiohttp
|
14 |
+
import sseclient
|
15 |
+
import requests
|
16 |
|
17 |
# Import MCP client to communicate with the MCP server
|
18 |
from client import client
|
19 |
|
20 |
+
# Server configuration
|
21 |
+
SERVER_URL = "http://localhost:8001" # Default port is now 8001 to match main.py
|
22 |
+
|
23 |
# Utility functions
|
24 |
def image_to_base64(img):
|
25 |
"""Convert a PIL image or numpy array to base64 string"""
|
|
|
31 |
img_str = base64.b64encode(buffered.getvalue()).decode()
|
32 |
return img_str
|
33 |
|
34 |
+
async def api_request(endpoint, method="GET", params=None, json_data=None):
|
35 |
+
"""Make an API request to the server"""
|
36 |
+
url = f"{SERVER_URL}/api/{endpoint}"
|
37 |
+
headers = {"Content-Type": "application/json"}
|
38 |
+
|
39 |
+
try:
|
40 |
+
async with aiohttp.ClientSession() as session:
|
41 |
+
if method.upper() == "GET":
|
42 |
+
async with session.get(url, params=params, headers=headers) as response:
|
43 |
+
if response.status == 200:
|
44 |
+
return await response.json()
|
45 |
+
else:
|
46 |
+
error = await response.text()
|
47 |
+
return {"error": f"API error: {response.status} - {error}"}
|
48 |
+
elif method.upper() == "POST":
|
49 |
+
async with session.post(url, json=json_data, headers=headers) as response:
|
50 |
+
if response.status == 200:
|
51 |
+
return await response.json()
|
52 |
+
else:
|
53 |
+
error = await response.text()
|
54 |
+
return {"error": f"API error: {response.status} - {error}"}
|
55 |
+
except Exception as e:
|
56 |
+
return {"error": f"Request failed: {str(e)}"}
|
57 |
+
|
58 |
# Create Gradio interface
|
59 |
with gr.Blocks(title="TutorX Educational AI", theme=gr.themes.Soft()) as demo:
|
60 |
gr.Markdown("# 📚 TutorX Educational AI Platform")
|
61 |
gr.Markdown("""
|
62 |
An adaptive, multi-modal, and collaborative AI tutoring platform built with MCP.
|
63 |
|
64 |
+
This interface demonstrates the functionality of the TutorX MCP server using SSE connections.
|
65 |
""")
|
66 |
|
67 |
# Set a default student ID for the demo
|
|
|
83 |
|
84 |
with gr.Column():
|
85 |
assessment_output = gr.JSON(label="Skill Assessment")
|
86 |
+
async def on_assess_click(concept_id):
|
87 |
+
result = await api_request("assess_skill", "GET", {"student_id": "student_12345", "concept_id": concept_id})
|
88 |
+
return result
|
89 |
+
|
90 |
assess_btn.click(
|
91 |
+
fn=on_assess_click,
|
92 |
inputs=[concept_id_input],
|
93 |
outputs=[assessment_output]
|
94 |
)
|
|
|
97 |
concept_graph_btn = gr.Button("Show Concept Graph")
|
98 |
concept_graph_output = gr.JSON(label="Concept Graph")
|
99 |
|
100 |
+
async def on_concept_graph_click():
|
101 |
+
result = await api_request("concept_graph")
|
102 |
+
return result
|
103 |
+
|
104 |
concept_graph_btn.click(
|
105 |
+
fn=on_concept_graph_click,
|
106 |
inputs=[],
|
107 |
outputs=[concept_graph_output]
|
108 |
)
|
|
|
121 |
with gr.Column():
|
122 |
quiz_output = gr.JSON(label="Generated Quiz")
|
123 |
|
124 |
+
async def on_generate_quiz(concepts, difficulty):
|
125 |
+
result = await api_request(
|
126 |
+
"generate_quiz",
|
127 |
+
"POST",
|
128 |
+
json_data={"concept_ids": concepts, "difficulty": difficulty}
|
129 |
+
)
|
130 |
+
return result
|
131 |
+
|
132 |
gen_quiz_btn.click(
|
133 |
+
fn=on_generate_quiz,
|
134 |
inputs=[concepts_input, diff_input],
|
135 |
outputs=[quiz_output]
|
136 |
)
|
|
|
262 |
|
263 |
# Launch the interface
|
264 |
if __name__ == "__main__":
|
265 |
+
demo.queue().launch(server_name="0.0.0.0", server_port=7860)
|
client.py
CHANGED
@@ -14,9 +14,12 @@ import os
|
|
14 |
|
15 |
# Get server configuration from environment variables with defaults
|
16 |
DEFAULT_HOST = os.getenv("MCP_HOST", "127.0.0.1")
|
17 |
-
DEFAULT_PORT = int(os.getenv("MCP_PORT", "
|
18 |
DEFAULT_SERVER_URL = f"http://{DEFAULT_HOST}:{DEFAULT_PORT}"
|
19 |
|
|
|
|
|
|
|
20 |
class TutorXClient:
|
21 |
"""Client for interacting with the TutorX MCP server"""
|
22 |
|
@@ -47,13 +50,16 @@ class TutorXClient:
|
|
47 |
"""
|
48 |
await self._ensure_session()
|
49 |
try:
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
|
|
|
|
|
|
57 |
except Exception as e:
|
58 |
return {
|
59 |
"error": f"Failed to call tool: {str(e)}",
|
@@ -72,12 +78,19 @@ class TutorXClient:
|
|
72 |
"""
|
73 |
await self._ensure_session()
|
74 |
try:
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
except Exception as e:
|
82 |
return {
|
83 |
"error": f"Failed to get resource: {str(e)}",
|
@@ -94,11 +107,12 @@ class TutorXClient:
|
|
94 |
await self._ensure_session()
|
95 |
try:
|
96 |
async with self.session.get(
|
97 |
-
f"{self.server_url}/health",
|
98 |
timeout=5
|
99 |
) as response:
|
100 |
return response.status == 200
|
101 |
-
except:
|
|
|
102 |
return False
|
103 |
|
104 |
# ------------ Core Features ------------
|
|
|
14 |
|
15 |
# Get server configuration from environment variables with defaults
|
16 |
DEFAULT_HOST = os.getenv("MCP_HOST", "127.0.0.1")
|
17 |
+
DEFAULT_PORT = int(os.getenv("MCP_PORT", "8001")) # Default port updated to 8001
|
18 |
DEFAULT_SERVER_URL = f"http://{DEFAULT_HOST}:{DEFAULT_PORT}"
|
19 |
|
20 |
+
# API endpoints
|
21 |
+
API_PREFIX = "/api"
|
22 |
+
|
23 |
class TutorXClient:
|
24 |
"""Client for interacting with the TutorX MCP server"""
|
25 |
|
|
|
50 |
"""
|
51 |
await self._ensure_session()
|
52 |
try:
|
53 |
+
url = f"{self.server_url}{API_PREFIX}/{tool_name}"
|
54 |
+
async with self.session.get(url, params=params, timeout=30) as response:
|
55 |
+
if response.status == 200:
|
56 |
+
return await response.json()
|
57 |
+
else:
|
58 |
+
error = await response.text()
|
59 |
+
return {
|
60 |
+
"error": f"API error ({response.status}): {error}",
|
61 |
+
"timestamp": datetime.now().isoformat()
|
62 |
+
}
|
63 |
except Exception as e:
|
64 |
return {
|
65 |
"error": f"Failed to call tool: {str(e)}",
|
|
|
78 |
"""
|
79 |
await self._ensure_session()
|
80 |
try:
|
81 |
+
# Extract the resource name from the URI (e.g., 'concept-graph' from 'concept-graph://')
|
82 |
+
resource_name = resource_uri.split('://')[0]
|
83 |
+
url = f"{self.server_url}{API_PREFIX}/{resource_name}"
|
84 |
+
|
85 |
+
async with self.session.get(url, timeout=30) as response:
|
86 |
+
if response.status == 200:
|
87 |
+
return await response.json()
|
88 |
+
else:
|
89 |
+
error = await response.text()
|
90 |
+
return {
|
91 |
+
"error": f"Failed to get resource: {error}",
|
92 |
+
"timestamp": datetime.now().isoformat()
|
93 |
+
}
|
94 |
except Exception as e:
|
95 |
return {
|
96 |
"error": f"Failed to get resource: {str(e)}",
|
|
|
107 |
await self._ensure_session()
|
108 |
try:
|
109 |
async with self.session.get(
|
110 |
+
f"{self.server_url}{API_PREFIX}/health",
|
111 |
timeout=5
|
112 |
) as response:
|
113 |
return response.status == 200
|
114 |
+
except Exception as e:
|
115 |
+
print(f"Server connection check failed: {str(e)}")
|
116 |
return False
|
117 |
|
118 |
# ------------ Core Features ------------
|
main.py
CHANGED
@@ -1,12 +1,16 @@
|
|
1 |
-
|
|
|
|
|
2 |
from mcp.server.fastmcp import FastMCP
|
3 |
import json
|
4 |
import os
|
5 |
import warnings
|
|
|
6 |
from typing import List, Dict, Any, Optional
|
7 |
from datetime import datetime
|
8 |
-
from fastapi import FastAPI, Query
|
9 |
from fastapi.responses import JSONResponse
|
|
|
10 |
|
11 |
# Filter out the tool registration warning
|
12 |
warnings.filterwarnings("ignore", message="Tool already exists")
|
@@ -27,9 +31,21 @@ from utils.assessment import (
|
|
27 |
|
28 |
# Get server configuration from environment variables with defaults
|
29 |
SERVER_HOST = os.getenv("MCP_HOST", "0.0.0.0") # Allow connections from any IP
|
30 |
-
SERVER_PORT = int(os.getenv("MCP_PORT", "8001"))
|
31 |
SERVER_TRANSPORT = os.getenv("MCP_TRANSPORT", "http")
|
32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
# Create the TutorX MCP server with explicit configuration
|
34 |
mcp = FastMCP(
|
35 |
"TutorX",
|
@@ -40,8 +56,8 @@ mcp = FastMCP(
|
|
40 |
cors_origins=["*"] # Allow CORS from any origin
|
41 |
)
|
42 |
|
43 |
-
#
|
44 |
-
|
45 |
|
46 |
# ------------------ Core Features ------------------
|
47 |
|
@@ -136,25 +152,43 @@ async def generate_quiz(concept_ids: List[str], difficulty: int = 2) -> Dict[str
|
|
136 |
]
|
137 |
}
|
138 |
|
|
|
|
|
|
|
|
|
|
|
139 |
@api_app.get("/api/assess_skill")
|
140 |
async def assess_skill_api(student_id: str = Query(...), concept_id: str = Query(...)):
|
141 |
-
|
142 |
-
|
|
|
|
|
|
|
143 |
|
144 |
@api_app.post("/api/generate_quiz")
|
145 |
-
async def generate_quiz_api(concept_ids:
|
146 |
-
|
147 |
-
|
|
|
|
|
|
|
148 |
|
149 |
-
# Mount
|
150 |
mcp.app = api_app
|
151 |
|
152 |
-
# Function to run the server
|
153 |
def run_server():
|
154 |
"""Run the MCP server with configured transport and port"""
|
155 |
print(f"Starting TutorX MCP Server on {SERVER_HOST}:{SERVER_PORT} using {SERVER_TRANSPORT} transport...")
|
156 |
try:
|
157 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
158 |
except Exception as e:
|
159 |
print(f"Error starting server: {str(e)}")
|
160 |
raise
|
|
|
1 |
+
"""
|
2 |
+
TutorX MCP Server
|
3 |
+
"""
|
4 |
from mcp.server.fastmcp import FastMCP
|
5 |
import json
|
6 |
import os
|
7 |
import warnings
|
8 |
+
import uvicorn
|
9 |
from typing import List, Dict, Any, Optional
|
10 |
from datetime import datetime
|
11 |
+
from fastapi import FastAPI, HTTPException, Query, Request
|
12 |
from fastapi.responses import JSONResponse
|
13 |
+
from fastapi.middleware.cors import CORSMiddleware
|
14 |
|
15 |
# Filter out the tool registration warning
|
16 |
warnings.filterwarnings("ignore", message="Tool already exists")
|
|
|
31 |
|
32 |
# Get server configuration from environment variables with defaults
|
33 |
SERVER_HOST = os.getenv("MCP_HOST", "0.0.0.0") # Allow connections from any IP
|
34 |
+
SERVER_PORT = int(os.getenv("MCP_PORT", "8001")) # Changed default port to 8001
|
35 |
SERVER_TRANSPORT = os.getenv("MCP_TRANSPORT", "http")
|
36 |
|
37 |
+
# Create FastAPI app
|
38 |
+
api_app = FastAPI(title="TutorX MCP Server", version="1.0.0")
|
39 |
+
|
40 |
+
# Add CORS middleware
|
41 |
+
api_app.add_middleware(
|
42 |
+
CORSMiddleware,
|
43 |
+
allow_origins=["*"],
|
44 |
+
allow_credentials=True,
|
45 |
+
allow_methods=["*"],
|
46 |
+
allow_headers=["*"],
|
47 |
+
)
|
48 |
+
|
49 |
# Create the TutorX MCP server with explicit configuration
|
50 |
mcp = FastMCP(
|
51 |
"TutorX",
|
|
|
56 |
cors_origins=["*"] # Allow CORS from any origin
|
57 |
)
|
58 |
|
59 |
+
# For FastMCP, we'll use it directly without mounting
|
60 |
+
# as it already creates its own FastAPI app internally
|
61 |
|
62 |
# ------------------ Core Features ------------------
|
63 |
|
|
|
152 |
]
|
153 |
}
|
154 |
|
155 |
+
# API Endpoints
|
156 |
+
@api_app.get("/api/health")
|
157 |
+
async def health_check():
|
158 |
+
return {"status": "ok", "timestamp": datetime.now().isoformat()}
|
159 |
+
|
160 |
@api_app.get("/api/assess_skill")
|
161 |
async def assess_skill_api(student_id: str = Query(...), concept_id: str = Query(...)):
|
162 |
+
try:
|
163 |
+
result = await assess_skill(student_id, concept_id)
|
164 |
+
return result
|
165 |
+
except Exception as e:
|
166 |
+
raise HTTPException(status_code=500, detail=str(e))
|
167 |
|
168 |
@api_app.post("/api/generate_quiz")
|
169 |
+
async def generate_quiz_api(concept_ids: List[str], difficulty: int = 2):
|
170 |
+
try:
|
171 |
+
result = await generate_quiz(concept_ids, difficulty)
|
172 |
+
return result
|
173 |
+
except Exception as e:
|
174 |
+
raise HTTPException(status_code=500, detail=str(e))
|
175 |
|
176 |
+
# Mount MCP app to /mcp path
|
177 |
mcp.app = api_app
|
178 |
|
|
|
179 |
def run_server():
|
180 |
"""Run the MCP server with configured transport and port"""
|
181 |
print(f"Starting TutorX MCP Server on {SERVER_HOST}:{SERVER_PORT} using {SERVER_TRANSPORT} transport...")
|
182 |
try:
|
183 |
+
# Run the MCP server directly
|
184 |
+
import uvicorn
|
185 |
+
uvicorn.run(
|
186 |
+
"main:mcp.app",
|
187 |
+
host=SERVER_HOST,
|
188 |
+
port=SERVER_PORT,
|
189 |
+
log_level="info",
|
190 |
+
reload=True
|
191 |
+
)
|
192 |
except Exception as e:
|
193 |
print(f"Error starting server: {str(e)}")
|
194 |
raise
|