Rulepython

Mcp Tools Rule

paths: ib_sec_mcp/mcp/**/*.py

View Source

MCP Tool Development Rules

Quick reference for MCP tool development in IB Analytics.

Tool Design Decision

| If you need... | Use... | Example | |----------------|--------|---------| | Complete analysis | Coarse-grained | analyze_performance | | Composable operations | Fine-grained | get_trades, calculate_metric | | Data filtering | Fine-grained | get_positions(symbol="AAPL") |

Tool Decorator Pattern

@mcp.tool
async def tool_name(
    required_param: str,
    optional_param: str | None = None,
    ctx: Context | None = None,
) -> str:
    """
    One-line description.

    Args:
        required_param: Description
        optional_param: Description (optional)
        ctx: MCP context for logging

    Returns:
        JSON string with results

    Raises:
        ValidationError: If validation fails

    Example:
        >>> result = await tool_name("value")
    """

Decimal JSON Serialization

# Decimal must be converted for JSON
result = {
    "price": str(price),  # Convert to string
    "formatted": f"${price:.2f}",  # Or format
}
return json.dumps(result)

# For complex objects, use custom encoder
class DecimalEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Decimal):
            return str(obj)
        return super().default(obj)

Error Handling

from ib_sec_mcp.mcp.exceptions import ValidationError

# Input validation
if not symbol:
    raise ValidationError("Symbol is required")

# API errors - mask internal details
try:
    data = await fetch_data()
except Exception as e:
    if ctx:
        await ctx.error(f"Internal error: {e}")
    raise ValidationError("Failed to fetch data")

Context Logging

if ctx:
    await ctx.info(f"Processing {symbol}")
    await ctx.debug(f"Found {len(trades)} trades")
    await ctx.warning(f"Missing data for {date}")

Caching Pattern

@mcp.tool
async def analyze_data(
    start_date: str,
    use_cache: bool = True,
    ctx: Context | None = None,
) -> str:
    # Check cache first
    if use_cache:
        cached = await get_cached_data(start_date)
        if cached:
            return cached
    ...

See /CLAUDE.md for coarse-grained vs fine-grained design philosophy.