This article is about:
- The wrapper pattern used by HatchMCP
- FastMCP integration design
- Automatic resource registration mechanism
- Module detection and URI generation
HatchMCP follows these core principles:
- Minimal Wrapper Pattern: Enhance without changing the core FastMCP experience
- Automatic Citation: Register citation resources transparently
- Scientific Attribution: Standardize citation information exposure
- Backward Compatibility: Work with existing FastMCP servers seamlessly
┌─────────────────────────────────────────────────────────────┐
│ HatchMCP │
├─────────────────────────────────────────────────────────────┤
│ Citation Resources │ Logger Setup │ Module Detection │
│ - origin:// │ - Named logger │ - Stack inspection│
│ - mcp:// │ - Consistent │ - Auto file path │
│ - name:// │ formatting │ - URI generation │
├─────────────────────────────────────────────────────────────┤
│ FastMCP Server │
│ (tools, resources, prompts) │
└─────────────────────────────────────────────────────────────┘
File: hatch_mcp_server/hatch_mcp.py
The HatchMCP class implements a composition pattern:
class HatchMCP():
def __init__(self, name, fast_mcp=None, origin_citation=None, mcp_citation=None):
# Create or wrap FastMCP instance
self.server = fast_mcp or FastMCP(name, log_level="WARNING")
# Store metadata
self.name = name
self._origin_citation = origin_citation or "No origin citation provided."
self._mcp_citation = mcp_citation or "No MCP citation provided."
# Auto-register citation resources
self._register_citation_resources()Design rationale:
- Composition over inheritance: Avoids tight coupling with FastMCP implementation details
- Optional wrapping: Can create new servers or wrap existing ones
- Transparent access:
hatch_mcp.serverprovides full FastMCP functionality
Purpose: Automatically determine the calling module for URI generation.
Implementation:
# Determine the filename of the calling module for citation URIs
try:
frame = inspect.stack()[1] # Get caller's frame
module = inspect.getmodule(frame[0])
if module and module.__file__:
self.module_name = module.__file__[1:] # Remove leading slash
self.logger.info(f"Module name for citation URIs: {self.module_name}")
except Exception:
raise RuntimeError("Unable to determine module name for citation URIs.")Design rationale:
- Automatic detection: No manual configuration required
- URI consistency: Provides consistent naming for citation resources
- Error handling: Fails fast if detection fails
Implementation pattern:
@self.server.resource(
uri=f"citation://origin/{name}",
name="Origin Citation",
description="Citation information for the original tools/algorithms",
mime_type="text/plain"
)
def get_origin_citation() -> str:
return self._origin_citationDesign rationale:
- Standard URI schemes:
citation://origin/,citation://mcp/,name:// - Automatic registration: Happens during initialization
- Closure pattern: Citation data captured in closure scope
- Standard MIME types: Plain text for simplicity
Logger setup:
self.logger = logging.getLogger("hatch_mcp_server.HatchMCP")Design rationale:
- Namespaced logging: Separate from FastMCP and user code
- Consistent naming: All HatchMCP instances use same logger namespace
- Standard Python logging: Integrates with existing logging configurations
- Parameter validation: Check required parameters
- FastMCP setup: Create new or wrap existing server
- Module detection: Inspect call stack for module information
- Citation storage: Store citation strings
- Resource registration: Register three citation resources automatically
- Logger configuration: Set up dedicated logger
User calls hatch_mcp.server.tool()
↓
Calls FastMCP.tool() decorator
↓
Tool registered in FastMCP server
↓
Citation resources remain available alongside tools
MCP Client requests citation://origin/ServerName
↓
FastMCP router matches URI to registered resource
↓
Citation closure function returns stored citation string
↓
Response sent to client
Decision: Use composition instead of inheritance.
Rationale:
- Avoids breaking changes when FastMCP updates
- Allows wrapping existing FastMCP instances
Trade-offs:
- Requires
.serverattribute access - Slightly more verbose than inheritance
Decision: Register citation resources automatically during initialization.
Rationale:
- Zero configuration required from users
- Consistent citation resource availability
- Prevents forgetting to register citation information
Trade-offs:
- Less flexibility for custom citation schemes
- Potential URI conflicts (mitigated by standard schemes)
Decision: Use inspect.stack() to determine calling module.
Rationale:
- Automatic detection without user configuration
- Consistent URI generation
- Works with standard Python module patterns
Trade-offs:
- Relies on Python internals
- Can fail in unusual execution contexts
- Adds complexity for edge cases
Decision: Expose only essential attributes and methods.
Rationale:
- Reduces maintenance burden
- Focuses on core citation functionality
- Easier to maintain backward compatibility
Trade-offs:
- Less customization options
- May need extension for advanced use cases
While not currently supported, the architecture allows for future extension:
# Potential future extension
def register_custom_citation(self, scheme: str, citation: str):
@self.server.resource(
uri=f"{scheme}://{self.name}",
name=f"Custom {scheme}",
description=f"Custom citation scheme: {scheme}",
mime_type="text/plain"
)
def get_custom_citation() -> str:
return citationFor unusual execution contexts:
# Potential override mechanism
def set_module_name(self, module_name: str):
"""Override automatic module detection."""
self.module_name = module_name
# Re-register name resource with new module name- Stack inspection: Minimal one-time cost during initialization
- Resource registration: Three additional resources per server
- Memory usage: Negligible - only stores citation strings
- Zero runtime cost: No performance impact on tool execution
- Citation access: Standard FastMCP resource access performance
- Logging: Standard Python logging performance
- Module detection failure: Raise
RuntimeErrorwith clear message - FastMCP creation failure: Let FastMCP errors propagate
- Citation parameter validation: Accept any string, use defaults for None
- Citation resource access: Standard FastMCP error handling
- Logging errors: Python logging framework handles gracefully
def test_hatch_mcp_initialization():
server = HatchMCP("test", origin_citation="test origin")
assert server.name == "test"
assert server._origin_citation == "test origin"
assert isinstance(server.server, FastMCP)
def test_citation_resource_registration():
server = HatchMCP("test")
# Verify resources are registered (implementation depends on FastMCP testing utilities)- Test with real FastMCP servers
- Verify citation URI resolution
- Test wrapping existing servers
This architecture provides a clean, minimal wrapper that enhances FastMCP with citation capabilities while maintaining full compatibility and ease of use.