"""Tests for JSON schema generation from FastMCP BaseModel classes. Validates that callable fields are properly excluded from generated schemas using SkipJsonSchema annotations. """ from fastmcp.prompts.function_prompt import FunctionPrompt from fastmcp.resources.function_resource import FunctionResource from fastmcp.resources.template import FunctionResourceTemplate from fastmcp.tools.base import Tool from fastmcp.tools.function_tool import FunctionTool from fastmcp.tools.tool_transform import TransformedTool class TestToolJsonSchema: """Test JSON schema generation for Tool classes.""" def test_tool_json_schema_generation(self): """Verify Tool.model_json_schema() works without errors.""" # This should not raise an error schema = Tool.model_json_schema() # Verify schema is valid assert schema["type"] == "object" assert "properties" in schema # Verify callable fields are excluded from schema assert "serializer" not in schema["properties"] # auth already uses exclude=True, so it shouldn't be in schema assert "auth" not in schema["properties"] def test_function_tool_json_schema_generation(self): """Verify FunctionTool.model_json_schema() works without errors.""" def sample_tool(x: int, y: int) -> int: """Add two numbers.""" return x + y tool = FunctionTool.from_function(sample_tool) # This should not raise an error schema = tool.model_json_schema() # Verify schema is valid assert schema["type"] == "object" assert "properties" in schema # Verify callable field 'fn' is excluded from schema assert "fn" not in schema["properties"] def test_transformed_tool_json_schema_generation(self): """Verify TransformedTool.model_json_schema() works without errors.""" def parent_fn(x: int) -> int: return x * 2 parent_tool = FunctionTool.from_function(parent_fn) transformed_tool = TransformedTool.from_tool(parent_tool, name="doubled") # This should not raise an error schema = transformed_tool.model_json_schema() # Verify schema is valid assert schema["type"] == "object" assert "properties" in schema # Verify callable fields are excluded from schema assert "fn" not in schema["properties"] assert "forwarding_fn" not in schema["properties"] assert "parent_tool" not in schema["properties"] class TestResourceJsonSchema: """Test JSON schema generation for Resource classes.""" def test_function_resource_json_schema_generation(self): """Verify FunctionResource.model_json_schema() works without errors.""" def sample_resource() -> str: """Return sample data.""" return "Hello, world!" resource = FunctionResource.from_function( sample_resource, uri="test://resource" ) # This should not raise an error schema = resource.model_json_schema() # Verify schema is valid assert schema["type"] == "object" assert "properties" in schema # Verify callable field 'fn' is excluded from schema assert "fn" not in schema["properties"] # auth already uses exclude=True assert "auth" not in schema["properties"] def test_function_resource_template_json_schema_generation(self): """Verify FunctionResourceTemplate.model_json_schema() works without errors.""" def sample_template(name: str) -> str: """Return greeting for name.""" return f"Hello, {name}!" template = FunctionResourceTemplate.from_function( sample_template, uri_template="greeting://{name}" ) # This should not raise an error schema = template.model_json_schema() # Verify schema is valid assert schema["type"] == "object" assert "properties" in schema # Verify callable field 'fn' is excluded from schema assert "fn" not in schema["properties"] class TestPromptJsonSchema: """Test JSON schema generation for Prompt classes.""" def test_function_prompt_json_schema_generation(self): """Verify FunctionPrompt.model_json_schema() works without errors.""" def sample_prompt(topic: str) -> str: """Generate prompt about topic.""" return f"Tell me about {topic}" prompt = FunctionPrompt.from_function(sample_prompt) # This should not raise an error schema = prompt.model_json_schema() # Verify schema is valid assert schema["type"] == "object" assert "properties" in schema # Verify callable field 'fn' is excluded from schema assert "fn" not in schema["properties"] # auth already uses exclude=True assert "auth" not in schema["properties"] class TestJsonSchemaIntegration: """Integration tests for JSON schema generation across all classes.""" def test_all_classes_generate_valid_schemas(self): """Verify all affected classes can generate valid JSON schemas.""" # Create instances of all affected classes def tool_fn(x: int) -> int: return x def resource_fn() -> str: return "data" def template_fn(id: str) -> str: return f"data-{id}" def prompt_fn(input: str) -> str: return f"Prompt: {input}" tool = FunctionTool.from_function(tool_fn) transformed_tool = TransformedTool.from_tool(tool) resource = FunctionResource.from_function(resource_fn, uri="test://resource") template = FunctionResourceTemplate.from_function( template_fn, uri_template="test://{id}" ) prompt = FunctionPrompt.from_function(prompt_fn) # All of these should succeed without errors schemas = [ Tool.model_json_schema(), tool.model_json_schema(), transformed_tool.model_json_schema(), resource.model_json_schema(), template.model_json_schema(), prompt.model_json_schema(), ] # Verify all schemas are valid for schema in schemas: assert isinstance(schema, dict) assert schema["type"] == "object" assert "properties" in schema def test_callable_fields_not_in_any_schema(self): """Verify no callable fields appear in any generated schema.""" # Define test functions def tool_fn(x: int) -> int: return x def resource_fn() -> str: return "data" def template_fn(id: str) -> str: return f"data-{id}" def prompt_fn(input: str) -> str: return f"Prompt: {input}" # Create instances tool = FunctionTool.from_function(tool_fn) transformed_tool = TransformedTool.from_tool(tool) resource = FunctionResource.from_function(resource_fn, uri="test://resource") template = FunctionResourceTemplate.from_function( template_fn, uri_template="test://{id}" ) prompt = FunctionPrompt.from_function(prompt_fn) # List of (instance, callable_field_names) tuples test_cases = [ (tool, ["fn", "serializer"]), (transformed_tool, ["fn", "forwarding_fn", "parent_tool", "serializer"]), (resource, ["fn"]), (template, ["fn"]), (prompt, ["fn"]), ] for instance, callable_fields in test_cases: schema = instance.model_json_schema() properties = schema.get("properties", {}) # Verify none of the callable fields are in the schema for field in callable_fields: assert field not in properties, ( f"Callable field '{field}' found in schema for {type(instance).__name__}" )