initial mcp server setup

This commit is contained in:
OwusuBlessing
2025-09-11 23:13:58 +01:00
commit 20f96c0f30
141 changed files with 14444 additions and 0 deletions
@@ -0,0 +1,7 @@
"""
Server Tools Module
"""
from .base_tool import BaseServerTool
from .tool_registry import ServerToolRegistry
__all__ = ['BaseServerTool', 'ServerToolRegistry']
@@ -0,0 +1,67 @@
"""
Base Server Tool Class
"""
from abc import ABC, abstractmethod
from typing import Any, Dict, Optional
from mcp.server.fastmcp import Context
from mcp.server.session import ServerSession
class BaseServerTool(ABC):
"""Base class for server tools that can be registered with FastMCP"""
def __init__(self, name: str, description: str, input_schema: Dict[str, Any]):
self.name = name
self.description = description
self.input_schema = input_schema
@abstractmethod
async def execute(self, **kwargs) -> Any:
"""Execute the tool with the provided arguments"""
pass
def get_tool_definition(self) -> Dict[str, Any]:
"""Get the tool definition for FastMCP registration"""
return {
"name": self.name,
"description": self.description,
"input_schema": self.input_schema
}
def create_fastmcp_tool(self, mcp_server):
"""Create a FastMCP tool decorator for this tool"""
@mcp_server.tool()
async def tool_wrapper(**kwargs):
return await self.execute(**kwargs)
# Set metadata
tool_wrapper.__name__ = self.name
tool_wrapper.__doc__ = self.description
return tool_wrapper
class ContextAwareTool(BaseServerTool):
"""Base class for tools that need access to MCP context"""
@abstractmethod
async def execute_with_context(self, ctx: Context[ServerSession, None], **kwargs) -> Any:
"""Execute the tool with MCP context"""
pass
async def execute(self, **kwargs) -> Any:
"""Default implementation that doesn't use context"""
# This will be overridden by the FastMCP wrapper
raise NotImplementedError("Use execute_with_context for context-aware tools")
def create_fastmcp_tool(self, mcp_server):
"""Create a FastMCP tool decorator for this context-aware tool"""
@mcp_server.tool()
async def tool_wrapper(ctx: Context[ServerSession, None], **kwargs):
return await self.execute_with_context(ctx, **kwargs)
# Set metadata
tool_wrapper.__name__ = self.name
tool_wrapper.__doc__ = self.description
return tool_wrapper
@@ -0,0 +1,48 @@
"""
Calculator Tool Example
"""
from .base_tool import BaseServerTool
class CalculatorTool(BaseServerTool):
"""A simple calculator tool"""
def __init__(self):
super().__init__(
name="calculator",
description="Perform basic mathematical operations",
input_schema={
"type": "object",
"properties": {
"operation": {
"type": "string",
"enum": ["add", "subtract", "multiply", "divide"],
"description": "The mathematical operation to perform"
},
"a": {
"type": "number",
"description": "First number"
},
"b": {
"type": "number",
"description": "Second number"
}
},
"required": ["operation", "a", "b"]
}
)
async def execute(self, operation: str, a: float, b: float) -> float:
"""Execute the calculator operation"""
if operation == "add":
return a + b
elif operation == "subtract":
return a - b
elif operation == "multiply":
return a * b
elif operation == "divide":
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
else:
raise ValueError(f"Unknown operation: {operation}")
@@ -0,0 +1,54 @@
"""
Greeting Tool Example
"""
from .base_tool import ContextAwareTool
from mcp.server.fastmcp import Context
from mcp.server.session import ServerSession
class GreetingTool(ContextAwareTool):
"""A greeting tool that uses MCP context"""
def __init__(self):
super().__init__(
name="greeting",
description="Generate personalized greetings with progress updates",
input_schema={
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name of the person to greet"
},
"style": {
"type": "string",
"enum": ["friendly", "formal", "casual"],
"description": "Style of greeting",
"default": "friendly"
}
},
"required": ["name"]
}
)
async def execute_with_context(self, ctx: Context[ServerSession, None], name: str, style: str = "friendly") -> str:
"""Generate a greeting with progress updates"""
await ctx.info(f"Generating {style} greeting for {name}")
# Simulate some work with progress updates
await ctx.report_progress(progress=0.3, total=1.0, message="Preparing greeting...")
styles = {
"friendly": f"Hello there, {name}! Great to see you!",
"formal": f"Good day, {name}. I hope you are well.",
"casual": f"Hey {name}! What's up?"
}
await ctx.report_progress(progress=0.7, total=1.0, message="Generating message...")
greeting = styles.get(style, styles["friendly"])
await ctx.report_progress(progress=1.0, total=1.0, message="Greeting complete!")
await ctx.debug(f"Generated greeting: {greeting}")
return greeting
@@ -0,0 +1,100 @@
"""
Server Tool Registry for dynamic tool registration
"""
import os
import importlib
import inspect
from typing import List, Dict, Any, Type,Optional
from pathlib import Path
from .base_tool import BaseServerTool
class ServerToolRegistry:
"""Registry for managing server tools from files"""
def __init__(self, tools_directory: str = None):
self.tools_directory = tools_directory or os.path.dirname(__file__)
self._registered_tools: Dict[str, BaseServerTool] = {}
self._tool_files: List[str] = []
@property
def directory(self):
"""Alias for tools_directory for backward compatibility"""
return self.tools_directory
@property
def _tools(self):
"""Alias for _registered_tools for backward compatibility"""
return self._registered_tools
def discover_tools(self) -> List[BaseServerTool]:
"""Discover all tools in the tools directory"""
tools = []
tools_dir = Path(self.tools_directory)
if not tools_dir.exists():
return tools
# Find all Python files in the tools directory
for file_path in tools_dir.glob("*.py"):
if file_path.name.startswith("__"):
continue
module_name = file_path.stem
try:
# Import the module
spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
# Find all BaseServerTool subclasses in the module
for name, obj in inspect.getmembers(module, inspect.isclass):
if (issubclass(obj, BaseServerTool) and
obj != BaseServerTool and
not inspect.isabstract(obj)):
try:
tool_instance = obj()
tools.append(tool_instance)
self._registered_tools[tool_instance.name] = tool_instance
except Exception as e:
print(f"Warning: Could not instantiate tool {name}: {e}")
except Exception as e:
print(f"Warning: Could not load tool file {file_path}: {e}")
return tools
def register_tool(self, tool: BaseServerTool) -> None:
"""Register a single tool"""
self._registered_tools[tool.name] = tool
def register_tools(self, tools: List[BaseServerTool]) -> None:
"""Register multiple tools"""
for tool in tools:
self.register_tool(tool)
def get_tool(self, name: str) -> Optional[BaseServerTool]:
"""Get a tool by name"""
return self._registered_tools.get(name)
def get_all_tools(self) -> List[BaseServerTool]:
"""Get all registered tools"""
return list(self._registered_tools.values())
def get_tool_names(self) -> List[str]:
"""Get all registered tool names"""
return list(self._registered_tools.keys())
def register_tools_with_server(self, mcp_server) -> None:
"""Register all discovered tools with a FastMCP server"""
# First discover tools if not already done
if not self._registered_tools:
self.discover_tools()
# Register each tool with the server
for tool in self._registered_tools.values():
tool.create_fastmcp_tool(mcp_server)
def clear_tools(self) -> None:
"""Clear all registered tools"""
self._registered_tools.clear()