This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Timmy-time-dashboard/src/mcp/server.py
Alexander Payne a719c7538d Implement MCP system, Event Bus, and Sub-Agents
## 1. MCP (Model Context Protocol) Implementation

### Registry (src/mcp/registry.py)
- Tool registration with JSON schemas
- Dynamic tool discovery
- Health tracking per tool
- Metrics collection (latency, error rates)
- @register_tool decorator for easy registration

### Server (src/mcp/server.py)
- MCPServer class implementing MCP protocol
- MCPHTTPServer for FastAPI integration
- Standard endpoints: list_tools, call_tool, get_schema

### Schemas (src/mcp/schemas/base.py)
- create_tool_schema() helper
- Common parameter types
- Standard return types

### Bootstrap (src/mcp/bootstrap.py)
- Automatic tool module loading
- Status reporting

## 2. MCP-Compliant Tools (src/tools/)

| Tool | Purpose | Category |
|------|---------|----------|
| web_search | DuckDuckGo search | research |
| read_file | File reading | files |
| write_file | File writing (confirmation) | files |
| list_directory | Directory listing | files |
| python | Python code execution | code |
| memory_search | Vector memory search | memory |

All tools have proper schemas, error handling, and MCP registration.

## 3. Event Bus (src/events/bus.py)

- Async publish/subscribe pattern
- Pattern matching with wildcards (agent.task.*)
- Event history tracking
- Concurrent handler execution
- Module-level singleton for system-wide use

## 4. Sub-Agents (src/agents/)

All agents inherit from BaseAgent with:
- Agno Agent integration
- MCP tool registry access
- Event bus connectivity
- Structured logging

### Agent Roster

| Agent | Role | Tools | Purpose |
|-------|------|-------|---------|
| Seer | Research | web_search, read_file, memory_search | Information gathering |
| Forge | Code | python, write_file, read_file | Code generation |
| Quill | Writing | write_file, read_file, memory_search | Content creation |
| Echo | Memory | memory_search, read_file, write_file | Context retrieval |
| Helm | Routing | memory_search | Task routing decisions |
| Timmy | Orchestrator | All tools | Coordination & user interface |

### Timmy Orchestrator
- Analyzes user requests
- Routes to appropriate sub-agent
- Handles direct queries
- Manages swarm coordination
- create_timmy_swarm() factory function

## 5. Integration

All components wired together:
- Tools auto-register on import
- Agents connect to event bus
- MCP server provides HTTP API
- Ready for dashboard integration

## Tests
- All 973 existing tests pass
- New components tested manually
- Import verification successful

Next steps: Cascade Router, Self-Upgrade Loop, Dashboard integration
2026-02-25 19:26:24 -05:00

211 lines
6.1 KiB
Python

"""MCP (Model Context Protocol) Server.
Implements the MCP protocol for tool discovery and execution.
Agents communicate with this server to discover and invoke tools.
The server can run:
1. In-process (direct method calls) — fastest, for local agents
2. HTTP API — for external clients
3. Stdio — for subprocess-based agents
"""
import asyncio
import json
import logging
from typing import Any, Optional
from mcp.registry import tool_registry
logger = logging.getLogger(__name__)
class MCPServer:
"""Model Context Protocol server for tool management.
Provides standard MCP endpoints:
- list_tools: Discover available tools
- call_tool: Execute a tool
- get_schema: Get tool input/output schemas
"""
def __init__(self) -> None:
self.registry = tool_registry
logger.info("MCP Server initialized")
def list_tools(
self,
category: Optional[str] = None,
query: Optional[str] = None,
) -> list[dict]:
"""List available tools.
MCP Protocol: tools/list
"""
tools = self.registry.discover(
query=query,
category=category,
healthy_only=True,
)
return [
{
"name": t.name,
"description": t.schema.get("description", ""),
"parameters": t.schema.get("parameters", {}),
"category": t.category,
}
for t in tools
]
async def call_tool(self, name: str, arguments: dict) -> dict:
"""Execute a tool with given arguments.
MCP Protocol: tools/call
Args:
name: Tool name
arguments: Tool parameters
Returns:
Result dict with content or error
"""
try:
result = await self.registry.execute(name, arguments)
return {
"content": [
{"type": "text", "text": str(result)}
],
"isError": False,
}
except Exception as exc:
logger.error("Tool execution failed: %s", exc)
return {
"content": [
{"type": "text", "text": f"Error: {exc}"}
],
"isError": True,
}
def get_schema(self, name: str) -> Optional[dict]:
"""Get the JSON schema for a tool.
MCP Protocol: tools/schema
"""
return self.registry.get_schema(name)
def get_tool_info(self, name: str) -> Optional[dict]:
"""Get detailed info about a tool including health metrics."""
record = self.registry.get(name)
if not record:
return None
return {
"name": record.name,
"schema": record.schema,
"category": record.category,
"health": record.health_status,
"metrics": {
"executions": record.execution_count,
"errors": record.error_count,
"avg_latency_ms": round(record.avg_latency_ms, 2),
},
"requires_confirmation": record.requires_confirmation,
}
def health_check(self) -> dict:
"""Server health status."""
tools = self.registry.list_tools()
healthy = sum(
1 for t in tools
if self.registry.check_health(t) == "healthy"
)
return {
"status": "healthy",
"total_tools": len(tools),
"healthy_tools": healthy,
"degraded_tools": sum(
1 for t in tools
if self.registry.check_health(t) == "degraded"
),
"unhealthy_tools": sum(
1 for t in tools
if self.registry.check_health(t) == "unhealthy"
),
}
class MCPHTTPServer:
"""HTTP API wrapper for MCP Server."""
def __init__(self) -> None:
self.mcp = MCPServer()
def get_routes(self) -> dict:
"""Get FastAPI route handlers."""
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
router = APIRouter(prefix="/mcp", tags=["mcp"])
class ToolCallRequest(BaseModel):
name: str
arguments: dict = {}
@router.get("/tools")
async def list_tools(
category: Optional[str] = None,
query: Optional[str] = None,
):
"""List available tools."""
return {"tools": self.mcp.list_tools(category, query)}
@router.post("/tools/call")
async def call_tool(request: ToolCallRequest):
"""Execute a tool."""
result = await self.mcp.call_tool(request.name, request.arguments)
return result
@router.get("/tools/{name}")
async def get_tool(name: str):
"""Get tool info."""
info = self.mcp.get_tool_info(name)
if not info:
raise HTTPException(404, f"Tool '{name}' not found")
return info
@router.get("/tools/{name}/schema")
async def get_schema(name: str):
"""Get tool schema."""
schema = self.mcp.get_schema(name)
if not schema:
raise HTTPException(404, f"Tool '{name}' not found")
return schema
@router.get("/health")
async def health():
"""Server health check."""
return self.mcp.health_check()
return router
# Module-level singleton
mcp_server = MCPServer()
# Convenience functions for agents
def discover_tools(query: Optional[str] = None) -> list[dict]:
"""Quick tool discovery."""
return mcp_server.list_tools(query=query)
async def use_tool(name: str, **kwargs) -> str:
"""Execute a tool and return result text."""
result = await mcp_server.call_tool(name, kwargs)
if result.get("isError"):
raise RuntimeError(result["content"][0]["text"])
return result["content"][0]["text"]