API Module Conventions
Guidelines for building API modules in the browse-mcp Python backend.
Module Structure
browse_mcp/
+-- __main__.py # MCP tool definitions (entry point)
+-- types.py # Data models and interfaces
+-- plugin.py # Plugin loading system
+-- sources/ # Searcher implementations
+-- __init__.py
+-- arxiv.py
+-- pubmed.py
+-- ...
Tool Definition Pattern
Use FastMCP decorators with Pydantic models:
from fastmcp import FastMCP
from pydantic import BaseModel, Field
mcp = FastMCP("browse_mcp")
class SearchQuery(BaseModel):
"""Query parameters for search."""
query: str = Field(..., min_length=1, max_length=500)
max_results: int = Field(default=10, ge=1, le=100)
@mcp.tool(
name="browse_search",
description="Search for content across sources.",
)
async def browse_search(query_list: List[SearchQuery]) -> str:
"""Implementation..."""
pass
Interface Design
ContentSource[T] Base Class
All searchers must implement:
class ContentSource(ABC, Generic[T]):
@abstractmethod
def search(self, query: str, max_results: int = 10, **kwargs) -> List[T]:
"""Search for content."""
pass
@abstractmethod
def download(self, content_id: str, save_path: str) -> str:
"""Download content to file. Returns file path."""
pass
@abstractmethod
def read(self, content_id: str, save_path: str, **kwargs) -> str:
"""Read/extract text content."""
pass
Async Patterns
- Use
async/awaitfor I/O operations - Use
httpx.AsyncClientfor HTTP requests - Batch operations should run concurrently with
asyncio.gather
async def batch_search(queries: List[Query]) -> List[Result]:
results = await asyncio.gather(*[
search_single(q) for q in queries
])
return results
Error Handling
from loguru import logger
try:
result = searcher.search(query)
except Exception as e:
logger.error(f"Search failed: {e}")
return f"Error: {e}"
Validation
Use Pydantic validators for input validation:
class Query(BaseModel):
query: str
@field_validator('query')
@classmethod
def validate_query(cls, v: str) -> str:
v = v.strip()
if not v:
raise ValueError("Query cannot be empty")
return v
Backward Compatibility
When renaming or changing APIs:
- Keep old names as deprecated aliases
- Log deprecation warnings
- Document migration path in changelog