Repository: jlowin/fastmcp
Pull request #52 (2024-12-03 18:55:26)

Add pyright + tests


Patch:
diff --git a/.github/workflows/lint.yml b/.github/workflows/run-static.yml
similarity index 74%
rename from .github/workflows/lint.yml
rename to .github/workflows/run-static.yml
index b837b5d..0eae35b 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/run-static.yml
@@ -28,3 +28,9 @@ jobs:
           python-version: "3.12"
       - name: Run pre-commit
         uses: pre-commit/action@v3.0.1
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install ".[tests]"
+      - name: Run pyright
+        run: pyright src tests
diff --git a/pyproject.toml b/pyproject.toml
index 15f0848..48036c6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -25,6 +25,7 @@ build-backend = "hatchling.build"
 [project.optional-dependencies]
 tests = [
     "pre-commit",
+    "pyright>=1.1.389",
     "pytest>=8.3.3",
     "pytest-asyncio>=0.23.5",
     "pytest-flakefinder",
@@ -39,3 +40,15 @@ asyncio_default_fixture_loop_scope = "session"
 
 [tool.hatch.version]
 source = "vcs"
+
+[tool.pyright]
+include = ["src", "tests"]
+exclude = ["**/node_modules", "**/__pycache__", ".venv", ".git", "dist"]
+pythonVersion = "3.10"
+pythonPlatform = "Darwin"
+typeCheckingMode = "basic"
+reportMissingImports = true
+reportMissingTypeStubs = false
+useLibraryCodeForTypes = true
+venvPath = "."
+venv = ".venv"
diff --git a/src/fastmcp/cli/cli.py b/src/fastmcp/cli/cli.py
index a735303..5921afa 100644
--- a/src/fastmcp/cli/cli.py
+++ b/src/fastmcp/cli/cli.py
@@ -2,6 +2,7 @@
 
 import importlib.metadata
 import importlib.util
+import os
 import subprocess
 import sys
 from pathlib import Path
@@ -242,6 +243,7 @@ def dev(
             [npx_cmd, "@modelcontextprotocol/inspector"] + uv_cmd,
             check=True,
             shell=shell,
+            env=dict(os.environ.items()),  # Convert to list of tuples for env update
         )
         sys.exit(process.returncode)
     except subprocess.CalledProcessError as e:
@@ -423,7 +425,10 @@ def install(
         # Load from .env file if specified
         if env_file:
             try:
-                env_dict.update(dotenv.dotenv_values(env_file))
+                env_values = dotenv.dotenv_values(env_file)
+                env_dict.update(
+                    (k, str(v)) for k, v in env_values.items() if v is not None
+                )
             except Exception as e:
                 logger.error(f"Failed to load .env file: {e}")
                 sys.exit(1)
diff --git a/src/fastmcp/prompts/base.py b/src/fastmcp/prompts/base.py
index 0cbc3c8..d44fc18 100644
--- a/src/fastmcp/prompts/base.py
+++ b/src/fastmcp/prompts/base.py
@@ -1,43 +1,52 @@
 """Base classes for FastMCP prompts."""
 
 import json
-from typing import Any, Callable, Dict, Literal, Optional, Sequence, Union
+from typing import Any, Callable, Dict, Literal, Optional, Sequence, Awaitable
 import inspect
 
-from pydantic import BaseModel, Field, TypeAdapter, field_validator, validate_call
+from pydantic import BaseModel, Field, TypeAdapter, validate_call
 from mcp.types import TextContent, ImageContent, EmbeddedResource
 import pydantic_core
 
+CONTENT_TYPES = TextContent | ImageContent | EmbeddedResource
+
 
 class Message(BaseModel):
     """Base class for all prompt messages."""
 
     role: Literal["user", "assistant"]
-    content: Union[TextContent, ImageContent, EmbeddedResource]
+    content: CONTENT_TYPES
 
-    def __init__(self, content, **kwargs):
+    def __init__(self, content: str | CONTENT_TYPES, **kwargs):
+        if isinstance(content, str):
+            content = TextContent(type="text", text=content)
         super().__init__(content=content, **kwargs)
 
-    @field_validator("content", mode="before")
-    def validate_content(cls, v):
-        if isinstance(v, str):
-            return TextContent(type="text", text=v)
-        return v
-
 
 class UserMessage(Message):
     """A message from the user."""
 
     role: Literal["user"] = "user"
 
+    def __init__(self, content: str | CONTENT_TYPES, **kwargs):
+        super().__init__(content=content, **kwargs)
+
 
 class AssistantMessage(Message):
     """A message from the assistant."""
 
     role: Literal["assistant"] = "assistant"
 
+    def __init__(self, content: str | CONTENT_TYPES, **kwargs):
+        super().__init__(content=content, **kwargs)
+
+
+message_validator = TypeAdapter(UserMessage | AssistantMessage)
 
-message_validator = TypeAdapter(Union[UserMessage, AssistantMessage])
+SyncPromptResult = (
+    str | Message | dict[str, Any] | Sequence[str | Message | dict[str, Any]]
+)
+PromptResult = SyncPromptResult | Awaitable[SyncPromptResult]
 
 
 class PromptArgument(BaseModel):
@@ -67,11 +76,18 @@ class Prompt(BaseModel):
     @classmethod
     def from_function(
         cls,
-        fn: Callable[..., Sequence[Message]],
+        fn: Callable[..., PromptResult],
         name: Optional[str] = None,
         description: Optional[str] = None,
     ) -> "Prompt":
-        """Create a Prompt from a function."""
+        """Create a Prompt from a function.
+
+        The function can return:
+        - A string (converted to a message)
+        - A Message object
+        - A dict (converted to a message)
+        - A sequence of any of the above
+        """
         func_name = name or fn.__name__
 
         if func_name == "<lambda>":
diff --git a/src/fastmcp/resources/base.py b/src/fastmcp/resources/base.py
index 28b0834..cf9c72b 100644
--- a/src/fastmcp/resources/base.py
+++ b/src/fastmcp/resources/base.py
@@ -1,14 +1,14 @@
 """Base classes and interfaces for FastMCP resources."""
 
 import abc
-from typing import Union
+from typing import Union, Annotated
 
 from pydantic import (
     AnyUrl,
     BaseModel,
     ConfigDict,
     Field,
-    FileUrl,
+    UrlConstraints,
     ValidationInfo,
     field_validator,
 )
@@ -19,8 +19,9 @@ class Resource(BaseModel, abc.ABC):
 
     model_config = ConfigDict(validate_default=True)
 
-    # uri: Annotated[AnyUrl, BeforeValidator(maybe_cast_str_to_any_url)] = Field(
-    uri: AnyUrl = Field(default=..., description="URI of the resource")
+    uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] = Field(
+        default=..., description="URI of the resource"
+    )
     name: str | None = Field(description="Name of the resource", default=None)
     description: str | None = Field(
         description="Description of the resource", default=None
@@ -31,15 +32,6 @@ class Resource(BaseModel, abc.ABC):
         pattern=r"^[a-zA-Z0-9]+/[a-zA-Z0-9\-+.]+$",
     )
 
-    @field_validator("uri", mode="before")
-    def validate_uri(cls, uri: AnyUrl | str) -> AnyUrl:
-        if isinstance(uri, str):
-            # AnyUrl doesn't support triple-slashes, but files do ("file:///absolute/path")
-            if uri.startswith("file://"):
-                return FileUrl(uri)
-            return AnyUrl(uri)
-        return uri
-
     @field_validator("name", mode="before")
     @classmethod
     def set_default_name(cls, name: str | None, info: ValidationInfo) -> str:
diff --git a/src/fastmcp/resources/templates.py b/src/fastmcp/resources/templates.py
index 77c28e7..dc83730 100644
--- a/src/fastmcp/resources/templates.py
+++ b/src/fastmcp/resources/templates.py
@@ -70,7 +70,7 @@ class ResourceTemplate(BaseModel):
                 result = await result
 
             return FunctionResource(
-                uri=uri,
+                uri=uri,  # type: ignore
                 name=self.name,
                 description=self.description,
                 mime_type=self.mime_type,
diff --git a/src/fastmcp/resources/types.py b/src/fastmcp/resources/types.py
index c5363a6..0b13816 100644
--- a/src/fastmcp/resources/types.py
+++ b/src/fastmcp/resources/types.py
@@ -8,7 +8,7 @@ from typing import Any, Callable, Union
 import httpx
 import pydantic.json
 import pydantic_core
-from pydantic import Field
+from pydantic import Field, ValidationInfo
 
 from fastmcp.resources.base import Resource
 
@@ -91,6 +91,15 @@ class FileResource(Resource):
             raise ValueError("Path must be absolute")
         return path
 
+    @pydantic.field_validator("is_binary")
+    @classmethod
+    def set_binary_from_mime_type(cls, is_binary: bool, info: ValidationInfo) -> bool:
+        """Set is_binary based on mime_type if not explicitly set."""
+        if is_binary:
+            return True
+        mime_type = info.data.get("mime_type", "text/plain")
+        return not mime_type.startswith("text/")
+
     async def read(self) -> Union[str, bytes]:
         """Read the file content."""
         try:
diff --git a/src/fastmcp/server.py b/src/fastmcp/server.py
index a215a9a..d898853 100644
--- a/src/fastmcp/server.py
+++ b/src/fastmcp/server.py
@@ -23,6 +23,7 @@ from mcp.types import (
 )
 from mcp.types import (
     Prompt as MCPPrompt,
+    PromptArgument as MCPPromptArgument,
 )
 from mcp.types import (
     Resource as MCPResource,
@@ -159,7 +160,7 @@ class FastMCP:
 
     async def call_tool(
         self, name: str, arguments: dict
-    ) -> Sequence[TextContent | ImageContent]:
+    ) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
         """Call a tool by name with arguments."""
         context = self.get_context()
         result = await self._tool_manager.call_tool(name, arguments, context=context)
@@ -462,11 +463,11 @@ class FastMCP:
                 name=prompt.name,
                 description=prompt.description,
                 arguments=[
-                    {
-                        "name": arg.name,
-                        "description": arg.description,
-                        "required": arg.required,
-                    }
+                    MCPPromptArgument(
+                        name=arg.name,
+                        description=arg.description,
+                        required=arg.required,
+                    )
                     for arg in (prompt.arguments or [])
                 ],
             )
diff --git a/src/fastmcp/utilities/func_metadata.py b/src/fastmcp/utilities/func_metadata.py
index 25c3baa..9bd4921 100644
--- a/src/fastmcp/utilities/func_metadata.py
+++ b/src/fastmcp/utilities/func_metadata.py
@@ -47,7 +47,7 @@ class FuncMetadata(BaseModel):
 
     async def call_fn_with_arg_validation(
         self,
-        fn: Callable | Awaitable,
+        fn: Callable[..., Any] | Awaitable[Any],
         fn_is_async: bool,
         arguments_to_validate: dict[str, Any],
         arguments_to_pass_directly: dict[str, Any] | None,
@@ -64,8 +64,12 @@ class FuncMetadata(BaseModel):
         arguments_parsed_dict |= arguments_to_pass_directly or {}
 
         if fn_is_async:
+            if isinstance(fn, Awaitable):
+                return await fn
             return await fn(**arguments_parsed_dict)
-        return fn(**arguments_parsed_dict)
+        if isinstance(fn, Callable):
+            return fn(**arguments_parsed_dict)
+        raise TypeError("fn must be either Callable or Awaitable")
 
     def pre_parse_json(self, data: dict[str, Any]) -> dict[str, Any]:
         """Pre-parse data from JSON.
@@ -123,6 +127,7 @@ def func_metadata(func: Callable, skip_names: Sequence[str] = ()) -> FuncMetadat
     sig = _get_typed_signature(func)
     params = sig.parameters
     dynamic_pydantic_model_params: dict[str, Any] = {}
+    globalns = getattr(func, "__globals__", {})
     for param in params.values():
         if param.name.startswith("_"):
             raise InvalidSignature(
@@ -153,7 +158,7 @@ def func_metadata(func: Callable, skip_names: Sequence[str] = ()) -> FuncMetadat
             ]
 
         field_info = FieldInfo.from_annotated_attribute(
-            annotation,
+            _get_typed_annotation(annotation, globalns),
             param.default
             if param.default is not inspect.Parameter.empty
             else PydanticUndefined,
diff --git a/src/fastmcp/utilities/types.py b/src/fastmcp/utilities/types.py
index 60bbf15..b93d244 100644
--- a/src/fastmcp/utilities/types.py
+++ b/src/fastmcp/utilities/types.py
@@ -47,7 +47,9 @@ class Image:
         if self.path:
             with open(self.path, "rb") as f:
                 data = base64.b64encode(f.read()).decode()
-        else:
+        elif self.data is not None:
             data = base64.b64encode(self.data).decode()
+        else:
+            raise ValueError("No image data available")
 
         return ImageContent(type="image", data=data, mimeType=self._mime_type)
diff --git a/uv.lock b/uv.lock
index 748660d..31fdfa7 100644
--- a/uv.lock
+++ b/uv.lock
@@ -228,7 +228,7 @@ wheels = [
 
 [[package]]
 name = "fastmcp"
-version = "0.3.6.dev0+gf03184b.d20241203"
+version = "0.3.6.dev5+g6a13ab9.d20241203"
 source = { editable = "." }
 dependencies = [
     { name = "httpx" },
@@ -245,6 +245,7 @@ dev = [
     { name = "ipython" },
     { name = "pdbpp" },
     { name = "pre-commit" },
+    { name = "pyright" },
     { name = "pytest" },
     { name = "pytest-asyncio" },
     { name = "pytest-flakefinder" },
@@ -253,6 +254,7 @@ dev = [
 ]
 tests = [
     { name = "pre-commit" },
+    { name = "pyright" },
     { name = "pytest" },
     { name = "pytest-asyncio" },
     { name = "pytest-flakefinder" },
@@ -271,6 +273,8 @@ requires-dist = [
     { name = "pre-commit", marker = "extra == 'tests'" },
     { name = "pydantic", specifier = ">=2.5.3,<3.0.0" },
     { name = "pydantic-settings", specifier = ">=2.6.1" },
+    { name = "pyright", marker = "extra == 'dev'", specifier = ">=1.1.389" },
+    { name = "pyright", marker = "extra == 'tests'", specifier = ">=1.1.389" },
     { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.3" },
     { name = "pytest", marker = "extra == 'tests'", specifier = ">=8.3.3" },
     { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.23.5" },
@@ -730,6 +734,19 @@ version = "0.9.0"
 source = { registry = "https://pypi.org/simple" }
 sdist = { url = "https://files.pythonhosted.org/packages/05/1b/ea40363be0056080454cdbabe880773c3c5bd66d7b13f0c8b8b8c8da1e0c/pyrepl-0.9.0.tar.gz", hash = "sha256:292570f34b5502e871bbb966d639474f2b57fbfcd3373c2d6a2f3d56e681a775", size = 48744 }
 
+[[package]]
+name = "pyright"
+version = "1.1.389"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+    { name = "nodeenv" },
+    { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/72/4e/9a5ab8745e7606b88c2c7ca223449ac9d82a71fd5e31df47b453f2cb39a1/pyright-1.1.389.tar.gz", hash = "sha256:716bf8cc174ab8b4dcf6828c3298cac05c5ed775dda9910106a5dcfe4c7fe220", size = 21940 }
+wheels = [
+    { url = "https://files.pythonhosted.org/packages/1b/26/c288cabf8cfc5a27e1aa9e5029b7682c0f920b8074f45d22bf844314d66a/pyright-1.1.389-py3-none-any.whl", hash = "sha256:41e9620bba9254406dc1f621a88ceab5a88af4c826feb4f614d95691ed243a60", size = 18581 },
+]
+
 [[package]]
 name = "pytest"
 version = "8.3.3"


Test patch:
diff --git a/tests/prompts/test_base.py b/tests/prompts/test_base.py
index 78418e8..4dca37d 100644
--- a/tests/prompts/test_base.py
+++ b/tests/prompts/test_base.py
@@ -1,3 +1,4 @@
+from pydantic import FileUrl
 import pytest
 from fastmcp.prompts.base import (
     Prompt,
@@ -102,7 +103,7 @@ class TestRenderPrompt:
                 content=EmbeddedResource(
                     type="resource",
                     resource=TextResourceContents(
-                        uri="file://file.txt",
+                        uri=FileUrl("file://file.txt"),
                         text="File contents",
                         mimeType="text/plain",
                     ),
@@ -115,7 +116,7 @@ class TestRenderPrompt:
                 content=EmbeddedResource(
                     type="resource",
                     resource=TextResourceContents(
-                        uri="file://file.txt",
+                        uri=FileUrl("file://file.txt"),
                         text="File contents",
                         mimeType="text/plain",
                     ),
@@ -133,7 +134,7 @@ class TestRenderPrompt:
                     content=EmbeddedResource(
                         type="resource",
                         resource=TextResourceContents(
-                            uri="file://file.txt",
+                            uri=FileUrl("file://file.txt"),
                             text="File contents",
                             mimeType="text/plain",
                         ),
@@ -151,7 +152,7 @@ class TestRenderPrompt:
                 content=EmbeddedResource(
                     type="resource",
                     resource=TextResourceContents(
-                        uri="file://file.txt",
+                        uri=FileUrl("file://file.txt"),
                         text="File contents",
                         mimeType="text/plain",
                     ),
@@ -171,7 +172,7 @@ class TestRenderPrompt:
                 "content": {
                     "type": "resource",
                     "resource": {
-                        "uri": "file://file.txt",
+                        "uri": FileUrl("file://file.txt"),
                         "text": "File contents",
                         "mimeType": "text/plain",
                     },
@@ -184,7 +185,7 @@ class TestRenderPrompt:
                 content=EmbeddedResource(
                     type="resource",
                     resource=TextResourceContents(
-                        uri="file://file.txt",
+                        uri=FileUrl("file://file.txt"),
                         text="File contents",
                         mimeType="text/plain",
                     ),
diff --git a/tests/resources/test_file_resources.py b/tests/resources/test_file_resources.py
index 15ddd05..83c8897 100644
--- a/tests/resources/test_file_resources.py
+++ b/tests/resources/test_file_resources.py
@@ -3,6 +3,7 @@ import os
 import pytest
 from pathlib import Path
 from tempfile import NamedTemporaryFile
+from pydantic import FileUrl
 
 from fastmcp.resources import FileResource
 
@@ -30,7 +31,7 @@ class TestFileResource:
     def test_file_resource_creation(self, temp_file: Path):
         """Test creating a FileResource."""
         resource = FileResource(
-            uri=temp_file.as_uri(),
+            uri=FileUrl(temp_file.as_uri()),
             name="test",
             description="test file",
             path=temp_file,
@@ -45,9 +46,9 @@ class TestFileResource:
     def test_file_resource_str_path_conversion(self, temp_file: Path):
         """Test FileResource handles string paths."""
         resource = FileResource(
-            uri=f"file://{temp_file}",
+            uri=FileUrl(f"file://{temp_file}"),
             name="test",
-            path=str(temp_file),
+            path=Path(str(temp_file)),
         )
         assert isinstance(resource.path, Path)
         assert resource.path.is_absolute()
@@ -55,7 +56,7 @@ class TestFileResource:
     async def test_read_text_file(self, temp_file: Path):
         """Test reading a text file."""
         resource = FileResource(
-            uri=f"file://{temp_file}",
+            uri=FileUrl(f"file://{temp_file}"),
             name="test",
             path=temp_file,
         )
@@ -66,7 +67,7 @@ class TestFileResource:
     async def test_read_binary_file(self, temp_file: Path):
         """Test reading a file as binary."""
         resource = FileResource(
-            uri=f"file://{temp_file}",
+            uri=FileUrl(f"file://{temp_file}"),
             name="test",
             path=temp_file,
             is_binary=True,
@@ -79,7 +80,7 @@ class TestFileResource:
         """Test error on relative path."""
         with pytest.raises(ValueError, match="Path must be absolute"):
             FileResource(
-                uri="file:///test.txt",
+                uri=FileUrl("file:///test.txt"),
                 name="test",
                 path=Path("test.txt"),
             )
@@ -89,7 +90,7 @@ class TestFileResource:
         # Create path to non-existent file
         missing = temp_file.parent / "missing.txt"
         resource = FileResource(
-            uri="file:///missing.txt",
+            uri=FileUrl("file:///missing.txt"),
             name="test",
             path=missing,
         )
@@ -104,7 +105,7 @@ class TestFileResource:
         temp_file.chmod(0o000)  # Remove all permissions
         try:
             resource = FileResource(
-                uri=temp_file.as_uri(),
+                uri=FileUrl(temp_file.as_uri()),
                 name="test",
                 path=temp_file,
             )
diff --git a/tests/resources/test_function_resources.py b/tests/resources/test_function_resources.py
index f124d39..3a2d5e5 100644
--- a/tests/resources/test_function_resources.py
+++ b/tests/resources/test_function_resources.py
@@ -1,4 +1,4 @@
-from pydantic import BaseModel
+from pydantic import BaseModel, AnyUrl
 import pytest
 from fastmcp.resources import FunctionResource
 
@@ -13,7 +13,7 @@ class TestFunctionResource:
             return "test content"
 
         resource = FunctionResource(
-            uri="fn://test",
+            uri=AnyUrl("fn://test"),
             name="test",
             description="test function",
             fn=my_func,
@@ -31,7 +31,7 @@ class TestFunctionResource:
             return "Hello, world!"
 
         resource = FunctionResource(
-            uri="function://test",
+            uri=AnyUrl("function://test"),
             name="test",
             fn=get_data,
         )
@@ -46,7 +46,7 @@ class TestFunctionResource:
             return b"Hello, world!"
 
         resource = FunctionResource(
-            uri="function://test",
+            uri=AnyUrl("function://test"),
             name="test",
             fn=get_data,
         )
@@ -60,11 +60,12 @@ class TestFunctionResource:
             return {"key": "value"}
 
         resource = FunctionResource(
-            uri="function://test",
+            uri=AnyUrl("function://test"),
             name="test",
             fn=get_data,
         )
         content = await resource.read()
+        assert isinstance(content, str)
         assert '"key": "value"' in content
 
     async def test_error_handling(self):
@@ -74,7 +75,7 @@ class TestFunctionResource:
             raise ValueError("Test error")
 
         resource = FunctionResource(
-            uri="function://test",
+            uri=AnyUrl("function://test"),
             name="test",
             fn=failing_func,
         )
@@ -88,7 +89,7 @@ class TestFunctionResource:
             name: str
 
         resource = FunctionResource(
-            uri="function://test",
+            uri=AnyUrl("function://test"),
             name="test",
             fn=lambda: MyModel(name="test"),
         )
@@ -106,7 +107,7 @@ class TestFunctionResource:
             return CustomData()
 
         resource = FunctionResource(
-            uri="function://test",
+            uri=AnyUrl("function://test"),
             name="test",
             fn=get_data,
         )
diff --git a/tests/resources/test_resource_manager.py b/tests/resources/test_resource_manager.py
index 0422ceb..87061d9 100644
--- a/tests/resources/test_resource_manager.py
+++ b/tests/resources/test_resource_manager.py
@@ -1,6 +1,7 @@
 import pytest
 from pathlib import Path
 from tempfile import NamedTemporaryFile
+from pydantic import AnyUrl, FileUrl
 
 from fastmcp.resources import (
     FileResource,
@@ -34,7 +35,7 @@ class TestResourceManager:
         """Test adding a resource."""
         manager = ResourceManager()
         resource = FileResource(
-            uri=f"file://{temp_file}",
+            uri=FileUrl(f"file://{temp_file}"),
             name="test",
             path=temp_file,
         )
@@ -46,7 +47,7 @@ class TestResourceManager:
         """Test adding the same resource twice."""
         manager = ResourceManager()
         resource = FileResource(
-            uri=f"file://{temp_file}",
+            uri=FileUrl(f"file://{temp_file}"),
             name="test",
             path=temp_file,
         )
@@ -59,7 +60,7 @@ class TestResourceManager:
         """Test warning on duplicate resources."""
         manager = ResourceManager()
         resource = FileResource(
-            uri=f"file://{temp_file}",
+            uri=FileUrl(f"file://{temp_file}"),
             name="test",
             path=temp_file,
         )
@@ -71,7 +72,7 @@ class TestResourceManager:
         """Test disabling warning on duplicate resources."""
         manager = ResourceManager(warn_on_duplicate_resources=False)
         resource = FileResource(
-            uri=f"file://{temp_file}",
+            uri=FileUrl(f"file://{temp_file}"),
             name="test",
             path=temp_file,
         )
@@ -83,7 +84,7 @@ class TestResourceManager:
         """Test getting a resource by URI."""
         manager = ResourceManager()
         resource = FileResource(
-            uri=f"file://{temp_file}",
+            uri=FileUrl(f"file://{temp_file}"),
             name="test",
             path=temp_file,
         )
@@ -105,7 +106,7 @@ class TestResourceManager:
         )
         manager._templates[template.uri_template] = template
 
-        resource = await manager.get_resource("greet://world")
+        resource = await manager.get_resource(AnyUrl("greet://world"))
         assert isinstance(resource, FunctionResource)
         content = await resource.read()
         assert content == "Hello, world!"
@@ -114,18 +115,18 @@ class TestResourceManager:
         """Test getting a non-existent resource."""
         manager = ResourceManager()
         with pytest.raises(ValueError, match="Unknown resource"):
-            await manager.get_resource("unknown://test")
+            await manager.get_resource(AnyUrl("unknown://test"))
 
     def test_list_resources(self, temp_file: Path):
         """Test listing all resources."""
         manager = ResourceManager()
         resource1 = FileResource(
-            uri=f"file://{temp_file}",
+            uri=FileUrl(f"file://{temp_file}"),
             name="test1",
             path=temp_file,
         )
         resource2 = FileResource(
-            uri=f"file://{temp_file}2",
+            uri=FileUrl(f"file://{temp_file}2"),
             name="test2",
             path=temp_file,
         )
diff --git a/tests/resources/test_resource_template.py b/tests/resources/test_resource_template.py
index 08faf2e..9b459d7 100644
--- a/tests/resources/test_resource_template.py
+++ b/tests/resources/test_resource_template.py
@@ -1,121 +1,72 @@
+import json
 import pytest
-from fastmcp.resources import ResourceTemplate, FunctionResource
+from pydantic import BaseModel
+
+from fastmcp.resources import FunctionResource, ResourceTemplate
 
 
 class TestResourceTemplate:
     """Test ResourceTemplate functionality."""
 
-    def test_template_from_function(self):
+    def test_template_creation(self):
         """Test creating a template from a function."""
 
-        def weather(city: str, units: str = "metric") -> str:
-            return f"Weather in {city} ({units})"
+        def my_func(key: str, value: int) -> dict:
+            return {"key": key, "value": value}
 
         template = ResourceTemplate.from_function(
-            fn=weather,
-            uri_template="weather://{city}/current",
-            name="weather",
-            description="Get current weather",
+            fn=my_func,
+            uri_template="test://{key}/{value}",
+            name="test",
         )
-
-        assert template.name == "weather"
-        assert template.uri_template == "weather://{city}/current"
-        assert template.mime_type == "text/plain"
-        assert "city" in template.parameters["properties"]
-
-    def test_template_from_lambda_error(self):
-        """Test error when creating template from lambda without name."""
-        with pytest.raises(
-            ValueError, match="You must provide a name for lambda functions"
-        ):
-            ResourceTemplate.from_function(
-                fn=lambda x: x,
-                uri_template="test://{x}",
-            )
+        assert template.uri_template == "test://{key}/{value}"
+        assert template.name == "test"
+        assert template.mime_type == "text/plain"  # default
+        test_input = {"key": "test", "value": 42}
+        assert template.fn(**test_input) == my_func(**test_input)
 
     def test_template_matches(self):
-        """Test URI matching against template."""
+        """Test matching URIs against a template."""
 
-        def dummy(x: str) -> str:
-            return x
+        def my_func(key: str, value: int) -> dict:
+            return {"key": key, "value": value}
 
         template = ResourceTemplate.from_function(
-            fn=dummy,
-            uri_template="test://{x}/value",
+            fn=my_func,
+            uri_template="test://{key}/{value}",
             name="test",
         )
 
-        # Test matching URI
-        params = template.matches("test://hello/value")
-        assert params == {"x": "hello"}
+        # Valid match
+        params = template.matches("test://foo/123")
+        assert params == {"key": "foo", "value": "123"}
 
-        # Test non-matching URI
-        params = template.matches("test://hello/wrong")
-        assert params is None
+        # No match
+        assert template.matches("test://foo") is None
+        assert template.matches("other://foo/123") is None
 
-    async def test_create_text_resource(self):
-        """Test creating a text resource from template."""
+    async def test_create_resource(self):
+        """Test creating a resource from a template."""
 
-        def greet(name: str) -> str:
-            return f"Hello, {name}!"
+        def my_func(key: str, value: int) -> dict:
+            return {"key": key, "value": value}
 
         template = ResourceTemplate.from_function(
-            fn=greet,
-            uri_template="greet://{name}",
-            name="greeter",
-        )
-
-        resource = await template.create_resource(
-            "greet://world",
-            {"name": "world"},
-        )
-
-        assert isinstance(resource, FunctionResource)
-        content = await resource.read()
-        assert content == "Hello, world!"
-
-    async def test_create_binary_resource(self):
-        """Test creating a binary resource from template."""
-
-        def get_bytes(value: str) -> bytes:
-            return value.encode()
-
-        template = ResourceTemplate.from_function(
-            fn=get_bytes,
-            uri_template="bytes://{value}",
-            name="bytes",
-        )
-
-        resource = await template.create_resource(
-            "bytes://test",
-            {"value": "test"},
-        )
-
-        assert isinstance(resource, FunctionResource)
-        content = await resource.read()
-        assert content == b"test"
-
-    async def test_json_conversion(self):
-        """Test automatic JSON conversion of non-string/bytes results."""
-
-        def get_data(key: str) -> dict:
-            return {"key": key, "value": 123}
-
-        template = ResourceTemplate.from_function(
-            fn=get_data,
-            uri_template="data://{key}",
-            name="data",
+            fn=my_func,
+            uri_template="test://{key}/{value}",
+            name="test",
         )
 
         resource = await template.create_resource(
-            "data://test",
-            {"key": "test"},
+            "test://foo/123",
+            {"key": "foo", "value": 123},
         )
 
         assert isinstance(resource, FunctionResource)
         content = await resource.read()
-        assert '"key": "test"' in content
-        assert '"value": 123' in content
+        assert isinstance(content, str)
+        data = json.loads(content)
+        assert data == {"key": "foo", "value": 123}
 
     async def test_template_error(self):
         """Test error handling in template resource creation."""
@@ -174,65 +125,57 @@ class TestResourceTemplate:
         content = await resource.read()
         assert content == b"test"
 
-    async def test_async_json_conversion(self):
-        """Test automatic JSON conversion of async results."""
+    async def test_basemodel_conversion(self):
+        """Test handling of BaseModel types."""
 
-        async def get_data(key: str) -> dict:
-            return {"key": key, "value": 123}
+        class MyModel(BaseModel):
+            key: str
+            value: int
+
+        def get_data(key: str, value: int) -> MyModel:
+            return MyModel(key=key, value=value)
 
         template = ResourceTemplate.from_function(
             fn=get_data,
-            uri_template="data://{key}",
-            name="data",
+            uri_template="test://{key}/{value}",
+            name="test",
         )
 
         resource = await template.create_resource(
-            "data://test",
-            {"key": "test"},
+            "test://foo/123",
+            {"key": "foo", "value": 123},
         )
 
         assert isinstance(resource, FunctionResource)
         content = await resource.read()
-        assert '"key": "test"' in content
-        assert '"value": 123' in content
-
-    async def test_async_error(self):
-        """Test error handling in async template."""
-
-        async def failing_func(x: str) -> str:
-            raise ValueError("Test error")
+        assert isinstance(content, str)
+        data = json.loads(content)
+        assert data == {"key": "foo", "value": 123}
 
-        template = ResourceTemplate.from_function(
-            fn=failing_func,
-            uri_template="fail://{x}",
-            name="fail",
-        )
-
-        with pytest.raises(
-            ValueError, match="Error creating resource from template: Test error"
-        ):
-            await template.create_resource("fail://test", {"x": "test"})
+    async def test_custom_type_conversion(self):
+        """Test handling of custom types."""
 
-    async def test_sync_returning_coroutine(self):
-        """Test sync function that returns a coroutine."""
+        class CustomData:
+            def __init__(self, value: str):
+                self.value = value
 
-        async def async_helper(name: str) -> str:
-            return f"Hello, {name}!"
+            def __str__(self) -> str:
+                return self.value
 
-        def get_greeting(name: str) -> str:
-            return async_helper(name)  # Returns coroutine
+        def get_data(value: str) -> CustomData:
+            return CustomData(value)
 
         template = ResourceTemplate.from_function(
-            fn=get_greeting,
-            uri_template="greet://{name}",
-            name="greeter",
+            fn=get_data,
+            uri_template="test://{value}",
+            name="test",
         )
 
         resource = await template.create_resource(
-            "greet://world",
-            {"name": "world"},
+            "test://hello",
+            {"value": "hello"},
         )
 
         assert isinstance(resource, FunctionResource)
         content = await resource.read()
-        assert content == "Hello, world!"
+        assert content == "hello"
diff --git a/tests/resources/test_resources.py b/tests/resources/test_resources.py
index 0dc5a1b..9eb3d37 100644
--- a/tests/resources/test_resources.py
+++ b/tests/resources/test_resources.py
@@ -1,4 +1,5 @@
 import pytest
+from pydantic import AnyUrl
 
 from fastmcp.resources import FunctionResource, Resource
 
@@ -14,7 +15,7 @@ class TestResourceValidation:
 
         # Valid URI
         resource = FunctionResource(
-            uri="http://example.com/data",
+            uri=AnyUrl("http://example.com/data"),
             name="test",
             fn=dummy_func,
         )
@@ -23,7 +24,7 @@ class TestResourceValidation:
         # Missing protocol
         with pytest.raises(ValueError, match="Input should be a valid URL"):
             FunctionResource(
-                uri="invalid",
+                uri=AnyUrl("invalid"),
                 name="test",
                 fn=dummy_func,
             )
@@ -31,7 +32,7 @@ class TestResourceValidation:
         # Missing host
         with pytest.raises(ValueError, match="Input should be a valid URL"):
             FunctionResource(
-                uri="http://",
+                uri=AnyUrl("http://"),
                 name="test",
                 fn=dummy_func,
             )
@@ -43,7 +44,7 @@ class TestResourceValidation:
             return "data"
 
         resource = FunctionResource(
-            uri="resource://my-resource",
+            uri=AnyUrl("resource://my-resource"),
             fn=dummy_func,
         )
         assert resource.name == "resource://my-resource"
@@ -62,7 +63,7 @@ class TestResourceValidation:
 
         # Explicit name takes precedence over URI
         resource = FunctionResource(
-            uri="resource://uri-name",
+            uri=AnyUrl("resource://uri-name"),
             name="explicit-name",
             fn=dummy_func,
         )
@@ -76,14 +77,14 @@ class TestResourceValidation:
 
         # Default mime type
         resource = FunctionResource(
-            uri="resource://test",
+            uri=AnyUrl("resource://test"),
             fn=dummy_func,
         )
         assert resource.mime_type == "text/plain"
 
         # Custom mime type
         resource = FunctionResource(
-            uri="resource://test",
+            uri=AnyUrl("resource://test"),
             fn=dummy_func,
             mime_type="application/json",
         )
@@ -96,4 +97,4 @@ class TestResourceValidation:
             pass
 
         with pytest.raises(TypeError, match="abstract method"):
-            ConcreteResource(uri="test://test", name="test")  # type: ignore
+            ConcreteResource(uri=AnyUrl("test://test"), name="test")  # type: ignore
diff --git a/tests/servers/test_file_server.py b/tests/servers/test_file_server.py
index d8f53a3..1eb750e 100644
--- a/tests/servers/test_file_server.py
+++ b/tests/servers/test_file_server.py
@@ -25,7 +25,7 @@ def mcp() -> FastMCP:
 
 
 @pytest.fixture(autouse=True)
-def resources(mcp: FastMCP, test_dir: Path) -> None:
+def resources(mcp: FastMCP, test_dir: Path) -> FastMCP:
     @mcp.resource("dir://test_dir")
     def list_test_dir() -> list[str]:
         """List the files in the test directory"""
@@ -59,7 +59,7 @@ def resources(mcp: FastMCP, test_dir: Path) -> None:
 
 
 @pytest.fixture(autouse=True)
-def tools(mcp: FastMCP, test_dir: Path) -> None:
+def tools(mcp: FastMCP, test_dir: Path) -> FastMCP:
     @mcp.tool()
     def delete_file(path: str) -> bool:
         # ensure path is in test_dir
@@ -68,6 +68,8 @@ def tools(mcp: FastMCP, test_dir: Path) -> None:
         Path(path).unlink()
         return True
 
+    return mcp
+
 
 async def test_list_resources(mcp: FastMCP):
     resources = await mcp.list_resources()
diff --git a/tests/test_cli.py b/tests/test_cli.py
index a26874a..fefd6ca 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -320,7 +320,11 @@ mcp = FastMCP("test", dependencies=["pandas", "numpy"])
                 x in deps_section for x in ["--with", "numpy", "--with", "pandas"]
             )
 
-            assert mock_run.call_args_list[1][1] == {"check": True, "shell": True}
+            # Verify subprocess call kwargs, allowing for environment variables
+            call_kwargs = mock_run.call_args_list[1][1]
+            assert call_kwargs["check"] is True
+            assert call_kwargs["shell"] is True
+            assert isinstance(call_kwargs["env"], dict)
         else:
             # same verification for unix, just with different command prefix
             actual_cmd = mock_run.call_args_list[0][0][0]
@@ -342,7 +346,11 @@ mcp = FastMCP("test", dependencies=["pandas", "numpy"])
                 x in deps_section for x in ["--with", "numpy", "--with", "pandas"]
             )
 
-            assert mock_run.call_args_list[0][1] == {"check": True, "shell": False}
+            # Verify subprocess call kwargs, allowing for environment variables
+            call_kwargs = mock_run.call_args_list[0][1]
+            assert call_kwargs["check"] is True
+            assert call_kwargs["shell"] is False
+            assert isinstance(call_kwargs["env"], dict)
 
 
 def test_run_with_dependencies(mock_config, server_file):
diff --git a/tests/test_func_metadata.py b/tests/test_func_metadata.py
index 980d2c5..d0429a1 100644
--- a/tests/test_func_metadata.py
+++ b/tests/test_func_metadata.py
@@ -192,9 +192,9 @@ def test_skip_names():
     assert "also_skip" not in meta.arg_model.model_fields
 
     # Validate that we can call with only non-skipped parameters
-    model = meta.arg_model.model_validate({"keep_this": 1, "also_keep": 2.5})
-    assert model.keep_this == 1
-    assert model.also_keep == 2.5
+    model: BaseModel = meta.arg_model.model_validate({"keep_this": 1, "also_keep": 2.5})  # type: ignore
+    assert model.keep_this == 1  # type: ignore
+    assert model.also_keep == 2.5  # type: ignore
 
 
 async def test_lambda_function():
diff --git a/tests/test_server.py b/tests/test_server.py
index 950cf22..6303cba 100644
--- a/tests/test_server.py
+++ b/tests/test_server.py
@@ -7,7 +7,13 @@ from mcp.shared.exceptions import McpError
 from mcp.shared.memory import (
     create_connected_server_and_client_session as client_session,
 )
-from mcp.types import ImageContent, TextContent
+from mcp.types import (
+    ImageContent,
+    TextContent,
+    TextResourceContents,
+    BlobResourceContents,
+)
+from pydantic import AnyUrl
 
 from fastmcp import Context, FastMCP
 from fastmcp.prompts.base import EmbeddedResource, Message, UserMessage
@@ -100,7 +106,7 @@ class TestServerTools:
         mcp.add_tool(tool_fn)
         async with client_session(mcp._mcp_server) as client:
             result = await client.call_tool("my_tool", {"arg1": "value"})
-            assert "error" not in result
+            assert not hasattr(result, "error")
             assert len(result.content) > 0
 
     async def test_tool_exception_handling(self):
@@ -109,29 +115,43 @@ class TestServerTools:
         async with client_session(mcp._mcp_server) as client:
             result = await client.call_tool("error_tool_fn", {})
             assert len(result.content) == 1
-            assert result.content[0].type == "text"
-            assert "Test error" in result.content[0].text
+            content = result.content[0]
+            assert isinstance(content, TextContent)
+            assert "Test error" in content.text
+            assert result.isError is True
+
+    async def test_tool_error_handling(self):
+        mcp = FastMCP()
+        mcp.add_tool(error_tool_fn)
+        async with client_session(mcp._mcp_server) as client:
+            result = await client.call_tool("error_tool_fn", {})
+            assert len(result.content) == 1
+            content = result.content[0]
+            assert isinstance(content, TextContent)
+            assert "Test error" in content.text
             assert result.isError is True
 
-    async def test_tool_exception_content(self):
+    async def test_tool_error_details(self):
         """Test that exception details are properly formatted in the response"""
         mcp = FastMCP()
         mcp.add_tool(error_tool_fn)
         async with client_session(mcp._mcp_server) as client:
             result = await client.call_tool("error_tool_fn", {})
-            assert result.content[0].type == "text"
-            assert isinstance(result.content[0].text, str)
-            assert "Test error" in result.content[0].text
+            content = result.content[0]
+            assert isinstance(content, TextContent)
+            assert isinstance(content.text, str)
+            assert "Test error" in content.text
             assert result.isError is True
 
-    async def test_tool_text_conversion(self):
+    async def test_tool_return_value_conversion(self):
         mcp = FastMCP()
         mcp.add_tool(tool_fn)
         async with client_session(mcp._mcp_server) as client:
             result = await client.call_tool("tool_fn", {"x": 1, "y": 2})
             assert len(result.content) == 1
-            assert result.content[0].type == "text"
-            assert result.content[0].text == "3"
+            content = result.content[0]
+            assert isinstance(content, TextContent)
+            assert content.text == "3"
 
     async def test_tool_image_helper(self, tmp_path: Path):
         # Create a test image
@@ -143,10 +163,12 @@ class TestServerTools:
         async with client_session(mcp._mcp_server) as client:
             result = await client.call_tool("image_tool_fn", {"path": str(image_path)})
             assert len(result.content) == 1
-            assert result.content[0].type == "image"
-            assert result.content[0].mimeType == "image/png"
+            content = result.content[0]
+            assert isinstance(content, ImageContent)
+            assert content.type == "image"
+            assert content.mimeType == "image/png"
             # Verify base64 encoding
-            decoded = base64.b64decode(result.content[0].data)
+            decoded = base64.b64decode(content.data)
             assert decoded == b"fake png data"
 
     async def test_tool_mixed_content(self):
@@ -155,11 +177,13 @@ class TestServerTools:
         async with client_session(mcp._mcp_server) as client:
             result = await client.call_tool("mixed_content_tool_fn", {})
             assert len(result.content) == 2
-            assert result.content[0].type == "text"
-            assert result.content[0].text == "Hello"
-            assert result.content[1].type == "image"
-            assert result.content[1].mimeType == "image/png"
-            assert result.content[1].data == "abc"
+            content1 = result.content[0]
+            content2 = result.content[1]
+            assert isinstance(content1, TextContent)
+            assert content1.text == "Hello"
+            assert isinstance(content2, ImageContent)
+            assert content2.mimeType == "image/png"
+            assert content2.data == "abc"
 
     async def test_tool_mixed_list_with_image(self, tmp_path: Path):
         """Test that lists containing Image objects and other types are handled correctly"""
@@ -181,18 +205,22 @@ class TestServerTools:
             result = await client.call_tool("mixed_list_fn", {})
             assert len(result.content) == 4
             # Check text conversion
-            assert result.content[0].type == "text"
-            assert "text message" in result.content[0].text
+            content1 = result.content[0]
+            assert isinstance(content1, TextContent)
+            assert content1.text == "text message"
             # Check image conversion
-            assert result.content[1].type == "image"
-            assert result.content[1].mimeType == "image/png"
-            assert base64.b64decode(result.content[1].data) == b"test image data"
+            content2 = result.content[1]
+            assert isinstance(content2, ImageContent)
+            assert content2.mimeType == "image/png"
+            assert base64.b64decode(content2.data) == b"test image data"
             # Check dict conversion
-            assert result.content[2].type == "text"
-            assert '"key": "value"' in result.content[2].text
+            content3 = result.content[2]
+            assert isinstance(content3, TextContent)
+            assert '"key": "value"' in content3.text
             # Check direct TextContent
-            assert result.content[3].type == "text"
-            assert result.content[3].text == "direct content"
+            content4 = result.content[3]
+            assert isinstance(content4, TextContent)
+            assert content4.text == "direct content"
 
 
 class TestServerResources:
@@ -202,11 +230,14 @@ class TestServerResources:
         def get_text():
             return "Hello, world!"
 
-        resource = FunctionResource(uri="resource://test", name="test", fn=get_text)
+        resource = FunctionResource(
+            uri=AnyUrl("resource://test"), name="test", fn=get_text
+        )
         mcp.add_resource(resource)
 
         async with client_session(mcp._mcp_server) as client:
-            result = await client.read_resource("resource://test")
+            result = await client.read_resource(AnyUrl("resource://test"))
+            assert isinstance(result.contents[0], TextResourceContents)
             assert result.contents[0].text == "Hello, world!"
 
     async def test_binary_resource(self):
@@ -216,16 +247,16 @@ class TestServerResources:
             return b"Binary data"
 
         resource = FunctionResource(
-            uri="resource://binary",
+            uri=AnyUrl("resource://binary"),
             name="binary",
             fn=get_binary,
-            is_binary=True,
             mime_type="application/octet-stream",
         )
         mcp.add_resource(resource)
 
         async with client_session(mcp._mcp_server) as client:
-            result = await client.read_resource("resource://binary")
+            result = await client.read_resource(AnyUrl("resource://binary"))
+            assert isinstance(result.contents[0], BlobResourceContents)
             assert result.contents[0].blob == base64.b64encode(b"Binary data").decode()
 
     async def test_file_resource_text(self, tmp_path: Path):
@@ -235,11 +266,14 @@ class TestServerResources:
         text_file = tmp_path / "test.txt"
         text_file.write_text("Hello from file!")
 
-        resource = FileResource(uri="file://test.txt", name="test.txt", path=text_file)
+        resource = FileResource(
+            uri=AnyUrl("file://test.txt"), name="test.txt", path=text_file
+        )
         mcp.add_resource(resource)
 
         async with client_session(mcp._mcp_server) as client:
-            result = await client.read_resource("file://test.txt")
+            result = await client.read_resource(AnyUrl("file://test.txt"))
+            assert isinstance(result.contents[0], TextResourceContents)
             assert result.contents[0].text == "Hello from file!"
 
     async def test_file_resource_binary(self, tmp_path: Path):
@@ -250,16 +284,16 @@ class TestServerResources:
         binary_file.write_bytes(b"Binary file data")
 
         resource = FileResource(
-            uri="file://test.bin",
+            uri=AnyUrl("file://test.bin"),
             name="test.bin",
             path=binary_file,
-            is_binary=True,
             mime_type="application/octet-stream",
         )
         mcp.add_resource(resource)
 
         async with client_session(mcp._mcp_server) as client:
-            result = await client.read_resource("file://test.bin")
+            result = await client.read_resource(AnyUrl("file://test.bin"))
+            assert isinstance(result.contents[0], BlobResourceContents)
             assert (
                 result.contents[0].blob
                 == base64.b64encode(b"Binary file data").decode()
@@ -275,7 +309,7 @@ class TestServerResourceTemplates:
         with pytest.raises(ValueError, match="Mismatch between URI parameters"):
 
             @mcp.resource("resource://data")
-            def get_data(param: str) -> str:
+            def get_data_fn(param: str) -> str:
                 return f"Data: {param}"
 
     async def test_resource_with_uri_params(self):
@@ -305,7 +339,8 @@ class TestServerResourceTemplates:
             return f"Data for {name}"
 
         async with client_session(mcp._mcp_server) as client:
-            result = await client.read_resource("resource://test/data")
+            result = await client.read_resource(AnyUrl("resource://test/data"))
+            assert isinstance(result.contents[0], TextResourceContents)
             assert result.contents[0].text == "Data for test"
 
     async def test_resource_mismatched_params(self):
@@ -327,7 +362,10 @@ class TestServerResourceTemplates:
             return f"Data for {org}/{repo}"
 
         async with client_session(mcp._mcp_server) as client:
-            result = await client.read_resource("resource://cursor/fastmcp/data")
+            result = await client.read_resource(
+                AnyUrl("resource://cursor/fastmcp/data")
+            )
+            assert isinstance(result.contents[0], TextResourceContents)
             assert result.contents[0].text == "Data for cursor/fastmcp"
 
     async def test_resource_multiple_mismatched_params(self):
@@ -337,18 +375,19 @@ class TestServerResourceTemplates:
         with pytest.raises(ValueError, match="Mismatch between URI parameters"):
 
             @mcp.resource("resource://{org}/{repo}/data")
-            def get_data(org: str, repo_2: str) -> str:
+            def get_data_mismatched(org: str, repo_2: str) -> str:
                 return f"Data for {org}"
 
         """Test that a resource with no parameters works as a regular resource"""
         mcp = FastMCP()
 
         @mcp.resource("resource://static")
-        def get_data() -> str:
+        def get_static_data() -> str:
             return "Static data"
 
         async with client_session(mcp._mcp_server) as client:
-            result = await client.read_resource("resource://static")
+            result = await client.read_resource(AnyUrl("resource://static"))
+            assert isinstance(result.contents[0], TextResourceContents)
             assert result.contents[0].text == "Static data"
 
     async def test_template_to_resource_conversion(self):
@@ -395,8 +434,10 @@ class TestContextInjection:
         async with client_session(mcp._mcp_server) as client:
             result = await client.call_tool("tool_with_context", {"x": 42})
             assert len(result.content) == 1
-            assert "Request" in result.content[0].text
-            assert "42" in result.content[0].text
+            content = result.content[0]
+            assert isinstance(content, TextContent)
+            assert "Request" in content.text
+            assert "42" in content.text
 
     async def test_async_context(self):
         """Test that context works in async functions."""
@@ -410,8 +451,10 @@ class TestContextInjection:
         async with client_session(mcp._mcp_server) as client:
             result = await client.call_tool("async_tool", {"x": 42})
             assert len(result.content) == 1
-            assert "Async request" in result.content[0].text
-            assert "42" in result.content[0].text
+            content = result.content[0]
+            assert isinstance(content, TextContent)
+            assert "Async request" in content.text
+            assert "42" in content.text
 
     async def test_context_logging(self):
         """Test that context logging methods work."""
@@ -428,7 +471,9 @@ class TestContextInjection:
         async with client_session(mcp._mcp_server) as client:
             result = await client.call_tool("logging_tool", {"msg": "test"})
             assert len(result.content) == 1
-            assert "Logged messages for test" in result.content[0].text
+            content = result.content[0]
+            assert isinstance(content, TextContent)
+            assert "Logged messages for test" in content.text
 
     async def test_optional_context(self):
         """Test that context is optional."""
@@ -441,7 +486,9 @@ class TestContextInjection:
         async with client_session(mcp._mcp_server) as client:
             result = await client.call_tool("no_context", {"x": 21})
             assert len(result.content) == 1
-            assert result.content[0].text == "42"
+            content = result.content[0]
+            assert isinstance(content, TextContent)
+            assert content.text == "42"
 
     async def test_context_resource_access(self):
         """Test that context can access resources."""
@@ -459,7 +506,9 @@ class TestContextInjection:
         async with client_session(mcp._mcp_server) as client:
             result = await client.call_tool("tool_with_resource", {})
             assert len(result.content) == 1
-            assert "Read resource: resource data" in result.content[0].text
+            content = result.content[0]
+            assert isinstance(content, TextContent)
+            assert "Read resource: resource data" in content.text
 
 
 class TestServerPrompts:
@@ -477,23 +526,26 @@ class TestServerPrompts:
         assert len(prompts) == 1
         assert prompts[0].name == "fn"
         # Don't compare functions directly since validate_call wraps them
-        assert await prompts[0].render() == [
-            UserMessage(content=TextContent(type="text", text="Hello, world!"))
-        ]
+        content = await prompts[0].render()
+        assert isinstance(content[0].content, TextContent)
+        assert content[0].content.text == "Hello, world!"
 
-    def test_prompt_decorator_with_name(self):
+    async def test_prompt_decorator_with_name(self):
         """Test prompt decorator with custom name."""
         mcp = FastMCP()
 
-        @mcp.prompt(name="custom")
+        @mcp.prompt(name="custom_name")
         def fn() -> str:
             return "Hello, world!"
 
         prompts = mcp._prompt_manager.list_prompts()
         assert len(prompts) == 1
-        assert prompts[0].name == "custom"
+        assert prompts[0].name == "custom_name"
+        content = await prompts[0].render()
+        assert isinstance(content[0].content, TextContent)
+        assert content[0].content.text == "Hello, world!"
 
-    def test_prompt_decorator_with_description(self):
+    async def test_prompt_decorator_with_description(self):
         """Test prompt decorator with custom description."""
         mcp = FastMCP()
 
@@ -504,13 +556,16 @@ class TestServerPrompts:
         prompts = mcp._prompt_manager.list_prompts()
         assert len(prompts) == 1
         assert prompts[0].description == "A custom description"
+        content = await prompts[0].render()
+        assert isinstance(content[0].content, TextContent)
+        assert content[0].content.text == "Hello, world!"
 
     def test_prompt_decorator_error(self):
         """Test error when decorator is used incorrectly."""
         mcp = FastMCP()
         with pytest.raises(TypeError, match="decorator was used incorrectly"):
 
-            @mcp.prompt
+            @mcp.prompt  # type: ignore
             def fn() -> str:
                 return "Hello, world!"
 
@@ -524,13 +579,16 @@ class TestServerPrompts:
 
         async with client_session(mcp._mcp_server) as client:
             result = await client.list_prompts()
+            assert result.prompts is not None
             assert len(result.prompts) == 1
-            assert result.prompts[0].name == "fn"
-            assert len(result.prompts[0].arguments) == 2
-            assert result.prompts[0].arguments[0].name == "name"
-            assert result.prompts[0].arguments[0].required is True
-            assert result.prompts[0].arguments[1].name == "optional"
-            assert result.prompts[0].arguments[1].required is False
+            prompt = result.prompts[0]
+            assert prompt.name == "fn"
+            assert prompt.arguments is not None
+            assert len(prompt.arguments) == 2
+            assert prompt.arguments[0].name == "name"
+            assert prompt.arguments[0].required is True
+            assert prompt.arguments[1].name == "optional"
+            assert prompt.arguments[1].required is False
 
     async def test_get_prompt(self):
         """Test getting a prompt through MCP protocol."""
@@ -543,9 +601,11 @@ class TestServerPrompts:
         async with client_session(mcp._mcp_server) as client:
             result = await client.get_prompt("fn", {"name": "World"})
             assert len(result.messages) == 1
-            assert result.messages[0].role == "user"
-            assert result.messages[0].content.type == "text"
-            assert result.messages[0].content.text == "Hello, World!"
+            message = result.messages[0]
+            assert message.role == "user"
+            content = message.content
+            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."""
@@ -556,22 +616,25 @@ class TestServerPrompts:
             return UserMessage(
                 content=EmbeddedResource(
                     type="resource",
-                    resource={
-                        "uri": "file://test.txt",
-                        "text": "File contents",
-                        "mimeType": "text/plain",
-                    },
+                    resource=TextResourceContents(
+                        uri=AnyUrl("file://file.txt"),
+                        text="File contents",
+                        mimeType="text/plain",
+                    ),
                 )
             )
 
         async with client_session(mcp._mcp_server) as client:
             result = await client.get_prompt("fn")
             assert len(result.messages) == 1
-            assert result.messages[0].role == "user"
-            assert result.messages[0].content.type == "resource"
-            assert str(result.messages[0].content.resource.uri) == "file://test.txt/"
-            assert result.messages[0].content.resource.text == "File contents"
-            assert result.messages[0].content.resource.mimeType == "text/plain"
+            message = result.messages[0]
+            assert message.role == "user"
+            content = message.content
+            assert isinstance(content, EmbeddedResource)
+            resource = content.resource
+            assert isinstance(resource, TextResourceContents)
+            assert resource.text == "File contents"
+            assert resource.mimeType == "text/plain"
 
     async def test_get_unknown_prompt(self):
         """Test error when getting unknown prompt."""
@@ -585,9 +648,9 @@ class TestServerPrompts:
         mcp = FastMCP()
 
         @mcp.prompt()
-        def fn(name: str) -> str:
+        def prompt_fn(name: str) -> str:
             return f"Hello, {name}!"
 
         async with client_session(mcp._mcp_server) as client:
             with pytest.raises(McpError, match="Missing required arguments"):
-                await client.get_prompt("fn")
+                await client.get_prompt("prompt_fn")