55 lines
1.8 KiB
Python
55 lines
1.8 KiB
Python
#!/usr/bin/env python3
|
|
"""DRACO MCP server — standalone streamable-http ASGI app on :8013.
|
|
Run: uvicorn mcp_server:app --host 127.0.0.1 --port 8013 (or python3 mcp_server.py)
|
|
nginx routes /mcp here; Flask keeps :8012.
|
|
"""
|
|
import os
|
|
|
|
from mcp.server.fastmcp import FastMCP
|
|
|
|
import draco_core as core
|
|
from draco_core import CFG
|
|
|
|
MCP_PORT = int(os.environ.get("DRACO_MCP_PORT", 8013))
|
|
|
|
mcp = FastMCP("draco", host="127.0.0.1", port=MCP_PORT, streamable_http_path="/mcp")
|
|
|
|
|
|
@mcp.tool()
|
|
def draco_ask(question: str) -> str:
|
|
"""Ask DRACO a coding or security question. Answers are grounded in hundreds of
|
|
real programming and hacking books, with [n] citations and source titles."""
|
|
prompt, hits = core.build_prompt(question, k=CFG.get("rag_k", 6))
|
|
try:
|
|
answer = core.ask_ollama(prompt)
|
|
except Exception as e:
|
|
return f"Model offline: {e}"
|
|
src = "\n".join(f"[{i+1}] {h['title']} ({h['category']})" for i, h in enumerate(hits))
|
|
return f"{answer}\n\nSOURCES:\n{src}"
|
|
|
|
|
|
@mcp.tool()
|
|
def draco_search(query: str, k: int = 8) -> str:
|
|
"""Search DRACO's book library (BM25) for passages matching a topic."""
|
|
hits = core.search_library(query, k=max(1, min(k, 25)))
|
|
if not hits:
|
|
return "No passages matched."
|
|
return "\n\n".join(
|
|
f"[{h['title']} · {h['category']} · score {h['score']}]\n{h['text'][:600]}"
|
|
for h in hits)
|
|
|
|
|
|
@mcp.tool()
|
|
def draco_status() -> str:
|
|
"""DRACO health + library size."""
|
|
s = core.get_stats()
|
|
return (f"DRACO {CFG['model']} · {s['books']} books · {s['chunks']} passages · "
|
|
f"API: {CFG['base_url']}/api · MCP: {CFG['base_url']}/mcp")
|
|
|
|
|
|
app = mcp.streamable_http_app()
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
uvicorn.run(app, host="127.0.0.1", port=MCP_PORT, log_level="warning")
|