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,5 @@
# MCP Server implementations
from .modular_server import ModularMCPServer
from .server_factory import MCPServerFactory
__all__ = ['ModularMCPServer', 'MCPServerFactory']
@@ -0,0 +1,99 @@
"""
Modular MCP Server Implementation
"""
from mcp.server.fastmcp import FastMCP
from typing import Optional, List
from .tools.tool_registry import ServerToolRegistry
from .prompts.prompt_registry import ServerPromptRegistry
from .resources.resource_registry import ServerResourceRegistry
class ModularMCPServer:
"""Modular MCP Server that automatically discovers and registers tools, prompts, and resources"""
def __init__(
self,
name: str,
host: str = "0.0.0.0",
port: int = 8050,
stateless_http: bool = True,
tools_directory: Optional[str] = None,
prompts_directory: Optional[str] = None,
resources_directory: Optional[str] = None
):
self.name = name
self.host = host
self.port = port
self.stateless_http = stateless_http
# Initialize registries
self.tool_registry = ServerToolRegistry(tools_directory)
self.prompt_registry = ServerPromptRegistry(prompts_directory)
self.resource_registry = ServerResourceRegistry(resources_directory)
# Create FastMCP server
self.mcp = FastMCP(
name=name,
host=host,
port=port,
stateless_http=stateless_http
)
self._initialized = False
async def initialize(self) -> None:
"""Initialize the server and register all components"""
if self._initialized:
return
print(f"Initializing {self.name} server...")
# Discover and register tools
print("Discovering tools...")
self.tool_registry.register_tools_with_server(self.mcp)
tool_count = len(self.tool_registry.get_all_tools())
print(f"Registered {tool_count} tools")
# Discover and register prompts
print("Discovering prompts...")
self.prompt_registry.register_prompts_with_server(self.mcp)
prompt_count = len(self.prompt_registry.get_all_prompts())
print(f"Registered {prompt_count} prompts")
# Discover and register resources
print("Discovering resources...")
self.resource_registry.register_resources_with_server(self.mcp)
resource_count = len(self.resource_registry.get_all_resources())
print(f"Registered {resource_count} resources")
self._initialized = True
print(f"Server initialization complete!")
def run(self, transport: str = "stdio") -> None:
"""Run the server with the specified transport"""
if not self._initialized:
import asyncio
asyncio.run(self.initialize())
print(f"Starting {self.name} server with {transport} transport...")
self.mcp.run(transport=transport)
def get_server_info(self) -> dict:
"""Get information about the server and its components"""
return {
"name": self.name,
"host": self.host,
"port": self.port,
"tools": {
"count": len(self.tool_registry.get_all_tools()),
"names": self.tool_registry.get_tool_names()
},
"prompts": {
"count": len(self.prompt_registry.get_all_prompts()),
"names": self.prompt_registry.get_prompt_names()
},
"resources": {
"count": len(self.resource_registry.get_all_resources()),
"uris": self.resource_registry.get_resource_uris()
}
}
@@ -0,0 +1,7 @@
"""
Server Prompts Module
"""
from .base_prompt import BaseServerPrompt
from .prompt_registry import ServerPromptRegistry
__all__ = ['BaseServerPrompt', 'ServerPromptRegistry']
@@ -0,0 +1,51 @@
"""
Base Server Prompt Class
"""
from abc import ABC, abstractmethod
from typing import Any, Dict, Optional
class BaseServerPrompt(ABC):
"""Base class for server prompts that can be registered with FastMCP"""
def __init__(self, name: str, description: str, template: str, arguments: Optional[Dict[str, Any]] = None):
self.name = name
self.description = description
self.template = template
self.arguments = arguments or {}
@abstractmethod
async def generate(self, **kwargs) -> str:
"""Generate the prompt with the provided arguments"""
pass
def get_prompt_definition(self) -> Dict[str, Any]:
"""Get the prompt definition for FastMCP registration"""
return {
"name": self.name,
"description": self.description,
"arguments": self.arguments
}
def create_fastmcp_prompt(self, mcp_server):
"""Create a FastMCP prompt decorator for this prompt"""
@mcp_server.prompt()
async def prompt_wrapper(**kwargs):
return await self.generate(**kwargs)
# Set metadata
prompt_wrapper.__name__ = self.name
prompt_wrapper.__doc__ = self.description
return prompt_wrapper
def _substitute_template(self, **kwargs) -> str:
"""Helper method to substitute variables in template"""
result = self.template
# Apply provided arguments
for key, value in kwargs.items():
placeholder = f"{{{key}}}"
result = result.replace(placeholder, str(value))
return result
@@ -0,0 +1,44 @@
"""
Greeting Prompt Example
"""
from .base_prompt import BaseServerPrompt
class GreetingPrompt(BaseServerPrompt):
"""A greeting prompt template"""
def __init__(self):
super().__init__(
name="greeting_prompt",
description="Generate a greeting prompt for AI models",
template="Please write a {style} greeting for someone named {name}. The greeting should be {tone} and include a {element}.",
arguments={
"style": {
"type": "string",
"enum": ["friendly", "formal", "casual"],
"description": "Style of greeting",
"default": "friendly"
},
"tone": {
"type": "string",
"enum": ["warm", "professional", "relaxed"],
"description": "Tone of the greeting",
"default": "warm"
},
"element": {
"type": "string",
"enum": ["compliment", "question", "observation"],
"description": "Element to include in greeting",
"default": "compliment"
}
}
)
async def generate(self, name: str, style: str = "friendly", tone: str = "warm", element: str = "compliment") -> str:
"""Generate the greeting prompt"""
return self._substitute_template(
name=name,
style=style,
tone=tone,
element=element
)
@@ -0,0 +1,100 @@
"""
Server Prompt Registry for dynamic prompt registration
"""
import os
import importlib
import inspect
from typing import List, Dict, Any, Type, Optional
from pathlib import Path
from .base_prompt import BaseServerPrompt
class ServerPromptRegistry:
"""Registry for managing server prompts from files"""
def __init__(self, prompts_directory: str = None):
self.prompts_directory = prompts_directory or os.path.dirname(__file__)
self._registered_prompts: Dict[str, BaseServerPrompt] = {}
self._prompt_files: List[str] = []
@property
def directory(self):
"""Alias for prompts_directory for backward compatibility"""
return self.prompts_directory
@property
def _prompts(self):
"""Alias for _registered_prompts for backward compatibility"""
return self._registered_prompts
def discover_prompts(self) -> List[BaseServerPrompt]:
"""Discover all prompts in the prompts directory"""
prompts = []
prompts_dir = Path(self.prompts_directory)
if not prompts_dir.exists():
return prompts
# Find all Python files in the prompts directory
for file_path in prompts_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 BaseServerPrompt subclasses in the module
for name, obj in inspect.getmembers(module, inspect.isclass):
if (issubclass(obj, BaseServerPrompt) and
obj != BaseServerPrompt and
not inspect.isabstract(obj)):
try:
prompt_instance = obj()
prompts.append(prompt_instance)
self._registered_prompts[prompt_instance.name] = prompt_instance
except Exception as e:
print(f"Warning: Could not instantiate prompt {name}: {e}")
except Exception as e:
print(f"Warning: Could not load prompt file {file_path}: {e}")
return prompts
def register_prompt(self, prompt: BaseServerPrompt) -> None:
"""Register a single prompt"""
self._registered_prompts[prompt.name] = prompt
def register_prompts(self, prompts: List[BaseServerPrompt]) -> None:
"""Register multiple prompts"""
for prompt in prompts:
self.register_prompt(prompt)
def get_prompt(self, name: str) -> Optional[BaseServerPrompt]:
"""Get a prompt by name"""
return self._registered_prompts.get(name)
def get_all_prompts(self) -> List[BaseServerPrompt]:
"""Get all registered prompts"""
return list(self._registered_prompts.values())
def get_prompt_names(self) -> List[str]:
"""Get all registered prompt names"""
return list(self._registered_prompts.keys())
def register_prompts_with_server(self, mcp_server) -> None:
"""Register all discovered prompts with a FastMCP server"""
# First discover prompts if not already done
if not self._registered_prompts:
self.discover_prompts()
# Register each prompt with the server
for prompt in self._registered_prompts.values():
prompt.create_fastmcp_prompt(mcp_server)
def clear_prompts(self) -> None:
"""Clear all registered prompts"""
self._registered_prompts.clear()
@@ -0,0 +1,7 @@
"""
Server Resources Module
"""
from .base_resource import BaseServerResource
from .resource_registry import ServerResourceRegistry
__all__ = ['BaseServerResource', 'ServerResourceRegistry']
@@ -0,0 +1,41 @@
"""
Base Server Resource Class
"""
from abc import ABC, abstractmethod
from typing import Any, Dict, Optional, Union
class BaseServerResource(ABC):
"""Base class for server resources that can be registered with FastMCP"""
def __init__(self, uri: str, name: str, description: str, mime_type: str = "text/plain"):
self.uri = uri
self.name = name
self.description = description
self.mime_type = mime_type
@abstractmethod
async def get_content(self, **kwargs) -> Union[str, bytes]:
"""Get the resource content with the provided arguments"""
pass
def get_resource_definition(self) -> Dict[str, Any]:
"""Get the resource definition for FastMCP registration"""
return {
"uri": self.uri,
"name": self.name,
"description": self.description,
"mime_type": self.mime_type
}
def create_fastmcp_resource(self, mcp_server):
"""Create a FastMCP resource decorator for this resource"""
@mcp_server.resource(self.uri)
async def resource_wrapper(**kwargs):
return await self.get_content(**kwargs)
# Set metadata
resource_wrapper.__name__ = self.name
resource_wrapper.__doc__ = self.description
return resource_wrapper
@@ -0,0 +1,37 @@
"""
Configuration Resource Example
"""
from .base_resource import BaseServerResource
class ConfigResource(BaseServerResource):
"""A configuration resource that provides server settings"""
def __init__(self):
super().__init__(
uri="config://settings",
name="Server Configuration",
description="Get the current server configuration settings",
mime_type="application/json"
)
async def get_content(self) -> str:
"""Get the configuration content"""
import json
config = {
"server_name": "MCP Template Server",
"version": "1.0.0",
"features": [
"tools",
"prompts",
"resources"
],
"transport": {
"supported": ["stdio", "sse"],
"default": "stdio"
},
"tools_count": 2,
"prompts_count": 1,
"resources_count": 1
}
return json.dumps(config, indent=2)
@@ -0,0 +1,28 @@
"""
Dynamic Resource Example
"""
from .base_resource import BaseServerResource
class DynamicResource(BaseServerResource):
"""A dynamic resource that accepts parameters"""
def __init__(self):
super().__init__(
uri="dynamic://greeting/{name}",
name="Dynamic Greeting",
description="Get a personalized greeting resource",
mime_type="text/plain"
)
async def get_content(self, name: str) -> str:
"""Get the dynamic greeting content"""
return f"Hello, {name}! This is a dynamic resource that was generated just for you.\n\n" \
f"Resource URI: dynamic://greeting/{name}\n" \
f"Generated at: {self._get_timestamp()}\n" \
f"Personalized for: {name}"
def _get_timestamp(self) -> str:
"""Get current timestamp"""
from datetime import datetime
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@@ -0,0 +1,100 @@
"""
Server Resource Registry for dynamic resource registration
"""
import os
import importlib
import inspect
from typing import List, Dict, Any, Type,Optional
from pathlib import Path
from .base_resource import BaseServerResource
class ServerResourceRegistry:
"""Registry for managing server resources from files"""
def __init__(self, resources_directory: str = None):
self.resources_directory = resources_directory or os.path.dirname(__file__)
self._registered_resources: Dict[str, BaseServerResource] = {}
self._resource_files: List[str] = []
@property
def directory(self):
"""Alias for resources_directory for backward compatibility"""
return self.resources_directory
@property
def _resources(self):
"""Alias for _registered_resources for backward compatibility"""
return self._registered_resources
def discover_resources(self) -> List[BaseServerResource]:
"""Discover all resources in the resources directory"""
resources = []
resources_dir = Path(self.resources_directory)
if not resources_dir.exists():
return resources
# Find all Python files in the resources directory
for file_path in resources_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 BaseServerResource subclasses in the module
for name, obj in inspect.getmembers(module, inspect.isclass):
if (issubclass(obj, BaseServerResource) and
obj != BaseServerResource and
not inspect.isabstract(obj)):
try:
resource_instance = obj()
resources.append(resource_instance)
self._registered_resources[resource_instance.uri] = resource_instance
except Exception as e:
print(f"Warning: Could not instantiate resource {name}: {e}")
except Exception as e:
print(f"Warning: Could not load resource file {file_path}: {e}")
return resources
def register_resource(self, resource: BaseServerResource) -> None:
"""Register a single resource"""
self._registered_resources[resource.uri] = resource
def register_resources(self, resources: List[BaseServerResource]) -> None:
"""Register multiple resources"""
for resource in resources:
self.register_resource(resource)
def get_resource(self, uri: str) -> Optional[BaseServerResource]:
"""Get a resource by URI"""
return self._registered_resources.get(uri)
def get_all_resources(self) -> List[BaseServerResource]:
"""Get all registered resources"""
return list(self._registered_resources.values())
def get_resource_uris(self) -> List[str]:
"""Get all registered resource URIs"""
return list(self._registered_resources.keys())
def register_resources_with_server(self, mcp_server) -> None:
"""Register all discovered resources with a FastMCP server"""
# First discover resources if not already done
if not self._registered_resources:
self.discover_resources()
# Register each resource with the server
for resource in self._registered_resources.values():
resource.create_fastmcp_resource(mcp_server)
def clear_resources(self) -> None:
"""Clear all registered resources"""
self._registered_resources.clear()
@@ -0,0 +1,258 @@
"""
MCP Server Factory for easy server creation and configuration
"""
from typing import List, Optional, Union
from ..core.types import MCPServerConfig, MCPTool, MCPResource, MCPPrompt, TransportType
from .modular_server import ModularMCPServer
from ..core.interfaces import IMCPServer
from ..tools.tool_registry import ToolRegistry
class MCPServerFactory:
"""Factory class for creating MCP servers with different configurations"""
@staticmethod
def create_server(
name: str,
version: str = "1.0.0",
transport: Union[str, TransportType] = TransportType.STDIO,
host: str = "0.0.0.0",
port: int = 8050,
stateless_http: bool = True,
tools: Optional[List[MCPTool]] = None,
resources: Optional[List[MCPResource]] = None,
prompts: Optional[List[MCPPrompt]] = None,
) -> IMCPServer:
"""Create an MCP server with the specified configuration"""
# Convert string transport to enum if needed
if isinstance(transport, str):
transport = TransportType(transport.lower())
config = MCPServerConfig(
name=name,
version=version,
transport=transport,
host=host,
port=port,
stateless_http=stateless_http,
tools=tools or [],
resources=resources or [],
prompts=prompts or [],
)
return ModularMCPServer(
name=config.name,
host=config.host,
port=config.port,
stateless_http=config.stateless_http
)
@staticmethod
def create_basic_calculator_server(
name: str = "CalculatorServer",
transport: Union[str, TransportType] = TransportType.STDIO,
host: str = "0.0.0.0",
port: int = 8050,
) -> IMCPServer:
"""Create a basic calculator server with common math tools"""
async def add(a: float, b: float) -> float:
"""Add two numbers together"""
return a + b
async def subtract(a: float, b: float) -> float:
"""Subtract b from a"""
return a - b
async def multiply(a: float, b: float) -> float:
"""Multiply two numbers"""
return a * b
async def divide(a: float, b: float) -> float:
"""Divide a by b"""
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
tools = [
MCPTool(
name="add",
description="Add two numbers together",
input_schema={
"type": "object",
"properties": {
"a": {"type": "number", "description": "First number"},
"b": {"type": "number", "description": "Second number"},
},
"required": ["a", "b"],
},
handler=add,
),
MCPTool(
name="subtract",
description="Subtract second number from first",
input_schema={
"type": "object",
"properties": {
"a": {"type": "number", "description": "First number"},
"b": {"type": "number", "description": "Number to subtract"},
},
"required": ["a", "b"],
},
handler=subtract,
),
MCPTool(
name="multiply",
description="Multiply two numbers",
input_schema={
"type": "object",
"properties": {
"a": {"type": "number", "description": "First number"},
"b": {"type": "number", "description": "Second number"},
},
"required": ["a", "b"],
},
handler=multiply,
),
MCPTool(
name="divide",
description="Divide first number by second",
input_schema={
"type": "object",
"properties": {
"a": {"type": "number", "description": "Dividend"},
"b": {"type": "number", "description": "Divisor (cannot be zero)"},
},
"required": ["a", "b"],
},
handler=divide,
),
]
return MCPServerFactory.create_server(
name=name,
transport=transport,
host=host,
port=port,
tools=tools,
)
@staticmethod
def create_knowledge_base_server(
name: str = "KnowledgeBaseServer",
transport: Union[str, TransportType] = TransportType.STDIO,
host: str = "0.0.0.0",
port: int = 8050,
kb_data: Optional[dict] = None,
) -> IMCPServer:
"""Create a knowledge base server with configurable data"""
if kb_data is None:
kb_data = {
"company_policy": "Default company policy information...",
"faq": "Frequently asked questions and answers...",
}
async def get_knowledge_base() -> str:
"""Retrieve the entire knowledge base as formatted string"""
formatted = "Knowledge Base:\n\n"
for key, value in kb_data.items():
formatted += f"**{key.replace('_', ' ').title()}:**\n{value}\n\n"
return formatted
async def search_kb(query: str) -> str:
"""Search the knowledge base for relevant information"""
query_lower = query.lower()
results = []
for key, value in kb_data.items():
if query_lower in key.lower() or query_lower in value.lower():
results.append(f"**{key.replace('_', ' ').title()}:**\n{value}")
if not results:
return f"No information found for query: {query}"
return "\n\n".join(results)
tools = [
MCPTool(
name="get_knowledge_base",
description="Retrieve the entire knowledge base as a formatted string",
input_schema={"type": "object", "properties": {}},
handler=get_knowledge_base,
),
MCPTool(
name="search_kb",
description="Search the knowledge base for relevant information",
input_schema={
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"},
},
"required": ["query"],
},
handler=search_kb,
),
]
return MCPServerFactory.create_server(
name=name,
transport=transport,
host=host,
port=port,
tools=tools,
)
@staticmethod
def create_server_from_categories(
name: str,
categories: List[str],
transport: Union[str, TransportType] = TransportType.STDIO,
host: str = "0.0.0.0",
port: int = 8050,
custom_tools: Optional[List[MCPTool]] = None,
resources: Optional[List[MCPResource]] = None,
prompts: Optional[List[MCPPrompt]] = None,
) -> IMCPServer:
"""Create a server using tool categories"""
# Get tools from categories
registry = ToolRegistry()
category_tools = registry.get_tools_by_categories(categories)
# Combine with custom tools
all_tools = category_tools
if custom_tools:
all_tools.extend(custom_tools)
return MCPServerFactory.create_server(
name=name,
transport=transport,
host=host,
port=port,
tools=all_tools,
resources=resources,
prompts=prompts,
)
@staticmethod
def create_modular_server(
name: str = "ModularServer",
host: str = "0.0.0.0",
port: int = 8050,
stateless_http: bool = True,
tools_directory: Optional[str] = None,
prompts_directory: Optional[str] = None,
resources_directory: Optional[str] = None,
) -> ModularMCPServer:
"""Create a modular server that auto-discovers tools, prompts, and resources"""
return ModularMCPServer(
name=name,
host=host,
port=port,
stateless_http=stateless_http,
tools_directory=tools_directory,
prompts_directory=prompts_directory,
resources_directory=resources_directory,
)
@@ -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()