Repository: jlowin/fastmcp
Pull request #260 (2025-04-25 20:55:55)

Added feature : Load MCP server using config 
The current Client supports loading server from ClientTransport , FastMCP server , script path, URL . It does not support config dictionary to add a server which is a standard way in many clients . 
I have added this feature for adding a single server through config dictionary. 

Example client.py 
```python
from fastmcp import Client
import asyncio
config = {
  "mcpServers": {
    "time": {
      "command": "uvx",
      "args": ["mcp-server-time", "--local-timezone=Asia/Kolkata"]
    }
  }
}
client = Client(config)
print(client.transport.args)
async def main():
    async with client:
        print(f"Client connected: {client.is_connected()}")

        tools = await client.list_tools()
        
        print(f"Available tools: {[tool.name for tool in tools]}")
      
        if any(tool.name == "get_current_time" for tool in tools):
            result = await client.call_tool("get_current_time", {"timezone": "Asia/Kolkata"})
            print(f"Time result: {result[0].text}")

    print(f"Client connected: {client.is_connected()}")

if __name__ == "__main__":
    asyncio.run(main())
``` 

Output : 
```powershell
['mcp-server-time', '--local-timezone=Asia/Kolkata']
Client connected: True
Available tools: ['get_current_time', 'convert_time']
Time result: {
  "timezone": "Asia/Kolkata",
  "datetime": "2025-04-26T02:11:10+05:30",
  "is_dst": false
}
Client connected: False
``` 
  


Patch:
diff --git a/README.md b/README.md
index 31275b4..d0b83eb 100644
--- a/README.md
+++ b/README.md
@@ -112,7 +112,7 @@ FastMCP aims to be:
 ### Servers
 - **Create** servers with minimal boilerplate using intuitive decorators
 - **Proxy** existing servers to modify configuration or transport
-- **Compose**  servers into complex applications
+- **Compose** servers by into complex applications
 - **Generate** servers from OpenAPI specs or FastAPI objects
 
 ### Clients
diff --git a/docs/servers/resources.mdx b/docs/servers/resources.mdx
index 447e854..4db09f2 100644
--- a/docs/servers/resources.mdx
+++ b/docs/servers/resources.mdx
@@ -249,21 +249,15 @@ With these two templates defined, clients can request a variety of resources:
 
 ### Wildcard Parameters
 
-<VersionBadge version="2.2.4" />
-
-<Tip>
-Please note: FastMCP's support for wildcard parameters is an **extension** of the Model Context Protocol standard, which otherwise follows RFC 6570. Since all template processing happens in the FastMCP server, this should not cause any compatibility issues with other MCP implementations.
-</Tip>
-
+<VersionBadge version="2.2.3" />
 
 Resource templates support wildcard parameters that can match multiple path segments. While standard parameters (`{param}`) only match a single path segment and don't cross "/" boundaries, wildcard parameters (`{param*}`) can capture multiple segments including slashes. Wildcards capture all subsequent path segments *up until* the defined part of the URI template (whether literal or another parameter). This allows you to have multiple wildcard parameters in a single URI template.
 
-```python {15, 23}
+```python
 from fastmcp import FastMCP
 
 mcp = FastMCP(name="DataServer")
 
-
 # Standard parameter only matches one segment
 @mcp.resource("files://{filename}")
 def get_file(filename: str) -> str:
@@ -271,7 +265,6 @@ def get_file(filename: str) -> str:
     # Will only match files://<single-segment>
     return f"File content for: {filename}"
 
-
 # Wildcard parameter can match multiple segments
 @mcp.resource("path://{filepath*}")
 def get_path_content(filepath: str) -> str:
@@ -279,7 +272,6 @@ def get_path_content(filepath: str) -> str:
     # Can match path://docs/server/resources.mdx
     return f"Content at path: {filepath}"
 
-
 # Mixing standard and wildcard parameters
 @mcp.resource("repo://{owner}/{path*}/template.py")
 def get_template_file(owner: str, path: str) -> dict:
diff --git a/src/fastmcp/cli/cli.py b/src/fastmcp/cli/cli.py
index cb4c112..379059d 100644
--- a/src/fastmcp/cli/cli.py
+++ b/src/fastmcp/cli/cli.py
@@ -223,27 +223,6 @@ def dev(
             help="Additional packages to install",
         ),
     ] = [],
-    inspector_version: Annotated[
-        str | None,
-        typer.Option(
-            "--inspector-version",
-            help="Version of the MCP Inspector to use",
-        ),
-    ] = None,
-    ui_port: Annotated[
-        int | None,
-        typer.Option(
-            "--ui-port",
-            help="Port for the MCP Inspector UI",
-        ),
-    ] = None,
-    server_port: Annotated[
-        int | None,
-        typer.Option(
-            "--server-port",
-            help="Port for the MCP Inspector Proxy server",
-        ),
-    ] = None,
 ) -> None:
     """Run a MCP server with the MCP Inspector."""
     file, server_object = _parse_file_path(file_spec)
