initial mcp server setup
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user