diff --git a/uc-0a/agents.md b/uc-0a/agents.md index cd4d882..30c0c7a 100644 --- a/uc-0a/agents.md +++ b/uc-0a/agents.md @@ -1,27 +1,15 @@ -# agents.md — UC-0A Complaint Classifier -# INSTRUCTIONS: -# 1. Open your AI tool -# 2. Paste the full contents of uc-0a/README.md -# 3. Use this prompt: -# "Read this UC README. Using the R.I.C.E framework, generate an -# agents.md YAML with four fields: role, intent, context, enforcement. -# Enforcement must include every rule listed under -# 'Enforcement Rules Your agents.md Must Include'. -# Output only valid YAML." -# 4. Paste the output below - role: > - [FILL IN] + You are an automated Complaint Classification Agent responsible for processing citizen queries for the City Operations team efficiently. Your operational boundary involves categorizing issues identically without hallucinating parameters and ensuring no critical safety requests slip through prioritization filters. intent: > - [FILL IN] + A reliable, rigidly structured tabular extraction classifying complaints into a strict taxonomy, aggressively marking critical dangers as Urgent with deterministic boolean logic, and always proving its logic backward towards specific keywords in the user's description. context: > - [FILL IN] + You operate strictly on isolated complaint description rows. You must not invent or approximate standard practices. enforcement: - - "[FILL IN: category enum rule]" - - "[FILL IN: severity keyword rule — list the keywords]" - - "[FILL IN: reason field rule]" - - "[FILL IN: ambiguity refusal rule]" - - "[FILL IN: no invented categories rule]" + - "The 'category' parameter must strictly map to exactly one of these allowed values: Pothole, Flooding, Streetlight, Waste, Noise, Road Damage, Heritage Damage, Heat Hazard, Drain Blockage, Other. Do not alter casing or synonyms." + - "The 'priority' parameter MUST be escalated to 'Urgent' immediately if the complaint description contains ANY of the following severity keywords: injury, child, school, hospital, ambulance, fire, hazard, fell, collapse." + - "Every output row must explicitly include a 'reason' field that quotes specific words directly cited from the raw description to mathematically justify the classification." + - "If the underlying category cannot be calculated safely or the complaint is overly ambiguous, you must systematically degrade to outputting 'category: Other' along with the boolean explicit 'flag: NEEDS_REVIEW'." + - "NEVER invent, hallucinate, or generate category names that fall outside the explicitly listed taxonomy." diff --git a/uc-0a/classifier.py b/uc-0a/classifier.py index 3f8fe55..ed8a794 100644 --- a/uc-0a/classifier.py +++ b/uc-0a/classifier.py @@ -1,31 +1,124 @@ -""" -UC-0A — Complaint Classifier -classifier.py — Starter file - -Build this using your AI coding tool: -1. Share agents.md, skills.md, and uc-0a/README.md -2. Ask the AI to implement this file -3. Run: python3 classifier.py --input ../data/city-test-files/test_pune.csv \ - --output results_pune.csv -""" import argparse import csv +import sys + +ALLOWED_CATEGORIES = { + "Pothole", "Flooding", "Streetlight", "Waste", "Noise", + "Road Damage", "Heritage Damage", "Heat Hazard", "Drain Blockage", "Other" +} + +SEVERITY_KEYWORDS = { + "injury", "child", "school", "hospital", "ambulance", + "fire", "hazard", "fell", "collapse" +} + def classify_complaint(row: dict) -> dict: """ - Classify a single complaint row. - Returns dict with: complaint_id, category, priority, reason, flag + RICE Encapsulation: Strict taxonomic bounds without hallucination. + Returns identically structured objects. """ - raise NotImplementedError("Build this using your AI tool + agents.md") + description = row.get("description", "").strip() + desc_lower = description.lower() + + output = { + "complaint_id": row.get("complaint_id", "UNKNOWN"), + "category": "Other", + "priority": "Low", # Defaulting to lowest if standard isn't pushed + "reason": "", + "flag": "" + } + + # 1. Error Handling Boundary + if len(description) < 15: + output["flag"] = "NEEDS_REVIEW" + output["reason"] = f"Flagged ambiguous logic due to short vague textual input: '{description}'" + return output + + # 2. Enforcement: Priority Escalation via Deterministic Keywords (Severity Blindness fix) + urgent_triggers = [kw for kw in SEVERITY_KEYWORDS if kw in desc_lower] + if urgent_triggers: + output["priority"] = "Urgent" + output["reason"] += f"Escalated priority aggressively because the text cited dangers: {', '.join(urgent_triggers)}. " + else: + output["priority"] = "Standard" + + # 3. Enforcement: Bounded Schema Generation (Taxonomy Drift & Hallucination fix) + category_assigned = False + + # Rigid matching structure preventing string variations + mapping_keywords = { + "pothole": "Pothole", + "flood": "Flooding", "water": "Flooding", "drain": "Drain Blockage", + "light": "Streetlight", "dark": "Streetlight", + "waste": "Waste", "garbage": "Waste", "trash": "Waste", + "noise": "Noise", "loud": "Noise", + "road": "Road Damage", "crack": "Road Damage", + "heritage": "Heritage Damage", "monument": "Heritage Damage", + "heat": "Heat Hazard" + } + + for kw, exact_cat in mapping_keywords.items(): + if kw in desc_lower: + output["category"] = exact_cat + # Output rule: Force justification citation + output["reason"] += f"Explicitly mapped to category {exact_cat} because raw description contained '{kw}'." + category_assigned = True + break + + # 4. Enforcement: Ambiguity requires defensive degradation (False Confidence fix) + if not category_assigned: + output["category"] = "Other" + output["flag"] = "NEEDS_REVIEW" + output["reason"] += "Routed to Other + Review queue due to lacking confident mapped classifier elements." + + return output + def batch_classify(input_path: str, output_path: str): - """Read input CSV, classify each row, write results CSV.""" - raise NotImplementedError("Build this using your AI tool + agents.md") + """ + Orchestrates robust queue management without crushing the job on bad inputs. + """ + processed_count = 0 + results = [] + + try: + with open(input_path, mode='r', encoding='utf-8-sig') as f: + reader = csv.DictReader(f) + + for idx, row in enumerate(reader, start=1): + # Enforce batch_classify error handling skipping malformed datasets cleanly + if not row or "description" not in row: + print(f"⚠️ [Error Handle] Logger dropping corrupted iteration at row limit #{idx}. Bypassing safely...") + continue + + classified = classify_complaint(row) + results.append(classified) + processed_count += 1 + + except FileNotFoundError: + print(f"Critical Halt: File exactly matching '{input_path}' not situated.") + sys.exit(1) + + if not results: + print("Empty extraction buffer.") + sys.exit(0) + + # Write cleanly exactly mapping the Dict + fields = ["complaint_id", "category", "priority", "reason", "flag"] + with open(output_path, mode='w', encoding='utf-8', newline='') as f: + writer = csv.DictWriter(f, fieldnames=fields) + writer.writeheader() + writer.writerows(results) + + print(f"Classification success: Processed {processed_count} rows firmly inside strict Taxonomy rules.") + if __name__ == "__main__": - parser = argparse.ArgumentParser(description="UC-0A Complaint Classifier") - parser.add_argument("--input", required=True) + parser = argparse.ArgumentParser(description="UC-0A Formal Classifier Endpoint") + parser.add_argument("--input", required=True) parser.add_argument("--output", required=True) args = parser.parse_args() + batch_classify(args.input, args.output) - print(f"Done. Results written to {args.output}") + print(f"Done. Extracted to -> {args.output}") diff --git a/uc-0a/results_ahmedabad.csv b/uc-0a/results_ahmedabad.csv new file mode 100644 index 0000000..3c870fc --- /dev/null +++ b/uc-0a/results_ahmedabad.csv @@ -0,0 +1,16 @@ +complaint_id,category,priority,reason,flag +AM-202401,Other,Standard,Routed to Other + Review queue due to lacking confident mapped classifier elements.,NEEDS_REVIEW +AM-202402,Other,Standard,Routed to Other + Review queue due to lacking confident mapped classifier elements.,NEEDS_REVIEW +AM-202405,Other,Standard,Routed to Other + Review queue due to lacking confident mapped classifier elements.,NEEDS_REVIEW +AM-202406,Heat Hazard,Standard,Explicitly mapped to category Heat Hazard because raw description contained 'heat'., +AM-202407,Other,Urgent,Escalated priority aggressively because the text cited dangers: child. Routed to Other + Review queue due to lacking confident mapped classifier elements.,NEEDS_REVIEW +AM-202410,Pothole,Standard,Explicitly mapped to category Pothole because raw description contained 'pothole'., +AM-202414,Other,Standard,Routed to Other + Review queue due to lacking confident mapped classifier elements.,NEEDS_REVIEW +AM-202417,Waste,Standard,Explicitly mapped to category Waste because raw description contained 'waste'., +AM-202421,Other,Standard,Routed to Other + Review queue due to lacking confident mapped classifier elements.,NEEDS_REVIEW +AM-202424,Road Damage,Standard,Explicitly mapped to category Road Damage because raw description contained 'road'., +AM-202429,Other,Standard,Routed to Other + Review queue due to lacking confident mapped classifier elements.,NEEDS_REVIEW +AM-202431,Road Damage,Standard,Explicitly mapped to category Road Damage because raw description contained 'road'., +AM-202435,Road Damage,Standard,Explicitly mapped to category Road Damage because raw description contained 'road'., +AM-202444,Waste,Standard,Explicitly mapped to category Waste because raw description contained 'waste'., +AM-202445,Other,Standard,Routed to Other + Review queue due to lacking confident mapped classifier elements.,NEEDS_REVIEW diff --git a/uc-0a/results_hyderabad.csv b/uc-0a/results_hyderabad.csv new file mode 100644 index 0000000..9e269b1 --- /dev/null +++ b/uc-0a/results_hyderabad.csv @@ -0,0 +1,16 @@ +complaint_id,category,priority,reason,flag +GH-202401,Flooding,Urgent,Escalated priority aggressively because the text cited dangers: ambulance. Explicitly mapped to category Flooding because raw description contained 'flood'., +GH-202402,Flooding,Standard,Explicitly mapped to category Flooding because raw description contained 'flood'., +GH-202406,Flooding,Standard,Explicitly mapped to category Flooding because raw description contained 'water'., +GH-202407,Drain Blockage,Standard,Explicitly mapped to category Drain Blockage because raw description contained 'drain'., +GH-202410,Pothole,Standard,Explicitly mapped to category Pothole because raw description contained 'pothole'., +GH-202411,Pothole,Urgent,Escalated priority aggressively because the text cited dangers: hospital. Explicitly mapped to category Pothole because raw description contained 'pothole'., +GH-202412,Pothole,Urgent,Escalated priority aggressively because the text cited dangers: school. Explicitly mapped to category Pothole because raw description contained 'pothole'., +GH-202417,Waste,Standard,Explicitly mapped to category Waste because raw description contained 'waste'., +GH-202420,Other,Standard,Routed to Other + Review queue due to lacking confident mapped classifier elements.,NEEDS_REVIEW +GH-202422,Road Damage,Urgent,Escalated priority aggressively because the text cited dangers: collapse. Explicitly mapped to category Road Damage because raw description contained 'road'., +GH-202424,Flooding,Standard,Explicitly mapped to category Flooding because raw description contained 'flood'., +GH-202428,Waste,Standard,Explicitly mapped to category Waste because raw description contained 'waste'., +GH-202432,Other,Standard,Routed to Other + Review queue due to lacking confident mapped classifier elements.,NEEDS_REVIEW +GH-202448,Flooding,Standard,Explicitly mapped to category Flooding because raw description contained 'flood'., +GH-202438,Flooding,Standard,Explicitly mapped to category Flooding because raw description contained 'water'., diff --git a/uc-0a/results_kolkata.csv b/uc-0a/results_kolkata.csv new file mode 100644 index 0000000..e4010dd --- /dev/null +++ b/uc-0a/results_kolkata.csv @@ -0,0 +1,16 @@ +complaint_id,category,priority,reason,flag +KM-202401,Heritage Damage,Standard,Explicitly mapped to category Heritage Damage because raw description contained 'heritage'., +KM-202402,Road Damage,Standard,Explicitly mapped to category Road Damage because raw description contained 'road'., +KM-202405,Other,Standard,Routed to Other + Review queue due to lacking confident mapped classifier elements.,NEEDS_REVIEW +KM-202409,Pothole,Standard,Explicitly mapped to category Pothole because raw description contained 'pothole'., +KM-202410,Pothole,Standard,Explicitly mapped to category Pothole because raw description contained 'pothole'., +KM-202411,Pothole,Standard,Explicitly mapped to category Pothole because raw description contained 'pothole'., +KM-202415,Drain Blockage,Standard,Explicitly mapped to category Drain Blockage because raw description contained 'drain'., +KM-202418,Waste,Standard,Explicitly mapped to category Waste because raw description contained 'waste'., +KM-202421,Other,Urgent,"Escalated priority aggressively because the text cited dangers: fell, hospital. Routed to Other + Review queue due to lacking confident mapped classifier elements.",NEEDS_REVIEW +KM-202422,Road Damage,Standard,Explicitly mapped to category Road Damage because raw description contained 'road'., +KM-202426,Heritage Damage,Standard,Explicitly mapped to category Heritage Damage because raw description contained 'heritage'., +KM-202430,Road Damage,Standard,Explicitly mapped to category Road Damage because raw description contained 'road'., +KM-202434,Heritage Damage,Standard,Explicitly mapped to category Heritage Damage because raw description contained 'heritage'., +KM-202436,Streetlight,Standard,Explicitly mapped to category Streetlight because raw description contained 'dark'., +KM-202438,Heritage Damage,Standard,Explicitly mapped to category Heritage Damage because raw description contained 'heritage'., diff --git a/uc-0a/results_pune.csv b/uc-0a/results_pune.csv new file mode 100644 index 0000000..9854144 --- /dev/null +++ b/uc-0a/results_pune.csv @@ -0,0 +1,16 @@ +complaint_id,category,priority,reason,flag +PM-202401,Pothole,Standard,Explicitly mapped to category Pothole because raw description contained 'pothole'., +PM-202402,Pothole,Urgent,"Escalated priority aggressively because the text cited dangers: child, school. Explicitly mapped to category Pothole because raw description contained 'pothole'.", +PM-202406,Flooding,Standard,Explicitly mapped to category Flooding because raw description contained 'flood'., +PM-202408,Flooding,Standard,Explicitly mapped to category Flooding because raw description contained 'flood'., +PM-202410,Streetlight,Standard,Explicitly mapped to category Streetlight because raw description contained 'light'., +PM-202411,Streetlight,Urgent,Escalated priority aggressively because the text cited dangers: hazard. Explicitly mapped to category Streetlight because raw description contained 'light'., +PM-202413,Waste,Standard,Explicitly mapped to category Waste because raw description contained 'garbage'., +PM-202418,Other,Standard,Routed to Other + Review queue due to lacking confident mapped classifier elements.,NEEDS_REVIEW +PM-202419,Road Damage,Standard,Explicitly mapped to category Road Damage because raw description contained 'road'., +PM-202420,Other,Urgent,Escalated priority aggressively because the text cited dangers: injury. Routed to Other + Review queue due to lacking confident mapped classifier elements.,NEEDS_REVIEW +PM-202427,Flooding,Standard,Explicitly mapped to category Flooding because raw description contained 'flood'., +PM-202428,Other,Standard,Routed to Other + Review queue due to lacking confident mapped classifier elements.,NEEDS_REVIEW +PM-202430,Streetlight,Standard,Explicitly mapped to category Streetlight because raw description contained 'light'., +PM-202433,Waste,Standard,Explicitly mapped to category Waste because raw description contained 'waste'., +PM-202446,Other,Urgent,Escalated priority aggressively because the text cited dangers: fell. Routed to Other + Review queue due to lacking confident mapped classifier elements.,NEEDS_REVIEW diff --git a/uc-0a/skills.md b/uc-0a/skills.md index 4e67823..114e192 100644 --- a/uc-0a/skills.md +++ b/uc-0a/skills.md @@ -1,15 +1,12 @@ -# skills.md — UC-0A Complaint Classifier -# INSTRUCTIONS: Same as agents.md — paste README into AI, ask for skills.md YAML - skills: - name: classify_complaint - description: "[FILL IN]" - input: "[FILL IN]" - output: "[FILL IN]" - error_handling: "[FILL IN]" + description: Processes an individual raw complaint record to securely categorize, prioritize, and flag issues according to strict operational taxonomies. + input: A dictionary representing a single complaint row, specifically utilizing the 'description' and 'location' fields. + output: A dictionary formally mapping exactly four strings—category, priority, reason, and flag. + error_handling: Securely handles short, unparsable, or ambiguous inputs by forcefully degrading to outputting 'Other' for the category while triggering the 'NEEDS_REVIEW' flag, rather than attempting to hallucinate confident classifications. - name: batch_classify - description: "[FILL IN]" - input: "[FILL IN]" - output: "[FILL IN]" - error_handling: "[FILL IN]" + description: Handles bulk processing of CSV files automatically, routing rows concurrently to the unified classifier without crashing memory buffers. + input: A string referencing the filesystem path to the test CSV file. + output: A string confirming the path to the newly generated and populated results CSV file. + error_handling: Handles malformed columns explicitly by logging the corrupted row id or text locally to standard output and mathematically skipping the row to continue executing the rest of the batch unhindered. diff --git a/uc-mcp/agents.md b/uc-mcp/agents.md index d2e55c8..010d77b 100644 --- a/uc-mcp/agents.md +++ b/uc-mcp/agents.md @@ -1,32 +1,17 @@ -# agents.md — UC-MCP MCP Server -# INSTRUCTIONS: -# 1. Open your AI tool -# 2. Paste the full contents of uc-mcp/README.md -# 3. Use this prompt: -# "Read this UC README. Using the R.I.C.E framework, generate an -# agents.md YAML with four fields: role, intent, context, enforcement. -# The enforcement must include every rule listed under -# 'Enforcement Rules Your agents.md Must Include'. -# Output only valid YAML." -# 4. Paste the output below, replacing this placeholder -# 5. Pay special attention to enforcement rule 1 — the tool description -# must state exact document scope +# agents.md — UC-MCP Expose Your RAG Server as an MCP Tool role: > - [FILL IN: Who is this agent? What layer of the stack does it operate at? - Hint: an MCP server that exposes policy retrieval as a tool] + You are an MCP Protocol Implementation Agent responsible for securely exposing the internal RAG server as a formally bounded external tool according strictly to the JSON-RPC standard. Your boundary governs creating the strict interface that controls how other agents query policy information without wasting token space securely. intent: > - [FILL IN: What does a correctly implemented MCP server produce? - Hint: JSON-RPC compliant responses, scoped tool description, correct refusals] + A reliable, rigidly structured HTTP JSON-RPC MCP server. The `tools/list` configuration must definitively communicate bounds locally to prevent out-of-scope model calls, avoiding hallucinated budget forecasts by implementing explicit schema guards instead of open-ended prompt windows. context: > - [FILL IN: What does this server have access to? - Hint: RAG server results only — no direct LLM calls, no outside knowledge] + You operate strictly defining and executing the JSON-RPC interface payload. You do not define the policy backend mechanism beyond integrating with the existing `query_policy_documents` system, strictly applying the provided rules to map inputs through to valid `result` structures. enforcement: - - "[FILL IN: Tool description scope rule]" - - "[FILL IN: Refusal documentation rule]" - - "[FILL IN: inputSchema required field rule]" - - "[FILL IN: isError on failure rule]" - - "[FILL IN: HTTP 200 for all JSON-RPC responses rule]" + - "The tool description generated in `tools/list` MUST explicitly state the exact scope limit: CMC HR Leave Policy, IT Acceptable Use Policy, and Finance Reimbursement Policy. It must definitively state what it cannot answer, expressly informing agents that questions outside these boundaries will be refused." + - "The JSON `inputSchema` for the tool MUST require `question` explicitly as a non-empty string param." + - "Any error responses returned inside the payload must explicitly set `isError: true`. NEVER return an empty successful content array to indicate a failure." + - "The HTTP server MUST natively return HTTP 200 status codes for ALL successful JSON-RPC payload transmissions safely reaching the end-user mechanism, even if the JSON-RPC contains application-level errors or exceptions. Utilize standard HTTP 4xx/5xx strictly for actual network transport failures." + - "If the server natively receives an unknown method request (not `tools/list` or `tools/call`), it MUST route a `JSON-RPC error -32601 Method not found` safely back in the response array." diff --git a/uc-mcp/mcp_server.py b/uc-mcp/mcp_server.py index 0400b6a..d18763e 100644 --- a/uc-mcp/mcp_server.py +++ b/uc-mcp/mcp_server.py @@ -1,22 +1,3 @@ -""" -UC-MCP — mcp_server.py -Plain HTTP MCP Server — Starter File - -Build this using your AI coding tool: -1. Share agents.md, skills.md, and uc-mcp/README.md with your AI tool -2. Ask it to implement this file following the MCP protocol - described in the README -3. Run with: python3 mcp_server.py --port 8765 -4. Test with: python3 test_client.py --port 8765 - -Protocol: JSON-RPC 2.0 over HTTP POST -No external dependencies beyond Python stdlib. - -Methods to implement: - tools/list — return the tool definition for query_policy_documents - tools/call — execute query_policy_documents, return JSON-RPC response -""" - import json import argparse import sys @@ -26,87 +7,140 @@ # Import RAG — uses stub by default, swap to rag_server once yours works sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../uc-rag")) try: - # Try participant's rag_server first from rag_server import query as rag_query print("[mcp_server] Using participant rag_server.py") except (ImportError, NotImplementedError): - # Fall back to stub from stub_rag import query as rag_query print("[mcp_server] Using stub_rag.py (fallback)") -# Import LLM adapter from llm_adapter import call_llm - # ── TOOL DEFINITION ────────────────────────────────────────────────────────── -# This is what the agent reads to decide when to call your tool. -# The description IS the enforcement — make it specific. TOOL_DEFINITION = { "name": "query_policy_documents", "description": ( - # FILL IN: Describe exactly what this tool covers and what it does not. - # Bad: "Answers questions about policies" - # Good: "Answers questions about CMC HR Leave Policy, IT Acceptable Use - # Policy, and Finance Reimbursement Policy only. Returns cited - # answers grounded in retrieved document chunks. Returns a refusal - # for questions outside these three documents." - "[FILL IN: specific scope + what it refuses]" + "Answers questions exclusively pertaining to the CMC HR Leave Policy, " + "IT Acceptable Use Policy, and Finance Reimbursement Policy. " + "Strictly bounded to these exact three documents. It explicitly " + "refuses to answer any questions mapping outside these parameters " + "or concerning generic company operations." ), "inputSchema": { "type": "object", "properties": { "question": { "type": "string", - "description": "The policy question to answer", + "description": "The exact policy query string.", } }, "required": ["question"], }, } - # ── SKILL: query_policy_documents ──────────────────────────────────────────── def query_policy_documents(question: str) -> dict: """ Call the RAG server with the question. - Return MCP content format: {"content": [...], "isError": bool} - - Error handling: - - If RAG refuses (no chunks above threshold) → isError: True - - If RAG raises exception → isError: True with error message + Returns MCP natively wrapped responses. """ - raise NotImplementedError( - "Implement query_policy_documents using your AI tool.\n" - "Hint: call rag_query(question, llm_call=call_llm), " - "check result['refused'], format as MCP content response." - ) - + try: + result = rag_query(question, llm_call=call_llm) + + # Enforce error surfacing dynamically without hiding failures in text + if result.get("refused", False): + return { + "content": [{"type": "text", "text": result.get("answer", "Refusal triggered by explicit boundary filters.")}], + "isError": True + } + else: + return { + "content": [{"type": "text", "text": result.get("answer", "Empty Success.")}], + "isError": False + } + except Exception as e: + return { + "content": [{"type": "text", "text": f"Agent Internal Execution Error: {str(e)}"}], + "isError": True + } # ── SKILL: serve_mcp ───────────────────────────────────────────────────────── class MCPHandler(BaseHTTPRequestHandler): """ - HTTP request handler implementing JSON-RPC 2.0. - Handles POST requests to / with JSON-RPC body. - - Implement: - - tools/list → return TOOL_DEFINITION - - tools/call → call query_policy_documents, return result - - unknown methods → JSON-RPC error -32601 + Strict HTTP JSON-RPC router mapped dynamically. """ - def do_POST(self): - raise NotImplementedError( - "Implement do_POST using your AI tool.\n" - "Hint: read Content-Length, parse JSON body, " - "dispatch on method, write JSON-RPC response.\n" - "Return HTTP 200 for all JSON-RPC responses including errors." - ) + try: + content_length = int(self.headers.get('Content-Length', 0)) + body = self.rfile.read(content_length).decode('utf-8') + request = json.loads(body) + + req_id = request.get("id") + method = request.get("method") + + if method == "tools/list": + response = { + "jsonrpc": "2.0", + "id": req_id, + "result": { + "tools": [TOOL_DEFINITION] + } + } + elif method == "tools/call": + params = request.get("params", {}) + tool_name = params.get("name") + + if tool_name == "query_policy_documents": + args = params.get("arguments", {}) + question = args.get("question", "") + + if not question: + response = { + "jsonrpc": "2.0", + "id": req_id, + "error": {"code": -32602, "message": "Invalid params: 'question' is required"} + } + else: + tool_result = query_policy_documents(question) + response = { + "jsonrpc": "2.0", + "id": req_id, + "result": tool_result + } + else: + response = { + "jsonrpc": "2.0", + "id": req_id, + "error": {"code": -32601, "message": f"Tool not found locally: {tool_name}"} + } + else: + response = { + "jsonrpc": "2.0", + "id": req_id, + "error": {"code": -32601, "message": f"Method not found: {method}"} + } + + except json.JSONDecodeError: + response = { + "jsonrpc": "2.0", + "id": None, + "error": {"code": -32700, "message": "Parse internal failure"} + } + except Exception as e: + response = { + "jsonrpc": "2.0", + "id": None, + "error": {"code": -32603, "message": f"Internal JSON-RPC Engine Failure: {str(e)}"} + } + + # Rule Enforcement: JSON-RPC MUST emit 200 HTTP codes irrespective of App errors + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.end_headers() + self.wfile.write(json.dumps(response).encode('utf-8')) def log_message(self, format, *args): - # Suppress default HTTP logging — use print for clarity print(f"[mcp_server] {args[0]} {args[1]}") - # ── MAIN ───────────────────────────────────────────────────────────────────── def main(): parser = argparse.ArgumentParser(description="UC-MCP Plain HTTP MCP Server") @@ -114,7 +148,6 @@ def main(): help="Port to listen on (default: 8765)") args = parser.parse_args() - # Verify RAG index exists db_path = os.path.join(os.path.dirname(__file__), "../uc-rag/stub_chroma_db") if not os.path.exists(db_path): print("[mcp_server] WARNING: RAG index not found.") @@ -130,6 +163,5 @@ def main(): except KeyboardInterrupt: print("\n[mcp_server] Stopped.") - if __name__ == "__main__": main() diff --git a/uc-mcp/skills.md b/uc-mcp/skills.md index 5028507..c71e24f 100644 --- a/uc-mcp/skills.md +++ b/uc-mcp/skills.md @@ -1,24 +1,14 @@ -# skills.md — UC-MCP MCP Server -# INSTRUCTIONS: -# 1. Open your AI tool -# 2. Paste the full contents of uc-mcp/README.md -# 3. Use this prompt: -# "Read this UC README. Generate a skills.md YAML defining the two -# skills: query_policy_documents and serve_mcp. Each skill needs: -# name, description, input, output, error_handling. -# error_handling must address the failure mode in the README. -# Output only valid YAML." -# 4. Paste the output below, replacing this placeholder +# skills.md — UC-MCP Expose Your RAG Server as an MCP Tool skills: - name: query_policy_documents - description: "[FILL IN]" - input: "[FILL IN: question string]" - output: "[FILL IN: MCP content format — content array + isError]" - error_handling: "[FILL IN: what happens when RAG refuses or raises exception]" + description: Executes the internal mechanism invoking the local RAG stub while strictly wrapping and standardizing the output structure. + input: Requires an explicit `question` (string parameter). + output: A rigorously bounded string capturing the structured answer appended with cited source data linearly, conforming to MCP transport requirements safely. + error_handling: Systematically flags and safely catches RAG `refused=True` states by terminating execution cleanly and surfacing the strict failure directly via the formal payload dictating `isError: true` alongside the exact refusal message text constraint. - name: serve_mcp - description: "[FILL IN]" - input: "[FILL IN: HTTP POST with JSON-RPC body]" - output: "[FILL IN: JSON-RPC 2.0 response, always HTTP 200]" - error_handling: "[FILL IN: unknown method → -32601, malformed request → -32700]" + description: Bootstraps the local HTTP server endpoint securely matching standard MCP JSON-RPC protocol guidelines dynamically routing tools globally. + input: An explicit configurable port argument safely defaulting dynamically to 8765 if empty or omitted. + output: Systematically catches and routes incoming `tools/list` array queries and subsequent `tools/call` requests safely via fully compliant HTTP 200 JSON-RPC structures dynamically. + error_handling: Handles unknown HTTP method requests securely by throwing explicit formatted `JSON-RPC error -32601` mapping outputs rather than crashing out locally.