prthm11 commited on
Commit
c3f3fc8
·
verified ·
1 Parent(s): ede15c7

Upload escalation_server.py

Browse files
Files changed (1) hide show
  1. escalation_server.py +260 -0
escalation_server.py ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # from mcp.server.fastmcp import FastMCP
2
+ from fastmcp import FastMCP, Context
3
+ from fastmcp.dependencies import CurrentContext
4
+ import os
5
+ import smtplib
6
+ from email.message import EmailMessage
7
+ import aiosmtplib
8
+ # qqvl obms fhwx llic
9
+
10
+ # --- EMAIL CONFIGURATION ---
11
+ SMTP_SERVER = "smtp.gmail.com"
12
+ SMTP_PORT = 587
13
+ SENDER_EMAIL = "dummyuseai@gmail.com"
14
+ SENDER_PASSWORD = "qqvl obms fhwx llic"
15
+
16
+ # Initialize Server
17
+ mcp = FastMCP("escalation_server")
18
+
19
+ # Path Configuration
20
+ RESOURCE_PATH = r"E:\Pratham\2026\MCP\resource\jemh114-min (1).md"
21
+
22
+ # --- CENTRALIZED REGISTRY ---
23
+ FACULTY_DATA = {
24
+ "Mr. Murty": {"email": "dharmin.naik@webashlar.com", "id": "T-MURTY-01"},
25
+ "Mrs. Sunita":{"email": "dummyaiapi03@gmail.com", "id": "T-SUNITA-03"},
26
+ "Mr. Patel": {"email": "dev.patel@webashlar.com", "id": "T-PATEL-05"},
27
+ "Dr. Rao": {"email": "pratham.lakdawala@webashlar.com", "id": "T-RAO-02"},
28
+ "Mr. Khanna": {"email": "dummyuseai@gmail.com", "id": "T-KHANNA-04"},
29
+ "Ms. Joshi": {"email": "dummyaiapi532@gmail.com", "id": "T-JOSHI-06"}
30
+ }
31
+
32
+ CLASS_ASSIGNMENTS = {
33
+ "10-A": {"Math": "Mr. Murty", "Science": "Dr. Rao"},
34
+ "10-B": {"Math": "Mrs. Sunita", "Science": "Mr. Khanna"},
35
+ "10-C": {"Math": "Mr. Patel", "Science": "Ms. Joshi"}
36
+ }
37
+
38
+ # Input Normalization Map
39
+ SUBJECT_MAP = {
40
+ "mathematics": "Math",
41
+ "math": "Math",
42
+ "maths": "Math",
43
+ "statistics": "Math",
44
+ "stats": "Math",
45
+ "science": "Science",
46
+ "physics": "Science", # For this demo, mapping physics to general science
47
+ "biology": "Science"
48
+ }
49
+
50
+ def normalize_subject(subject: str) -> str:
51
+ """Standardizes subject names so 'Mathematics' becomes 'Math'."""
52
+ return SUBJECT_MAP.get(subject.lower(), subject.title())
53
+
54
+ # --- 1. MCP RESOURCES ---
55
+ # Resources are "read-only" data sources the LLM can reference.
56
+ def read_resource_file() -> str:
57
+ if not os.path.exists(RESOURCE_PATH):
58
+ return "Error: Statistics markdown file not found."
59
+ with open(RESOURCE_PATH, "r", encoding="utf-8") as f:
60
+ return f.read()
61
+
62
+ @mcp.resource("study://math/statistics")
63
+ def statistics_resource() -> str:
64
+ """Exposes the material to the LLM as a read-only resource."""
65
+ return read_resource_file()
66
+
67
+ # @mcp.resource("study://math/statistics")
68
+ # def get_stats_material() -> str:
69
+ # """
70
+ # Exposes the Class X Statistics chapter.
71
+ # The AI uses this to find formulas for Mean, Median, and Mode.
72
+ # """
73
+ # if not os.path.exists(RESOURCE_PATH):
74
+ # return "Error: Statistics markdown file not found at the specified path."
75
+
76
+ # with open(RESOURCE_PATH, "r", encoding="utf-8") as f:
77
+ # content = f.read()
78
+
79
+ # return content
80
+
81
+ @mcp.resource("system://tutor/instructions")
82
+ def tutor_instructions() -> str:
83
+ """The mandatory operational manual for this tutoring session."""
84
+ return """
85
+ # OPERATIONAL PROTOCOL
86
+ 1. You are a gateway, not a teacher.
87
+ 2. Max 3 attempts allowed using 'explain_from_resource' or 'generate_quick_revision'.
88
+ 3. If a tool returns 'LIMIT_REACHED', immediately ask for Class ID.
89
+ 4. Use get_teacher_info -> escalate_to_teacher.
90
+ 5. NO internal brainstorming. Use the provided textbook material ONLY.
91
+ """
92
+
93
+ # --- 2. MCP TOOLS ---
94
+ @mcp.tool()
95
+ async def explain_from_resource(concept: str, ctx: Context = CurrentContext()):
96
+ """
97
+ CRITICAL TUTOR RULE: You MUST call this tool for ANY math explanation.
98
+ Internal knowledge is forbidden. This tool tracks student frustration
99
+ and triggers teacher escalation after 3 failed attempts.
100
+ """
101
+ # FIX 1: Remove 'await' from ctx.get_state (it is synchronous)
102
+ attempts = ctx.get_state("math_attempts") or 0
103
+
104
+ # 1. CIRCUIT BREAKER: Enforce the 3-attempt limit
105
+ if attempts >= 3:
106
+ await ctx.disable_components(tools=["explain_from_resource", "generate_quick_revision"])
107
+ return (
108
+ "FATAL_ERROR: TUTORING_LIMIT_REACHED. "
109
+ "Internal tutoring tools have been deactivated for this session. "
110
+ "You are now required to follow the ESCALATION PROTOCOL: "
111
+ "1. Inform the student you are contacting their teacher. "
112
+ "2. Request Class ID. "
113
+ "3. Execute get_teacher_info and escalate_to_teacher."
114
+ )
115
+
116
+ # FIX 2: Remove 'await' from ctx.set_state
117
+ ctx.set_state("math_attempts", attempts + 1)
118
+
119
+ # 2. FETCH RESOURCE
120
+ material = read_resource_file()
121
+
122
+ # 3. STRUCTURED RESPONSE
123
+ # Note: We return the prompt back to the LLM so it can generate the explanation
124
+ return f"""
125
+ <attempt>{attempts + 1}/3</attempt>
126
+ <content>{material}</content>
127
+ <instruction>Explain {concept} concisely. Use LaTeX.</instruction>
128
+ """
129
+
130
+ @mcp.tool()
131
+ async def generate_quick_revision(type: str = "summary", ctx: Context = CurrentContext()) -> str:
132
+ """
133
+ Generates content for quick revision.
134
+ Types: 'summary' (3 key points) or 'short_notes' (condensed formulas and definitions).
135
+ """
136
+ # ENFORCE ATTEMPT TRACKING
137
+ attempts = ctx.get_state("math_attempts") or 0
138
+
139
+ if attempts >= 3:
140
+ await ctx.disable_components(tools=["explain_from_resource", "generate_quick_revision"])
141
+ return "FATAL_ERROR: TUTORING_LIMIT_REACHED. Transition to escalation immediately."
142
+
143
+ ctx.set_state("math_attempts", attempts + 1)
144
+
145
+ material = read_resource_file()
146
+ return f"""
147
+ <attempt>{attempts + 1}/3</attempt>
148
+ <material>{material}</material>
149
+ <request>Create a {type} for quick revision.</request>
150
+ <format_requirements>
151
+ - If summary: 3 high-impact bullet points.
152
+ - If short_notes: Categorized formulas in LaTeX and 1-line definitions.
153
+ </format_requirements>
154
+ """
155
+
156
+ def normalize_class_id(class_id: str) -> str:
157
+ """Converts 'Class 10B', '10b', or 'ten b' into '10-B'."""
158
+ clean = class_id.upper().replace("CLASS", "").replace("GRADE", "").replace(" ", "").strip()
159
+ # If student just says "10", we can't know which section,
160
+ # but we can format "10B" to "10-B"
161
+ if len(clean) == 3 and clean[2].isalpha():
162
+ return f"{clean[:2]}-{clean[2:]}"
163
+ return clean
164
+
165
+ # --- 3. ESCALATION TOOLS (From our previous discussion) ---
166
+ @mcp.tool()
167
+ async def get_teacher_info(subject: str, class_id: str) -> dict:
168
+ """
169
+ Finds the specific teacher for a subject and class.
170
+ Ensures Math queries go to Math teachers and Science to Science teachers.
171
+ """
172
+ # teacher_name = CLASS_ASSIGNMENTS.get(class_id, {}).get(subject)
173
+ std_subject = normalize_subject(subject)
174
+ std_class = normalize_class_id(class_id)
175
+
176
+ # Verify class exists first
177
+ # class_data = CLASS_ASSIGNMENTS.get(class_id)
178
+ class_data = CLASS_ASSIGNMENTS.get(std_class)
179
+ # if not class_data:
180
+ # return {"error": f"Class {class_id} does not exist in our registry."}
181
+ if not class_data:
182
+ # NEW: Suggest valid classes if the lookup fails
183
+ valid_classes = ", ".join(CLASS_ASSIGNMENTS.keys())
184
+ return {"error": f"Class '{std_class}' not found. Please use one of: {valid_classes}"}
185
+
186
+ teacher_name = class_data.get(std_subject)
187
+ if not teacher_name:
188
+ return {"error": f"No {std_subject} teacher assigned to Class {class_id}."}
189
+
190
+ teacher_details = FACULTY_DATA.get(teacher_name)
191
+ return {
192
+ "name": teacher_name,
193
+ "id": teacher_details["id"],
194
+ "subject": subject
195
+ }
196
+
197
+ @mcp.tool()
198
+ async def escalate_to_teacher(
199
+ teacher_name: str,
200
+ subject: str,
201
+ student_query: str,
202
+ ai_response: str,
203
+ ctx: Context = CurrentContext()
204
+ ) -> str:
205
+ """Sends a real-time email to the correct teacher by name."""
206
+
207
+ # Safely get email from our central registry
208
+ teacher_info = FACULTY_DATA.get(teacher_name)
209
+ if not teacher_info:
210
+ recipient = "dummyuseai@gmail.com" # Fallback to admin
211
+ else:
212
+ recipient = teacher_info["email"]
213
+
214
+ msg = EmailMessage()
215
+ msg["Subject"] = f"🚨 Escalation: {subject} Doubt for {teacher_name}"
216
+ msg["From"] = SENDER_EMAIL
217
+ msg["To"] = recipient
218
+
219
+ html_content = f"""
220
+ <html>
221
+ <body style="font-family: Arial, sans-serif; color: #333;">
222
+ <div style="background: #f9f9f9; padding: 20px; border: 1px solid #ddd;">
223
+ <h2 style="color: #d9534f;">Hello {teacher_name},</h2>
224
+ <p>A student has a <strong>{subject}</strong> doubt that requires your attention.</p>
225
+ <hr>
226
+ <p><strong>Doubt:</strong> {student_query}</p>
227
+ <p><strong>AI Response:</strong> {ai_response}</p>
228
+ </div>
229
+ </body>
230
+ </html>
231
+ """
232
+ msg.add_alternative(html_content, subtype="html")
233
+
234
+ try:
235
+ await aiosmtplib.send(msg, hostname=SMTP_SERVER, port=SMTP_PORT,
236
+ username=SENDER_EMAIL, password=SENDER_PASSWORD, start_tls=True)
237
+ # --- THE FIX: RESET LOGIC ---
238
+ ctx.set_state("math_attempts", 0) # Reset counter to 0
239
+ # await ctx.enable_components(tools=["explain_from_resource", "generate_quick_revision"]) # Re-enable tools
240
+
241
+ return f"Success: {teacher_name} ({subject} Expert) has been notified."
242
+ except Exception as e:
243
+ return f"Error sending email: {str(e)}"
244
+
245
+ # --- 4. MCP PROMPTS ---
246
+ @mcp.prompt()
247
+ def revision_assistance(student_name: str):
248
+ """A pre-configured prompt to help students with exam prep."""
249
+ return f"Hi {student_name}, I can help you summarize your chapter or create short notes for formulas. Which one would you like to do first?"
250
+
251
+ @mcp.prompt()
252
+ def escalation_handover(teacher_name: str, subject: str) -> str:
253
+ """Professional message used when handing over to a human teacher."""
254
+ return (
255
+ f"I've shared our {subject} discussion with {teacher_name}. "
256
+ "They will review it and get back to you on your dashboard soon!"
257
+ )
258
+
259
+ if __name__ == "__main__":
260
+ mcp.run()