fastmcp icon indicating copy to clipboard operation
fastmcp copied to clipboard

Pydantic integration not working.

Open Javedgouri opened this issue 8 months ago • 4 comments

Description

As I have created tool request body with Pydantic model .Unable to view Pydantic model request body in MCP Inspector (stdio)

I have attached code below and screenshot of mcp inscpector

Image

Example Code

from fastmcp import FastMCP

from models import (
    CalculateRequest, KmToMilesRequest,
    CurrencyConversionRequest, StockPriceRequest
)

# Initialize the MCP server with a name
mcp = FastMCP("Tools Server")


@mcp.tool()
def calculate(request: CalculateRequest) -> int | float:
    """Perform basic arithmetic operations."""
    operations = {
        "add": lambda x, y: x + y,
        "subtract": lambda x, y: x - y,
        "multiply": lambda x, y: x * y,
        "divide": lambda x, y: "Error: Cannot divide by zero" if y == 0 else x / y
    }
    result = operations.get(request.operation, lambda x, y: None)(request.number1, request.number2)
    return result


@mcp.tool()
def convert_km_to_miles(request: KmToMilesRequest) -> float:
    """Convert kilometers to miles."""
    miles = round(request.km * 0.621371, 4)
    return float(miles)


@mcp.tool()
def convert_currency(request: CurrencyConversionRequest) -> float:
    """Convert currency."""
    dummy_rates = {
        ("USD", "INR"): 83.0,
        ("INR", "USD"): 0.012,
        ("EUR", "USD"): 1.1,
    }
    rate = dummy_rates.get((request.from_currency.upper(), request.to_currency.upper()), 1.0)
    converted_amount = round(request.amount * rate, 2)

    return float(converted_amount)


@mcp.tool()
def get_stock_price(request: StockPriceRequest) -> str:
    """Get stock price."""
    dummy_prices = {
        "AAPL": 173.54,
        "GOOGL": 134.21,
        "TSLA": 192.35,
    }
    price = dummy_prices.get(request.symbol.upper(), 100.0)
    message = f"{request.symbol.upper()} is trading at ${price}"
    return message


####### models.py 
from typing import Literal, Optional, Union
from pydantic import BaseModel, Field


# Request Models
class CalculateRequest(BaseModel):
    """Request model for performing basic arithmetic operations."""
    operation: Literal['add', 'subtract', 'multiply', 'divide'] = Field(
        description="The arithmetic operation to perform"
    )
    number1: int = Field(description="First operand for the calculation")
    number2: int = Field(description="Second operand for the calculation")


class KmToMilesRequest(BaseModel):
    """Request model for converting kilometers to miles."""
    km: float = Field(description="Distance in kilometers to be converted")


class CurrencyConversionRequest(BaseModel):
    """Request model for currency conversion."""
    amount: float = Field(description="Amount of currency to convert")
    from_currency: str = Field(description="Source currency code (e.g., USD, EUR)")
    to_currency: str = Field(description="Target currency code (e.g., INR, USD)")

class StockPriceRequest(BaseModel):
    """Request model for retrieving stock price."""
    symbol: str = Field(description="Stock symbol (e.g., AAPL, GOOGL, TSLA)")

Version Information

factmcp version - 2.2.2

Additional Context

No response

Javedgouri avatar Apr 24 '25 20:04 Javedgouri

I believe this is an mcp inspector issue, if you look at the schema the schema is correct and the LLM is able to interact with it

strawgate avatar Apr 25 '25 00:04 strawgate

I'm able to replicate but I'm inclined to agree this is a Inspector issue. I think it might be that the inspector doesn't properly resolve $ref references in JsonSchema (which are always produced when Pydantic objects are used as arguments). I am checking if it's possible to flatten them with jsonref but this might lead to performance regressions elsewhere.

jlowin avatar Apr 25 '25 01:04 jlowin

@jlowin This is input schema of one of the tool which doesn't seems correct { "$defs": { "CalculateRequest": { "description": "Request model for performing basic arithmetic operations.", "properties": { "number1": { "description": "First operand for the calculation", "title": "Number1", "type": "integer" }, "number2": { "description": "Second operand for the calculation", "title": "Number2", "type": "integer" }, "operation": { "description": "The arithmetic operation to perform", "enum": ["add", "subtract", "multiply", "divide"], "title": "Operation", "type": "string" } }, "required": ["operation", "number1", "number2"], "title": "CalculateRequest", "type": "object" } }, "properties": { "request": { "$ref": "#/$defs/CalculateRequest" } }, "required": ["request"], "title": "calculateArguments", "type": "object" }

I am getting this issue while calling this tool with client | fastmcp.exceptions.ClientError: Error executing tool calculate: 1 validation error for calculateArguments | request | Field required [type=missing, input_value={'operation': 'add', 'number1': 5, 'number2': 10}, input_type=dict]

Javedgouri avatar Apr 25 '25 03:04 Javedgouri

That schema looks correct to me, what part looks incorrect?

strawgate avatar Apr 25 '25 12:04 strawgate

The validation error is because you are supplying the wrong arguments, you defined the tool with a request key:

async with Client(mcp) as client:
    result = await client.call_tool(
        "calculate",
        {
            "request": {
                "operation": "add",
                "number1": 5,
                "number2": 10,
            }
        },
    )
    print(result)

Above will print [TextContent(type='text', text='15', annotations=None)]

jlowin avatar Apr 25 '25 14:04 jlowin

I believe this is either an intentional (see https://github.com/modelcontextprotocol/inspector/pull/284 and https://github.com/modelcontextprotocol/inspector/issues/331) or accidental (https://github.com/modelcontextprotocol/inspector/issues/332) decision in the MCP Inspector. I can confirm that FastMCP is producing the correct tool schemas with complex objects and agents like Pydantic AI agents call them correctly.

jlowin avatar Apr 25 '25 15:04 jlowin