diff --git a/AGENTS.md b/AGENTS.md
index ac67314a8a..a82e485fa8 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -130,7 +130,6 @@ async with Client(transport=StreamableHttpTransport(server_url)) as client:
- Follow existing patterns and maintain consistency
- **Prioritize readable, understandable code** - clarity over cleverness
- Avoid obfuscated or confusing patterns even if they're shorter
-- Use `# type: ignore[attr-defined]` in tests for MCP results instead of type assertions
- Each feature needs corresponding tests
### Module Exports
@@ -264,7 +263,6 @@ uv sync # Installs all deps including dev tools
### Error Handling
- Never use bare `except` - be specific with exception types
-- Use `# type: ignore[attr-defined]` in tests for MCP results
### Build Issues (Common Solutions)
diff --git a/src/fastmcp/server/providers/base.py b/src/fastmcp/server/providers/base.py
index f7eff819fc..f25bc1725b 100644
--- a/src/fastmcp/server/providers/base.py
+++ b/src/fastmcp/server/providers/base.py
@@ -31,11 +31,12 @@ async def get_tool(self, name: str) -> Tool | None:
from collections.abc import AsyncIterator, Sequence
from contextlib import asynccontextmanager
from dataclasses import dataclass
+from typing import Any
-from fastmcp.prompts.prompt import Prompt
-from fastmcp.resources.resource import Resource
+from fastmcp.prompts.prompt import Prompt, PromptResult
+from fastmcp.resources.resource import Resource, ResourceContent
from fastmcp.resources.template import ResourceTemplate
-from fastmcp.tools.tool import Tool
+from fastmcp.tools.tool import Tool, ToolResult
@dataclass
@@ -228,6 +229,83 @@ async def get_prompt(self, name: str) -> Prompt | None:
prompts = await self.list_prompts()
return next((p for p in prompts if p.name == name), None)
+ # -------------------------------------------------------------------------
+ # Execution methods (optional - default implementations delegate to components)
+ # -------------------------------------------------------------------------
+
+ async def call_tool(
+ self, name: str, arguments: dict[str, Any]
+ ) -> ToolResult | None:
+ """Call a tool by name.
+
+ Default implementation gets the tool and calls its run() method.
+ Override for custom execution logic.
+
+ Returns:
+ ToolResult if the tool was found and executed, None otherwise.
+ """
+ tool = await self.get_tool(name)
+ if tool is None:
+ return None
+ return await tool.run(arguments)
+
+ async def read_resource(self, uri: str) -> ResourceContent | None:
+ """Read a resource by URI.
+
+ Default implementation gets the resource and calls its read() method.
+ Override for custom execution logic.
+
+ Returns:
+ ResourceContent if the resource was found and read, None otherwise.
+ """
+ resource = await self.get_resource(uri)
+ if resource is None:
+ return None
+ result = await resource.read()
+ if isinstance(result, ResourceContent):
+ return result
+ return ResourceContent.from_value(result)
+
+ async def read_resource_template(self, uri: str) -> ResourceContent | None:
+ """Read a resource template by URI.
+
+ Default implementation gets the template, extracts parameters from the URI,
+ and calls its read() method with those parameters.
+ Override for custom execution logic.
+
+ Returns:
+ ResourceContent if the template was found and read, None otherwise.
+ """
+ template = await self.get_resource_template(uri)
+ if template is None:
+ return None
+ params = template.matches(uri)
+ if params is None:
+ return None
+ result = await template.read(params)
+ if isinstance(result, ResourceContent):
+ return result
+ return ResourceContent.from_value(result)
+
+ async def render_prompt(
+ self, name: str, arguments: dict[str, Any] | None
+ ) -> PromptResult | None:
+ """Render a prompt by name.
+
+ Default implementation gets the prompt and calls its render() method.
+ Override for custom execution logic.
+
+ Returns:
+ PromptResult if the prompt was found and rendered, None otherwise.
+ """
+ prompt = await self.get_prompt(name)
+ if prompt is None:
+ return None
+ result = await prompt.render(arguments)
+ if isinstance(result, PromptResult):
+ return result
+ return PromptResult.from_value(result)
+
# -------------------------------------------------------------------------
# Task registration
# -------------------------------------------------------------------------
diff --git a/src/fastmcp/server/providers/transforming.py b/src/fastmcp/server/providers/transforming.py
index 0d45f5a743..8c27d70e4d 100644
--- a/src/fastmcp/server/providers/transforming.py
+++ b/src/fastmcp/server/providers/transforming.py
@@ -9,13 +9,13 @@
import re
from collections.abc import AsyncIterator, Sequence
from contextlib import asynccontextmanager
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING
-from fastmcp.prompts.prompt import Prompt, PromptResult
-from fastmcp.resources.resource import Resource, ResourceContent
+from fastmcp.prompts.prompt import Prompt
+from fastmcp.resources.resource import Resource
from fastmcp.resources.template import ResourceTemplate
from fastmcp.server.providers.base import Provider, TaskComponents
-from fastmcp.tools.tool import Tool, ToolResult
+from fastmcp.tools.tool import Tool
if TYPE_CHECKING:
from fastmcp.prompts.prompt import FunctionPrompt
@@ -74,7 +74,7 @@ def __init__(
use the specified name instead of namespace prefixing.
"""
super().__init__()
- self._wrapped = provider
+ self._wrapped: Provider = provider
self.namespace = namespace
self.tool_renames = tool_renames or {}
@@ -186,15 +186,6 @@ async def get_tool(self, name: str) -> Tool | None:
return tool.model_copy(update={"name": name})
return None
- async def call_tool(
- self, name: str, arguments: dict[str, Any]
- ) -> ToolResult | None:
- """Call tool by transformed name."""
- original = self._reverse_tool_name(name)
- if original is None:
- return None
- return await self._wrapped.call_tool(original, arguments)
-
# -------------------------------------------------------------------------
# Resource methods
# -------------------------------------------------------------------------
@@ -217,13 +208,6 @@ async def get_resource(self, uri: str) -> Resource | None:
return resource.model_copy(update={"uri": uri})
return None
- async def read_resource(self, uri: str) -> ResourceContent | None:
- """Read resource by transformed URI."""
- original = self._reverse_resource_uri(uri)
- if original is None:
- return None
- return await self._wrapped.read_resource(original)
-
# -------------------------------------------------------------------------
# Resource template methods
# -------------------------------------------------------------------------
@@ -252,13 +236,6 @@ async def get_resource_template(self, uri: str) -> ResourceTemplate | None:
)
return None
- async def read_resource_template(self, uri: str) -> ResourceContent | None:
- """Read resource template by transformed URI."""
- original = self._reverse_resource_uri(uri)
- if original is None:
- return None
- return await self._wrapped.read_resource_template(original)
-
# -------------------------------------------------------------------------
# Prompt methods
# -------------------------------------------------------------------------
@@ -281,15 +258,6 @@ async def get_prompt(self, name: str) -> Prompt | None:
return prompt.model_copy(update={"name": name})
return None
- async def render_prompt(
- self, name: str, arguments: dict[str, Any] | None
- ) -> PromptResult | None:
- """Render prompt by transformed name."""
- original = self._reverse_prompt_name(name)
- if original is None:
- return None
- return await self._wrapped.render_prompt(original, arguments)
-
# -------------------------------------------------------------------------
# Task registration
# -------------------------------------------------------------------------
diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py
index 10e86b8a73..741a1bf3c2 100644
--- a/tests/cli/test_cli.py
+++ b/tests/cli/test_cli.py
@@ -48,6 +48,7 @@ def test_version_command_execution(self):
def test_version_command_parsing(self):
"""Test that the version command parses arguments correctly."""
command, bound, _ = app.parse_args(["version"])
+ assert callable(command)
assert command.__name__ == "version" # type: ignore[attr-defined]
# Default arguments aren't included in bound.arguments
assert bound.arguments == {}
@@ -55,6 +56,7 @@ def test_version_command_parsing(self):
def test_version_command_with_copy_flag(self):
"""Test that the version command parses --copy flag correctly."""
command, bound, _ = app.parse_args(["version", "--copy"])
+ assert callable(command)
assert command.__name__ == "version" # type: ignore[attr-defined]
assert bound.arguments == {"copy": True}
diff --git a/tests/cli/test_tasks.py b/tests/cli/test_tasks.py
index a48c44ff7a..202b0e7c76 100644
--- a/tests/cli/test_tasks.py
+++ b/tests/cli/test_tasks.py
@@ -30,6 +30,7 @@ class TestWorkerCommand:
def test_worker_command_parsing(self):
"""Test that worker command parses arguments correctly."""
command, bound, _ = tasks_app.parse_args(["worker", "server.py"])
+ assert callable(command)
assert command.__name__ == "worker" # type: ignore[attr-defined]
assert bound.arguments["server_spec"] == "server.py"
diff --git a/tests/client/auth/test_oauth_client.py b/tests/client/auth/test_oauth_client.py
index 832d7ce02b..4752d576c3 100644
--- a/tests/client/auth/test_oauth_client.py
+++ b/tests/client/auth/test_oauth_client.py
@@ -2,6 +2,7 @@
import httpx
import pytest
+from mcp.types import TextResourceContents
from fastmcp.client import Client
from fastmcp.client.auth import OAuth
@@ -103,7 +104,8 @@ async def test_read_resource(client_with_headless_oauth: Client):
"""Test that we can read a resource."""
async with client_with_headless_oauth:
resource = await client_with_headless_oauth.read_resource("resource://test")
- assert resource[0].text == "Hello from authenticated resource!" # type: ignore[attr-defined]
+ assert isinstance(resource[0], TextResourceContents)
+ assert resource[0].text == "Hello from authenticated resource!"
async def test_oauth_server_metadata_discovery(streamable_http_server: str):
diff --git a/tests/client/tasks/test_prompt_task_mcp_message.py b/tests/client/tasks/test_prompt_task_mcp_message.py
index a32f2fe92f..33539058b7 100644
--- a/tests/client/tasks/test_prompt_task_mcp_message.py
+++ b/tests/client/tasks/test_prompt_task_mcp_message.py
@@ -4,6 +4,7 @@
"""
import mcp.types
+from mcp.types import TextContent
from fastmcp import FastMCP
from fastmcp.client import Client
@@ -25,7 +26,8 @@ async def greeting(name: str) -> list[mcp.types.PromptMessage]:
async with Client(mcp_server) as client:
task = await client.get_prompt("greeting", {"name": "World"}, task=True)
result = await task.result()
- assert "Hello World" in result.messages[0].content.text # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert "Hello World" in result.messages[0].content.text
async def test_prompt_task_with_multiple_mcp_prompt_messages():
@@ -53,5 +55,7 @@ async def conversation(topic: str) -> list[mcp.types.PromptMessage]:
task = await client.get_prompt("conversation", {"topic": "space"}, task=True)
result = await task.result()
assert len(result.messages) == 2
- assert "Tell me about space" in result.messages[0].content.text # type: ignore[attr-defined]
- assert "space is fascinating" in result.messages[1].content.text # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert "Tell me about space" in result.messages[0].content.text
+ assert isinstance(result.messages[1].content, TextContent)
+ assert "space is fascinating" in result.messages[1].content.text
diff --git a/tests/client/test_client.py b/tests/client/test_client.py
index 521a35408e..b2b7adecab 100644
--- a/tests/client/test_client.py
+++ b/tests/client/test_client.py
@@ -8,6 +8,7 @@
import pytest
from mcp import ClientSession, McpError
from mcp.client.auth import OAuthClientProvider
+from mcp.types import TextContent
from pydantic import AnyUrl
import fastmcp
@@ -124,7 +125,8 @@ async def test_call_tool(fastmcp_server):
async with client:
result = await client.call_tool("greet", {"name": "World"})
- assert result.content[0].text == "Hello, World!" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "Hello, World!"
assert result.structured_content == {"result": "Hello, World!"}
assert result.data == "Hello, World!"
assert result.is_error is False
@@ -249,7 +251,8 @@ async def test_get_prompt(fastmcp_server):
result = await client.get_prompt("welcome", {"name": "Developer"})
# The result should contain our welcome message
- assert result.messages[0].content.text == "Welcome to FastMCP, Developer!" # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert result.messages[0].content.text == "Welcome to FastMCP, Developer!"
assert result.description == "Example greeting prompt."
@@ -261,7 +264,8 @@ async def test_get_prompt_mcp(fastmcp_server):
result = await client.get_prompt_mcp("welcome", {"name": "Developer"})
# The result should contain our welcome message
- assert result.messages[0].content.text == "Welcome to FastMCP, Developer!" # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert result.messages[0].content.text == "Welcome to FastMCP, Developer!"
assert result.description == "Example greeting prompt."
@@ -286,7 +290,8 @@ def echo_args(arg1: str, arg2: str, arg3: str) -> str:
},
)
- content = result.messages[0].content.text # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ content = result.messages[0].content.text
assert "arg1: hello" in content
assert "arg2: [1,2,3]" in content # JSON serialized list
assert 'arg3: {"key":"value"}' in content # JSON serialized dict
@@ -309,7 +314,8 @@ def typed_prompt(numbers: list[int], config: dict[str, str]) -> str:
{"numbers": [1, 2, 3, 4], "config": {"theme": "dark", "lang": "en"}},
)
- content = result.messages[0].content.text # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ content = result.messages[0].content.text
assert "Got 4 numbers and 2 config items" in content
@@ -800,8 +806,9 @@ def error_tool():
async with client:
result = await client.call_tool_mcp("error_tool", {})
assert result.isError
- assert "test error" in result.content[0].text # type: ignore[attr-defined]
- assert "abc" in result.content[0].text # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert "test error" in result.content[0].text
+ assert "abc" in result.content[0].text
async def test_general_tool_exceptions_are_masked_when_enabled(self):
mcp = FastMCP("TestServer", mask_error_details=True)
@@ -815,8 +822,9 @@ def error_tool():
async with client:
result = await client.call_tool_mcp("error_tool", {})
assert result.isError
- assert "test error" not in result.content[0].text # type: ignore[attr-defined]
- assert "abc" not in result.content[0].text # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert "test error" not in result.content[0].text
+ assert "abc" not in result.content[0].text
async def test_validation_errors_are_not_masked_when_enabled(self):
mcp = FastMCP("TestServer", mask_error_details=True)
@@ -829,7 +837,8 @@ def validated_tool(x: int) -> int:
result = await client.call_tool_mcp("validated_tool", {"x": "abc"})
assert result.isError
# Pydantic validation error message should NOT be masked
- assert "Input should be a valid integer" in result.content[0].text # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert "Input should be a valid integer" in result.content[0].text
async def test_specific_tool_errors_are_sent_to_client(self):
mcp = FastMCP("TestServer")
@@ -843,8 +852,9 @@ def custom_error_tool():
async with client:
result = await client.call_tool_mcp("custom_error_tool", {})
assert result.isError
- assert "test error" in result.content[0].text # type: ignore[attr-defined]
- assert "abc" in result.content[0].text # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert "test error" in result.content[0].text
+ assert "abc" in result.content[0].text
async def test_general_resource_exceptions_are_not_masked_by_default(self):
mcp = FastMCP("TestServer")
diff --git a/tests/client/test_elicitation.py b/tests/client/test_elicitation.py
index b762eef26c..360743628d 100644
--- a/tests/client/test_elicitation.py
+++ b/tests/client/test_elicitation.py
@@ -1,6 +1,6 @@
from dataclasses import asdict, dataclass
from enum import Enum
-from typing import Literal
+from typing import Any, Literal, cast
import pytest
from mcp.types import ElicitRequestFormParams, ElicitRequestParams
@@ -36,7 +36,9 @@ async def ask_for_name(context: Context) -> str:
response_type=Person,
)
if result.action == "accept":
- return f"Hello, {result.data.name}!" # type: ignore[attr-defined]
+ assert isinstance(result, AcceptedElicitation)
+ assert isinstance(result.data, Person)
+ return f"Hello, {result.data.name}!"
else:
return "No name provided."
@@ -128,7 +130,9 @@ async def ask_for_optional_info(context: Context) -> str:
if result.action == "cancel":
return "Request was canceled"
elif result.action == "accept":
- return f"Age: {result.data}" # type: ignore[attr-defined]
+ assert isinstance(result, AcceptedElicitation)
+ assert isinstance(result.data, int)
+ return f"Age: {result.data}"
else:
return "No response provided"
@@ -146,9 +150,11 @@ async def test_elicitation_no_response(self):
mcp = FastMCP("TestServer")
@mcp.tool
- async def my_tool(context: Context) -> None:
+ async def my_tool(context: Context) -> dict[str, Any]:
result = await context.elicit(message="", response_type=None)
- return result.data # type: ignore[attr-defined]
+ assert isinstance(result, AcceptedElicitation)
+ assert isinstance(result.data, dict)
+ return cast(dict[str, Any], result.data)
async def elicitation_handler(
message, response_type, params: ElicitRequestParams, ctx
@@ -167,9 +173,13 @@ async def test_elicitation_empty_response(self):
mcp = FastMCP("TestServer")
@mcp.tool
- async def my_tool(context: Context) -> None:
+ async def my_tool(context: Context) -> dict[str, Any]:
result = await context.elicit(message="", response_type=None)
- return result.data # type: ignore[attr-defined]
+ assert result.action == "accept"
+ assert isinstance(result, AcceptedElicitation)
+ accepted = cast(AcceptedElicitation[dict[str, Any]], result)
+ assert isinstance(accepted.data, dict)
+ return accepted.data
async def elicitation_handler(
message, response_type, params: ElicitRequestParams, ctx
@@ -185,9 +195,13 @@ async def test_elicitation_response_when_no_response_requested(self):
mcp = FastMCP("TestServer")
@mcp.tool
- async def my_tool(context: Context) -> None:
+ async def my_tool(context: Context) -> dict[str, Any]:
result = await context.elicit(message="", response_type=None)
- return result.data # type: ignore[attr-defined]
+ assert result.action == "accept"
+ assert isinstance(result, AcceptedElicitation)
+ accepted = cast(AcceptedElicitation[dict[str, Any]], result)
+ assert isinstance(accepted.data, dict)
+ return accepted.data
async def elicitation_handler(message, response_type, params, ctx):
return ElicitResult(action="accept", content={"value": "hello"})
@@ -205,7 +219,9 @@ async def test_elicitation_str_response(self):
@mcp.tool
async def my_tool(context: Context) -> str:
result = await context.elicit(message="", response_type=str)
- return result.data # type: ignore[attr-defined]
+ assert isinstance(result, AcceptedElicitation)
+ assert isinstance(result.data, str)
+ return result.data
async def elicitation_handler(message, response_type, params, ctx):
return ElicitResult(action="accept", content={"value": "hello"})
@@ -221,7 +237,9 @@ async def test_elicitation_int_response(self):
@mcp.tool
async def my_tool(context: Context) -> int:
result = await context.elicit(message="", response_type=int)
- return result.data # type: ignore[attr-defined]
+ assert isinstance(result, AcceptedElicitation)
+ assert isinstance(result.data, int)
+ return result.data
async def elicitation_handler(message, response_type, params, ctx):
return ElicitResult(action="accept", content={"value": 42})
@@ -237,7 +255,9 @@ async def test_elicitation_float_response(self):
@mcp.tool
async def my_tool(context: Context) -> float:
result = await context.elicit(message="", response_type=float)
- return result.data # type: ignore[attr-defined]
+ assert isinstance(result, AcceptedElicitation)
+ assert isinstance(result.data, float)
+ return result.data
async def elicitation_handler(message, response_type, params, ctx):
return ElicitResult(action="accept", content={"value": 3.14})
@@ -253,7 +273,9 @@ async def test_elicitation_bool_response(self):
@mcp.tool
async def my_tool(context: Context) -> bool:
result = await context.elicit(message="", response_type=bool)
- return result.data # type: ignore[attr-defined]
+ assert isinstance(result, AcceptedElicitation)
+ assert isinstance(result.data, bool)
+ return result.data
async def elicitation_handler(message, response_type, params, ctx):
return ElicitResult(action="accept", content={"value": True})
@@ -268,8 +290,12 @@ async def test_elicitation_literal_response(self):
@mcp.tool
async def my_tool(context: Context) -> Literal["x", "y"]:
- result = await context.elicit(message="", response_type=Literal["x", "y"]) # type: ignore
- return result.data # type: ignore[attr-defined]
+ # Literal types work at runtime but type checker doesn't recognize them in overloads
+ result = await context.elicit(message="", response_type=Literal["x", "y"]) # type: ignore[arg-type]
+ assert isinstance(result, AcceptedElicitation)
+ accepted = cast(AcceptedElicitation[Literal["x", "y"]], result)
+ assert isinstance(accepted.data, str)
+ return accepted.data
async def elicitation_handler(message, response_type, params, ctx):
return ElicitResult(action="accept", content={"value": "x"})
@@ -289,7 +315,9 @@ class ResponseEnum(Enum):
@mcp.tool
async def my_tool(context: Context) -> ResponseEnum:
result = await context.elicit(message="", response_type=ResponseEnum)
- return result.data # type: ignore[attr-defined]
+ assert isinstance(result, AcceptedElicitation)
+ assert isinstance(result.data, ResponseEnum)
+ return result.data
async def elicitation_handler(message, response_type, params, ctx):
return ElicitResult(action="accept", content={"value": "x"})
@@ -305,7 +333,9 @@ async def test_elicitation_list_of_strings_response(self):
@mcp.tool
async def my_tool(context: Context) -> str:
result = await context.elicit(message="", response_type=["x", "y"])
- return result.data # type: ignore[attr-defined]
+ assert isinstance(result, AcceptedElicitation)
+ assert isinstance(result.data, str)
+ return result.data
async def elicitation_handler(message, response_type, params, ctx):
return ElicitResult(action="accept", content={"value": "x"})
@@ -417,10 +447,19 @@ async def get_user_info(context: Context) -> str:
assert isinstance(result, AcceptedElicitation)
if result.action == "accept":
+ assert isinstance(result, AcceptedElicitation)
if isinstance(result.data, dict):
- return f"User: {result.data['name']}, age: {result.data['age']}" # type: ignore[index]
+ data_dict = cast(dict[str, Any], result.data)
+ name = data_dict.get("name")
+ age = data_dict.get("age")
+ assert name is not None
+ assert age is not None
+ return f"User: {name}, age: {age}"
else:
- return f"User: {result.data.name}, age: {result.data.age}" # type: ignore[attr-defined]
+ # result.data is a structured type (UserInfo, UserInfoTypedDict, or UserInfoPydantic)
+ assert hasattr(result.data, "name")
+ assert hasattr(result.data, "age")
+ return f"User: {result.data.name}, age: {result.data.age}"
return "No user info provided"
async def elicitation_handler(message, response_type, params, ctx):
@@ -467,7 +506,9 @@ class Data:
@mcp.tool
async def get_data(context: Context) -> Data:
result = await context.elicit(message="Enter data", response_type=Data)
- return result.data # type: ignore[attr-defined]
+ assert isinstance(result, AcceptedElicitation)
+ assert isinstance(result.data, Data)
+ return result.data
async def elicitation_handler(message, response_type, params, ctx):
return ElicitResult(
@@ -738,7 +779,9 @@ async def my_tool(ctx: Context) -> str:
},
)
if result.action == "accept":
- return result.data # type: ignore[attr-defined]
+ assert isinstance(result, AcceptedElicitation)
+ assert isinstance(result.data, str)
+ return result.data
return "declined"
async def elicitation_handler(message, response_type, params, ctx):
@@ -760,7 +803,9 @@ async def my_tool(ctx: Context) -> str:
response_type=[["bug", "feature", "documentation"]],
)
if result.action == "accept":
- return ",".join(result.data) # type: ignore[attr-defined]
+ assert isinstance(result, AcceptedElicitation)
+ assert isinstance(result.data, list)
+ return ",".join(result.data)
return "declined"
async def elicitation_handler(message, response_type, params, ctx):
@@ -796,7 +841,9 @@ async def my_tool(ctx: Context) -> str:
],
)
if result.action == "accept":
- return ",".join(result.data) # type: ignore[attr-defined]
+ assert isinstance(result, AcceptedElicitation)
+ assert isinstance(result.data, list)
+ return ",".join(result.data)
return "declined"
async def elicitation_handler(message, response_type, params, ctx):
@@ -857,7 +904,9 @@ async def my_tool(ctx: Context) -> str:
response_type=list[Priority], # Type annotation for multi-select
)
if result.action == "accept":
- priorities = result.data # type: ignore[attr-defined]
+ assert isinstance(result, AcceptedElicitation)
+ assert isinstance(result.data, list)
+ priorities = result.data
return ",".join(
[p.value if isinstance(p, Priority) else str(p) for p in priorities]
)
diff --git a/tests/client/test_openapi.py b/tests/client/test_openapi.py
index ac420aab64..2ea6f5adb0 100644
--- a/tests/client/test_openapi.py
+++ b/tests/client/test_openapi.py
@@ -2,6 +2,7 @@
import pytest
from fastapi import FastAPI, Request
+from mcp.types import TextResourceContents
from fastmcp import Client, FastMCP
from fastmcp.client.transports import SSETransport, StreamableHttpTransport
@@ -70,14 +71,16 @@ async def proxy_server(shttp_server: str):
async def test_fastapi_client_headers_streamable_http_resource(shttp_server: str):
async with Client(transport=StreamableHttpTransport(shttp_server)) as client:
result = await client.read_resource("resource://get_headers_headers_get")
- headers = json.loads(result[0].text) # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ headers = json.loads(result[0].text)
assert headers["x-server-header"] == "test-abc"
async def test_fastapi_client_headers_sse_resource(sse_server: str):
async with Client(transport=SSETransport(sse_server)) as client:
result = await client.read_resource("resource://get_headers_headers_get")
- headers = json.loads(result[0].text) # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ headers = json.loads(result[0].text)
assert headers["x-server-header"] == "test-abc"
@@ -100,7 +103,8 @@ async def test_client_headers_sse_resource(sse_server: str):
transport=SSETransport(sse_server, headers={"X-TEST": "test-123"})
) as client:
result = await client.read_resource("resource://get_headers_headers_get")
- headers = json.loads(result[0].text) # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ headers = json.loads(result[0].text)
assert headers["x-test"] == "test-123"
@@ -109,7 +113,8 @@ async def test_client_headers_shttp_resource(shttp_server: str):
transport=StreamableHttpTransport(shttp_server, headers={"X-TEST": "test-123"})
) as client:
result = await client.read_resource("resource://get_headers_headers_get")
- headers = json.loads(result[0].text) # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ headers = json.loads(result[0].text)
assert headers["x-test"] == "test-123"
@@ -120,7 +125,8 @@ async def test_client_headers_sse_resource_template(sse_server: str):
result = await client.read_resource(
"resource://get_header_by_name_headers/x-test"
)
- header = json.loads(result[0].text) # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ header = json.loads(result[0].text)
assert header == "test-123"
@@ -131,7 +137,8 @@ async def test_client_headers_shttp_resource_template(shttp_server: str):
result = await client.read_resource(
"resource://get_header_by_name_headers/x-test"
)
- header = json.loads(result[0].text) # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ header = json.loads(result[0].text)
assert header == "test-123"
@@ -160,7 +167,8 @@ async def test_client_overrides_server_headers(shttp_server: str):
)
) as client:
result = await client.read_resource("resource://get_headers_headers_get")
- headers = json.loads(result[0].text) # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ headers = json.loads(result[0].text)
assert headers["x-server-header"] == "test-client"
@@ -176,7 +184,8 @@ async def test_client_with_excluded_header_is_ignored(sse_server: str):
)
) as client:
result = await client.read_resource("resource://get_headers_headers_get")
- headers = json.loads(result[0].text) # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ headers = json.loads(result[0].text)
assert headers["not-host"] == "1.2.3.4"
assert headers["host"] == "fastapi"
@@ -188,5 +197,6 @@ async def test_client_headers_proxy(proxy_server: str):
"""
async with Client(transport=StreamableHttpTransport(proxy_server)) as client:
result = await client.read_resource("resource://get_headers_headers_get")
- headers = json.loads(result[0].text) # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ headers = json.loads(result[0].text)
assert headers["x-server-header"] == "test-abc"
diff --git a/tests/client/test_sampling.py b/tests/client/test_sampling.py
index c6cd42fad9..e379d6707d 100644
--- a/tests/client/test_sampling.py
+++ b/tests/client/test_sampling.py
@@ -8,7 +8,7 @@
from fastmcp import Client, Context, FastMCP
from fastmcp.client.sampling import RequestContext, SamplingMessage, SamplingParams
-from fastmcp.server.sampling import SamplingTool
+from fastmcp.server.sampling import SamplingResult, SamplingTool
from fastmcp.utilities.types import Image
@@ -19,12 +19,16 @@ def fastmcp_server():
@mcp.tool
async def simple_sample(message: str, context: Context) -> str:
result = await context.sample("Hello, world!")
- return result.text # type: ignore[attr-defined]
+ assert isinstance(result, SamplingResult)
+ assert result.text is not None
+ return result.text
@mcp.tool
async def sample_with_system_prompt(message: str, context: Context) -> str:
result = await context.sample("Hello, world!", system_prompt="You love FastMCP")
- return result.text # type: ignore[attr-defined]
+ assert isinstance(result, SamplingResult)
+ assert result.text is not None
+ return result.text
@mcp.tool
async def sample_with_messages(message: str, context: Context) -> str:
@@ -39,7 +43,9 @@ async def sample_with_messages(message: str, context: Context) -> str:
),
]
)
- return result.text # type: ignore[attr-defined]
+ assert isinstance(result, SamplingResult)
+ assert result.text is not None
+ return result.text
@mcp.tool
async def sample_with_image(image_bytes: bytes, context: Context) -> str:
@@ -57,7 +63,9 @@ async def sample_with_image(image_bytes: bytes, context: Context) -> str:
),
]
)
- return result.text # type: ignore[attr-defined]
+ assert isinstance(result, SamplingResult)
+ assert result.text is not None
+ return result.text
return mcp
@@ -476,7 +484,8 @@ async def test_unknown(context: Context) -> str:
assert tool_result is not None
assert tool_result.isError is True
# Content is list of TextContent objects
- error_text = tool_result.content[0].text # type: ignore[union-attr]
+ assert isinstance(tool_result.content[0], TextContent)
+ error_text = tool_result.content[0].text
assert "Unknown tool" in error_text
assert result.data == "Handled error"
@@ -549,7 +558,8 @@ async def test_exception(context: Context) -> str:
assert tool_result is not None
assert tool_result.isError is True
# Content is list of TextContent objects
- error_text = tool_result.content[0].text # type: ignore[union-attr]
+ assert isinstance(tool_result.content[0], TextContent)
+ error_text = tool_result.content[0].text
assert "Tool failed intentionally" in error_text
assert result.data == "Handled error"
@@ -597,7 +607,8 @@ async def math_tool(context: Context) -> str:
result_type=MathResult,
)
# result.result should be a MathResult object
- return f"{result.result.answer}: {result.result.explanation}" # type: ignore[attr-defined]
+ assert isinstance(result.result, MathResult)
+ return f"{result.result.answer}: {result.result.explanation}"
async with Client(mcp) as client:
result = await client.call_tool("math_tool", {})
@@ -675,7 +686,8 @@ async def research(context: Context) -> str:
tools=[search],
result_type=SearchResult,
)
- return f"{result.result.summary} - {len(result.result.sources)} sources" # type: ignore[attr-defined]
+ assert isinstance(result.result, SearchResult)
+ return f"{result.result.summary} - {len(result.result.sources)} sources"
async with Client(mcp) as client:
result = await client.call_tool("research", {})
@@ -741,7 +753,8 @@ async def validate_tool(context: Context) -> str:
messages="Give me a number",
result_type=StrictResult,
)
- return str(result.result.value) # type: ignore[attr-defined]
+ assert isinstance(result.result, StrictResult)
+ return str(result.result.value)
async with Client(mcp) as client:
result = await client.call_tool("validate_tool", {})
@@ -765,7 +778,8 @@ async def validate_tool(context: Context) -> str:
break
assert tool_result is not None
assert tool_result.isError is True
- error_text = tool_result.content[0].text # type: ignore[union-attr]
+ assert isinstance(tool_result.content[0], TextContent)
+ error_text = tool_result.content[0].text
assert "Validation error" in error_text
# Final result should be correct
diff --git a/tests/client/test_sse.py b/tests/client/test_sse.py
index b0c9199b20..cdcd0cfcaa 100644
--- a/tests/client/test_sse.py
+++ b/tests/client/test_sse.py
@@ -4,6 +4,7 @@
import pytest
from mcp import McpError
+from mcp.types import TextResourceContents
from fastmcp.client import Client
from fastmcp.client.transports import SSETransport
@@ -75,7 +76,8 @@ async def test_http_headers(sse_server: str):
transport=SSETransport(sse_server, headers={"X-DEMO-HEADER": "ABC"})
) as client:
raw_result = await client.read_resource("request://headers")
- json_result = json.loads(raw_result[0].text) # type: ignore[attr-defined]
+ assert isinstance(raw_result[0], TextResourceContents)
+ json_result = json.loads(raw_result[0].text)
assert "x-demo-header" in json_result
assert json_result["x-demo-header"] == "ABC"
diff --git a/tests/client/test_streamable_http.py b/tests/client/test_streamable_http.py
index 5c1753d9af..baf2756774 100644
--- a/tests/client/test_streamable_http.py
+++ b/tests/client/test_streamable_http.py
@@ -5,6 +5,7 @@
import pytest
from mcp import McpError
+from mcp.types import TextResourceContents
from fastmcp import Context
from fastmcp.client import Client
@@ -170,7 +171,8 @@ async def test_http_headers(streamable_http_server: str):
)
) as client:
raw_result = await client.read_resource("request://headers")
- json_result = json.loads(raw_result[0].text) # type: ignore[attr-defined]
+ assert isinstance(raw_result[0], TextResourceContents)
+ json_result = json.loads(raw_result[0].text)
assert "x-demo-header" in json_result
assert json_result["x-demo-header"] == "ABC"
diff --git a/tests/client/transports/test_transports.py b/tests/client/transports/test_transports.py
index 1bf7ebed93..acf6001cb7 100644
--- a/tests/client/transports/test_transports.py
+++ b/tests/client/transports/test_transports.py
@@ -2,6 +2,7 @@
import httpx
+from fastmcp.client.auth.oauth import OAuth
from fastmcp.client.transports import SSETransport, StreamableHttpTransport
@@ -14,7 +15,8 @@ async def test_oauth_uses_same_client_as_transport_streamable_http():
auth="oauth",
)
- async with transport.auth.httpx_client_factory() as httpx_client: # type: ignore[attr-defined]
+ assert isinstance(transport.auth, OAuth)
+ async with transport.auth.httpx_client_factory() as httpx_client:
assert httpx_client._transport is not None
assert (
httpx_client._transport._pool._ssl_context.verify_mode # type: ignore[attr-defined]
@@ -31,7 +33,8 @@ async def test_oauth_uses_same_client_as_transport_sse():
auth="oauth",
)
- async with transport.auth.httpx_client_factory() as httpx_client: # type: ignore[attr-defined]
+ assert isinstance(transport.auth, OAuth)
+ async with transport.auth.httpx_client_factory() as httpx_client:
assert httpx_client._transport is not None
assert (
httpx_client._transport._pool._ssl_context.verify_mode # type: ignore[attr-defined]
diff --git a/tests/deprecated/test_import_server.py b/tests/deprecated/test_import_server.py
index 503a4216ae..11208dbf04 100644
--- a/tests/deprecated/test_import_server.py
+++ b/tests/deprecated/test_import_server.py
@@ -1,6 +1,8 @@
import json
from urllib.parse import quote
+from mcp.types import TextContent, TextResourceContents
+
from fastmcp.client.client import Client
from fastmcp.server.server import FastMCP
from fastmcp.tools.tool import FunctionTool, Tool
@@ -328,7 +330,8 @@ def greeting(name: str) -> str:
async with Client(main_app) as client:
result = await client.get_prompt("api_greeting", {"name": "World"})
- assert result.messages[0].content.text == "Hello, World from API!" # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert result.messages[0].content.text == "Hello, World from API!"
assert result.description == "Example greeting prompt."
@@ -357,7 +360,8 @@ def get_config():
# Access the resource through the main app with the prefixed key
async with Client(main_app) as client:
result = await client.read_resource("config://api/settings")
- content = json.loads(result[0].text) # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ content = json.loads(result[0].text)
assert content["api_key"] == "12345"
assert content["base_url"] == "https://api.example.com"
@@ -387,7 +391,8 @@ def create_user(name: str, email: str):
quoted_email = quote("john@example.com", safe="")
async with Client(main_app) as client:
result = await client.read_resource(f"user://api/{quoted_name}/{quoted_email}")
- content = json.loads(result[0].text) # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ content = json.loads(result[0].text)
assert content["name"] == "John Doe"
assert content["email"] == "john@example.com"
@@ -430,16 +435,19 @@ def sub_prompt() -> str:
# Test resource
resource_result = await client.read_resource("data://config")
- assert resource_result[0].text == "Sub resource data" # type: ignore[attr-defined]
+ assert isinstance(resource_result[0], TextResourceContents)
+ assert resource_result[0].text == "Sub resource data"
# Test template
template_result = await client.read_resource("users://123/info")
- assert template_result[0].text == "Sub template for user 123" # type: ignore[attr-defined]
+ assert isinstance(template_result[0], TextResourceContents)
+ assert template_result[0].text == "Sub template for user 123"
# Test prompt
prompt_result = await client.get_prompt("sub_prompt", {})
assert prompt_result.messages is not None
- assert prompt_result.messages[0].content.text == "Sub prompt content" # type: ignore[attr-defined]
+ assert isinstance(prompt_result.messages[0].content, TextContent)
+ assert prompt_result.messages[0].content.text == "Sub prompt content"
async def test_import_conflict_resolution_tools():
@@ -497,7 +505,8 @@ def second_resource():
assert resource_uris.count("shared://data") == 1 # Should only appear once
result = await client.read_resource("shared://data")
- assert result[0].text == "Second app data" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Second app data"
async def test_import_conflict_resolution_templates():
@@ -528,7 +537,8 @@ def second_template(user_id: str):
) # Should only appear once
result = await client.read_resource("users://123/profile")
- assert result[0].text == "Second app user 123" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Second app user 123"
async def test_import_conflict_resolution_prompts():
@@ -558,7 +568,8 @@ def second_shared_prompt() -> str:
result = await client.get_prompt("shared_prompt", {})
assert result.messages is not None
- assert result.messages[0].content.text == "Second app prompt" # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert result.messages[0].content.text == "Second app prompt"
async def test_import_conflict_resolution_with_prefix():
@@ -660,10 +671,13 @@ def get_template_resource(param: str):
# Verify we can access the resources
async with Client(target_server) as client:
result = await client.read_resource("resource://imported/test-resource")
- assert result[0].text == "Resource content" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Resource content"
result = await client.read_resource("resource://imported//absolute/path")
- assert result[0].text == "Absolute resource content" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Absolute resource content"
result = await client.read_resource("resource://imported/param-value/template")
- assert result[0].text == "Template resource with param-value" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Template resource with param-value"
diff --git a/tests/integration_tests/test_github_mcp_remote.py b/tests/integration_tests/test_github_mcp_remote.py
index 6f86b3611e..afd367c335 100644
--- a/tests/integration_tests/test_github_mcp_remote.py
+++ b/tests/integration_tests/test_github_mcp_remote.py
@@ -3,7 +3,7 @@
import pytest
from mcp import McpError
-from mcp.types import Tool
+from mcp.types import TextContent, Tool
from fastmcp import Client
from fastmcp.client import StreamableHttpTransport
@@ -111,7 +111,8 @@ async def test_call_tool_list_commits(
assert result.structured_content is None
assert isinstance(result.content, list)
assert len(result.content) == 1
- commits = json.loads(result.content[0].text) # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ commits = json.loads(result.content[0].text)
for commit in commits:
assert isinstance(commit, dict)
assert "sha" in commit
diff --git a/tests/prompts/test_prompt_manager.py b/tests/prompts/test_prompt_manager.py
index b53c26ae46..d9c67270be 100644
--- a/tests/prompts/test_prompt_manager.py
+++ b/tests/prompts/test_prompt_manager.py
@@ -413,7 +413,8 @@ def prompt_with_context(x: int, ctx: Context) -> str:
assert isinstance(result, PromptResult)
assert len(result.messages) == 1
- assert result.messages[0].content.text == "42" # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert result.messages[0].content.text == "42"
async def test_context_optional(self):
"""Test that context is optional when rendering prompts."""
@@ -436,7 +437,8 @@ def prompt_with_context(x: int, ctx: Context | None = None) -> str:
assert isinstance(result, PromptResult)
assert len(result.messages) == 1
- assert result.messages[0].content.text == "42" # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert result.messages[0].content.text == "42"
async def test_annotated_context_parameter_detection(self):
"""Test that annotated context parameters are properly detected in
@@ -473,4 +475,5 @@ async def decorated_prompt(ctx: Context, topic: str) -> str:
async with context:
result = await prompt.render(arguments={"topic": "cats"})
assert isinstance(result, PromptResult)
- assert result.messages[0].content.text == "Write about cats" # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert result.messages[0].content.text == "Write about cats"
diff --git a/tests/server/auth/providers/test_azure.py b/tests/server/auth/providers/test_azure.py
index 955ea2e66d..72fb398eb6 100644
--- a/tests/server/auth/providers/test_azure.py
+++ b/tests/server/auth/providers/test_azure.py
@@ -10,6 +10,7 @@
from pydantic import AnyUrl
from fastmcp.server.auth.providers.azure import OIDC_SCOPES, AzureProvider
+from fastmcp.server.auth.providers.jwt import JWTVerifier
class TestAzureProvider:
@@ -404,12 +405,13 @@ def test_base_authority_defaults_to_public_cloud(self):
provider._upstream_token_endpoint
== "https://login.microsoftonline.com/test-tenant/oauth2/v2.0/token"
)
+ assert isinstance(provider._token_validator, JWTVerifier)
assert (
- provider._token_validator.issuer # type: ignore[attr-defined]
+ provider._token_validator.issuer
== "https://login.microsoftonline.com/test-tenant/v2.0"
)
assert (
- provider._token_validator.jwks_uri # type: ignore[attr-defined]
+ provider._token_validator.jwks_uri
== "https://login.microsoftonline.com/test-tenant/discovery/v2.0/keys"
)
@@ -432,12 +434,13 @@ def test_base_authority_azure_government(self):
provider._upstream_token_endpoint
== "https://login.microsoftonline.us/gov-tenant-id/oauth2/v2.0/token"
)
+ assert isinstance(provider._token_validator, JWTVerifier)
assert (
- provider._token_validator.issuer # type: ignore[attr-defined]
+ provider._token_validator.issuer
== "https://login.microsoftonline.us/gov-tenant-id/v2.0"
)
assert (
- provider._token_validator.jwks_uri # type: ignore[attr-defined]
+ provider._token_validator.jwks_uri
== "https://login.microsoftonline.us/gov-tenant-id/discovery/v2.0/keys"
)
@@ -464,12 +467,13 @@ def test_base_authority_from_environment_variable(self):
provider._upstream_token_endpoint
== "https://login.microsoftonline.us/env-tenant-id/oauth2/v2.0/token"
)
+ assert isinstance(provider._token_validator, JWTVerifier)
assert (
- provider._token_validator.issuer # type: ignore[attr-defined]
+ provider._token_validator.issuer
== "https://login.microsoftonline.us/env-tenant-id/v2.0"
)
assert (
- provider._token_validator.jwks_uri # type: ignore[attr-defined]
+ provider._token_validator.jwks_uri
== "https://login.microsoftonline.us/env-tenant-id/discovery/v2.0/keys"
)
diff --git a/tests/server/auth/providers/test_descope.py b/tests/server/auth/providers/test_descope.py
index 7ddeeb0ee4..4682cf7a26 100644
--- a/tests/server/auth/providers/test_descope.py
+++ b/tests/server/auth/providers/test_descope.py
@@ -9,6 +9,7 @@
from fastmcp import Client, FastMCP
from fastmcp.client.transports import StreamableHttpTransport
from fastmcp.server.auth.providers.descope import DescopeProvider
+from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.utilities.tests import HeadlessOAuth, run_server_async
@@ -56,8 +57,9 @@ def test_init_with_old_env_vars(self):
assert provider.project_id == "P2oldenv123"
assert str(provider.base_url) == "https://envserver.com/"
assert str(provider.descope_base_url) == "https://api.descope.com"
+ assert isinstance(provider.token_verifier, JWTVerifier)
assert (
- provider.token_verifier.issuer # type: ignore[attr-defined]
+ provider.token_verifier.issuer
== "https://api.descope.com/v1/apps/P2oldenv123"
)
@@ -121,12 +123,12 @@ def test_backwards_compatibility_with_project_id_and_descope_base_url(self):
assert str(provider.base_url) == "https://myserver.com/"
# Check that JWT verifier uses the old issuer format
+ assert isinstance(provider.token_verifier, JWTVerifier)
assert (
- provider.token_verifier.issuer # type: ignore[attr-defined]
- == "https://api.descope.com/v1/apps/P2abc123"
+ provider.token_verifier.issuer == "https://api.descope.com/v1/apps/P2abc123"
)
assert (
- provider.token_verifier.jwks_uri # type: ignore[attr-defined]
+ provider.token_verifier.jwks_uri
== "https://api.descope.com/P2abc123/.well-known/jwks.json"
)
@@ -139,9 +141,9 @@ def test_backwards_compatibility_descope_base_url_without_scheme(self):
)
assert str(provider.descope_base_url) == "https://api.descope.com"
+ assert isinstance(provider.token_verifier, JWTVerifier)
assert (
- provider.token_verifier.issuer # type: ignore[attr-defined]
- == "https://api.descope.com/v1/apps/P2abc123"
+ provider.token_verifier.issuer == "https://api.descope.com/v1/apps/P2abc123"
)
def test_config_url_takes_precedence_over_old_api(self):
@@ -156,8 +158,9 @@ def test_config_url_takes_precedence_over_old_api(self):
# Should use values from config_url, not the old API
assert provider.project_id == "P2new123"
assert str(provider.descope_base_url) == "https://api.descope.com"
+ assert isinstance(provider.token_verifier, JWTVerifier)
assert (
- provider.token_verifier.issuer # type: ignore[attr-defined]
+ provider.token_verifier.issuer
== "https://api.descope.com/v1/apps/agentic/P2new123/M123"
)
@@ -172,14 +175,14 @@ def test_jwt_verifier_configured_correctly(self):
)
# Check that JWT verifier uses the correct endpoints
+ assert isinstance(provider.token_verifier, JWTVerifier)
assert (
- provider.token_verifier.jwks_uri # type: ignore[attr-defined]
+ provider.token_verifier.jwks_uri
== "https://api.descope.com/P2abc123/.well-known/jwks.json"
)
- assert (
- provider.token_verifier.issuer == issuer_url # type: ignore[attr-defined]
- )
- assert provider.token_verifier.audience == "P2abc123" # type: ignore[attr-defined]
+ assert provider.token_verifier.issuer == issuer_url
+ assert isinstance(provider.token_verifier, JWTVerifier)
+ assert provider.token_verifier.audience == "P2abc123"
def test_required_scopes_support(self):
"""Test that required_scopes are supported and passed to JWT verifier."""
@@ -190,7 +193,8 @@ def test_required_scopes_support(self):
)
# Check that required_scopes are set on the token verifier
- assert provider.token_verifier.required_scopes == ["read", "write"] # type: ignore[attr-defined]
+ assert isinstance(provider.token_verifier, JWTVerifier)
+ assert provider.token_verifier.required_scopes == ["read", "write"]
def test_required_scopes_with_old_api(self):
"""Test that required_scopes work with the old API (project_id + descope_base_url)."""
@@ -202,7 +206,8 @@ def test_required_scopes_with_old_api(self):
)
# Check that required_scopes are set on the token verifier
- assert provider.token_verifier.required_scopes == ["openid", "email"] # type: ignore[attr-defined]
+ assert isinstance(provider.token_verifier, JWTVerifier)
+ assert provider.token_verifier.required_scopes == ["openid", "email"]
def test_required_scopes_from_env(self):
"""Test that required_scopes can be set via environment variable."""
@@ -216,7 +221,8 @@ def test_required_scopes_from_env(self):
):
provider = DescopeProvider()
- assert provider.token_verifier.required_scopes == ["read", "write"] # type: ignore[attr-defined]
+ assert isinstance(provider.token_verifier, JWTVerifier)
+ assert provider.token_verifier.required_scopes == ["read", "write"]
@pytest.fixture
diff --git a/tests/server/auth/providers/test_scalekit.py b/tests/server/auth/providers/test_scalekit.py
index 70470a1492..d43b1b92ce 100644
--- a/tests/server/auth/providers/test_scalekit.py
+++ b/tests/server/auth/providers/test_scalekit.py
@@ -8,6 +8,7 @@
from fastmcp import Client, FastMCP
from fastmcp.client.transports import StreamableHttpTransport
+from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.server.auth.providers.scalekit import ScalekitProvider
from fastmcp.utilities.tests import HeadlessOAuth, run_server_async
@@ -125,16 +126,10 @@ def test_jwt_verifier_configured_correctly(self):
)
# Check that JWT verifier uses the correct endpoints
- assert (
- provider.token_verifier.jwks_uri # type: ignore[attr-defined]
- == "https://my-env.scalekit.com/keys"
- )
- assert (
- provider.token_verifier.issuer == "https://my-env.scalekit.com" # type: ignore[attr-defined]
- )
- assert (
- provider.token_verifier.audience is None # type: ignore[attr-defined]
- )
+ assert isinstance(provider.token_verifier, JWTVerifier)
+ assert provider.token_verifier.jwks_uri == "https://my-env.scalekit.com/keys"
+ assert provider.token_verifier.issuer == "https://my-env.scalekit.com"
+ assert provider.token_verifier.audience is None
def test_required_scopes_hooks_into_verifier(self):
"""Token verifier should enforce required scopes when provided."""
@@ -145,7 +140,8 @@ def test_required_scopes_hooks_into_verifier(self):
required_scopes=["read"],
)
- assert provider.token_verifier.required_scopes == ["read"] # type: ignore[attr-defined]
+ assert isinstance(provider.token_verifier, JWTVerifier)
+ assert provider.token_verifier.required_scopes == ["read"]
def test_authorization_servers_configuration(self):
"""Test that authorization servers are configured correctly."""
diff --git a/tests/server/auth/providers/test_supabase.py b/tests/server/auth/providers/test_supabase.py
index 7cdc081309..6799d6d000 100644
--- a/tests/server/auth/providers/test_supabase.py
+++ b/tests/server/auth/providers/test_supabase.py
@@ -9,6 +9,7 @@
from fastmcp import Client, FastMCP
from fastmcp.client.transports import StreamableHttpTransport
+from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.server.auth.providers.supabase import SupabaseProvider
from fastmcp.utilities.tests import HeadlessOAuth, run_server_in_process
@@ -81,14 +82,13 @@ def test_jwt_verifier_configured_correctly(self):
)
# Check that JWT verifier uses the correct endpoints
+ assert isinstance(provider.token_verifier, JWTVerifier)
assert (
- provider.token_verifier.jwks_uri # type: ignore[attr-defined]
+ provider.token_verifier.jwks_uri
== "https://abc123.supabase.co/auth/v1/.well-known/jwks.json"
)
- assert (
- provider.token_verifier.issuer == "https://abc123.supabase.co/auth/v1" # type: ignore[attr-defined]
- )
- assert provider.token_verifier.algorithm == "ES256" # type: ignore[attr-defined]
+ assert provider.token_verifier.issuer == "https://abc123.supabase.co/auth/v1"
+ assert provider.token_verifier.algorithm == "ES256"
def test_jwt_verifier_with_required_scopes(self):
"""Test that JWT verifier respects required_scopes."""
@@ -98,7 +98,8 @@ def test_jwt_verifier_with_required_scopes(self):
required_scopes=["openid", "email"],
)
- assert provider.token_verifier.required_scopes == ["openid", "email"] # type: ignore[attr-defined]
+ assert isinstance(provider.token_verifier, JWTVerifier)
+ assert provider.token_verifier.required_scopes == ["openid", "email"]
def test_authorization_servers_configured(self):
"""Test that authorization servers list is configured correctly."""
@@ -125,7 +126,8 @@ def test_algorithm_configuration(self, algorithm):
algorithm=algorithm,
)
- assert provider.token_verifier.algorithm == algorithm # type: ignore[attr-defined]
+ assert isinstance(provider.token_verifier, JWTVerifier)
+ assert provider.token_verifier.algorithm == algorithm
def test_algorithm_default_es256(self):
"""Test that algorithm defaults to ES256 when not specified."""
@@ -134,7 +136,8 @@ def test_algorithm_default_es256(self):
base_url="https://myserver.com",
)
- assert provider.token_verifier.algorithm == "ES256" # type: ignore[attr-defined]
+ assert isinstance(provider.token_verifier, JWTVerifier)
+ assert provider.token_verifier.algorithm == "ES256"
def test_algorithm_from_env_var(self):
"""Test that algorithm can be configured via environment variable."""
@@ -148,7 +151,8 @@ def test_algorithm_from_env_var(self):
):
provider = SupabaseProvider()
- assert provider.token_verifier.algorithm == "RS256" # type: ignore[attr-defined]
+ assert isinstance(provider.token_verifier, JWTVerifier)
+ assert provider.token_verifier.algorithm == "RS256"
def run_mcp_server(host: str, port: int) -> None:
diff --git a/tests/server/http/test_bearer_auth_backend.py b/tests/server/http/test_bearer_auth_backend.py
index a1aaf77aa9..2bafa24547 100644
--- a/tests/server/http/test_bearer_auth_backend.py
+++ b/tests/server/http/test_bearer_auth_backend.py
@@ -4,7 +4,7 @@
from mcp.server.auth.middleware.bearer_auth import BearerAuthBackend
from starlette.requests import HTTPConnection
-from fastmcp.server.auth import AccessToken
+from fastmcp.server.auth import AccessToken, TokenVerifier
from fastmcp.server.auth.providers.jwt import JWTVerifier, RSAKeyPair
@@ -41,7 +41,8 @@ def test_bearer_auth_backend_constructor_accepts_token_verifier(
"""Test that BearerAuthBackend constructor accepts TokenVerifier."""
# This should not raise an error
backend = BearerAuthBackend(jwt_verifier)
- assert backend.token_verifier is jwt_verifier # type: ignore[attr-defined]
+ assert isinstance(backend.token_verifier, TokenVerifier)
+ assert backend.token_verifier is jwt_verifier
async def test_bearer_auth_backend_authenticate_with_valid_token(
self, jwt_verifier: JWTVerifier, valid_token: str
diff --git a/tests/server/http/test_http_dependencies.py b/tests/server/http/test_http_dependencies.py
index bd8da0c4ec..a4b9659a61 100644
--- a/tests/server/http/test_http_dependencies.py
+++ b/tests/server/http/test_http_dependencies.py
@@ -1,6 +1,7 @@
import json
import pytest
+from mcp.types import TextContent, TextResourceContents
from fastmcp.client import Client
from fastmcp.client.transports import SSETransport, StreamableHttpTransport
@@ -61,7 +62,8 @@ async def test_http_headers_resource_shttp(shttp_server: str):
)
) as client:
raw_result = await client.read_resource("request://headers")
- json_result = json.loads(raw_result[0].text) # type: ignore[attr-defined]
+ assert isinstance(raw_result[0], TextResourceContents)
+ json_result = json.loads(raw_result[0].text)
assert "x-demo-header" in json_result
assert json_result["x-demo-header"] == "ABC"
@@ -72,7 +74,8 @@ async def test_http_headers_resource_sse(sse_server: str):
transport=SSETransport(sse_server, headers={"X-DEMO-HEADER": "ABC"})
) as client:
raw_result = await client.read_resource("request://headers")
- json_result = json.loads(raw_result[0].text) # type: ignore[attr-defined]
+ assert isinstance(raw_result[0], TextResourceContents)
+ json_result = json.loads(raw_result[0].text)
assert "x-demo-header" in json_result
assert json_result["x-demo-header"] == "ABC"
@@ -106,7 +109,8 @@ async def test_http_headers_prompt_shttp(shttp_server: str):
)
) as client:
result = await client.get_prompt("get_headers_prompt")
- json_result = json.loads(result.messages[0].content.text) # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ json_result = json.loads(result.messages[0].content.text)
assert "x-demo-header" in json_result
assert json_result["x-demo-header"] == "ABC"
@@ -117,6 +121,7 @@ async def test_http_headers_prompt_sse(sse_server: str):
transport=SSETransport(sse_server, headers={"X-DEMO-HEADER": "ABC"})
) as client:
result = await client.get_prompt("get_headers_prompt")
- json_result = json.loads(result.messages[0].content.text) # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ json_result = json.loads(result.messages[0].content.text)
assert "x-demo-header" in json_result
assert json_result["x-demo-header"] == "ABC"
diff --git a/tests/server/middleware/test_initialization_middleware.py b/tests/server/middleware/test_initialization_middleware.py
index 5c2266268e..ae5aba5d38 100644
--- a/tests/server/middleware/test_initialization_middleware.py
+++ b/tests/server/middleware/test_initialization_middleware.py
@@ -5,7 +5,7 @@
import mcp.types as mt
import pytest
from mcp import McpError
-from mcp.types import ErrorData
+from mcp.types import ErrorData, TextContent
from fastmcp import Client, FastMCP
from fastmcp.server.middleware import CallNext, Middleware, MiddlewareContext
@@ -138,7 +138,8 @@ def test_tool(x: int) -> str:
# Test that the tool still works
result = await client.call_tool("test_tool", {"x": 42})
- assert result.content[0].text == "Result: 42" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "Result: 42"
async def test_client_detection_middleware():
@@ -245,7 +246,8 @@ def test_tool() -> str:
# Call a tool - state should be accessible
result = await client.call_tool("test_tool", {})
- assert result.content[0].text == "success" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "success"
# State should have been accessible during tool call
# Note: State is request-scoped, so it won't persist across requests
diff --git a/tests/server/middleware/test_middleware.py b/tests/server/middleware/test_middleware.py
index 46dda31819..c21291befa 100644
--- a/tests/server/middleware/test_middleware.py
+++ b/tests/server/middleware/test_middleware.py
@@ -439,13 +439,19 @@ async def on_call_tool(
):
# modify argument
if context.message.name == "add":
- context.message.arguments["a"] += 100 # type: ignore
+ assert context.message.arguments is not None
+ args = context.message.arguments
+ assert isinstance(args["a"], int)
+ args["a"] += 100
result = await call_next(context)
# modify result
if context.message.name == "add":
- result.structured_content["result"] += 5 # type: ignore
+ assert result.structured_content is not None
+ content = result.structured_content
+ assert isinstance(content["result"], int)
+ content["result"] += 5
return result
@@ -454,7 +460,8 @@ async def on_call_tool(
async with Client(server) as client:
result = await client.call_tool("add", {"a": 1, "b": 2})
- assert result.structured_content["result"] == 108 # type: ignore
+ assert isinstance(result.structured_content["result"], int)
+ assert result.structured_content["result"] == 108
class TestNestedMiddlewareHooks:
diff --git a/tests/server/middleware/test_tool_injection.py b/tests/server/middleware/test_tool_injection.py
index c8aee1875d..0b4e34a48f 100644
--- a/tests/server/middleware/test_tool_injection.py
+++ b/tests/server/middleware/test_tool_injection.py
@@ -93,7 +93,8 @@ async def test_call_injected_tool(self, base_server: FastMCP):
)
assert result.structured_content is not None
- assert result.structured_content["result"] == 42 # type: ignore[attr-defined]
+ assert isinstance(result.structured_content, dict)
+ assert result.structured_content["result"] == 42
async def test_call_base_tool_still_works(self, base_server: FastMCP):
"""Test that base server tools still work after injecting tools."""
@@ -110,7 +111,8 @@ async def test_call_base_tool_still_works(self, base_server: FastMCP):
)
assert result.structured_content is not None
- assert result.structured_content["result"] == 15 # type: ignore[attr-defined]
+ assert isinstance(result.structured_content, dict)
+ assert result.structured_content["result"] == 15
async def test_injected_tool_error_handling(self, base_server: FastMCP):
"""Test that errors in injected tools are properly handled."""
@@ -161,11 +163,13 @@ def modulo(a: int, b: int) -> int:
async with Client(base_server) as client:
power_result = await client.call_tool("power", {"a": 2, "b": 3})
assert power_result.structured_content is not None
- assert power_result.structured_content["result"] == 8 # type: ignore[attr-defined]
+ assert isinstance(power_result.structured_content, dict)
+ assert power_result.structured_content["result"] == 8
modulo_result = await client.call_tool("modulo", {"a": 10, "b": 3})
assert modulo_result.structured_content is not None
- assert modulo_result.structured_content["result"] == 1 # type: ignore[attr-defined]
+ assert isinstance(modulo_result.structured_content, dict)
+ assert modulo_result.structured_content["result"] == 1
async def test_injected_tool_with_complex_return_type(self, base_server: FastMCP):
"""Test injected tools with complex return types."""
@@ -270,7 +274,8 @@ async def test_empty_tool_injection(self, base_server: FastMCP):
assert "add" in tool_names
assert "subtract" in tool_names
assert result.structured_content is not None
- assert result.structured_content["result"] == 7 # type: ignore[attr-defined]
+ assert isinstance(result.structured_content, dict)
+ assert result.structured_content["result"] == 7
class TestPromptToolMiddleware:
diff --git a/tests/server/providers/test_fastmcp_provider.py b/tests/server/providers/test_fastmcp_provider.py
index 0ef7b266b5..49b3a7a05a 100644
--- a/tests/server/providers/test_fastmcp_provider.py
+++ b/tests/server/providers/test_fastmcp_provider.py
@@ -179,7 +179,8 @@ def my_resource() -> str:
async with Client(main) as client:
result = await client.read_resource("resource://data")
- assert result[0].text == "content" # type: ignore[attr-defined]
+ assert isinstance(result[0], mt.TextResourceContents)
+ assert result[0].text == "content"
class TestResourceTemplateOperations:
@@ -225,7 +226,8 @@ def my_template(id: str) -> str:
async with Client(main) as client:
result = await client.read_resource("resource://123/data")
- assert result[0].text == "data for 123" # type: ignore[attr-defined]
+ assert isinstance(result[0], mt.TextResourceContents)
+ assert result[0].text == "data for 123"
class TestPromptOperations:
@@ -277,7 +279,8 @@ def greet(name: str) -> str:
async with Client(main) as client:
result = await client.get_prompt("greet", {"name": "World"})
- assert result.messages[0].content.text == "Hello, World!" # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, mt.TextContent)
+ assert result.messages[0].content.text == "Hello, World!"
class TestServerReference:
@@ -359,7 +362,8 @@ async def get_data() -> str:
async with Client(parent) as client:
result = await client.read_resource("data://c/gc/value")
- assert result[0].text == "result" # type: ignore[attr-defined]
+ assert isinstance(result[0], mt.TextResourceContents)
+ assert result[0].text == "result"
assert calls == [
"parent:before",
@@ -394,7 +398,8 @@ async def greet(name: str) -> str:
async with Client(parent) as client:
result = await client.get_prompt("c_gc_greet", {"name": "World"})
- assert result.messages[0].content.text == "Hello, World!" # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, mt.TextContent)
+ assert result.messages[0].content.text == "Hello, World!"
assert calls == [
"parent:before",
@@ -429,7 +434,8 @@ async def get_item(id: str) -> str:
async with Client(parent) as client:
result = await client.read_resource("item://c/gc/42")
- assert result[0].text == "item-42" # type: ignore[attr-defined]
+ assert isinstance(result[0], mt.TextResourceContents)
+ assert result[0].text == "item-42"
assert calls == [
"parent:before",
diff --git a/tests/server/proxy/test_proxy_client.py b/tests/server/proxy/test_proxy_client.py
index c15374a4aa..45a8edafc1 100644
--- a/tests/server/proxy/test_proxy_client.py
+++ b/tests/server/proxy/test_proxy_client.py
@@ -16,6 +16,7 @@
from fastmcp.client.logging import LogMessage
from fastmcp.client.sampling import RequestContext, SamplingMessage, SamplingParams
from fastmcp.exceptions import ToolError
+from fastmcp.server.elicitation import AcceptedElicitation
from fastmcp.server.proxy import ProxyClient
@@ -57,7 +58,9 @@ async def elicit(context: Context) -> str:
)
if result.action == "accept":
- return f"Hello, {result.data.name}!" # type: ignore[attr-defined]
+ assert isinstance(result, AcceptedElicitation)
+ assert isinstance(result.data, Person)
+ return f"Hello, {result.data.name}!"
else:
return "No name provided."
@@ -368,7 +371,9 @@ class TestModel(BaseModel):
)
if result.action == "accept":
- return f"Content: {result.data.content}, Acknowledge: {result.data.acknowledge}" # type: ignore[attr-defined]
+ assert isinstance(result, AcceptedElicitation)
+ assert isinstance(result.data, TestModel)
+ return f"Content: {result.data.content}, Acknowledge: {result.data.acknowledge}"
else:
return f"Elicitation {result.action}"
diff --git a/tests/server/proxy/test_proxy_server.py b/tests/server/proxy/test_proxy_server.py
index 3a4344f634..0c9e3170d8 100644
--- a/tests/server/proxy/test_proxy_server.py
+++ b/tests/server/proxy/test_proxy_server.py
@@ -6,7 +6,7 @@
from anyio import create_task_group
from dirty_equals import Contains
from mcp import McpError
-from mcp.types import Icon
+from mcp.types import Icon, TextContent, TextResourceContents
from pydantic import AnyUrl
from fastmcp import FastMCP
@@ -132,7 +132,7 @@ def test_as_proxy_with_url():
assert isinstance(proxy, FastMCPProxy)
client = cast(Client, proxy.client_factory())
assert isinstance(client.transport, StreamableHttpTransport)
- assert client.transport.url == "http://example.com/mcp/" # type: ignore[attr-defined]
+ assert client.transport.url == "http://example.com/mcp/"
async def test_proxy_with_async_client_factory():
@@ -147,7 +147,7 @@ async def async_factory():
client = await proxy.client_factory() # type: ignore[misc]
assert isinstance(client, Client)
assert isinstance(client.transport, StreamableHttpTransport)
- assert client.transport.url == "http://example.com/mcp/" # type: ignore[attr-defined]
+ assert client.transport.url == "http://example.com/mcp/"
class TestTools:
@@ -231,7 +231,8 @@ def tool_with_meta(value: str) -> ToolResult:
async with Client(proxy_server) as client:
result = await client.call_tool("tool_with_meta", {"value": "test"})
- assert result.content[0].text == "Result: test" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "Result: test"
assert result.meta == {"custom_key": "custom_value", "processed": True}
async def test_proxy_can_overwrite_proxied_tool(self, proxy_server):
@@ -315,7 +316,8 @@ async def test_list_resources_same_as_original(self, fastmcp_server, proxy_serve
async def test_read_resource(self, proxy_server: FastMCPProxy):
async with Client(proxy_server) as client:
result = await client.read_resource("resource://wave")
- assert result[0].text == "👋" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "👋"
async def test_read_resource_same_as_original(self, fastmcp_server, proxy_server):
async with Client(fastmcp_server) as client:
@@ -327,7 +329,8 @@ async def test_read_resource_same_as_original(self, fastmcp_server, proxy_server
async def test_read_json_resource(self, proxy_server: FastMCPProxy):
async with Client(proxy_server) as client:
result = await client.read_resource("data://users")
- assert json.loads(result[0].text) == USERS # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert json.loads(result[0].text) == USERS
async def test_read_resource_returns_none_if_not_found(self, proxy_server):
with pytest.raises(
@@ -347,7 +350,8 @@ def overwritten_wave() -> str:
async with Client(proxy_server) as client:
result = await client.read_resource("resource://wave")
- assert result[0].text == "Overwritten wave! 🌊" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Overwritten wave! 🌊"
async def test_proxy_errors_if_overwritten_resource_is_disabled(self, proxy_server):
"""
@@ -420,7 +424,8 @@ async def test_list_resource_templates_same_as_original(
async def test_read_resource_template(self, proxy_server: FastMCPProxy, id: int):
async with Client(proxy_server) as client:
result = await client.read_resource(f"data://user/{id}")
- assert json.loads(result[0].text) == USERS[id - 1] # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert json.loads(result[0].text) == USERS[id - 1]
async def test_read_resource_template_same_as_original(
self, fastmcp_server, proxy_server
@@ -447,7 +452,8 @@ def overwritten_get_user(user_id: str) -> dict[str, Any]:
async with Client(proxy_server) as client:
result = await client.read_resource("data://user/1")
- user_data = json.loads(result[0].text) # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ user_data = json.loads(result[0].text)
assert user_data["name"] == "Overwritten User"
assert user_data["extra"] == "data"
@@ -537,7 +543,8 @@ async def test_render_prompt_calls_prompt(self, proxy_server):
async with Client(proxy_server) as client:
result = await client.get_prompt("welcome", {"name": "Alice"})
assert result.messages[0].role == "user"
- assert result.messages[0].content.text == "Welcome to FastMCP, Alice!" # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert result.messages[0].content.text == "Welcome to FastMCP, Alice!"
async def test_proxy_can_overwrite_proxied_prompt(self, proxy_server):
"""
@@ -553,8 +560,9 @@ def welcome(name: str, extra: str = "friend") -> str:
"welcome", {"name": "Alice", "extra": "colleague"}
)
assert result.messages[0].role == "user"
+ assert isinstance(result.messages[0].content, TextContent)
assert (
- result.messages[0].content.text # type: ignore[attr-defined]
+ result.messages[0].content.text
== "Overwritten welcome, Alice! You are my colleague."
)
diff --git a/tests/server/tasks/test_task_config_modes.py b/tests/server/tasks/test_task_config_modes.py
index d2e7510b5d..dbd77fd23a 100644
--- a/tests/server/tasks/test_task_config_modes.py
+++ b/tests/server/tasks/test_task_config_modes.py
@@ -8,11 +8,14 @@
import pytest
from mcp.shared.exceptions import McpError
+from mcp.types import TextContent, ToolExecution
+from mcp.types import Tool as MCPTool
from fastmcp import FastMCP
from fastmcp.client import Client
from fastmcp.exceptions import ToolError
from fastmcp.server.tasks import TaskConfig
+from fastmcp.tools.tool import Tool
class TestTaskConfigNormalization:
@@ -27,8 +30,8 @@ async def my_tool() -> str:
return "ok"
tool = await mcp._tool_manager.get_tool("my_tool")
- assert tool is not None
- assert tool.task_config.mode == "optional" # type: ignore[attr-defined]
+ assert isinstance(tool, Tool)
+ assert tool.task_config.mode == "optional"
async def test_task_false_normalizes_to_forbidden(self):
"""task=False should normalize to TaskConfig(mode='forbidden')."""
@@ -39,8 +42,8 @@ async def my_tool() -> str:
return "ok"
tool = await mcp._tool_manager.get_tool("my_tool")
- assert tool is not None
- assert tool.task_config.mode == "forbidden" # type: ignore[attr-defined]
+ assert isinstance(tool, Tool)
+ assert tool.task_config.mode == "forbidden"
async def test_task_config_passed_directly(self):
"""TaskConfig should be preserved when passed directly."""
@@ -51,8 +54,8 @@ async def my_tool() -> str:
return "ok"
tool = await mcp._tool_manager.get_tool("my_tool")
- assert tool is not None
- assert tool.task_config.mode == "required" # type: ignore[attr-defined]
+ assert isinstance(tool, Tool)
+ assert tool.task_config.mode == "required"
async def test_default_task_inherits_server_default(self):
"""Default task value should inherit from server default."""
@@ -64,8 +67,8 @@ def my_tool_sync() -> str:
return "ok"
tool = await mcp_no_tasks._tool_manager.get_tool("my_tool_sync")
- assert tool is not None
- assert tool.task_config.mode == "forbidden" # type: ignore[attr-defined]
+ assert isinstance(tool, Tool)
+ assert tool.task_config.mode == "forbidden"
# Server with tasks enabled
mcp_tasks = FastMCP("test", tasks=True)
@@ -75,8 +78,8 @@ async def my_tool_async() -> str:
return "ok"
tool2 = await mcp_tasks._tool_manager.get_tool("my_tool_async")
- assert tool2 is not None
- assert tool2.task_config.mode == "optional" # type: ignore[attr-defined]
+ assert isinstance(tool2, Tool)
+ assert tool2.task_config.mode == "optional"
class TestToolModeEnforcement:
@@ -254,7 +257,8 @@ async def test_forbidden_prompt_without_task_succeeds(self, server):
"""Forbidden mode succeeds when called without task metadata."""
async with Client(server) as client:
result = await client.get_prompt("forbidden_prompt")
- assert "forbidden message" in str(result.messages[0].content) # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert "forbidden message" in str(result.messages[0].content)
class TestToolExecutionMetadata:
@@ -271,8 +275,9 @@ async def my_tool() -> str:
async with Client(mcp) as client:
tools = await client.list_tools()
tool = next(t for t in tools if t.name == "my_tool")
- assert tool.execution is not None
- assert tool.execution.taskSupport == "optional" # type: ignore[attr-defined]
+ assert isinstance(tool, MCPTool)
+ assert isinstance(tool.execution, ToolExecution)
+ assert tool.execution.taskSupport == "optional"
async def test_required_tool_exposes_task_support(self):
"""Tools with mode=required should expose taskSupport='required'."""
@@ -285,8 +290,9 @@ async def my_tool() -> str:
async with Client(mcp) as client:
tools = await client.list_tools()
tool = next(t for t in tools if t.name == "my_tool")
- assert tool.execution is not None
- assert tool.execution.taskSupport == "required" # type: ignore[attr-defined]
+ assert isinstance(tool, MCPTool)
+ assert isinstance(tool.execution, ToolExecution)
+ assert tool.execution.taskSupport == "required"
async def test_forbidden_tool_has_no_execution(self):
"""Tools with mode=forbidden should not expose execution metadata."""
@@ -344,5 +350,5 @@ def sync_tool() -> str:
return "ok"
tool = await mcp._tool_manager.get_tool("sync_tool")
- assert tool is not None
- assert tool.task_config.mode == "forbidden" # type: ignore[attr-defined]
+ assert isinstance(tool, Tool)
+ assert tool.task_config.mode == "forbidden"
diff --git a/tests/server/tasks/test_task_proxy.py b/tests/server/tasks/test_task_proxy.py
index 2d5da5b753..0bf37a116e 100644
--- a/tests/server/tasks/test_task_proxy.py
+++ b/tests/server/tasks/test_task_proxy.py
@@ -12,6 +12,7 @@
import pytest
from mcp.shared.exceptions import McpError
+from mcp.types import TextContent, TextResourceContents
from fastmcp import FastMCP
from fastmcp.client import Client
@@ -114,7 +115,8 @@ async def test_prompt_sync_execution_works(self, proxy_server: FastMCP):
"""Prompt called without task=True works through proxy."""
async with Client(proxy_server) as client:
result = await client.get_prompt("greeting_prompt", {"name": "Alice"})
- assert "Hello, Alice!" in result.messages[0].content.text # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert "Hello, Alice!" in result.messages[0].content.text
class TestProxyPromptsTaskForbidden:
@@ -136,13 +138,15 @@ async def test_resource_sync_execution_works(self, proxy_server: FastMCP):
"""Resource read without task=True works through proxy."""
async with Client(proxy_server) as client:
result = await client.read_resource("data://info.txt")
- assert "Important information from the backend" in result[0].text # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert "Important information from the backend" in result[0].text
async def test_resource_template_sync_execution_works(self, proxy_server: FastMCP):
"""Resource template without task=True works through proxy."""
async with Client(proxy_server) as client:
result = await client.read_resource("data://user/42.json")
- assert '"id": "42"' in result[0].text # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert '"id": "42"' in result[0].text
class TestProxyResourcesTaskForbidden:
diff --git a/tests/server/test_dependencies.py b/tests/server/test_dependencies.py
index a05d3d4366..61522f4617 100644
--- a/tests/server/test_dependencies.py
+++ b/tests/server/test_dependencies.py
@@ -755,7 +755,8 @@ async def my_tool(client_id: str = Depends(validate_client_id)) -> str:
result = await client.call_tool("my_tool", {}, raise_on_error=False)
assert result.is_error
# The original error message should be preserved (not wrapped in RuntimeError)
- assert result.content[0].text == "Client ID is required - select a client first" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "Client ID is required - select a client first"
async def test_validation_error_propagates_from_dependency(mcp: FastMCP):
@@ -776,4 +777,5 @@ async def tool_with_validation(val: str = Depends(validate_input)) -> str:
"tool_with_validation", {}, raise_on_error=False
)
assert result.is_error
- assert result.content[0].text == "Invalid input format" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "Invalid input format"
diff --git a/tests/server/test_event_store.py b/tests/server/test_event_store.py
index d9629f7ff1..54fbfa8281 100644
--- a/tests/server/test_event_store.py
+++ b/tests/server/test_event_store.py
@@ -233,5 +233,6 @@ async def callback(event: EventMessage):
await event_store.replay_events_after(event_id, callback)
assert len(replayed) == 1
- assert replayed[0].message.root.method == "tools/call" # type: ignore[attr-defined]
- assert replayed[0].message.root.id == "request-456" # type: ignore[attr-defined]
+ assert isinstance(replayed[0].message.root, JSONRPCRequest)
+ assert replayed[0].message.root.method == "tools/call"
+ assert replayed[0].message.root.id == "request-456"
diff --git a/tests/server/test_input_validation.py b/tests/server/test_input_validation.py
index e50f2ee7e4..f986f37698 100644
--- a/tests/server/test_input_validation.py
+++ b/tests/server/test_input_validation.py
@@ -9,6 +9,7 @@
import json
import pytest
+from mcp.types import TextContent
from pydantic import BaseModel
from fastmcp import Client, FastMCP
@@ -59,7 +60,8 @@ def add_numbers(a: int, b: int) -> int:
async with Client(mcp) as client:
# String integers should be coerced to integers
result = await client.call_tool("add_numbers", {"a": "10", "b": "20"})
- assert result.content[0].text == "30" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "30"
async def test_default_is_not_strict(self):
"""By default, strict_input_validation should be False."""
@@ -73,7 +75,8 @@ def multiply(x: int, y: int) -> int:
async with Client(mcp) as client:
# Should work with string integers by default
result = await client.call_tool("multiply", {"x": "5", "y": "3"})
- assert result.content[0].text == "15" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "15"
async def test_string_float_coercion(self):
"""Test that string floats are also coerced."""
@@ -88,7 +91,8 @@ def calculate_area(length: float, width: float) -> float:
result = await client.call_tool(
"calculate_area", {"length": "10.5", "width": "20.0"}
)
- assert result.content[0].text == "210.0" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "210.0"
async def test_invalid_coercion_still_fails(self):
"""Even without strict validation, truly invalid inputs should fail."""
@@ -122,8 +126,9 @@ def create_user(profile: UserProfile) -> str:
"create_user",
{"profile": {"name": "Alice", "age": 30, "email": "alice@example.com"}},
)
- assert "Alice" in result.content[0].text # type: ignore[attr-defined]
- assert "30" in result.content[0].text # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert "Alice" in result.content[0].text
+ assert "30" in result.content[0].text
async def test_pydantic_model_with_stringified_json_no_strict(self):
"""Test if stringified JSON is accepted for Pydantic models without strict validation."""
@@ -144,7 +149,8 @@ def create_user(profile: UserProfile) -> str:
try:
result = await client.call_tool("create_user", {"profile": stringified})
# If this succeeds, we're handling stringified JSON
- assert "Bob" in result.content[0].text # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert "Bob" in result.content[0].text
stringified_json_works = True
except Exception as e:
# If this fails, we're not handling stringified JSON
@@ -182,8 +188,9 @@ def create_user(profile: UserProfile) -> str:
}
},
)
- assert "Charlie" in result.content[0].text # type: ignore[attr-defined]
- assert "35" in result.content[0].text # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert "Charlie" in result.content[0].text
+ assert "35" in result.content[0].text
async def test_pydantic_model_strict_validation(self):
"""With strict validation, Pydantic models should enforce exact types."""
@@ -292,7 +299,8 @@ def format_message(text: str, repeat: int = 1) -> str:
result = await client.call_tool(
"format_message", {"text": "hi", "repeat": "3"}
)
- assert result.content[0].text == "hihihi" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "hihihi"
async def test_none_values(self):
"""Test handling of None values."""
@@ -305,7 +313,8 @@ def process_optional(value: int | None) -> str:
async with Client(mcp) as client:
result = await client.call_tool("process_optional", {"value": None})
- assert "None" in result.content[0].text # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert "None" in result.content[0].text
async def test_empty_string_to_int(self):
"""Empty strings should fail conversion to int."""
@@ -332,11 +341,13 @@ def toggle(enabled: bool) -> str:
async with Client(mcp) as client:
# String "true" should be coerced to boolean
result = await client.call_tool("toggle", {"enabled": "true"})
- assert "enabled" in result.content[0].text.lower() # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert "enabled" in result.content[0].text.lower()
# String "false" should be coerced to boolean
result = await client.call_tool("toggle", {"enabled": "false"})
- assert "disabled" in result.content[0].text.lower() # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert "disabled" in result.content[0].text.lower()
async def test_list_of_integers_with_string_elements(self):
"""Test lists containing string representations of integers."""
@@ -350,4 +361,5 @@ def sum_numbers(numbers: list[int]) -> int:
async with Client(mcp) as client:
# List with string integers
result = await client.call_tool("sum_numbers", {"numbers": ["1", "2", "3"]})
- assert result.content[0].text == "6" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "6"
diff --git a/tests/server/test_mount.py b/tests/server/test_mount.py
index 6152882536..98a209a83d 100644
--- a/tests/server/test_mount.py
+++ b/tests/server/test_mount.py
@@ -3,6 +3,7 @@
from contextlib import asynccontextmanager
import pytest
+from mcp.types import TextContent, TextResourceContents
from fastmcp import FastMCP
from fastmcp.client import Client
@@ -144,7 +145,8 @@ def sub_resource():
# Test actual functionality
async with Client(main_app) as client:
resource_result = await client.read_resource("data://config")
- assert resource_result[0].text == "Sub resource data" # type: ignore[attr-defined]
+ assert isinstance(resource_result[0], TextResourceContents)
+ assert resource_result[0].text == "Sub resource data"
async def test_mount_resource_templates_no_prefix(self):
"""Test mounting a server with resource templates without prefix."""
@@ -165,7 +167,8 @@ def sub_template(user_id: str):
# Test actual functionality
async with Client(main_app) as client:
template_result = await client.read_resource("users://123/info")
- assert template_result[0].text == "Sub template for user 123" # type: ignore[attr-defined]
+ assert isinstance(template_result[0], TextResourceContents)
+ assert template_result[0].text == "Sub template for user 123"
async def test_mount_prompts_no_prefix(self):
"""Test mounting a server with prompts without prefix."""
@@ -413,7 +416,8 @@ def second_resource():
# Test that reading the resource uses the first server's implementation
result = await client.read_resource("shared://data")
- assert result[0].text == "First app data" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "First app data"
async def test_first_server_wins_resources_same_prefix(self):
"""Test that first mounted server wins for resources when same prefix is used."""
@@ -444,7 +448,8 @@ def second_resource():
# Test that reading the resource uses the first server's implementation
result = await client.read_resource("shared://api/data")
- assert result[0].text == "First app data" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "First app data"
async def test_first_server_wins_resource_templates_no_prefix(self):
"""Test that first mounted server wins for resource templates when no prefix is used."""
@@ -475,7 +480,8 @@ def second_template(user_id: str):
# Test that reading the resource uses the first server's implementation
result = await client.read_resource("users://123/profile")
- assert result[0].text == "First app user 123" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "First app user 123"
async def test_first_server_wins_resource_templates_same_prefix(self):
"""Test that first mounted server wins for resource templates when same prefix is used."""
@@ -506,7 +512,8 @@ def second_template(user_id: str):
# Test that reading the resource uses the first server's implementation
result = await client.read_resource("users://api/123/profile")
- assert result[0].text == "First app user 123" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "First app user 123"
async def test_first_server_wins_prompts_no_prefix(self):
"""Test that first mounted server wins for prompts when no prefix is used."""
@@ -536,7 +543,8 @@ def second_shared_prompt() -> str:
# Test that getting the prompt uses the first server's implementation
result = await client.get_prompt("shared_prompt", {})
assert result.messages is not None
- assert result.messages[0].content.text == "First app prompt" # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert result.messages[0].content.text == "First app prompt"
async def test_first_server_wins_prompts_same_prefix(self):
"""Test that first mounted server wins for prompts when same prefix is used."""
@@ -568,7 +576,8 @@ def second_shared_prompt() -> str:
# Test that getting the prompt uses the first server's implementation
result = await client.get_prompt("api_shared_prompt", {})
assert result.messages is not None
- assert result.messages[0].content.text == "First app prompt" # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert result.messages[0].content.text == "First app prompt"
class TestDynamicChanges:
@@ -646,7 +655,8 @@ async def get_users():
# Check that resource can be accessed
async with Client(main_app) as client:
result = await client.read_resource("data://data/users")
- assert json.loads(result[0].text) == ["user1", "user2"] # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert json.loads(result[0].text) == ["user1", "user2"]
async def test_mount_with_resource_templates(self):
"""Test mounting a server with resource templates."""
@@ -667,7 +677,8 @@ def get_user_profile(user_id: str) -> dict:
# Check template instantiation
async with Client(main_app) as client:
result = await client.read_resource("users://api/123/profile")
- profile = json.loads(result[0].text) # type: ignore
+ assert isinstance(result[0], TextResourceContents)
+ profile = json.loads(result[0].text)
assert profile["id"] == "123"
assert profile["name"] == "User 123"
@@ -691,7 +702,8 @@ def get_config():
# Check access to the resource
async with Client(main_app) as client:
result = await client.read_resource("data://data/config")
- config = json.loads(result[0].text) # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ config = json.loads(result[0].text)
assert config["version"] == "1.0"
@@ -817,7 +829,8 @@ def get_config():
# Resource should be accessible through main app
async with Client(main_app) as client:
result = await client.read_resource("config://proxy/settings")
- config = json.loads(result[0].text) # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ config = json.loads(result[0].text)
assert config["api_key"] == "12345"
async def test_proxy_server_with_prompts(self):
@@ -1148,7 +1161,8 @@ async def test_route(request):
routes = server._get_additional_http_routes()
assert len(routes) == 1
- assert routes[0].path == "/test" # type: ignore[attr-defined]
+ assert hasattr(routes[0], "path")
+ assert routes[0].path == "/test"
async def test_mounted_servers_tracking(self):
"""Test that _providers list tracks mounted servers correctly."""
@@ -1195,7 +1209,7 @@ async def route2(request):
routes = server._get_additional_http_routes()
assert len(routes) == 2
- route_paths = [route.path for route in routes] # type: ignore[attr-defined]
+ route_paths = [route.path for route in routes if hasattr(route, "path")]
assert "/route1" in route_paths
assert "/route2" in route_paths
@@ -1306,13 +1320,15 @@ def middle_prompt(name: str) -> str:
async with Client(root) as client:
# Prompt at level 2 should work
result = await client.get_prompt("middle_middle_prompt", {"name": "World"})
- assert "Hello from middle: World" in result.messages[0].content.text # type: ignore[union-attr]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert "Hello from middle: World" in result.messages[0].content.text
# Prompt at level 3 should also work
result = await client.get_prompt(
"middle_leaf_leaf_prompt", {"name": "Test"}
)
- assert "Hello from leaf: Test" in result.messages[0].content.text # type: ignore[union-attr]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert "Hello from leaf: Test" in result.messages[0].content.text
async def test_four_level_nested_tool_invocation(self):
"""Test invoking tools from servers mounted 4 levels deep."""
diff --git a/tests/server/test_providers.py b/tests/server/test_providers.py
index 5c4761053f..205b493f52 100644
--- a/tests/server/test_providers.py
+++ b/tests/server/test_providers.py
@@ -4,7 +4,7 @@
from typing import Any
import pytest
-from mcp.types import AnyUrl
+from mcp.types import AnyUrl, TextContent
from mcp.types import Tool as MCPTool
from fastmcp import FastMCP
@@ -169,8 +169,9 @@ async def test_call_dynamic_tool(
)
assert result.structured_content is not None
- assert result.structured_content["result"] == 42 # type: ignore[attr-defined]
- assert result.structured_content["operation"] == "multiply" # type: ignore[attr-defined]
+ assert isinstance(result.structured_content, dict)
+ assert result.structured_content["result"] == 42
+ assert result.structured_content["operation"] == "multiply"
async def test_call_dynamic_tool_with_config(
self, base_server: FastMCP, dynamic_tools: list[Tool]
@@ -186,7 +187,8 @@ async def test_call_dynamic_tool_with_config(
assert result.structured_content is not None
# 5 + 3 + 100 (value offset) = 108
- assert result.structured_content["result"] == 108 # type: ignore[attr-defined]
+ assert isinstance(result.structured_content, dict)
+ assert result.structured_content["result"] == 108
async def test_call_static_tool_still_works(
self, base_server: FastMCP, dynamic_tools: list[Tool]
@@ -201,7 +203,8 @@ async def test_call_static_tool_still_works(
)
assert result.structured_content is not None
- assert result.structured_content["result"] == 15 # type: ignore[attr-defined]
+ assert isinstance(result.structured_content, dict)
+ assert result.structured_content["result"] == 15
async def test_call_tool_uses_get_tool_for_efficient_lookup(
self, base_server: FastMCP, dynamic_tools: list[Tool]
@@ -280,7 +283,8 @@ async def test_tool_not_found_falls_through_to_static(
)
assert result.structured_content is not None
- assert result.structured_content["result"] == 7 # type: ignore[attr-defined]
+ assert isinstance(result.structured_content, dict)
+ assert result.structured_content["result"] == 7
class TestProviderClass:
@@ -379,7 +383,8 @@ async def test_call_tool_default_implementation(self):
result = await client.call_tool("test_tool", {"a": 1, "b": 2})
assert result.structured_content is not None
- assert result.structured_content["result"] == 3 # type: ignore[attr-defined]
+ assert isinstance(result.structured_content, dict)
+ assert result.structured_content["result"] == 3
async def test_read_resource_default_implementation(self):
"""Test that default read_resource uses get_resource and reads it."""
@@ -448,4 +453,5 @@ async def list_prompts(self) -> Sequence[Prompt]:
result = await client.get_prompt("greeting", {"name": "World"})
assert len(result.messages) == 1
- assert result.messages[0].content.text == "Hello, World!" # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert result.messages[0].content.text == "Hello, World!"
diff --git a/tests/server/test_server.py b/tests/server/test_server.py
index 1c6f9b52f8..10b89b847f 100644
--- a/tests/server/test_server.py
+++ b/tests/server/test_server.py
@@ -5,11 +5,12 @@
import pytest
from mcp import McpError
+from mcp.types import BlobResourceContents, TextContent, TextResourceContents
from pydantic import Field
from fastmcp import Client, FastMCP
from fastmcp.exceptions import NotFoundError
-from fastmcp.prompts.prompt import FunctionPrompt, Prompt
+from fastmcp.prompts.prompt import FunctionPrompt, Prompt, PromptResult
from fastmcp.resources import Resource, ResourceContent, ResourceTemplate
from fastmcp.tools import FunctionTool
from fastmcp.tools.tool import Tool
@@ -451,7 +452,8 @@ def get_data() -> str:
async with Client(mcp) as client:
result = await client.read_resource("resource://data")
- assert result[0].text == "Hello, world!" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Hello, world!"
async def test_resource_decorator_incorrect_usage(self):
mcp = FastMCP()
@@ -478,7 +480,8 @@ def get_data() -> str:
async with Client(mcp) as client:
result = await client.read_resource("resource://data")
- assert result[0].text == "Hello, world!" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Hello, world!"
async def test_resource_decorator_with_description(self):
mcp = FastMCP()
@@ -525,7 +528,8 @@ def get_data(self) -> str:
async with Client(mcp) as client:
result = await client.read_resource("resource://data")
- assert result[0].text == "My prefix: Hello, world!" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "My prefix: Hello, world!"
async def test_resource_decorator_classmethod(self):
mcp = FastMCP()
@@ -545,7 +549,8 @@ def get_data(cls) -> str:
async with Client(mcp) as client:
result = await client.read_resource("resource://data")
- assert result[0].text == "Class prefix: Hello, world!" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Class prefix: Hello, world!"
async def test_resource_decorator_classmethod_error(self):
mcp = FastMCP()
@@ -569,7 +574,8 @@ def get_data() -> str:
async with Client(mcp) as client:
result = await client.read_resource("resource://data")
- assert result[0].text == "Static Hello, world!" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Static Hello, world!"
async def test_resource_decorator_async_function(self):
mcp = FastMCP()
@@ -580,7 +586,8 @@ async def get_data() -> str:
async with Client(mcp) as client:
result = await client.read_resource("resource://data")
- assert result[0].text == "Async Hello, world!" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Async Hello, world!"
async def test_resource_decorator_staticmethod_order(self):
"""Test that both decorator orders work for static methods"""
@@ -594,7 +601,8 @@ def get_data() -> str:
async with Client(mcp) as client:
result = await client.read_resource("resource://data")
- assert result[0].text == "Static Hello, world!" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Static Hello, world!"
async def test_resource_decorator_with_meta(self):
"""Test that meta parameter is passed through the resource decorator."""
@@ -626,10 +634,12 @@ def get_widget() -> ResourceContent:
async with Client(mcp) as client:
result = await client.read_resource("resource://widget")
assert len(result) == 1
- assert result[0].text == "content" # type: ignore[attr-defined]
- assert result[0].mimeType == "text/html" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "content"
+ assert result[0].mimeType == "text/html"
# Meta should be in the response
- assert result[0].meta == {"csp": "script-src 'self'", "version": "1.0"} # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].meta == {"csp": "script-src 'self'", "version": "1.0"}
async def test_resource_content_binary_with_meta(self):
"""Test that ResourceContent with binary content and meta works."""
@@ -647,7 +657,8 @@ def get_binary() -> ResourceContent:
assert len(result) == 1
# Binary content comes back as blob
assert hasattr(result[0], "blob")
- assert result[0].meta == {"encoding": "raw"} # type: ignore[attr-defined]
+ assert isinstance(result[0], BlobResourceContents)
+ assert result[0].meta == {"encoding": "raw"}
async def test_resource_content_without_meta(self):
"""Test that ResourceContent without meta works (meta is None)."""
@@ -660,9 +671,11 @@ def get_plain() -> ResourceContent:
async with Client(mcp) as client:
result = await client.read_resource("resource://plain")
assert len(result) == 1
- assert result[0].text == "plain content" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "plain content"
# Meta should be None
- assert result[0].meta is None # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].meta is None
class TestTemplateDecorator:
@@ -681,7 +694,8 @@ def get_data(name: str) -> str:
async with Client(mcp) as client:
result = await client.read_resource("resource://test/data")
- assert result[0].text == "Data for test" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Data for test"
async def test_template_decorator_incorrect_usage(self):
mcp = FastMCP()
@@ -708,7 +722,8 @@ def get_data(name: str) -> str:
async with Client(mcp) as client:
result = await client.read_resource("resource://test/data")
- assert result[0].text == "Data for test" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Data for test"
async def test_template_decorator_with_description(self):
mcp = FastMCP()
@@ -742,7 +757,8 @@ def get_data(self, name: str) -> str:
async with Client(mcp) as client:
result = await client.read_resource("resource://test/data")
- assert result[0].text == "My prefix: Data for test" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "My prefix: Data for test"
async def test_template_decorator_classmethod(self):
mcp = FastMCP()
@@ -763,7 +779,8 @@ def get_data(cls, name: str) -> str:
async with Client(mcp) as client:
result = await client.read_resource("resource://test/data")
- assert result[0].text == "Class prefix: Data for test" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Class prefix: Data for test"
async def test_template_decorator_staticmethod(self):
mcp = FastMCP()
@@ -776,7 +793,8 @@ def get_data(name: str) -> str:
async with Client(mcp) as client:
result = await client.read_resource("resource://test/data")
- assert result[0].text == "Static Data for test" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Static Data for test"
async def test_template_decorator_async_function(self):
mcp = FastMCP()
@@ -787,7 +805,8 @@ async def get_data(name: str) -> str:
async with Client(mcp) as client:
result = await client.read_resource("resource://test/data")
- assert result[0].text == "Async Data for test" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Async Data for test"
async def test_template_decorator_with_tags(self):
"""Test that the template decorator properly sets tags."""
@@ -843,7 +862,10 @@ def fn() -> str:
assert prompt.name == "fn"
# Don't compare functions directly since validate_call wraps them
content = await prompt.render()
- assert content.messages[0].content.text == "Hello, world!" # type: ignore[attr-defined]
+ if not isinstance(content, PromptResult):
+ content = PromptResult.from_value(content)
+ assert isinstance(content.messages[0].content, TextContent)
+ assert content.messages[0].content.text == "Hello, world!"
async def test_prompt_decorator_without_parentheses(self):
mcp = FastMCP()
@@ -861,7 +883,8 @@ def fn() -> str:
async with Client(mcp) as client:
result = await client.get_prompt("fn")
assert len(result.messages) == 1
- assert result.messages[0].content.text == "Hello, world!" # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert result.messages[0].content.text == "Hello, world!"
async def test_prompt_decorator_with_name(self):
mcp = FastMCP()
@@ -875,7 +898,10 @@ def fn() -> str:
prompt = prompts_dict["custom_name"]
assert prompt.name == "custom_name"
content = await prompt.render()
- assert content.messages[0].content.text == "Hello, world!" # type: ignore[attr-defined]
+ if not isinstance(content, PromptResult):
+ content = PromptResult.from_value(content)
+ assert isinstance(content.messages[0].content, TextContent)
+ assert content.messages[0].content.text == "Hello, world!"
async def test_prompt_decorator_with_description(self):
mcp = FastMCP()
@@ -889,7 +915,10 @@ def fn() -> str:
prompt = prompts_dict["fn"]
assert prompt.description == "A custom description"
content = await prompt.render()
- assert content.messages[0].content.text == "Hello, world!" # type: ignore[attr-defined]
+ if not isinstance(content, PromptResult):
+ content = PromptResult.from_value(content)
+ assert isinstance(content.messages[0].content, TextContent)
+ assert content.messages[0].content.text == "Hello, world!"
async def test_prompt_decorator_with_parameters(self):
mcp = FastMCP()
@@ -912,14 +941,16 @@ def test_prompt(name: str, greeting: str = "Hello") -> str:
result = await client.get_prompt("test_prompt", {"name": "World"})
assert len(result.messages) == 1
message = result.messages[0]
- assert message.content.text == "Hello, World!" # type: ignore[attr-defined]
+ assert isinstance(message.content, TextContent)
+ assert message.content.text == "Hello, World!"
result = await client.get_prompt(
"test_prompt", {"name": "World", "greeting": "Hi"}
)
assert len(result.messages) == 1
message = result.messages[0]
- assert message.content.text == "Hi, World!" # type: ignore[attr-defined]
+ assert isinstance(message.content, TextContent)
+ assert message.content.text == "Hi, World!"
async def test_prompt_decorator_instance_method(self):
mcp = FastMCP()
@@ -938,7 +969,8 @@ def test_prompt(self) -> str:
result = await client.get_prompt("test_prompt")
assert len(result.messages) == 1
message = result.messages[0]
- assert message.content.text == "My prefix: Hello, world!" # type: ignore[attr-defined]
+ assert isinstance(message.content, TextContent)
+ assert message.content.text == "My prefix: Hello, world!"
async def test_prompt_decorator_classmethod(self):
mcp = FastMCP()
@@ -956,7 +988,8 @@ def test_prompt(cls) -> str:
result = await client.get_prompt("test_prompt")
assert len(result.messages) == 1
message = result.messages[0]
- assert message.content.text == "Class prefix: Hello, world!" # type: ignore[attr-defined]
+ assert isinstance(message.content, TextContent)
+ assert message.content.text == "Class prefix: Hello, world!"
async def test_prompt_decorator_classmethod_error(self):
mcp = FastMCP()
@@ -982,7 +1015,8 @@ def test_prompt() -> str:
result = await client.get_prompt("test_prompt")
assert len(result.messages) == 1
message = result.messages[0]
- assert message.content.text == "Static Hello, world!" # type: ignore[attr-defined]
+ assert isinstance(message.content, TextContent)
+ assert message.content.text == "Static Hello, world!"
async def test_prompt_decorator_async_function(self):
mcp = FastMCP()
@@ -995,7 +1029,8 @@ async def test_prompt() -> str:
result = await client.get_prompt("test_prompt")
assert len(result.messages) == 1
message = result.messages[0]
- assert message.content.text == "Async Hello, world!" # type: ignore[attr-defined]
+ assert isinstance(message.content, TextContent)
+ assert message.content.text == "Async Hello, world!"
async def test_prompt_decorator_with_tags(self):
"""Test that the prompt decorator properly sets tags."""
@@ -1028,7 +1063,8 @@ def my_function() -> str:
async with Client(mcp) as client:
result = await client.get_prompt("string_named_prompt")
assert len(result.messages) == 1
- assert result.messages[0].content.text == "Hello from string named prompt!" # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert result.messages[0].content.text == "Hello from string named prompt!"
async def test_prompt_direct_function_call(self):
"""Test that prompts can be registered via direct function call."""
@@ -1052,7 +1088,8 @@ def standalone_function() -> str:
async with Client(mcp) as client:
result = await client.get_prompt("direct_call_prompt")
assert len(result.messages) == 1
- assert result.messages[0].content.text == "Hello from direct call!" # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert result.messages[0].content.text == "Hello from direct call!"
async def test_prompt_decorator_conflicting_names_error(self):
"""Test that providing both positional and keyword names raises an error."""
@@ -1081,7 +1118,8 @@ def test_prompt() -> str:
result = await client.get_prompt("test_prompt")
assert len(result.messages) == 1
message = result.messages[0]
- assert message.content.text == "Static Hello, world!" # type: ignore[attr-defined]
+ assert isinstance(message.content, TextContent)
+ assert message.content.text == "Static Hello, world!"
async def test_prompt_decorator_with_meta(self):
"""Test that meta parameter is passed through the prompt decorator."""
@@ -1135,17 +1173,20 @@ def get_template_resource(param: str):
async with Client(main_server) as client:
# Regular resource
result = await client.read_resource("resource://prefix/test-resource")
- assert result[0].text == "Resource content" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Resource content"
# Absolute path resource
result = await client.read_resource("resource://prefix//absolute/path")
- assert result[0].text == "Absolute resource content" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Absolute resource content"
# Template resource
result = await client.read_resource(
"resource://prefix/param-value/template"
)
- assert result[0].text == "Template resource with param-value" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Template resource with param-value"
class TestShouldIncludeComponent:
@@ -1344,4 +1385,5 @@ def greet(name: str) -> str:
# Verify it works with a client
async with Client(mcp) as client:
result = await client.call_tool("greet", {"name": "World"})
- assert result.content[0].text == "Hello, World!" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "Hello, World!"
diff --git a/tests/server/test_server_interactions.py b/tests/server/test_server_interactions.py
index e272b1e745..9ce4c60ff2 100644
--- a/tests/server/test_server_interactions.py
+++ b/tests/server/test_server_interactions.py
@@ -26,7 +26,7 @@
from fastmcp.client.client import CallToolResult
from fastmcp.client.transports import FastMCPTransport
from fastmcp.exceptions import ToolError
-from fastmcp.prompts.prompt import Prompt, PromptMessage
+from fastmcp.prompts.prompt import Prompt, PromptMessage, PromptResult
from fastmcp.resources import FileResource, ResourceTemplate
from fastmcp.resources.resource import FunctionResource
from fastmcp.tools.tool import Tool, ToolResult
@@ -156,13 +156,15 @@ async def test_list_tools(self, tool_server: FastMCP):
async def test_call_tool_mcp(self, tool_server: FastMCP):
async with Client(tool_server) as client:
result = await client.call_tool_mcp("add", {"x": 1, "y": 2})
- assert result.content[0].text == "3" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "3"
assert result.structuredContent == {"result": 3}
async def test_call_tool(self, tool_server: FastMCP):
async with Client(tool_server) as client:
result = await client.call_tool("add", {"x": 1, "y": 2})
- assert result.content[0].text == "3" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "3"
assert result.structured_content == {"result": 3}
assert result.data == 3
@@ -190,7 +192,8 @@ async def test_tool_returns_list(self, tool_server: FastMCP):
result = await client.call_tool("list_tool", {})
# Adjacent non-MCP list items are combined into single content block
assert len(result.content) == 1
- assert result.content[0].text == '["x",2]' # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == '["x",2]'
assert result.data == ["x", 2]
async def test_file_text_tool(self, tool_server: FastMCP):
@@ -914,7 +917,7 @@ async def test_simple_output_schema(self, annotation):
mcp = FastMCP()
@mcp.tool
- def f() -> annotation: # type: ignore
+ def f() -> annotation:
return "hello"
async with Client(mcp) as client:
@@ -940,7 +943,7 @@ async def test_structured_output_schema(self, annotation):
mcp = FastMCP()
@mcp.tool
- def f() -> annotation: # type: ignore[valid-type]
+ def f() -> annotation:
return {"name": "John", "age": 30}
async with Client(mcp) as client:
@@ -966,7 +969,8 @@ def f() -> int:
async with Client(mcp) as client:
result = await client.call_tool("f", {})
- assert result.content[0].text == "42" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "42"
assert result.structured_content is None
assert result.data is None
@@ -983,7 +987,8 @@ def f() -> ToolResult:
async with Client(mcp) as client:
result = await client.call_tool("f", {})
- assert result.content[0].text == "Hello, world!" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "Hello, world!"
assert result.structured_content == {"message": "Hello, world!"}
assert result.data == {"message": "Hello, world!"}
@@ -1007,7 +1012,8 @@ def simple_tool() -> int:
result = await client.call_tool("simple_tool", {})
assert result.structured_content is None
assert result.data is None
- assert result.content[0].text == "42" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "42"
async def test_output_schema_explicit_object_full_handshake(self):
"""Test explicit object output schema through full client/server handshake."""
@@ -1044,6 +1050,8 @@ def explicit_tool() -> dict[str, Any]:
result = await client.call_tool("explicit_tool", {})
assert result.structured_content == {"greeting": "Hello", "count": 42}
# Client deserializes according to schema, so check fields
+ # result.data is a dynamically generated Root type, so check attributes directly
+ assert result.data is not None
assert result.data.greeting == "Hello" # type: ignore[attr-defined]
assert result.data.count == 42 # type: ignore[attr-defined]
@@ -1131,6 +1139,8 @@ def dataclass_tool() -> User:
result = await client.call_tool("dataclass_tool", {})
assert result.structured_content == {"name": "Alice", "age": 30}
# Client deserializes according to schema
+ # result.data is a dynamically generated Root type, so check attributes directly
+ assert result.data is not None
assert result.data.name == "Alice" # type: ignore[attr-defined]
assert result.data.age == 30 # type: ignore[attr-defined]
@@ -1450,7 +1460,8 @@ def get_text():
async with Client(mcp) as client:
result = await client.read_resource(AnyUrl("resource://test"))
- assert result[0].text == "Hello, world!" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Hello, world!"
async def test_binary_resource(self):
mcp = FastMCP()
@@ -1468,7 +1479,8 @@ def get_binary():
async with Client(mcp) as client:
result = await client.read_resource(AnyUrl("resource://binary"))
- assert result[0].blob == base64.b64encode(b"Binary data").decode() # type: ignore[attr-defined]
+ assert isinstance(result[0], BlobResourceContents)
+ assert result[0].blob == base64.b64encode(b"Binary data").decode()
async def test_file_resource_text(self, tmp_path: Path):
mcp = FastMCP()
@@ -1484,7 +1496,8 @@ async def test_file_resource_text(self, tmp_path: Path):
async with Client(mcp) as client:
result = await client.read_resource(AnyUrl("file://test.txt"))
- assert result[0].text == "Hello from file!" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Hello from file!"
async def test_file_resource_binary(self, tmp_path: Path):
mcp = FastMCP()
@@ -1503,7 +1516,8 @@ async def test_file_resource_binary(self, tmp_path: Path):
async with Client(mcp) as client:
result = await client.read_resource(AnyUrl("file://test.bin"))
- assert result[0].blob == base64.b64encode(b"Binary file data").decode() # type: ignore[attr-defined]
+ assert isinstance(result[0], BlobResourceContents)
+ assert result[0].blob == base64.b64encode(b"Binary file data").decode()
async def test_resource_with_annotations(self):
mcp = FastMCP()
@@ -1587,7 +1601,8 @@ async def test_read_included_resource(self):
async with Client(mcp) as client:
result = await client.read_resource(AnyUrl("resource://1"))
- assert result[0].text == "1" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "1"
with pytest.raises(McpError, match="Unknown resource"):
await client.read_resource(AnyUrl("resource://2"))
@@ -1611,7 +1626,8 @@ def resource_with_context(ctx: Context) -> str:
async with Client(mcp) as client:
result = await client.read_resource(AnyUrl("resource://test"))
- assert result[0].text == "1" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "1"
class TestResourceEnabled:
@@ -1756,7 +1772,8 @@ def get_data(name: str) -> str:
async with Client(mcp) as client:
result = await client.read_resource(AnyUrl("resource://test/data"))
- assert result[0].text == "Data for test" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Data for test"
async def test_resource_mismatched_params(self):
"""Test that mismatched parameters raise an error"""
@@ -1783,7 +1800,8 @@ def get_data(org: str, repo: str) -> str:
result = await client.read_resource(
AnyUrl("resource://cursor/fastmcp/data")
)
- assert result[0].text == "Data for cursor/fastmcp" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Data for cursor/fastmcp"
async def test_resource_multiple_mismatched_params(self):
"""Test that mismatched parameters raise an error"""
@@ -1807,7 +1825,8 @@ def get_static_data() -> str:
async with Client(mcp) as client:
result = await client.read_resource(AnyUrl("resource://static"))
- assert result[0].text == "Static data" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Static data"
async def test_template_with_varkwargs(self):
"""Test that a template can have **kwargs."""
@@ -1819,7 +1838,8 @@ def func(**kwargs: int) -> int:
async with Client(mcp) as client:
result = await client.read_resource(AnyUrl("test://1/2/3"))
- assert result[0].text == "6" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "6"
async def test_template_with_default_params(self):
"""Test that a template can have default parameters."""
@@ -1838,11 +1858,13 @@ def add(x: int, y: int = 10) -> int:
# Call the template and verify it uses the default value
async with Client(mcp) as client:
result = await client.read_resource(AnyUrl("math://add/5"))
- assert result[0].text == "15" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "15"
# Can also call with explicit params
result2 = await client.read_resource(AnyUrl("math://add/7"))
- assert result2[0].text == "17" # type: ignore[attr-defined]
+ assert isinstance(result2[0], TextResourceContents)
+ assert result2[0].text == "17"
async def test_template_to_resource_conversion(self):
"""Test that a template can be converted to a resource."""
@@ -1861,7 +1883,8 @@ def get_data(name: str) -> str:
# When accessed, should create a concrete resource
async with Client(mcp) as client:
result = await client.read_resource(AnyUrl("resource://test/data"))
- assert result[0].text == "Data for test" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Data for test"
async def test_template_decorator_with_tags(self):
mcp = FastMCP()
@@ -1883,7 +1906,8 @@ def template_resource(param: str) -> str:
async with Client(mcp) as client:
result = await client.read_resource(AnyUrl("resource://test/data"))
- assert result[0].text == "Template resource: test/data" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Template resource: test/data"
async def test_template_with_query_params(self):
"""Test RFC 6570 query parameters in resource templates."""
@@ -1896,17 +1920,20 @@ def get_data(id: str, format: str = "json", limit: int = 10) -> str:
async with Client(mcp) as client:
# No query params - uses defaults
result = await client.read_resource(AnyUrl("data://123"))
- assert result[0].text == "id=123, format=json, limit=10" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "id=123, format=json, limit=10"
# One query param
result = await client.read_resource(AnyUrl("data://123?format=xml"))
- assert result[0].text == "id=123, format=xml, limit=10" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "id=123, format=xml, limit=10"
# Multiple query params
result = await client.read_resource(
AnyUrl("data://123?format=csv&limit=50")
)
- assert result[0].text == "id=123, format=csv, limit=50" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "id=123, format=csv, limit=50"
async def test_templates_match_in_order_of_definition(self):
"""
@@ -1926,10 +1953,12 @@ def template_resource_with_params(x: str, y: str) -> str:
async with Client(mcp) as client:
result = await client.read_resource(AnyUrl("resource://a/b/c"))
- assert result[0].text == "Template resource 1: a/b/c" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Template resource 1: a/b/c"
result = await client.read_resource(AnyUrl("resource://a/b"))
- assert result[0].text == "Template resource 1: a/b" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Template resource 1: a/b"
async def test_templates_shadow_each_other_reorder(self):
"""
@@ -1948,10 +1977,12 @@ def template_resource(param: str) -> str:
async with Client(mcp) as client:
result = await client.read_resource(AnyUrl("resource://a/b/c"))
- assert result[0].text == "Template resource 2: a/b/c" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Template resource 2: a/b/c"
result = await client.read_resource(AnyUrl("resource://a/b"))
- assert result[0].text == "Template resource 1: a/b" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Template resource 1: a/b"
async def test_resource_template_with_annotations(self):
"""Test that resource template annotations are visible to clients."""
@@ -2035,7 +2066,8 @@ async def test_read_resource_template_includes_tags(self):
async with Client(mcp) as client:
result = await client.read_resource("resource://1/x")
- assert result[0].text == "Template resource 1: x" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Template resource 1: x"
with pytest.raises(McpError, match="Unknown resource"):
await client.read_resource("resource://2/x")
@@ -2048,7 +2080,8 @@ async def test_read_resource_template_excludes_tags(self):
await client.read_resource("resource://1/x")
result = await client.read_resource("resource://2/x")
- assert result[0].text == "Template resource 2: x" # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text == "Template resource 2: x"
class TestResourceTemplateContext:
@@ -2062,7 +2095,8 @@ def resource_template(param: str, ctx: Context) -> str:
async with Client(mcp) as client:
result = await client.read_resource(AnyUrl("resource://test"))
- assert result[0].text.startswith("Resource template: test 1") # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text.startswith("Resource template: test 1")
async def test_resource_template_context_with_callable_object(self):
mcp = FastMCP()
@@ -2078,7 +2112,8 @@ def __call__(self, param: str, ctx: Context) -> str:
async with Client(mcp) as client:
result = await client.read_resource(AnyUrl("resource://test"))
- assert result[0].text.startswith("Resource template: test 1") # type: ignore[attr-defined]
+ assert isinstance(result[0], TextResourceContents)
+ assert result[0].text.startswith("Resource template: test 1")
class TestResourceTemplateEnabled:
@@ -2194,7 +2229,10 @@ def fn() -> str:
assert prompt.name == "fn"
# Don't compare functions directly since validate_call wraps them
content = await prompt.render()
- assert content.messages[0].content.text == "Hello, world!" # type: ignore[attr-defined]
+ if not isinstance(content, PromptResult):
+ content = PromptResult.from_value(content)
+ assert isinstance(content.messages[0].content, TextContent)
+ assert content.messages[0].content.text == "Hello, world!"
async def test_prompt_decorator_with_name(self):
"""Test prompt decorator with custom name."""
@@ -2209,7 +2247,10 @@ def fn() -> str:
prompt = prompts_dict["custom_name"]
assert prompt.name == "custom_name"
content = await prompt.render()
- assert content.messages[0].content.text == "Hello, world!" # type: ignore[attr-defined]
+ if not isinstance(content, PromptResult):
+ content = PromptResult.from_value(content)
+ assert isinstance(content.messages[0].content, TextContent)
+ assert content.messages[0].content.text == "Hello, world!"
async def test_prompt_decorator_with_description(self):
"""Test prompt decorator with custom description."""
@@ -2224,7 +2265,10 @@ def fn() -> str:
prompt = prompts_dict["fn"]
assert prompt.description == "A custom description"
content = await prompt.render()
- assert content.messages[0].content.text == "Hello, world!" # type: ignore[attr-defined]
+ if not isinstance(content, PromptResult):
+ content = PromptResult.from_value(content)
+ assert isinstance(content.messages[0].content, TextContent)
+ assert content.messages[0].content.text == "Hello, world!"
async def test_prompt_decorator_with_parens(self):
mcp = FastMCP()
@@ -2331,7 +2375,8 @@ def fn(name: str) -> str:
message = result.messages[0]
assert message.role == "user"
content = message.content
- assert content.text == "Hello, World!" # type: ignore[attr-defined]
+ assert isinstance(content, TextContent)
+ assert content.text == "Hello, World!"
async def test_get_prompt_with_resource(self):
"""Test getting a prompt that returns resource content."""
@@ -2543,7 +2588,8 @@ def __call__(self, name: str, ctx: Context) -> str:
assert len(result.messages) == 1
message = result.messages[0]
assert message.role == "user"
- assert message.content.text == "Hello, World! 1" # type: ignore[attr-defined]
+ assert isinstance(message.content, TextContent)
+ assert message.content.text == "Hello, World! 1"
class TestPromptTags:
@@ -2600,7 +2646,8 @@ async def test_read_prompt_includes_tags(self):
async with Client(mcp) as client:
result = await client.get_prompt("prompt_1")
- assert result.messages[0].content.text == "1" # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert result.messages[0].content.text == "1"
with pytest.raises(McpError, match="Unknown prompt"):
await client.get_prompt("prompt_2")
@@ -2613,7 +2660,8 @@ async def test_read_prompt_excludes_tags(self):
await client.get_prompt("prompt_1")
result = await client.get_prompt("prompt_2")
- assert result.messages[0].content.text == "2" # type: ignore[attr-defined]
+ assert isinstance(result.messages[0].content, TextContent)
+ assert result.messages[0].content.text == "2"
class TestMeta:
diff --git a/tests/server/test_tool_annotations.py b/tests/server/test_tool_annotations.py
index 3052da4542..4a9983c72e 100644
--- a/tests/server/test_tool_annotations.py
+++ b/tests/server/test_tool_annotations.py
@@ -1,6 +1,7 @@
from typing import Any
-from mcp.types import ToolAnnotations
+from mcp.types import Tool as MCPTool
+from mcp.types import ToolAnnotations, ToolExecution
from fastmcp import Client, FastMCP
from fastmcp.tools.tool import Tool
@@ -234,8 +235,9 @@ async def background_tool(data: str) -> str:
tools_result = await client.list_tools()
assert len(tools_result) == 1
assert tools_result[0].name == "background_tool"
- assert tools_result[0].execution is not None
- assert tools_result[0].execution.taskSupport == "optional" # type: ignore[attr-defined]
+ assert isinstance(tools_result[0], MCPTool)
+ assert isinstance(tools_result[0].execution, ToolExecution)
+ assert tools_result[0].execution.taskSupport == "optional"
async def test_task_execution_omitted_for_task_disabled_tool():
diff --git a/tests/tools/test_tool.py b/tests/tools/test_tool.py
index 1f21ffc778..4d0b49bbfe 100644
--- a/tests/tools/test_tool.py
+++ b/tests/tools/test_tool.py
@@ -741,7 +741,8 @@ def func() -> dict[str, str]:
# Dict objects automatically become structured content even without schema
assert result.structured_content == {"message": "Hello, world!"}
assert len(result.content) == 1
- assert result.content[0].text == '{"message":"Hello, world!"}' # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == '{"message":"Hello, world!"}'
async def test_output_schema_none_disables_structured_content(self):
"""Test that output_schema=None explicitly disables structured content."""
@@ -755,7 +756,8 @@ def func() -> int:
result = await tool.run({})
assert result.structured_content is None
assert len(result.content) == 1
- assert result.content[0].text == "42" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "42"
async def test_output_schema_inferred_when_not_specified(self):
"""Test that output schema is inferred when not explicitly specified."""
@@ -795,7 +797,8 @@ def func() -> dict[str, int]:
result = await tool.run({})
# Dict result with object schema is used directly
assert result.structured_content == {"value": 42}
- assert result.content[0].text == '{"value":42}' # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == '{"value":42}'
async def test_explicit_object_schema_with_non_dict_return_fails(self):
"""Test that explicit object schemas fail when function returns non-dict."""
@@ -849,7 +852,8 @@ def func() -> str:
result = await tool.run({})
# Unstructured content
assert len(result.content) == 1
- assert result.content[0].text == "hello" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "hello"
# Structured content should be wrapped
assert result.structured_content == {"result": "hello"}
@@ -917,7 +921,9 @@ def func() -> int:
assert result_default.structured_content == {
"result": 123
} # Schema-based generation with wrapping
- assert result_none.content[0].text == result_default.content[0].text == "123" # type: ignore[attr-defined]
+ assert isinstance(result_none.content[0], TextContent)
+ assert isinstance(result_default.content[0], TextContent)
+ assert result_none.content[0].text == result_default.content[0].text == "123"
async def test_non_object_output_schema_raises_error(self):
"""Test that providing a non-object output schema raises a ValueError."""
diff --git a/tests/tools/test_tool_manager.py b/tests/tools/test_tool_manager.py
index c9c88fd98e..18637af99a 100644
--- a/tests/tools/test_tool_manager.py
+++ b/tests/tools/test_tool_manager.py
@@ -136,7 +136,9 @@ def image_tool(data: bytes) -> Image:
def test_add_noncallable_tool(self):
manager = ToolManager()
with pytest.raises(TypeError, match="not a callable object"):
- tool = Tool.from_function(1) # type: ignore
+ assert isinstance(1, int) # Intentionally passing invalid type
+ # Intentionally passing invalid type to test error handling
+ tool = Tool.from_function(1) # type: ignore[arg-type]
manager.add_tool(tool)
def test_add_lambda(self):
@@ -407,7 +409,8 @@ def add(a: int, b: int) -> int:
manager.add_tool(tool)
result = await manager.call_tool("add", {"a": 1, "b": 2})
- assert result.content[0].text == "3" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "3"
assert result.structured_content == {"result": 3}
async def test_call_async_tool(self):
@@ -419,7 +422,8 @@ async def double(n: int) -> int:
tool = Tool.from_function(double)
manager.add_tool(tool)
result = await manager.call_tool("double", {"n": 5})
- assert result.content[0].text == "10" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "10"
assert result.structured_content == {"result": 10}
async def test_call_tool_callable_object(self):
@@ -434,7 +438,8 @@ def __call__(self, x: int, y: int) -> int:
tool = Tool.from_function(Adder())
manager.add_tool(tool)
result = await manager.call_tool("Adder", {"x": 1, "y": 2})
- assert result.content[0].text == "3" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "3"
assert result.structured_content == {"result": 3}
async def test_call_tool_callable_object_async(self):
@@ -449,7 +454,8 @@ async def __call__(self, x: int, y: int) -> int:
tool = Tool.from_function(Adder())
manager.add_tool(tool)
result = await manager.call_tool("Adder", {"x": 1, "y": 2})
- assert result.content[0].text == "3" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "3"
assert result.structured_content == {"result": 3}
async def test_call_tool_with_default_args(self):
@@ -462,7 +468,8 @@ def add(a: int, b: int = 1) -> int:
manager.add_tool(tool)
result = await manager.call_tool("add", {"a": 1})
- assert result.content[0].text == "2" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "2"
assert result.structured_content == {"result": 2}
async def test_call_tool_with_missing_args(self):
@@ -511,7 +518,8 @@ def add(a: int, b: int) -> int:
result = await manager.call_tool(
"add_transformed", {"a_transformed": 1, "b_transformed": 2}
)
- assert result.content[0].text == "3" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "3"
assert result.structured_content == {"result": 3}
async def test_call_tool_with_list_int_input(self):
@@ -523,7 +531,8 @@ def sum_vals(vals: list[int]) -> int:
manager.add_tool(tool)
result = await manager.call_tool("sum_vals", {"vals": [1, 2, 3]})
- assert result.content[0].text == "6" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "6"
assert result.structured_content == {"result": 6}
async def test_call_tool_with_list_str_or_str_input(self):
@@ -536,11 +545,13 @@ def concat_strs(vals: list[str] | str) -> str:
# Try both with plain python object and with JSON list
result = await manager.call_tool("concat_strs", {"vals": ["a", "b", "c"]})
- assert result.content[0].text == "abc" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "abc"
assert result.structured_content == {"result": "abc"}
result = await manager.call_tool("concat_strs", {"vals": "a"})
- assert result.content[0].text == "a" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "a"
assert result.structured_content == {"result": "a"}
async def test_call_tool_with_complex_model(self):
@@ -596,7 +607,8 @@ def get_data() -> dict:
return {"key": "value", "number": 123}
result = await manager.call_tool("get_data", {})
- assert result.content[0].text == 'CUSTOM:{"key": "value", "number": 123}' # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == 'CUSTOM:{"key": "value", "number": 123}'
assert result.structured_content == {"key": "value", "number": 123}
async def test_call_tool_with_list_result_custom_serializer(self):
@@ -653,10 +665,8 @@ def get_data() -> uuid.UUID:
return uuid_result
result = await manager.call_tool("get_data", {})
- assert (
- result.content[0].text # type: ignore[attr-defined]
- == pydantic_core.to_json(uuid_result).decode()
- )
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == pydantic_core.to_json(uuid_result).decode()
assert result.structured_content == {"result": str(uuid_result)}
@@ -727,7 +737,8 @@ def tool_with_context(x: int, ctx: Context) -> str:
async with context:
result = await manager.call_tool("tool_with_context", {"x": 42})
- assert result.content[0].text == "42" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "42"
assert result.structured_content == {"result": "42"}
async def test_context_injection_async(self):
@@ -746,7 +757,8 @@ async def async_tool(x: int, ctx: Context) -> str:
async with context:
result = await manager.call_tool("async_tool", {"x": 42})
- assert result.content[0].text == "42" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "42"
assert result.structured_content == {"result": "42"}
async def test_context_optional(self):
@@ -765,7 +777,8 @@ def tool_with_context(x: int, ctx: Context | None) -> int:
async with context:
result = await manager.call_tool("tool_with_context", {"x": 42})
- assert result.content[0].text == "42" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "42"
assert result.structured_content == {"result": 42}
def test_parameterized_context_parameter_detection(self):
@@ -882,7 +895,8 @@ def multiply(a: int, b: int) -> int:
# Tool should be callable by its custom name
result = await manager.call_tool("custom_multiply", {"a": 5, "b": 3})
- assert result.content[0].text == "15" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "15"
assert result.structured_content == {"result": 15}
# Original name should not be registered
@@ -1037,7 +1051,8 @@ async def test_mounted_components_raise_on_load_error_default_false(self):
# Create a failing mounted server by corrupting it
parent_mcp.mount(child_mcp, namespace="child")
# Corrupt the parent's providers to make it fail during loading
- parent_mcp._providers.append("invalid") # type: ignore
+ assert isinstance(parent_mcp._providers, list)
+ parent_mcp._providers.append("invalid") # type: ignore[arg-type]
# Should not raise, just warn; use server middleware path now
tools = await parent_mcp._list_tools_middleware()
@@ -1051,7 +1066,8 @@ async def test_mounted_components_raise_on_load_error_true(self):
# Create a failing mounted server
parent_mcp.mount(child_mcp, namespace="child")
# Corrupt the parent's providers to make it fail during loading
- parent_mcp._providers.append("invalid") # type: ignore
+ assert isinstance(parent_mcp._providers, list)
+ parent_mcp._providers.append("invalid") # type: ignore[arg-type]
# Use temporary settings context manager
with temporary_settings(mounted_components_raise_on_load_error=True):
diff --git a/tests/tools/test_tool_transform.py b/tests/tools/test_tool_transform.py
index 565e16ad33..2624988680 100644
--- a/tests/tools/test_tool_transform.py
+++ b/tests/tools/test_tool_transform.py
@@ -129,7 +129,8 @@ async def test_hidden_arg_without_default_uses_parent_default(add_tool):
assert sorted(new_tool.parameters["properties"]) == ["old_x"]
# Should pass old_x=3 and let parent use its default old_y=10
result = await new_tool.run(arguments={"old_x": 3})
- assert result.content[0].text == "13" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "13"
assert result.structured_content == {"result": 13}
@@ -155,7 +156,8 @@ async def custom_fn(visible_x: int) -> ToolResult:
assert sorted(new_tool.parameters["properties"]) == ["visible_x"]
# Should pass visible_x=7 as old_x=7 and old_y=25 to parent
result = await new_tool.run(arguments={"visible_x": 7})
- assert result.content[0].text == "32" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "32"
assert result.structured_content == {"result": 32}
@@ -238,7 +240,8 @@ async def custom_fn(new_x: int, new_y: int = 5) -> ToolResult:
)
result = await new_tool.run(arguments={"new_x": 2, "new_y": 3})
- assert result.content[0].text == "5" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "5"
assert result.structured_content == {"result": 5}
@@ -279,18 +282,21 @@ async def custom_fn(new_x: int, new_y: int = 5) -> ToolResult:
)
result = await new_tool.run(arguments={"new_x": 2, "new_y": 3})
- assert result.content[0].text == "5" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "5"
assert result.structured_content == {"result": 5}
async def test_custom_fn_with_kwargs_and_no_transform_args(add_tool):
async def custom_fn(extra: int, **kwargs) -> int:
sum = await forward(**kwargs)
- return int(sum.content[0].text) + extra # type: ignore[attr-defined]
+ assert isinstance(sum.content[0], TextContent)
+ return int(sum.content[0].text) + extra
new_tool = Tool.from_tool(add_tool, transform_fn=custom_fn)
result = await new_tool.run(arguments={"extra": 1, "old_x": 2, "old_y": 3})
- assert result.content[0].text == "6" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "6"
assert result.structured_content == {"result": 6}
assert new_tool.parameters["required"] == IsList(
"extra", "old_x", check_order=False
@@ -308,7 +314,8 @@ async def custom_fn(new_y: int = 5, **kwargs) -> ToolResult:
new_tool = Tool.from_tool(add_tool, transform_fn=custom_fn)
result = await new_tool.run(arguments={"new_y": 2, "old_y": 3})
- assert result.content[0].text == "5" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "5"
assert result.structured_content == {"result": 5}
@@ -327,7 +334,8 @@ async def custom_fn(new_x: int, **kwargs) -> ToolResult:
transform_args={"old_x": ArgTransform(name="new_x")},
)
result = await new_tool.run(arguments={"new_x": 2, "old_y": 3})
- assert result.content[0].text == "5" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "5"
assert result.structured_content == {"result": 5}
@@ -350,7 +358,8 @@ async def custom_fn(
result = await new_tool.run(
arguments={"new_x": 3, "old_y": 7, "some_other_param": "test"}
)
- assert result.content[0].text == "10" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "10"
assert result.structured_content == {"result": 10}
@@ -369,7 +378,8 @@ async def custom_fn(new_x: int, **kwargs) -> ToolResult:
transform_args={"old_x": ArgTransform(name="new_x")},
) # only map 'a'
result = await new_tool.run(arguments={"new_x": 1, "old_y": 5})
- assert result.content[0].text == "6" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "6"
assert result.structured_content == {"result": 6}
@@ -393,7 +403,8 @@ async def custom_fn(new_x: int, **kwargs) -> ToolResult:
) # drop 'old_y'
result = await new_tool.run(arguments={"new_x": 8})
# 8 + 10 (default value of b in parent)
- assert result.content[0].text == "18" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "18"
async def test_forward_outside_context_raises_error():
@@ -531,18 +542,21 @@ async def test_tool_transform_chaining(add_tool):
tool2 = Tool.from_tool(tool1, transform_args={"x": ArgTransform(name="final_x")})
result = await tool2.run(arguments={"final_x": 5})
- assert result.content[0].text == "15" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "15"
# Transform tool1 with custom function that handles all parameters
async def custom(final_x: int, **kwargs) -> str:
result = await forward(final_x=final_x, **kwargs)
- return f"custom {result.content[0].text}" # Extract text from content # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ return f"custom {result.content[0].text}" # Extract text from content
tool3 = Tool.from_tool(
tool1, transform_fn=custom, transform_args={"x": ArgTransform(name="final_x")}
)
result = await tool3.run(arguments={"final_x": 3, "old_y": 5})
- assert result.content[0].text == "custom 8" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "custom 8"
class MyModel(BaseModel):
@@ -670,7 +684,8 @@ def base(x: int, y: str = "base_default") -> str:
# Function signature has different types/defaults than ArgTransform
async def custom_fn(x: str = "function_default", **kwargs) -> str:
result = await forward(x=x, **kwargs)
- return f"custom: {result.content[0].text}" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ return f"custom: {result.content[0].text}"
tool = Tool.from_tool(
base,
@@ -697,7 +712,8 @@ async def custom_fn(x: str = "function_default", **kwargs) -> str:
# Test it works at runtime
result = await tool.run(arguments={"y": "test"})
# Should use ArgTransform default of 42
- assert "42: test" in result.content[0].text # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert "42: test" in result.content[0].text
def test_arg_transform_combined_attributes():
@@ -742,7 +758,8 @@ async def custom_fn(x: str, y: int = 10) -> str:
# Convert string back to int for the original function
result = await forward_raw(x=int(x), y=y)
# Extract the text from the result
- result_text = result.content[0].text # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ result_text = result.content[0].text
return f"String input '{x}' converted to result: {result_text}"
tool = Tool.from_tool(
@@ -754,8 +771,9 @@ async def custom_fn(x: str, y: int = 10) -> str:
# Test it works with string input
result = await tool.run(arguments={"x": "5", "y": 3})
- assert "String input '5'" in result.content[0].text # type: ignore[attr-defined]
- assert "result: 8" in result.content[0].text # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert "String input '5'" in result.content[0].text
+ assert "result: 8" in result.content[0].text
class TestProxy:
@@ -790,7 +808,8 @@ async def test_transform_proxy(self, proxy_server: FastMCP):
async with Client(proxy_server) as client:
# The tool should be registered with its transformed name
result = await client.call_tool("add_transformed", {"new_x": 1, "old_y": 2})
- assert result.content[0].text == "3" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "3"
async def test_arg_transform_default_factory():
@@ -813,7 +832,8 @@ def base_tool(x: int, timestamp: float) -> str:
# Should work without providing timestamp (gets value from factory)
result = await new_tool.run(arguments={"x": 42})
- assert result.content[0].text == "42_12345.0" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "42_12345.0"
async def test_arg_transform_default_factory_called_each_time():
@@ -841,11 +861,13 @@ def base_tool(x: int, counter: int = 0) -> str:
# First call
result1 = await new_tool.run(arguments={"x": 1})
- assert result1.content[0].text == "1_1" # type: ignore[attr-defined]
+ assert isinstance(result1.content[0], TextContent)
+ assert result1.content[0].text == "1_1"
# Second call should get a different value
result2 = await new_tool.run(arguments={"x": 2})
- assert result2.content[0].text == "2_2" # type: ignore[attr-defined]
+ assert isinstance(result2.content[0], TextContent)
+ assert result2.content[0].text == "2_2"
async def test_arg_transform_hidden_with_default_factory():
@@ -870,7 +892,8 @@ def make_request_id():
# Should pass hidden request_id with factory value
result = await new_tool.run(arguments={"x": 42})
- assert result.content[0].text == "42_req_123" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "42_req_123"
async def test_arg_transform_default_and_factory_raises_error():
@@ -907,7 +930,8 @@ def base_tool(optional_param: int = 42) -> str:
# Should work when parameter is provided
result = await new_tool.run(arguments={"optional_param": 100})
- assert result.content[0].text == "value: 100" # type: ignore
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "value: 100"
# Should fail when parameter is not provided
with pytest.raises(TypeError, match="Missing required argument"):
@@ -925,9 +949,10 @@ def base_tool(required_param: int) -> str:
ValueError,
match="Cannot specify 'required=False'. Set a default value instead.",
):
+ # Intentionally passing invalid argument to test error handling
Tool.from_tool(
base_tool,
- transform_args={"required_param": ArgTransform(required=False, default=99)}, # type: ignore
+ transform_args={"required_param": ArgTransform(required=False, default=99)}, # type: ignore[arg-type]
)
@@ -954,7 +979,8 @@ def base_tool(optional_param: int = 42) -> str:
# Should work with new name
result = await new_tool.run(arguments={"new_param": 200})
- assert result.content[0].text == "value: 200" # type: ignore
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "value: 200"
async def test_arg_transform_required_true_with_default_raises_error():
@@ -996,7 +1022,8 @@ def base_tool(required_param: int, optional_param: int = 42) -> str:
# Should work as expected
result = await new_tool.run(arguments={"req": 1})
- assert result.content[0].text == "values: 1, 42" # type: ignore
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "values: 1, 42"
async def test_arg_transform_hide_and_required_raises_error():
@@ -1033,7 +1060,8 @@ def add(x: int, y: int = 10) -> int:
assert {tool.name for tool in tools} == {"new_add"}
result = await client.call_tool("new_add", {"x": 1, "y": 2})
- assert result.content[0].text == "3" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "3"
with pytest.raises(ToolError):
await client.call_tool("add", {"x": 1, "y": 2})
@@ -1109,7 +1137,8 @@ async def test_transform_output_schema_none_runtime(self, base_string_tool):
result = await new_tool.run({"x": 5})
# Even with output_schema=None, structured content should be generated via fallback logic
assert result.structured_content == {"result": "Result: 5"}
- assert result.content[0].text == "Result: 5" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "Result: 5"
def test_transform_with_explicit_output_schema_dict(self, base_string_tool):
"""Test that explicit output schema overrides parent."""
@@ -1130,14 +1159,16 @@ async def test_transform_explicit_schema_runtime(self, base_string_tool):
result = await new_tool.run({"x": 10})
# Non-object explicit schemas disable structured content
assert result.structured_content is None
- assert result.content[0].text == "Result: 10" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "Result: 10"
def test_transform_with_custom_function_inferred_schema(self, base_dict_tool):
"""Test that custom function's output schema is inferred."""
async def custom_fn(x: int) -> str:
result = await forward(x=x)
- return f"Custom: {result.content[0].text}" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ return f"Custom: {result.content[0].text}"
new_tool = Tool.from_tool(base_dict_tool, transform_fn=custom_fn)
@@ -1155,7 +1186,8 @@ async def test_transform_custom_function_runtime(self, base_dict_tool):
async def custom_fn(x: int) -> str:
result = await forward(x=x)
- return f"Custom: {result.content[0].text}" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ return f"Custom: {result.content[0].text}"
new_tool = Tool.from_tool(base_dict_tool, transform_fn=custom_fn)
@@ -1202,7 +1234,8 @@ async def custom_fn(x: int) -> dict[str, int]:
# Object types should not be wrapped
expected_schema = TypeAdapter(dict[str, int]).json_schema()
assert new_tool.output_schema == expected_schema
- assert "x-fastmcp-wrap-result" not in new_tool.output_schema # type: ignore[attr-defined]
+ assert isinstance(new_tool.output_schema, dict)
+ assert "x-fastmcp-wrap-result" not in new_tool.output_schema
result = await new_tool.run({"x": 4})
# Direct value, not wrapped
@@ -1215,7 +1248,8 @@ async def test_transform_preserves_wrap_marker_behavior(self, base_string_tool):
result = await new_tool.run({"x": 7})
# Should wrap because parent schema has wrap marker
assert result.structured_content == {"result": "Result: 7"}
- assert "x-fastmcp-wrap-result" in new_tool.output_schema # type: ignore[attr-defined]
+ assert isinstance(new_tool.output_schema, dict)
+ assert "x-fastmcp-wrap-result" in new_tool.output_schema
def test_transform_chained_output_schema_inheritance(self, base_string_tool):
"""Test output schema inheritance through multiple transformations."""
@@ -1260,14 +1294,16 @@ async def custom_fn(x: int):
# Test ToolResult return
result2 = await new_tool.run({"x": 2})
assert result2.structured_content == {"custom_value": 2}
- assert result2.content[0].text == "Custom: 2" # type: ignore[attr-defined]
+ assert isinstance(result2.content[0], TextContent)
+ assert result2.content[0].text == "Custom: 2"
def test_transform_output_schema_with_arg_transforms(self, base_string_tool):
"""Test that output schema works correctly with argument transformations."""
async def custom_fn(new_x: int) -> dict[str, str]:
result = await forward(new_x=new_x)
- return {"transformed": result.content[0].text} # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ return {"transformed": result.content[0].text}
new_tool = Tool.from_tool(
base_string_tool,
@@ -1299,7 +1335,9 @@ async def test_transform_output_schema_default_vs_none(self, base_string_tool):
assert result_explicit_none.structured_content == {
"result": "Result: 5"
} # Generated via fallback logic
- assert result_default.content[0].text == result_explicit_none.content[0].text # type: ignore[attr-defined]
+ assert isinstance(result_default.content[0], TextContent)
+ assert isinstance(result_explicit_none.content[0], TextContent)
+ assert result_default.content[0].text == result_explicit_none.content[0].text
async def test_transform_output_schema_with_tool_result_return(
self, base_string_tool
@@ -1320,7 +1358,8 @@ async def custom_fn(x: int) -> ToolResult:
result = await new_tool.run({"x": 6})
# Should use ToolResult content directly
- assert result.content[0].text == "Direct: 6" # type: ignore[attr-defined]
+ assert isinstance(result.content[0], TextContent)
+ assert result.content[0].text == "Direct: 6"
assert result.structured_content == {"direct_value": 6, "doubled": 12}
diff --git a/tests/utilities/test_json_schema_type.py b/tests/utilities/test_json_schema_type.py
index 1aeac75a5f..a6c3cd0110 100644
--- a/tests/utilities/test_json_schema_type.py
+++ b/tests/utilities/test_json_schema_type.py
@@ -1569,7 +1569,6 @@ def test_field_with_default_uses_default(self):
generated_type = json_schema_to_type(schema)
validator = TypeAdapter(generated_type)
result = validator.validate_python({})
-
assert result.flag is False # type: ignore[attr-defined]
def test_field_with_default_accepts_explicit_value(self):
@@ -1582,5 +1581,4 @@ def test_field_with_default_accepts_explicit_value(self):
generated_type = json_schema_to_type(schema)
validator = TypeAdapter(generated_type)
result = validator.validate_python({"flag": True})
-
assert result.flag is True # type: ignore[attr-defined]