Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions uc-mcp/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,22 @@
# must state exact document scope

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]
An MCP server that operates as a policy retrieval layer, exposing RAG-based
document querying as a structured tool for AI agents.

intent: >
[FILL IN: What does a correctly implemented MCP server produce?
Hint: JSON-RPC compliant responses, scoped tool description, correct refusals]
To produce JSON-RPC compliant responses, providing a clearly scoped tool
description for policy retrieval and ensuring correct refusals for
out-of-scope queries.

context: >
[FILL IN: What does this server have access to?
Hint: RAG server results only — no direct LLM calls, no outside knowledge]
The server has access to RAG results based on CMC HR, IT, and Finance policies.
It must not make direct LLM calls or use outside knowledge to answer queries
outside this scope.

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]"
- "Tool description must state the exact document scope: CMC HR Leave Policy, IT Acceptable Use Policy, Finance Reimbursement Policy."
- "Tool description must state what it cannot answer: questions outside these three documents return the refusal template."
- "inputSchema must require `question` as a non-empty string."
- "Error responses must use `isError: true` — never return an empty content array on failure."
- "The server must return HTTP 200 for all JSON-RPC responses including errors."
2 changes: 1 addition & 1 deletion uc-mcp/llm_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def call_llm(prompt: str) -> str:
try:
import google.generativeai as genai
genai.configure(api_key=api_key)
model = genai.GenerativeModel("gemini-1.5-flash")
model = genai.GenerativeModel("gemini-flash-latest")
response = model.generate_content(prompt)
return response.text
except ImportError:
Expand Down
95 changes: 77 additions & 18 deletions uc-mcp/mcp_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,10 @@
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 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."
),
"inputSchema": {
"type": "object",
Expand All @@ -75,11 +72,22 @@ def query_policy_documents(question: str) -> dict:
- If RAG refuses (no chunks above threshold) → isError: True
- If RAG raises exception → isError: True with error message
"""
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:
# Call the RAG query interface (from stub_rag.py or rag_server.py)
result = rag_query(question, llm_call=call_llm)

answer = result.get("answer", "No answer provided.")
refused = result.get("refused", False)

return {
"content": [{"type": "text", "text": answer}],
"isError": refused
}
except Exception as e:
return {
"content": [{"type": "text", "text": f"Error querying documents: {str(e)}"}],
"isError": True
}


# ── SKILL: serve_mcp ─────────────────────────────────────────────────────────
Expand All @@ -95,12 +103,63 @@ class MCPHandler(BaseHTTPRequestHandler):
"""

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."
)
"""Handle JSON-RPC 2.0 requests."""
try:
content_length = int(self.headers.get('Content-Length', 0))
post_data = self.rfile.read(content_length).decode('utf-8')
request = json.loads(post_data)

method = request.get("method")
request_id = request.get("id")

if method == "tools/list":
self._send_result({"tools": [TOOL_DEFINITION]}, request_id)
elif method == "tools/call":
params = request.get("params", {})
tool_name = params.get("name")
arguments = params.get("arguments", {})

if tool_name == "query_policy_documents":
question = arguments.get("question")
if not question:
self._send_error(-32602, "Invalid params: 'question' is required", request_id)
return

result = query_policy_documents(question)
self._send_result(result, request_id)
else:
self._send_error(-32601, f"Tool not found: {tool_name}", request_id)
else:
self._send_error(-32601, f"Method not found: {method}", request_id)

except json.JSONDecodeError:
self._send_error(-32700, "Parse error", None)
except Exception as e:
self._send_error(-32603, f"Internal error: {str(e)}", None)

def _send_result(self, result, request_id):
response = {
"jsonrpc": "2.0",
"id": request_id,
"result": result
}
self._send_json(response)

def _send_error(self, code, message, request_id):
response = {
"jsonrpc": "2.0",
"id": request_id,
"error": {"code": code, "message": message}
}
self._send_json(response)

def _send_json(self, data):
content = json.dumps(data).encode('utf-8')
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", len(content))
self.end_headers()
self.wfile.write(content)

def log_message(self, format, *args):
# Suppress default HTTP logging — use print for clarity
Expand Down
16 changes: 8 additions & 8 deletions uc-mcp/skills.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@

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: "Queries CMC policy documents (HR, IT, Finance) using a RAG server and returns the answer with citations."
input: "question (string): The user query about CMC policies."
output: "A result object containing a content array with text blocks and an isError boolean."
error_handling: "If the RAG result indicates a refusal (out of scope), returns isError: true with the refusal text."

- 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: "Starts a plain HTTP server that implements the MCP JSON-RPC protocol on port 8765."
input: "JSON-RPC 2.0 requests (tools/list, tools/call) sent via HTTP POST."
output: "JSON-RPC 2.0 compliant responses, always returning HTTP 200 for application-level results."
error_handling: "Returns standard JSON-RPC error codes (e.g., -32601 for unknown methods)."
30 changes: 8 additions & 22 deletions uc-rag/agents.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,17 @@
# agents.md — UC-RAG RAG Server
# INSTRUCTIONS:
# 1. Open your AI tool
# 2. Paste the full contents of uc-rag/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, replacing this placeholder
# 5. Check every enforcement rule against the README before saving

role: >
[FILL IN: Who is this agent? What is its operational boundary?
Hint: a retrieval-augmented policy assistant for city staff]
You are a retrieval-augmented policy assistant for city staff. You operate precisely within the bounds of the provided policy documents (HR, IT, Finance) to answer staff inquiries accurately without hallucination or generalization.

intent: >
[FILL IN: What does a correct output look like?
Hint: answer + cited chunks + refusal when not covered]
To answer staff questions strictly by extracting information from retrieved policy chunks. Your output must contain the specific answer alongside a list of cited chunks. If the retrieved chunks do not cover the question (or do not pass the relevance threshold), you must output the designated refusal template.

context: >
[FILL IN: What sources may the agent use?
Hint: retrieved chunks only — no general knowledge]
You will base your answers exclusively on the retrieved chunks provided to you from the City Municipal Corporation policy documents. You must not use general knowledge or assumptions outside the exact text provided in the chunks.

enforcement:
- "[FILL IN: Chunk size rule]"
- "[FILL IN: Citation rule]"
- "[FILL IN: Similarity threshold + refusal rule]"
- "[FILL IN: Context grounding rule]"
- "[FILL IN: Cross-document rule]"
- "Chunk size must not exceed 400 tokens. Never split mid-sentence."
- "Every answer must cite the source document name and chunk index."
- "If no retrieved chunk scores above similarity threshold 0.6 — output the refusal template. Never generate an answer from general knowledge."
- "Answer must use only information present in the retrieved chunks. Never add context from outside the retrieved set."
- "If the query spans two documents — retrieve from each separately. Never merge retrieved chunks from different documents into one answer."
Loading