""" MCP server exposing Manual Slop's custom tools (mcp_client.py) to Claude Code. All 26 tools from mcp_client.MCP_TOOL_SPECS are served, plus run_powershell. Delegates to mcp_client.dispatch() for all tools except run_powershell, which routes through shell_runner.run_powershell() directly. Usage (in .claude/settings.json mcpServers): "command": "uv", "args": ["run", "python", "scripts/mcp_server.py"] """ import asyncio import os import sys # Add project root to sys.path sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) import mcp_client import shell_runner from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent # run_powershell is handled by shell_runner, not mcp_client.dispatch() # Define its spec here since it's not in MCP_TOOL_SPECS RUN_POWERSHELL_SPEC = { "name": "run_powershell", "description": ( "Run a PowerShell script within the project base directory. " "Returns combined stdout, stderr, and exit code. " "60-second timeout. Use for builds, tests, and system commands." ), "parameters": { "type": "object", "properties": { "script": { "type": "string", "description": "PowerShell script content to execute." } }, "required": ["script"] } } server = Server("manual-slop-tools") @server.list_tools() async def list_tools() -> list[Tool]: tools = [] for spec in mcp_client.MCP_TOOL_SPECS: tools.append(Tool( name=spec["name"], description=spec["description"], inputSchema=spec["parameters"], )) # Add run_powershell tools.append(Tool( name=RUN_POWERSHELL_SPEC["name"], description=RUN_POWERSHELL_SPEC["description"], inputSchema=RUN_POWERSHELL_SPEC["parameters"], )) return tools @server.call_tool() async def call_tool(name: str, arguments: dict) -> list[TextContent]: try: if name == "run_powershell": script = arguments.get("script", "") result = shell_runner.run_powershell(script, os.getcwd()) else: result = mcp_client.dispatch(name, arguments) return [TextContent(type="text", text=str(result))] except Exception as e: return [TextContent(type="text", text=f"ERROR: {e}")] async def main() -> None: # Configure mcp_client with the project root so py_* tools are not ACCESS DENIED project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) mcp_client.configure([], extra_base_dirs=[project_root]) async with stdio_server() as (read_stream, write_stream): await server.run( read_stream, write_stream, server.create_initialization_options(), ) if __name__ == "__main__": asyncio.run(main())