| """MCP Server for Stack 2.9 - Exposes Stack tools via Model Context Protocol""" |
|
|
| import asyncio |
| import os |
| import sys |
| from typing import Any |
|
|
| |
| _project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| if _project_root not in sys.path: |
| sys.path.insert(0, _project_root) |
|
|
| from mcp.server.fastmcp import FastMCP |
|
|
| |
| from src.tools import ( |
| BaseTool, |
| ToolResult, |
| get_registry, |
| file_read, |
| grep_tool, |
| task_management, |
| team_tool, |
| agent_tool, |
| ) |
|
|
|
|
| def _tool_to_mcp(tool: BaseTool) -> dict[str, Any]: |
| """Convert a Stack 2.9 tool to MCP tool schema.""" |
| schema = tool.input_schema |
| if callable(schema): |
| schema = schema() |
| return { |
| "name": tool.name, |
| "description": tool.description, |
| "inputSchema": schema, |
| } |
|
|
|
|
| def _call_tool_sync(tool: BaseTool, arguments: dict[str, Any]) -> Any: |
| """Call a tool and extract result data, handling sync/async execute.""" |
| import inspect |
|
|
| execute = tool.execute |
|
|
| |
| if inspect.iscoroutinefunction(execute): |
| |
| loop = asyncio.get_event_loop() |
| if inspect.iscoroutinefunction(execute): |
| result = loop.run_until_complete(execute(**arguments)) |
| else: |
| result = execute(**arguments) |
| else: |
| |
| if hasattr(tool, 'input_schema') and not callable(tool.input_schema): |
| result = execute(arguments) |
| else: |
| result = execute(**arguments) |
|
|
| if isinstance(result, ToolResult): |
| if result.success: |
| return {"success": True, "data": result.data} |
| else: |
| return {"success": False, "error": result.error} |
| return result |
|
|
|
|
| def _register_tool(mcp: FastMCP, tool: BaseTool) -> None: |
| """Register a single Stack tool as an MCP tool.""" |
| tool_name = tool.name |
| schema = tool.input_schema |
| if callable(schema): |
| schema = schema() |
|
|
| async def handler(arguments: dict[str, Any]) -> dict[str, Any]: |
| return _call_tool_sync(tool, arguments) |
|
|
| mcp.add_tool(handler, name=tool_name, description=tool.description) |
|
|
|
|
| def _register_all_tools(mcp: FastMCP) -> int: |
| """Register all tools from the Stack 2.9 registry.""" |
| registry = get_registry() |
| count = 0 |
| for tool in registry._tools.values(): |
| try: |
| _register_tool(mcp, tool) |
| count += 1 |
| except Exception as e: |
| print(f"Failed to register tool {getattr(tool, 'name', 'unknown')}: {e}") |
| return count |
|
|
|
|
| |
| mcp = FastMCP("Stack2.9") |
|
|
|
|
| def main(): |
| """Main entry point - register tools and run the server.""" |
| |
| from src.tools import ( |
| agent_tool, |
| ask_question, |
| brief_tool, |
| config_tool, |
| file_edit, |
| file_read, |
| file_write, |
| glob_tool, |
| grep_tool, |
| messaging, |
| plan_mode, |
| remote_trigger, |
| scheduling, |
| skill_tool, |
| sleep_tool, |
| synthetic_output, |
| task_get, |
| task_management, |
| team_delete, |
| team_tool, |
| todo_tool, |
| tool_discovery, |
| web_fetch, |
| web_search, |
| worktree_tool, |
| ) |
|
|
| |
| count = _register_all_tools(mcp) |
| print(f"Registered {count} Stack 2.9 tools as MCP tools") |
|
|
| |
| mcp.run() |
|
|
|
|
| if __name__ == "__main__": |
| main() |