Spaces:
Build error
Build error
Download headroom/ccr/mcp_server.py from minhtudragon/headroom_3: direct link, hf CLI and curl.
- Browser
- Download file 9.89 kB
-
https://huggingface.co/spaces/minhtudragon/headroom_3/resolve/cd5ea2ea1d50b68290f368d2d9e61ff9b4f428ec/headroom/ccr/mcp_server.py
- Command line
-
hf download hf://spaces/minhtudragon/headroom_3@cd5ea2ea1d50b68290f368d2d9e61ff9b4f428ec/headroom/ccr/mcp_server.py
-
curl -L -o mcp_server.py https://huggingface.co/spaces/minhtudragon/headroom_3/resolve/cd5ea2ea1d50b68290f368d2d9e61ff9b4f428ec/headroom/ccr/mcp_server.py
9.89 kB
| """CCR MCP Server - Exposes headroom_retrieve as an MCP tool. | |
| This MCP server allows LLMs to retrieve compressed content via MCP instead | |
| of through injected tool definitions. It connects to the Headroom proxy's | |
| CompressionStore to serve retrieval requests. | |
| Usage: | |
| # As standalone server (stdio transport) | |
| python -m headroom.ccr.mcp_server | |
| # With custom proxy URL | |
| python -m headroom.ccr.mcp_server --proxy-url http://localhost:8787 | |
| # Add to Claude Code's MCP config (~/.claude/mcp.json): | |
| { | |
| "mcpServers": { | |
| "headroom": { | |
| "command": "python", | |
| "args": ["-m", "headroom.ccr.mcp_server"] | |
| } | |
| } | |
| } | |
| When MCP is configured, the proxy will detect the tool is already present | |
| and skip tool injection, avoiding duplicate tools. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import asyncio | |
| import json | |
| import logging | |
| import os | |
| from typing import Any | |
| # Try to import MCP SDK | |
| try: | |
| from mcp.server import Server | |
| from mcp.server.stdio import stdio_server | |
| from mcp.types import TextContent, Tool | |
| MCP_AVAILABLE = True | |
| except ImportError: | |
| MCP_AVAILABLE = False | |
| Server = None | |
| stdio_server = None | |
| # Try to import httpx for proxy communication | |
| try: | |
| import httpx | |
| HTTPX_AVAILABLE = True | |
| except ImportError: | |
| HTTPX_AVAILABLE = False | |
| httpx = None # type: ignore[assignment] | |
| from .tool_injection import CCR_TOOL_NAME | |
| logger = logging.getLogger("headroom.ccr.mcp") | |
| # Default proxy URL (can be overridden via env or args) | |
| DEFAULT_PROXY_URL = os.environ.get("HEADROOM_PROXY_URL", "http://127.0.0.1:8787") | |
| class CCRMCPServer: | |
| """MCP Server that exposes headroom_retrieve tool. | |
| This server can operate in two modes: | |
| 1. HTTP mode: Calls the proxy's /v1/retrieve endpoint (default) | |
| 2. Direct mode: Uses CompressionStore directly (same process) | |
| HTTP mode is recommended as it ensures consistency with the proxy. | |
| """ | |
| def __init__( | |
| self, | |
| proxy_url: str = DEFAULT_PROXY_URL, | |
| direct_mode: bool = False, | |
| ): | |
| """Initialize CCR MCP Server. | |
| Args: | |
| proxy_url: URL of the Headroom proxy server. | |
| direct_mode: If True, access CompressionStore directly instead of via HTTP. | |
| """ | |
| self.proxy_url = proxy_url | |
| self.direct_mode = direct_mode | |
| self._http_client: httpx.AsyncClient | None = None | |
| if not MCP_AVAILABLE: | |
| raise ImportError("MCP SDK not installed. Install with: pip install mcp") | |
| if not direct_mode and not HTTPX_AVAILABLE: | |
| raise ImportError( | |
| "httpx not installed (required for HTTP mode). Install with: pip install httpx" | |
| ) | |
| self.server = Server("headroom-ccr") | |
| self._setup_handlers() | |
| def _setup_handlers(self): | |
| """Set up MCP tool handlers.""" | |
| async def list_tools() -> list[Tool]: | |
| """Return available tools.""" | |
| return [ | |
| Tool( | |
| name=CCR_TOOL_NAME, | |
| description=( | |
| "Retrieve original uncompressed content that was compressed " | |
| "to save tokens. Use this when you need more data than what's " | |
| "shown in compressed tool results. The hash is provided in " | |
| "compression markers like [N items compressed... hash=abc123]." | |
| ), | |
| inputSchema={ | |
| "type": "object", | |
| "properties": { | |
| "hash": { | |
| "type": "string", | |
| "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", | |
| }, | |
| "query": { | |
| "type": "string", | |
| "description": ( | |
| "Optional search query to filter results. " | |
| "If provided, only returns items matching the query. " | |
| "If omitted, returns all original items." | |
| ), | |
| }, | |
| }, | |
| "required": ["hash"], | |
| }, | |
| ) | |
| ] | |
| async def call_tool(name: str, arguments: dict[str, Any]) -> list[TextContent]: | |
| """Handle tool calls.""" | |
| if name != CCR_TOOL_NAME: | |
| return [ | |
| TextContent( | |
| type="text", | |
| text=json.dumps({"error": f"Unknown tool: {name}"}), | |
| ) | |
| ] | |
| hash_key = arguments.get("hash") | |
| query = arguments.get("query") | |
| if not hash_key: | |
| return [ | |
| TextContent( | |
| type="text", | |
| text=json.dumps({"error": "hash parameter is required"}), | |
| ) | |
| ] | |
| # Retrieve content | |
| try: | |
| if self.direct_mode: | |
| result = await self._retrieve_direct(hash_key, query) | |
| else: | |
| result = await self._retrieve_via_proxy(hash_key, query) | |
| return [ | |
| TextContent( | |
| type="text", | |
| text=json.dumps(result, indent=2), | |
| ) | |
| ] | |
| except Exception as e: | |
| logger.error(f"Retrieval failed: {e}") | |
| return [ | |
| TextContent( | |
| type="text", | |
| text=json.dumps({"error": str(e)}), | |
| ) | |
| ] | |
| async def _retrieve_via_proxy( | |
| self, | |
| hash_key: str, | |
| query: str | None, | |
| ) -> dict[str, Any]: | |
| """Retrieve content via proxy's HTTP endpoint.""" | |
| if self._http_client is None: | |
| self._http_client = httpx.AsyncClient(timeout=30.0) | |
| url = f"{self.proxy_url}/v1/retrieve" | |
| payload = {"hash": hash_key} | |
| if query: | |
| payload["query"] = query | |
| response = await self._http_client.post(url, json=payload) | |
| if response.status_code == 404: | |
| return { | |
| "error": "Entry not found or expired (TTL: 5 minutes)", | |
| "hash": hash_key, | |
| } | |
| response.raise_for_status() | |
| result: dict[str, Any] = response.json() | |
| return result | |
| async def _retrieve_direct( | |
| self, | |
| hash_key: str, | |
| query: str | None, | |
| ) -> dict[str, Any]: | |
| """Retrieve content directly from CompressionStore.""" | |
| from headroom.cache.compression_store import get_compression_store | |
| store = get_compression_store() | |
| if query: | |
| results = store.search(hash_key, query) | |
| return { | |
| "hash": hash_key, | |
| "query": query, | |
| "results": results, | |
| "count": len(results), | |
| } | |
| else: | |
| entry = store.retrieve(hash_key) | |
| if entry: | |
| return { | |
| "hash": hash_key, | |
| "original_content": entry.original_content, | |
| "original_item_count": entry.original_item_count, | |
| "compressed_item_count": entry.compressed_item_count, | |
| "retrieval_count": entry.retrieval_count, | |
| } | |
| return { | |
| "error": "Entry not found or expired (TTL: 5 minutes)", | |
| "hash": hash_key, | |
| } | |
| async def run_stdio(self): | |
| """Run the server with stdio transport.""" | |
| async with stdio_server() as (read_stream, write_stream): | |
| logger.info(f"CCR MCP Server starting (proxy: {self.proxy_url})") | |
| await self.server.run( | |
| read_stream, | |
| write_stream, | |
| self.server.create_initialization_options(), | |
| ) | |
| async def cleanup(self): | |
| """Clean up resources.""" | |
| if self._http_client: | |
| await self._http_client.aclose() | |
| def create_ccr_mcp_server( | |
| proxy_url: str = DEFAULT_PROXY_URL, | |
| direct_mode: bool = False, | |
| ) -> CCRMCPServer: | |
| """Create a CCR MCP server instance. | |
| Args: | |
| proxy_url: URL of the Headroom proxy server. | |
| direct_mode: If True, access CompressionStore directly. | |
| Returns: | |
| CCRMCPServer instance. | |
| Example: | |
| ```python | |
| server = create_ccr_mcp_server() | |
| await server.run_stdio() | |
| ``` | |
| """ | |
| return CCRMCPServer(proxy_url=proxy_url, direct_mode=direct_mode) | |
| async def main(): | |
| """Run the CCR MCP server.""" | |
| parser = argparse.ArgumentParser( | |
| description="CCR MCP Server - Retrieve compressed content via MCP" | |
| ) | |
| parser.add_argument( | |
| "--proxy-url", | |
| default=DEFAULT_PROXY_URL, | |
| help=f"Headroom proxy URL (default: {DEFAULT_PROXY_URL})", | |
| ) | |
| parser.add_argument( | |
| "--direct", | |
| action="store_true", | |
| help="Use direct CompressionStore access instead of HTTP", | |
| ) | |
| parser.add_argument( | |
| "--debug", | |
| action="store_true", | |
| help="Enable debug logging", | |
| ) | |
| args = parser.parse_args() | |
| if args.debug: | |
| logging.basicConfig(level=logging.DEBUG) | |
| else: | |
| logging.basicConfig(level=logging.INFO) | |
| server = create_ccr_mcp_server( | |
| proxy_url=args.proxy_url, | |
| direct_mode=args.direct, | |
| ) | |
| try: | |
| await server.run_stdio() | |
| finally: | |
| await server.cleanup() | |
| if __name__ == "__main__": | |
| asyncio.run(main()) | |