@@ -255,8 +234,6 @@ def dev(
             "server_object": server_object,
             "with_editable": str(with_editable) if with_editable else None,
             "with_packages": with_packages,
-            "ui_port": ui_port,
-            "server_port": server_port,
         },
     )
 
@@ -266,11 +243,7 @@ def dev(
         if hasattr(server, "dependencies"):
             with_packages = list(set(with_packages + server.dependencies))
 
-        env_vars = {}
-        if ui_port:
-            env_vars["CLIENT_PORT"] = str(ui_port)
-        if server_port:
-            env_vars["SERVER_PORT"] = str(server_port)
+        uv_cmd = _build_uv_command(file_spec, with_editable, with_packages)
 
         # Get the correct npx command
         npx_cmd = _get_npx_command()
@@ -281,19 +254,13 @@ def dev(
             )
             sys.exit(1)
 
-        inspector_cmd = "@modelcontextprotocol/inspector"
-        if inspector_version:
-            inspector_cmd += f"@{inspector_version}"
-
-        uv_cmd = _build_uv_command(file_spec, with_editable, with_packages)
-
         # Run the MCP Inspector command with shell=True on Windows
         shell = sys.platform == "win32"
         process = subprocess.run(
-            [npx_cmd, inspector_cmd] + uv_cmd,
+            [npx_cmd, "@modelcontextprotocol/inspector"] + uv_cmd,
             check=True,
             shell=shell,
-            env=dict(os.environ.items()) | env_vars,
+            env=dict(os.environ.items()),  # Convert to list of tuples for env update
         )
         sys.exit(process.returncode)
     except subprocess.CalledProcessError as e:
diff --git a/src/fastmcp/client/base.py b/src/fastmcp/client/base.py
index 8b13789..e69de29 100644
--- a/src/fastmcp/client/base.py
+++ b/src/fastmcp/client/base.py
@@ -1,1 +0,0 @@
-
diff --git a/src/fastmcp/client/client.py b/src/fastmcp/client/client.py
index 7fdd218..8509511 100644
--- a/src/fastmcp/client/client.py
+++ b/src/fastmcp/client/client.py
@@ -35,7 +35,7 @@ class Client:
 
     def __init__(
         self,
-        transport: ClientTransport | FastMCP | AnyUrl | Path | str,
+        transport: ClientTransport | FastMCP | AnyUrl | Path | dict[str, Any] | str,
         # Common args
         roots: RootsList | RootsHandler | None = None,
         sampling_handler: SamplingHandler | None = None,
diff --git a/src/fastmcp/client/transports.py b/src/fastmcp/client/transports.py
index 210286b..4f73e7c 100644
--- a/src/fastmcp/client/transports.py
+++ b/src/fastmcp/client/transports.py
@@ -6,9 +6,7 @@ import shutil
 import sys
 from collections.abc import AsyncIterator
 from pathlib import Path
-from typing import (
-    TypedDict,
-)
+from typing import Any, TypedDict
 
 from exceptiongroup import BaseExceptionGroup, catch
 from mcp import ClientSession, McpError, StdioServerParameters
@@ -416,7 +414,7 @@ class FastMCPTransport(ClientTransport):
 
 
 def infer_transport(
-    transport: ClientTransport | FastMCPServer | AnyUrl | Path | str,
+    transport: ClientTransport | FastMCPServer | AnyUrl | Path | dict[str, Any] | str,
 ) -> ClientTransport:
     """
     Infer the appropriate transport type from the given transport argument.
@@ -450,6 +448,41 @@ def infer_transport(
     elif isinstance(transport, AnyUrl | str) and str(transport).startswith("ws"):
         return WSTransport(url=transport)
 
+    ## if the transport is a config dict
+    elif isinstance(transport, dict):
+        if "mcpServers" not in transport:
+            raise ValueError("Invalid transport dictionary: missing 'mcpServers' key")
+        else:
+            server = transport["mcpServers"]
+            if len(list(server.keys())) > 1:
+                raise ValueError(
+                    "Invalid transport dictionary: multiple servers found - only one expected"
+                )
+            server_name = list(server.keys())[0]
+            # Stdio transport
+            if "command" in server[server_name] and "args" in server[server_name]:
+                return StdioTransport(
+                    command=server[server_name]["command"],
+                    args=server[server_name]["args"],
+                    env=server[server_name].get("env", None),
+                    cwd=server[server_name].get("cwd", None),
+                )
+
+            # HTTP transport
+            elif "url" in server:
+                return SSETransport(
+                    url=server["url"],
+                    headers=server.get("headers", None),
+                )
+
+            # WebSocket transport
+            elif "ws_url" in server:
+                return WSTransport(
+                    url=server["ws_url"],
+                )
+
+            raise ValueError("Cannot determine transport type from dictionary")
+
     # the transport is an unknown type
     else:
         raise ValueError(f"Could not infer a valid transport from: {transport}")
diff --git a/src/fastmcp/resources/template.py b/src/fastmcp/resources/template.py
index 0d804f3..d174365 100644
--- a/src/fastmcp/resources/template.py
+++ b/src/fastmcp/resources/template.py
@@ -95,7 +95,7 @@ class ResourceTemplate(BaseModel):
             raise ValueError("You must provide a name for lambda functions")
 
         # Validate that URI params match function params
-        uri_params = set(re.findall(r"{(\w+)(?:\*)?}", uri_template))
+        uri_params = set(re.findall(r"{(\w+)}", uri_template))
         if not uri_params:
             raise ValueError("URI template must contain at least one parameter")
 
diff --git a/src/fastmcp/server/openapi.py b/src/fastmcp/server/openapi.py
index 0911cae..bc65c2f 100644
--- a/src/fastmcp/server/openapi.py
+++ b/src/fastmcp/server/openapi.py
@@ -257,7 +257,7 @@ class OpenAPIResource(Resource):
         self._client = client
         self._route = route
 
-    async def read(self) -> str | bytes:
+    async def read(self) -> str:
         """Fetch the resource data by making an HTTP request."""
         try:
             # Extract path parameters from the URI if present
@@ -297,16 +297,15 @@ class OpenAPIResource(Resource):
             # Raise for 4xx/5xx responses
             response.raise_for_status()
 
-            # Determine content type and return appropriate format
-            content_type = response.headers.get("content-type", "").lower()
-
-            if "application/json" in content_type:
-                result = response.json()
-                return json.dumps(result)
-            elif any(ct in content_type for ct in ["text/", "application/xml"]):
-                return response.text
+            # Return response content based on mime type
+            if self.mime_type == "application/json":
+                try:
+                    return response.json()
+                except (json.JSONDecodeError, ValueError):
+                    # Fallback to returning the text
+                    return response.text
             else:
-                return response.content
+                return response.text
 
         except httpx.HTTPStatusError as e:
             # Handle HTTP errors (4xx, 5xx)
@@ -344,13 +343,59 @@ class OpenAPIResourceTemplate(ResourceTemplate):
             uri_template=uri_template,
             name=name,
             description=description,
-            fn=lambda **kwargs: None,
+            fn=self._create_resource_fn,
             parameters=parameters,
             tags=tags,
         )
         self._client = client
         self._route = route
 
+    async def _create_resource_fn(self, **kwargs):
+        """Create a resource with parameters."""
+        # Prepare the path with parameters
+        path = self._route.path
+        for param_name, param_value in kwargs.items():
+            path = path.replace(f"{{{param_name}}}", str(param_value))
+
+        try:
+            response = await self._client.request(
+                method=self._route.method,
+                url=path,
+                timeout=30.0,  # Default timeout
+            )
+
+            # Raise for 4xx/5xx responses
+            response.raise_for_status()
+
+            # Determine the mime type from the response
+            content_type = response.headers.get("content-type", "application/json")
+            mime_type = content_type.split(";")[0].strip()
+
+            # Return the appropriate data
+            if mime_type == "application/json":
+                try:
+                    return response.json()
+                except (json.JSONDecodeError, ValueError):
+                    return response.text
+            else:
+                return response.text
+
+        except httpx.HTTPStatusError as e:
+            error_message = (
+                f"HTTP error {e.response.status_code}: {e.response.reason_phrase}"
+            )
+            try:
+                error_data = e.response.json()
+                error_message += f" - {error_data}"
+            except (json.JSONDecodeError, ValueError):
+                if e.response.text:
+                    error_message += f" - {e.response.text}"
+
+            raise ValueError(error_message)
+
+        except httpx.RequestError as e:
+            raise ValueError(f"Request error: {str(e)}")
+
     async def create_resource(self, uri: str, params: dict[str, Any]) -> Resource:
         """Create a resource with the given parameters."""
         # Generate a URI for this resource instance
@@ -364,8 +409,9 @@ class OpenAPIResourceTemplate(ResourceTemplate):
             route=self._route,
             uri=uri,
             name=f"{self.name}-{'-'.join(uri_parts)}",
-            description=self.description or f"Resource for {self._route.path}",
-            mime_type="application/json",
+            description=self.description
+            or f"Resource for {self._route.path}",  # Provide default if None
+            mime_type="application/json",  # Default, will be updated when read
             tags=set(self._route.tags or []),
         )
 
diff --git a/src/fastmcp/utilities/func_metadata.py b/src/fastmcp/utilities/func_metadata.py
index 138cb43..777e7d7 100644
--- a/src/fastmcp/utilities/func_metadata.py
+++ b/src/fastmcp/utilities/func_metadata.py
@@ -7,15 +7,7 @@ from typing import (
     ForwardRef,
 )
 
-from pydantic import (
-    BaseModel,
-    ConfigDict,
-    Field,
-    TypeAdapter,
-    ValidationError,
-    WithJsonSchema,
-    create_model,
-)
+from pydantic import BaseModel, ConfigDict, Field, WithJsonSchema, create_model
 from pydantic._internal._typing_extra import eval_type_backport
 from pydantic.fields import FieldInfo
 from pydantic_core import PydanticUndefined
@@ -88,18 +80,14 @@ class FuncMetadata(BaseModel):
         dicts (JSON objects) as JSON strings, which can be pre-parsed here.
         """
         new_data = data.copy()  # Shallow copy
-        for field_name, field_info in self.arg_model.model_fields.items():
+        for field_name, _field_info in self.arg_model.model_fields.items():
             if field_name not in data.keys():
                 continue
             if isinstance(data[field_name], str):
                 try:
                     pre_parsed = json.loads(data[field_name])
-
-                    # Check if the pre_parsed value is valid for the field
-                    validator = TypeAdapter(field_info.annotation)
-                    validator.validate_python(pre_parsed)
-                except (json.JSONDecodeError, ValidationError):
-                    continue  # Not JSON or invalid for the field
+                except json.JSONDecodeError:
+                    continue  # Not JSON - skip
                 if isinstance(pre_parsed, str | int | float):
                     # This is likely that the raw value is e.g. `"hello"` which we
                     # Should really be parsed as '"hello"' in Python - but if we parse


Test patch:
diff --git a/tests/resources/test_resource_template.py b/tests/resources/test_resource_template.py
index 86fd24d..38b2f45 100644
--- a/tests/resources/test_resource_template.py
+++ b/tests/resources/test_resource_template.py
@@ -297,66 +297,6 @@ class TestResourceTemplate:
         content = await resource.read()
         assert content == "hello"
 
-    async def test_wildcard_param_can_create_resource(self):
-        """Test that wildcard parameters are valid."""
-
-        def identity(path: str) -> str:
-            return path
-
-        template = ResourceTemplate.from_function(
-            fn=identity,
-            uri_template="test://{path*}.py",
-            name="test",
-        )
-
-        assert await template.create_resource(
-            "test://path/to/test.py",
-            {"path": "path/to/test.py"},
-        )
-
-    async def test_wildcard_param_matches(self):
-        def identify(path: str) -> str:
-            return path
-
-        template = ResourceTemplate.from_function(
-            fn=identify,
-            uri_template="test://src/{path*}.py",
-            name="test",
-        )
-        # Valid match
-        params = template.matches("test://src/path/to/test.py")
-        assert params == {"path": "path/to/test"}
-
-    async def test_multiple_wildcard_params(self):
-        """Test that multiple wildcard parameters are valid."""
-
-        def identity(path: str, path2: str) -> str:
-            return f"{path}/{path2}"
-
-        template = ResourceTemplate.from_function(
-            fn=identity,
-            uri_template="test://{path*}/xyz/{path2*}",
-            name="test",
-        )
-
-        params = template.matches("test://path/to/xyz/abc")
-        assert params == {"path": "path/to", "path2": "abc"}
-
-    async def test_wildcard_param_with_regular_param(self):
-        """Test that a wildcard parameter can be used with a regular parameter."""
-
-        def identity(prefix: str, path: str) -> str:
-            return f"{prefix}/{path}"
-
-        template = ResourceTemplate.from_function(
-            fn=identity,
-            uri_template="test://{prefix}/{path*}",
-            name="test",
-        )
-
-        params = template.matches("test://src/path/to/test.py")
-        assert params == {"prefix": "src", "path": "path/to/test.py"}
-
 
 class TestMatchUriTemplate:
     """Test match_uri_template function."""
@@ -503,20 +443,3 @@ class TestMatchUriTemplate:
         uri_template = "test://a/{x}{y}"
         result = match_uri_template(uri=uri, uri_template=uri_template)
         assert result is None
-
-    @pytest.mark.parametrize(
-        "uri, expected_params",
-        [
-            ("file://abc/xyz.py", {"path": "xyz"}),
-            ("file://abc/x/y/z.py", {"path": "x/y/z"}),
-            ("file://abc/x/y/z/.py", {"path": "x/y/z/"}),
-            ("file://abc/x/y/z.md", None),
-            ("file://x/y/z.txt", None),
-        ],
-    )
-    def test_match_uri_template_with_non_slash_suffix(
-        self, uri: str, expected_params: dict[str, str]
-    ):
-        uri_template = "file://abc/{path*}.py"
-        result = match_uri_template(uri=uri, uri_template=uri_template)
-        assert result == expected_params
diff --git a/tests/server/test_openapi.py b/tests/server/test_openapi.py
index 8b6e828..afad3b6 100644
--- a/tests/server/test_openapi.py
+++ b/tests/server/test_openapi.py
@@ -1,14 +1,12 @@
-import base64
 import json
 import re
 
 import httpx
 import pytest
 from dirty_equals import IsStr
-from fastapi import FastAPI, HTTPException, Response
-from fastapi.responses import PlainTextResponse
+from fastapi import FastAPI, HTTPException
 from httpx import ASGITransport, AsyncClient
-from mcp.types import BlobResourceContents, TextContent, TextResourceContents
+from mcp.types import TextContent
 from pydantic import BaseModel, TypeAdapter
 from pydantic.networks import AnyUrl
 
@@ -68,17 +66,6 @@ def fastapi_app(users_db: dict[int, User]) -> FastAPI:
         user.name = name
         return user
 
-    @app.get("/ping", response_class=PlainTextResponse)
-    async def ping() -> str:
-        """Ping the server."""
-        return "pong"
-
-    @app.get("/ping-bytes")
-    async def ping_bytes() -> Response:
-        """Ping the server and get a bytes response."""
-
-        return Response(content=b"pong")
-
     return app
 
 
@@ -133,8 +120,7 @@ class TestTools:
         """
         By default, tools exclude GET methods
         """
-        async with Client(fastmcp_openapi_server) as client:
-            tools = await client.list_tools()
+        tools = await fastmcp_openapi_server._mcp_list_tools()
         assert len(tools) == 2
 
         assert tools[0].model_dump() == dict(
@@ -170,10 +156,9 @@ class TestTools:
         """
         The tool created by the OpenAPI server should be the same as the original
         """
-        async with Client(fastmcp_openapi_server) as client:
-            tool_response = await client.call_tool(
-                "create_user_users_post", {"name": "David", "active": False}
-            )
+        tool_response = await fastmcp_openapi_server._mcp_call_tool(
+            "create_user_users_post", {"name": "David", "active": False}
+        )
 
         # Convert TextContent to dict for comparison
         assert isinstance(tool_response, list) and len(tool_response) == 1
@@ -188,13 +173,10 @@ class TestTools:
         assert len(response.json()) == 4
 
         # Check that the user was created via MCP
-        async with Client(fastmcp_openapi_server) as client:
-            user_response = await client.read_resource(
-                "resource://openapi/get_user_users__user_id__get/4"
-            )
-            assert isinstance(user_response[0], TextResourceContents)
-            response_text = user_response[0].text
-            user = json.loads(response_text)
+        user_response = await fastmcp_openapi_server._mcp_read_resource(
+            "resource://openapi/get_user_users__user_id__get/4"
+        )
+        user = user_response[0].content
         assert user == expected_user
 
     async def test_call_update_user_name_tool(
@@ -203,11 +185,9 @@ class TestTools:
         """
         The tool created by the OpenAPI server should be the same as the original
         """
-        async with Client(fastmcp_openapi_server) as client:
-            tool_response = await client.call_tool(
-                "update_user_name_users__user_id__name_patch",
-                {"user_id": 1, "name": "XYZ"},
-            )
+        tool_response = await fastmcp_openapi_server._mcp_call_tool(
+            "update_user_name_users__user_id__name_patch", {"user_id": 1, "name": "XYZ"}
+        )
 
         # Convert TextContent to dict for comparison
         assert isinstance(tool_response, list) and len(tool_response) == 1
@@ -222,13 +202,10 @@ class TestTools:
         assert expected_data in response.json()
 
         # Check that the user was updated via MCP
-        async with Client(fastmcp_openapi_server) as client:
-            user_response = await client.read_resource(
-                "resource://openapi/get_user_users__user_id__get/1"
-            )
-            assert isinstance(user_response[0], TextResourceContents)
-            response_text = user_response[0].text
-            user = json.loads(response_text)
+        user_response = await fastmcp_openapi_server._mcp_read_resource(
+            "resource://openapi/get_user_users__user_id__get/1"
+        )
+        user = user_response[0].content
         assert user == expected_data
 
 
@@ -237,9 +214,8 @@ class TestResources:
         """
         By default, resources exclude GET methods without parameters
         """
-        async with Client(fastmcp_openapi_server) as client:
-            resources = await client.list_resources()
-        assert len(resources) == 3
+        resources = await fastmcp_openapi_server._mcp_list_resources()
+        assert len(resources) == 1
         assert resources[0].uri == AnyUrl("resource://openapi/get_users_users_get")
         assert resources[0].name == "get_users_users_get"
 
@@ -252,47 +228,17 @@ class TestResources:
         """
         The resource created by the OpenAPI server should be the same as the original
         """
-
         json_users = TypeAdapter(list[User]).dump_python(
             sorted(users_db.values(), key=lambda x: x.id)
         )
-        async with Client(fastmcp_openapi_server) as client:
-            resource_response = await client.read_resource(
-                "resource://openapi/get_users_users_get"
-            )
-            assert isinstance(resource_response[0], TextResourceContents)
-            response_text = resource_response[0].text
-            resource = json.loads(response_text)
+        resource_response = await fastmcp_openapi_server._mcp_read_resource(
+            "resource://openapi/get_users_users_get"
+        )
+        resource = resource_response[0].content
         assert resource == json_users
         response = await api_client.get("/users")
         assert response.json() == json_users
 
-    async def test_get_bytes_resource(
-        self,
-        fastmcp_openapi_server: FastMCPOpenAPI,
-        api_client,
-    ):
-        """Test reading a resource that returns bytes."""
-        async with Client(fastmcp_openapi_server) as client:
-            resource_response = await client.read_resource(
-                "resource://openapi/ping_bytes_ping_bytes_get"
-            )
-            assert isinstance(resource_response[0], BlobResourceContents)
-            assert base64.b64decode(resource_response[0].blob) == b"pong"
-
-    async def test_get_str_resource(
-        self,
-        fastmcp_openapi_server: FastMCPOpenAPI,
-        api_client,
-    ):
-        """Test reading a resource that returns a string."""
-        async with Client(fastmcp_openapi_server) as client:
-            resource_response = await client.read_resource(
-                "resource://openapi/ping_ping_get"
-            )
-            assert isinstance(resource_response[0], TextResourceContents)
-            assert resource_response[0].text == "pong"
-
 
 class TestResourceTemplates:
     async def test_list_resource_templates(
@@ -301,8 +247,7 @@ class TestResourceTemplates:
         """
         By default, resource templates exclude GET methods without parameters
         """
-        async with Client(fastmcp_openapi_server) as client:
-            resource_templates = await client.list_resource_templates()
+        resource_templates = await fastmcp_openapi_server._mcp_list_resource_templates()
         assert len(resource_templates) == 1
         assert resource_templates[0].name == "get_user_users__user_id__get"
         assert (
@@ -320,14 +265,11 @@ class TestResourceTemplates:
         The resource template created by the OpenAPI server should be the same as the original
         """
         user_id = 2
-        async with Client(fastmcp_openapi_server) as client:
-            resource_response = await client.read_resource(
-                f"resource://openapi/get_user_users__user_id__get/{user_id}"
-            )
-            assert isinstance(resource_response[0], TextResourceContents)
-            response_text = resource_response[0].text
-            resource = json.loads(response_text)
+        resource_response = await fastmcp_openapi_server._mcp_read_resource(
+            f"resource://openapi/get_user_users__user_id__get/{user_id}"
+        )
 
+        resource = resource_response[0].content
         assert resource == users_db[user_id].model_dump()
         response = await api_client.get(f"/users/{user_id}")
         assert resource == response.json()
@@ -338,8 +280,7 @@ class TestPrompts:
         """
         By default, there are no prompts.
         """
-        async with Client(fastmcp_openapi_server) as client:
-            prompts = await client.list_prompts()
+        prompts = await fastmcp_openapi_server._mcp_list_prompts()
         assert len(prompts) == 0
 
 
@@ -553,23 +494,20 @@ class TestOpenAPI30Compatibility:
 
     async def test_resource_discovery(self, openapi_30_server):
         """Test that resources are correctly discovered from an OpenAPI 3.0 spec."""
-        async with Client(openapi_30_server) as client:
-            resources = await client.list_resources()
+        resources = await openapi_30_server._mcp_list_resources()
         assert len(resources) == 1
         assert resources[0].uri == AnyUrl("resource://openapi/listProducts")
 
     async def test_resource_template_discovery(self, openapi_30_server):
         """Test that resource templates are correctly discovered from an OpenAPI 3.0 spec."""
-        async with Client(openapi_30_server) as client:
-            templates = await client.list_resource_templates()
+        templates = await openapi_30_server._mcp_list_resource_templates()
         assert len(templates) == 1
         assert templates[0].name == "getProduct"
         assert templates[0].uriTemplate == r"resource://openapi/getProduct/{product_id}"
 
     async def test_tool_discovery(self, openapi_30_server):
         """Test that tools are correctly discovered from an OpenAPI 3.0 spec."""
-        async with Client(openapi_30_server) as client:
-            tools = await client.list_tools()
+        tools = await openapi_30_server._mcp_list_tools()
         assert len(tools) == 1
         assert tools[0].name == "createProduct"
         assert "name" in tools[0].inputSchema["properties"]
@@ -577,26 +515,20 @@ class TestOpenAPI30Compatibility:
 
     async def test_resource_access(self, openapi_30_server):
         """Test reading a resource from an OpenAPI 3.0 server."""
-        async with Client(openapi_30_server) as client:
-            resource_response = await client.read_resource(
-                "resource://openapi/listProducts"
-            )
-            assert isinstance(resource_response[0], TextResourceContents)
-            response_text = resource_response[0].text
-            content = json.loads(response_text)
+        resource_response = await openapi_30_server._mcp_read_resource(
+            "resource://openapi/listProducts"
+        )
+        content = resource_response[0].content
         assert len(content) == 2
         assert content[0]["name"] == "Product 1"
         assert content[1]["name"] == "Product 2"
 
     async def test_resource_template_access(self, openapi_30_server):
         """Test reading a resource from template from an OpenAPI 3.0 server."""
-        async with Client(openapi_30_server) as client:
-            resource_response = await client.read_resource(
-                "resource://openapi/getProduct/p1"
-            )
-            assert isinstance(resource_response[0], TextResourceContents)
-            response_text = resource_response[0].text
-            content = json.loads(response_text)
+        resource_response = await openapi_30_server._mcp_read_resource(
+            "resource://openapi/getProduct/p1"
+        )
+        content = resource_response[0].content
         assert content["id"] == "p1"
         assert content["name"] == "Product 1"
         assert content["price"] == 19.99
@@ -733,23 +665,20 @@ class TestOpenAPI31Compatibility:
 
     async def test_resource_discovery(self, openapi_31_server):
         """Test that resources are correctly discovered from an OpenAPI 3.1 spec."""
-        async with Client(openapi_31_server) as client:
-            resources = await client.list_resources()
+        resources = await openapi_31_server._mcp_list_resources()
         assert len(resources) == 1
         assert resources[0].uri == AnyUrl("resource://openapi/listOrders")
 
     async def test_resource_template_discovery(self, openapi_31_server):
         """Test that resource templates are correctly discovered from an OpenAPI 3.1 spec."""
-        async with Client(openapi_31_server) as client:
-            templates = await client.list_resource_templates()
+        templates = await openapi_31_server._mcp_list_resource_templates()
         assert len(templates) == 1
         assert templates[0].name == "getOrder"
         assert templates[0].uriTemplate == r"resource://openapi/getOrder/{order_id}"
 
     async def test_tool_discovery(self, openapi_31_server):
         """Test that tools are correctly discovered from an OpenAPI 3.1 spec."""
-        async with Client(openapi_31_server) as client:
-            tools = await client.list_tools()
+        tools = await openapi_31_server._mcp_list_tools()
         assert len(tools) == 1
         assert tools[0].name == "createOrder"
         assert "customer" in tools[0].inputSchema["properties"]
@@ -757,26 +686,20 @@ class TestOpenAPI31Compatibility:
 
     async def test_resource_access(self, openapi_31_server):
         """Test reading a resource from an OpenAPI 3.1 server."""
-        async with Client(openapi_31_server) as client:
-            resource_response = await client.read_resource(
-                "resource://openapi/listOrders"
-            )
-            assert isinstance(resource_response[0], TextResourceContents)
-            response_text = resource_response[0].text
-            content = json.loads(response_text)
+        resource_response = await openapi_31_server._mcp_read_resource(
+            "resource://openapi/listOrders"
+        )
+        content = resource_response[0].content
         assert len(content) == 2
         assert content[0]["customer"] == "Alice"
         assert content[1]["customer"] == "Bob"
 
     async def test_resource_template_access(self, openapi_31_server):
         """Test reading a resource from template from an OpenAPI 3.1 server."""
-        async with Client(openapi_31_server) as client:
-            resource_response = await client.read_resource(
-                "resource://openapi/getOrder/o1"
-            )
-            assert isinstance(resource_response[0], TextResourceContents)
-            response_text = resource_response[0].text
-            content = json.loads(response_text)
+        resource_response = await openapi_31_server._mcp_read_resource(
+            "resource://openapi/getOrder/o1"
+        )
+        content = resource_response[0].content
         assert content["id"] == "o1"
         assert content["customer"] == "Alice"
         assert content["items"] == ["item1", "item2"]
@@ -806,9 +729,8 @@ class TestMountFastMCP:
         await mcp.import_server("fastapi", fastmcp_openapi_server)
 
         # Check that resources are available with prefixed URIs
-        async with Client(mcp) as client:
-            resources = await client.list_resources()
-        assert len(resources) == 3
+        resources = await mcp._mcp_list_resources()
+        assert len(resources) == 1
         # We're checking the key used by mcp to store the resource
         # The prefixed URI is used as the key, but the resource's original uri is preserved
         prefixed_uri = "fastapi+resource://openapi/get_users_users_get"
@@ -816,8 +738,7 @@ class TestMountFastMCP:
         assert resource is not None
 
         # Check that templates are available with prefixed URIs
-        async with Client(mcp) as client:
-            templates = await client.list_resource_templates()
+        templates = await mcp._mcp_list_resource_templates()
         assert len(templates) == 1
         assert templates[0].name == "get_user_users__user_id__get"
         prefixed_template_uri = (
@@ -827,12 +748,10 @@ class TestMountFastMCP:
         assert template is not None
 
         # Check that tools are available with prefixed names
-        async with Client(mcp) as client:
-            tools = await client.list_tools()
+        tools = await mcp._mcp_list_tools()
         assert len(tools) == 2
         assert tools[0].name == "fastapi_create_user_users_post"
         assert tools[1].name == "fastapi_update_user_name_users__user_id__name_patch"
 
-        async with Client(mcp) as client:
-            prompts = await client.list_prompts()
+        prompts = await mcp._mcp_list_prompts()
         assert len(prompts) == 0
diff --git a/tests/server/test_server.py b/tests/server/test_server.py
index f27e0c0..42a47ae 100644
--- a/tests/server/test_server.py
+++ b/tests/server/test_server.py
@@ -531,18 +531,6 @@ class TestTemplateDecorator:
         template = templates_dict["resource://{param}"]
         assert template.tags == {"template", "test-tag"}
 
-    async def test_template_decorator_wildcard_param(self):
-        mcp = FastMCP()
-
-        @mcp.resource("resource://{param*}")
-        def template_resource(param: str) -> str:
-            return f"Template resource: {param}"
-
-        templates_dict = await mcp.get_resource_templates()
-        template = templates_dict["resource://{param*}"]
-        assert template.uri_template == "resource://{param*}"
-        assert template.name == "template_resource"
-
 
 class TestPromptDecorator:
     async def test_prompt_decorator(self):
@@ -1155,67 +1143,6 @@ class TestServerResourceTemplates:
         template = templates_dict["resource://{param}"]
         assert template.tags == {"template", "test-tag"}
 
-    async def test_template_decorator_wildcard_param(self):
-        mcp = FastMCP()
-
-        @mcp.resource("resource://{param*}")
-        def template_resource(param: str) -> str:
-            return f"Template resource: {param}"
-
-        async with Client(mcp) as client:
-            result = await client.read_resource(AnyUrl("resource://test/data"))
-            assert isinstance(result[0], TextResourceContents)
-            assert result[0].text == "Template resource: test/data"
-
-    async def test_templates_match_in_order_of_definition(self):
-        """
-        If a wildcard template is defined first, it will take priority over another
-        matching template.
-
-        """
-        mcp = FastMCP()
-
-        @mcp.resource("resource://{param*}")
-        def template_resource(param: str) -> str:
-            return f"Template resource 1: {param}"
-
-        @mcp.resource("resource://{x}/{y}")
-        def template_resource_with_params(x: str, y: str) -> str:
-            return f"Template resource 2: {x}/{y}"
-
-        async with Client(mcp) as client:
-            result = await client.read_resource(AnyUrl("resource://a/b/c"))
-            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 isinstance(result[0], TextResourceContents)
-            assert result[0].text == "Template resource 1: a/b"
-
-    async def test_templates_shadow_each_other_reorder(self):
-        """
-        If a wildcard template is defined second, it will *not* take priority over
-        another matching template.
-        """
-        mcp = FastMCP()
-
-        @mcp.resource("resource://{x}/{y}")
-        def template_resource_with_params(x: str, y: str) -> str:
-            return f"Template resource 1: {x}/{y}"
-
-        @mcp.resource("resource://{param*}")
-        def template_resource(param: str) -> str:
-            return f"Template resource 2: {param}"
-
-        async with Client(mcp) as client:
-            result = await client.read_resource(AnyUrl("resource://a/b/c"))
-            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 isinstance(result[0], TextResourceContents)
-            assert result[0].text == "Template resource 1: a/b"
-
 
 class TestContextInjection:
     """Test context injection in tools."""
diff --git a/tests/utilities/test_func_metadata.py b/tests/utilities/test_func_metadata.py
index 9beb27e..96d8c6f 100644
--- a/tests/utilities/test_func_metadata.py
+++ b/tests/utilities/test_func_metadata.py
@@ -174,74 +174,6 @@ def test_str_vs_list_str():
     assert result["str_or_list"] == ["hello", "world"]
 
 
-def test_keep_str_as_str():
-    """Test that string arguments are kept as strings"""
-
-    def func_with_str_types(string: str):
-        return string
-
-    meta = func_metadata(func_with_str_types)
-    result = meta.pre_parse_json(
-        {"string": "{'nice to meet you': 'hello', 'goodbye': 5}"}
-    )
-    assert result["string"] == "{'nice to meet you': 'hello', 'goodbye': 5}"
-
-
-def test_missing_annotation():
-    """Test that missing annotations don't cause errors"""
-
-    def fn(x, y):
-        return x + y
-
-    meta = func_metadata(fn)
-    result = meta.pre_parse_json({"x": "1", "y": "2"})
-    assert result["x"] == "1"
-    assert result["y"] == "2"
-
-
-def test_keep_str_union_as_str():
-    """Test that string arguments are kept as strings"""
-
-    def func_with_str_types(string: str | dict[int, str] | None):
-        return string
-
-    meta = func_metadata(func_with_str_types)
-    result = meta.pre_parse_json(
-        {"string": "{'nice to meet you': 'hello', 'goodbye': 5}"}
-    )
-    assert result["string"] == "{'nice to meet you': 'hello', 'goodbye': 5}"
-
-
-def test_keep_str_complex_type_as_str():
-    """Test that string arguments are kept as strings because it's invalid for the field"""
-
-    class SomeModel(BaseModel):
-        x: int
-        y: dict[int, str]
-
-    def func_with_str_types(string: str | SomeModel | None):
-        return string
-
-    meta = func_metadata(func_with_str_types)
-    result = meta.pre_parse_json({"string": '{"x": 1, "y": {"invalid": "hello"}}'})
-    assert result["string"] == '{"x": 1, "y": {"invalid": "hello"}}'
-
-
-def test_convert_str_to_complex_type():
-    """Test that string arguments are converted to the complex type because it's valid for the field"""
-
-    class SomeModel(BaseModel):
-        x: int
-        y: dict[int, str]
-
-    def func_with_str_types(string: str | SomeModel | None):
-        return string
-
-    meta = func_metadata(func_with_str_types)
-    result = meta.pre_parse_json({"string": '{"x": 1, "y": {"1": "hello"}}'})
-    assert result["string"] == {"x": 1, "y": {"1": "hello"}}
-
-
 def test_skip_names():
     """Test that skipped parameters are not included in the model"""