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
Files changed (3) hide show
  1. app.py +52 -6
  2. client.py +30 -16
  3. main.py +47 -13
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=lambda x: asyncio.run(client.assess_skill("student_12345", x)),
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=lambda: asyncio.run(client.get_concept_graph()),
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=lambda x, y: asyncio.run(client.generate_quiz(x, y)),
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", "8000"))
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
- async with self.session.post(
51
- f"{self.server_url}/mcp/tools/{tool_name}",
52
- json=params,
53
- timeout=30
54
- ) as response:
55
- response.raise_for_status()
56
- return await response.json()
 
 
 
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
- async with self.session.get(
76
- f"{self.server_url}/mcp/resources?uri={resource_uri}",
77
- timeout=30
78
- ) as response:
79
- response.raise_for_status()
80
- return await response.json()
 
 
 
 
 
 
 
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
- # TutorX MCP Server
 
 
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
- # Create FastAPI app
44
- api_app = FastAPI()
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
- result = await assess_skill(student_id, concept_id)
142
- return JSONResponse(content=result)
 
 
 
143
 
144
  @api_app.post("/api/generate_quiz")
145
- async def generate_quiz_api(concept_ids: list[str], difficulty: int = 2):
146
- result = await generate_quiz(concept_ids, difficulty)
147
- return JSONResponse(content=result)
 
 
 
148
 
149
- # Mount FastAPI app to MCP server
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
- mcp.run(transport="sse")
 
 
 
 
 
 
 
 
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