feat(skills): add regensburg_ris_mcp.py and universal_product_extractor.py
This commit is contained in:
+233
@@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env python3
|
||||
# /// script
|
||||
# dependencies = [
|
||||
# "beautifulsoup4>=4.12.0",
|
||||
# "requests>=2.31.0",
|
||||
# ]
|
||||
# ///
|
||||
"""
|
||||
Regensburg Stadtrat (ALLRIS) MCP Server
|
||||
Exposes municipal council documents, upcoming meetings, agenda items (TOPs),
|
||||
and binding resolutions as Model Context Protocol (MCP) tools over stdio.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import traceback
|
||||
|
||||
# Ensure current directory is on sys.path to import AllrisClient
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
from regensburg_ris import AllrisClient
|
||||
|
||||
TOOLS = [
|
||||
{
|
||||
"name": "stadtrat_search",
|
||||
"description": "Search Regensburg city council documents, resolutions (Beschlüsse), and motions (Vorlagen) by keyword (e.g. 'Kaufhof', 'Protzenweiherbrücke', 'Stadtbahn', 'Haushalt').",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "Keyword to search in ALLRIS net (e.g. 'Brücke', 'Kaufhof', 'Verkehr')."
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Maximum number of results to return (default: 10).",
|
||||
"default": 10
|
||||
}
|
||||
},
|
||||
"required": ["query"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stadtrat_calendar",
|
||||
"description": "Fetch upcoming municipal council and committee meetings (Stadtrat, Ausschüsse) in Regensburg.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"days": {
|
||||
"type": "integer",
|
||||
"description": "Number of days in the future to search (default: 30).",
|
||||
"default": 30
|
||||
},
|
||||
"from_date": {
|
||||
"type": "string",
|
||||
"description": "Start date in DD.MM.YYYY format (default: today)."
|
||||
},
|
||||
"to_date": {
|
||||
"type": "string",
|
||||
"description": "End date in DD.MM.YYYY format."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stadtrat_session",
|
||||
"description": "Fetch detailed meeting agenda items (Tagesordnungspunkte / TOPs) and linked Vorlagen IDs for a meeting ID (SILFDNR).",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"session_id": {
|
||||
"type": "string",
|
||||
"description": "ALLRIS session ID (SILFDNR, e.g. '2665')."
|
||||
}
|
||||
},
|
||||
"required": ["session_id"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stadtrat_vorlage",
|
||||
"description": "Inspect a specific council proposal (Vorlage) by VOLFDNR ID: extracts voting outcomes, responsible department, and verbatim resolution text.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"vorlage_id": {
|
||||
"type": "string",
|
||||
"description": "ALLRIS proposal ID (VOLFDNR, e.g. '21362')."
|
||||
},
|
||||
"convert_attachments": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to convert linked PDF attachments to Markdown (default: false).",
|
||||
"default": False
|
||||
}
|
||||
},
|
||||
"required": ["vorlage_id"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stadtrat_compile_topic",
|
||||
"description": "Compile a multi-document synthesis of a topic across ALLRIS into the Obsidian knowledge-wiki.",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"topic": {
|
||||
"type": "string",
|
||||
"description": "Topic keyword (e.g. 'Protzenweiherbrücke', 'Kaufhof')."
|
||||
},
|
||||
"vault": {
|
||||
"type": "string",
|
||||
"description": "Path to the Obsidian knowledge-wiki vault.",
|
||||
"default": "/home/node/.openclaw/wiki/main"
|
||||
}
|
||||
},
|
||||
"required": ["topic"]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def run_mcp_server():
|
||||
client = AllrisClient()
|
||||
|
||||
for line in sys.stdin:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
req = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
method = req.get("method")
|
||||
msg_id = req.get("id")
|
||||
params = req.get("params", {})
|
||||
|
||||
if method == "initialize":
|
||||
resp = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": msg_id,
|
||||
"result": {
|
||||
"protocolVersion": "2024-11-05",
|
||||
"serverInfo": {
|
||||
"name": "regensburg-stadtrat-mcp",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"capabilities": {
|
||||
"tools": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
sys.stdout.write(json.dumps(resp) + "\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
elif method == "notifications/initialized":
|
||||
pass
|
||||
|
||||
elif method == "tools/list":
|
||||
resp = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": msg_id,
|
||||
"result": {
|
||||
"tools": TOOLS
|
||||
}
|
||||
}
|
||||
sys.stdout.write(json.dumps(resp) + "\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
elif method == "tools/call":
|
||||
tool_name = params.get("name")
|
||||
args = params.get("arguments", {})
|
||||
try:
|
||||
if tool_name == "stadtrat_search":
|
||||
data = client.search(
|
||||
query=args.get("query", ""),
|
||||
limit=int(args.get("limit", 10))
|
||||
)
|
||||
elif tool_name == "stadtrat_calendar":
|
||||
data = client.get_calendar(
|
||||
days=int(args.get("days", 30)),
|
||||
from_date=args.get("from_date"),
|
||||
to_date=args.get("to_date")
|
||||
)
|
||||
elif tool_name == "stadtrat_session":
|
||||
data = client.get_session(
|
||||
silfdnr=str(args.get("session_id", ""))
|
||||
)
|
||||
elif tool_name == "stadtrat_vorlage":
|
||||
data = client.get_vorlage(
|
||||
volfdnr=str(args.get("vorlage_id", "")),
|
||||
convert_attachments=bool(args.get("convert_attachments", False))
|
||||
)
|
||||
elif tool_name == "stadtrat_compile_topic":
|
||||
vault_path = args.get("vault", "/home/node/.openclaw/wiki/main")
|
||||
data = client.compile_topic(
|
||||
topic=args.get("topic", ""),
|
||||
vault=vault_path
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Unknown tool: {tool_name}")
|
||||
|
||||
text_result = json.dumps(data, indent=2, ensure_ascii=False)
|
||||
resp = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": msg_id,
|
||||
"result": {
|
||||
"content": [{"type": "text", "text": text_result}]
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
resp = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": msg_id,
|
||||
"result": {
|
||||
"isError": True,
|
||||
"content": [{"type": "text", "text": f"Error executing {tool_name}: {str(e)}\n{traceback.format_exc()}"}]
|
||||
}
|
||||
}
|
||||
sys.stdout.write(json.dumps(resp) + "\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
else:
|
||||
if msg_id is not None:
|
||||
resp = {
|
||||
"jsonrpc": "2.0",
|
||||
"id": msg_id,
|
||||
"error": {"code": -32601, "message": f"Method not found: {method}"}
|
||||
}
|
||||
sys.stdout.write(json.dumps(resp) + "\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_mcp_server()
|
||||
Reference in New Issue
Block a user