mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 07:09:11 +02:00
Repoint tests and examples off removed deprecations
Replaces FastMCP.as_proxy() helper calls with create_proxy(), rewrites the mount() as_proxy=/prefix= kwarg tests to plain mount() (the params are gone), and deletes deprecation-only tests for as_proxy() and remove_tool().
This commit is contained in:
parent
e5d9d1f17d
commit
6a6fdcb2bb
11 changed files with 39 additions and 176 deletions
|
|
@ -3,7 +3,7 @@ This example demonstrates how to set up and use an in-memory FastMCP proxy.
|
|||
|
||||
It illustrates the pattern:
|
||||
1. Create an original FastMCP server with some tools.
|
||||
2. Create a proxy FastMCP server using ``FastMCP.as_proxy(original_server)``.
|
||||
2. Create a proxy FastMCP server using ``create_proxy(original_server)``.
|
||||
3. Use another Client to connect to the proxy server (in-memory) and interact with the original server's tools through the proxy.
|
||||
"""
|
||||
|
||||
|
|
@ -13,6 +13,7 @@ from mcp.types import TextContent
|
|||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.client import Client
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
|
||||
class EchoService:
|
||||
|
|
@ -38,9 +39,9 @@ async def main():
|
|||
# 2. Proxy Server Creation
|
||||
print("\nStep 2: Creating the Proxy Server (InMemoryProxy)...")
|
||||
print(
|
||||
f" (Using FastMCP.as_proxy to wrap '{original_server.name}' directly)"
|
||||
f" (Using create_proxy to wrap '{original_server.name}' directly)"
|
||||
)
|
||||
proxy_server = FastMCP.as_proxy(original_server, name="InMemoryProxy")
|
||||
proxy_server = create_proxy(original_server, name="InMemoryProxy")
|
||||
print(
|
||||
f" -> Proxy Server '{proxy_server.name}' created, proxying '{original_server.name}'."
|
||||
)
|
||||
|
|
|
|||
|
|
@ -63,9 +63,9 @@ def check_app_status() -> dict[str, str]:
|
|||
|
||||
|
||||
# Mount sub-applications
|
||||
app.mount(server=weather_app, prefix="weather")
|
||||
app.mount(server=weather_app, namespace="weather")
|
||||
|
||||
app.mount(server=news_app, prefix="news")
|
||||
app.mount(server=news_app, namespace="news")
|
||||
|
||||
|
||||
async def get_server_details():
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from mcp_types import TextResourceContents
|
|||
|
||||
from fastmcp import Client, FastMCP
|
||||
from fastmcp.client.transports import SSETransport, StreamableHttpTransport
|
||||
from fastmcp.server import create_proxy
|
||||
from fastmcp.server.providers.openapi import MCPType, RouteMap
|
||||
from fastmcp.utilities.tests import run_server_async
|
||||
|
||||
|
|
@ -63,7 +64,7 @@ async def sse_server():
|
|||
@pytest.fixture
|
||||
async def proxy_server(shttp_server: str):
|
||||
"""Start a proxy server."""
|
||||
proxy = FastMCP.as_proxy(StreamableHttpTransport(shttp_server))
|
||||
proxy = create_proxy(StreamableHttpTransport(shttp_server))
|
||||
async with run_server_async(proxy, transport="http") as url:
|
||||
yield url
|
||||
|
||||
|
|
|
|||
|
|
@ -46,10 +46,12 @@ class TestParallelCalls:
|
|||
return script_file
|
||||
|
||||
async def test_parallel_calls(self, stdio_script):
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
backend_transport = PythonStdioTransport(script_path=stdio_script)
|
||||
backend_client = Client(transport=backend_transport)
|
||||
|
||||
proxy = FastMCP.as_proxy(backend=backend_client, name="PROXY")
|
||||
proxy = create_proxy(backend_client, name="PROXY")
|
||||
|
||||
count = 10
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from mcp.server.context import ServerRequestContext
|
|||
|
||||
from fastmcp import Client, FastMCP
|
||||
from fastmcp.exceptions import ToolError
|
||||
from fastmcp.server import create_proxy
|
||||
from fastmcp.server.context import Context
|
||||
from fastmcp.server.middleware import CallNext, Middleware, MiddlewareContext
|
||||
from fastmcp.tools.base import ToolResult
|
||||
|
|
@ -475,7 +476,7 @@ class TestProxyServer:
|
|||
):
|
||||
# proxy server will have its tools listed as well as called in order to
|
||||
# apply transforms and filters prior to the call.
|
||||
proxy_server = FastMCP.as_proxy(mcp_server, name="Proxy Server")
|
||||
proxy_server = create_proxy(mcp_server, name="Proxy Server")
|
||||
async with Client(proxy_server) as client:
|
||||
await client.call_tool("add", {"a": 1, "b": 2})
|
||||
|
||||
|
|
@ -492,7 +493,7 @@ class TestProxyServer:
|
|||
):
|
||||
"""Tests that tags on remote FastMCP servers are visible to middleware
|
||||
via proxy. See https://github.com/PrefectHQ/fastmcp/issues/1300"""
|
||||
proxy_server = FastMCP.as_proxy(mcp_server, name="Proxy Server")
|
||||
proxy_server = create_proxy(mcp_server, name="Proxy Server")
|
||||
|
||||
TAGS = []
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from mcp_types import TextContent
|
|||
from fastmcp import FastMCP
|
||||
from fastmcp.client import Client
|
||||
from fastmcp.client.transports import SSETransport
|
||||
from fastmcp.server import create_proxy
|
||||
from fastmcp.tools.base import Tool
|
||||
from fastmcp.tools.tool_transform import TransformedTool
|
||||
|
||||
|
|
@ -272,7 +273,7 @@ class TestMultipleServerMount:
|
|||
)
|
||||
|
||||
# Create a proxy server that will fail to connect
|
||||
unreachable_proxy = FastMCP.as_proxy(
|
||||
unreachable_proxy = create_proxy(
|
||||
unreachable_client, name="unreachable_proxy"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ from contextlib import asynccontextmanager
|
|||
from fastmcp import FastMCP
|
||||
from fastmcp.client import Client
|
||||
from fastmcp.client.transports import FastMCPTransport
|
||||
from fastmcp.server import create_proxy
|
||||
from fastmcp.server.providers import FastMCPProvider
|
||||
from fastmcp.server.providers.proxy import FastMCPProxy
|
||||
from fastmcp.server.providers.wrapped_provider import _WrappedProvider
|
||||
from fastmcp.server.transforms import Namespace
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ class TestProxyServer:
|
|||
return f"Data for {query}"
|
||||
|
||||
# Create proxy server
|
||||
proxy_server = FastMCP.as_proxy(FastMCPTransport(original_server))
|
||||
proxy_server = create_proxy(FastMCPTransport(original_server))
|
||||
|
||||
# Mount proxy server
|
||||
main_app = FastMCP("MainApp")
|
||||
|
|
@ -45,7 +45,7 @@ class TestProxyServer:
|
|||
original_server = FastMCP("OriginalServer")
|
||||
|
||||
# Create proxy server
|
||||
proxy_server = FastMCP.as_proxy(FastMCPTransport(original_server))
|
||||
proxy_server = create_proxy(FastMCPTransport(original_server))
|
||||
|
||||
# Mount proxy server
|
||||
main_app = FastMCP("MainApp")
|
||||
|
|
@ -74,7 +74,7 @@ class TestProxyServer:
|
|||
return json.dumps({"api_key": "12345"})
|
||||
|
||||
# Create proxy server
|
||||
proxy_server = FastMCP.as_proxy(FastMCPTransport(original_server))
|
||||
proxy_server = create_proxy(FastMCPTransport(original_server))
|
||||
|
||||
# Mount proxy server
|
||||
main_app = FastMCP("MainApp")
|
||||
|
|
@ -96,7 +96,7 @@ class TestProxyServer:
|
|||
return f"Welcome, {name}!"
|
||||
|
||||
# Create proxy server
|
||||
proxy_server = FastMCP.as_proxy(FastMCPTransport(original_server))
|
||||
proxy_server = create_proxy(FastMCPTransport(original_server))
|
||||
|
||||
# Mount proxy server
|
||||
main_app = FastMCP("MainApp")
|
||||
|
|
@ -108,10 +108,10 @@ class TestProxyServer:
|
|||
# The message should contain our welcome text
|
||||
|
||||
|
||||
class TestAsProxyKwarg:
|
||||
"""Test the as_proxy kwarg."""
|
||||
class TestMountProviderStructure:
|
||||
"""Test the provider structure produced by mounting."""
|
||||
|
||||
async def test_as_proxy_defaults_false(self):
|
||||
async def test_mount_wraps_in_fastmcp_provider(self):
|
||||
mcp = FastMCP("Main")
|
||||
sub = FastMCP("Sub")
|
||||
|
||||
|
|
@ -133,53 +133,6 @@ class TestAsProxyKwarg:
|
|||
tools = await mcp.list_tools()
|
||||
assert {t.name for t in tools} == {"sub_sub_tool"}
|
||||
|
||||
async def test_as_proxy_false(self):
|
||||
mcp = FastMCP("Main")
|
||||
sub = FastMCP("Sub")
|
||||
|
||||
@sub.tool
|
||||
def sub_tool() -> str:
|
||||
return "test"
|
||||
|
||||
mcp.mount(sub, "sub", as_proxy=False)
|
||||
|
||||
# Index 1 because LocalProvider is at index 0
|
||||
provider = mcp.providers[1]
|
||||
# Provider is wrapped with Namespace transform
|
||||
assert isinstance(provider, _WrappedProvider)
|
||||
assert len(provider._transforms) == 1
|
||||
assert isinstance(provider._transforms[0], Namespace)
|
||||
# Inner provider is FastMCPProvider
|
||||
assert isinstance(provider._inner, FastMCPProvider)
|
||||
assert provider._inner.server is sub
|
||||
# Verify namespace is applied
|
||||
tools = await mcp.list_tools()
|
||||
assert {t.name for t in tools} == {"sub_sub_tool"}
|
||||
|
||||
async def test_as_proxy_true(self):
|
||||
mcp = FastMCP("Main")
|
||||
sub = FastMCP("Sub")
|
||||
|
||||
@sub.tool
|
||||
def sub_tool() -> str:
|
||||
return "test"
|
||||
|
||||
mcp.mount(sub, "sub", as_proxy=True)
|
||||
|
||||
# Index 1 because LocalProvider is at index 0
|
||||
provider = mcp.providers[1]
|
||||
# Provider is wrapped with Namespace transform
|
||||
assert isinstance(provider, _WrappedProvider)
|
||||
assert len(provider._transforms) == 1
|
||||
assert isinstance(provider._transforms[0], Namespace)
|
||||
# Inner provider is FastMCPProvider wrapping a proxy
|
||||
assert isinstance(provider._inner, FastMCPProvider)
|
||||
assert provider._inner.server is not sub
|
||||
assert isinstance(provider._inner.server, FastMCPProxy)
|
||||
# Verify namespace is applied
|
||||
tools = await mcp.list_tools()
|
||||
assert {t.name for t in tools} == {"sub_sub_tool"}
|
||||
|
||||
async def test_lifespan_server_mounted_directly(self):
|
||||
"""Test that servers with lifespan are mounted directly (not auto-proxied).
|
||||
|
||||
|
|
@ -214,10 +167,10 @@ class TestAsProxyKwarg:
|
|||
tools = await mcp.list_tools()
|
||||
assert {t.name for t in tools} == {"sub_sub_tool"}
|
||||
|
||||
async def test_as_proxy_ignored_for_proxy_mounts_default(self):
|
||||
async def test_mounting_a_proxy_preserves_the_proxy(self):
|
||||
mcp = FastMCP("Main")
|
||||
sub = FastMCP("Sub")
|
||||
sub_proxy = FastMCP.as_proxy(FastMCPTransport(sub))
|
||||
sub_proxy = create_proxy(FastMCPTransport(sub))
|
||||
|
||||
mcp.mount(sub_proxy, "sub")
|
||||
|
||||
|
|
@ -231,45 +184,11 @@ class TestAsProxyKwarg:
|
|||
assert isinstance(provider._inner, FastMCPProvider)
|
||||
assert provider._inner.server is sub_proxy
|
||||
|
||||
async def test_as_proxy_ignored_for_proxy_mounts_false(self):
|
||||
mcp = FastMCP("Main")
|
||||
sub = FastMCP("Sub")
|
||||
sub_proxy = FastMCP.as_proxy(FastMCPTransport(sub))
|
||||
|
||||
mcp.mount(sub_proxy, "sub", as_proxy=False)
|
||||
|
||||
# Index 1 because LocalProvider is at index 0
|
||||
provider = mcp.providers[1]
|
||||
# Provider is wrapped with Namespace transform
|
||||
assert isinstance(provider, _WrappedProvider)
|
||||
assert len(provider._transforms) == 1
|
||||
assert isinstance(provider._transforms[0], Namespace)
|
||||
# Inner provider is FastMCPProvider
|
||||
assert isinstance(provider._inner, FastMCPProvider)
|
||||
assert provider._inner.server is sub_proxy
|
||||
|
||||
async def test_as_proxy_ignored_for_proxy_mounts_true(self):
|
||||
mcp = FastMCP("Main")
|
||||
sub = FastMCP("Sub")
|
||||
sub_proxy = FastMCP.as_proxy(FastMCPTransport(sub))
|
||||
|
||||
mcp.mount(sub_proxy, "sub", as_proxy=True)
|
||||
|
||||
# Index 1 because LocalProvider is at index 0
|
||||
provider = mcp.providers[1]
|
||||
# Provider is wrapped with Namespace transform
|
||||
assert isinstance(provider, _WrappedProvider)
|
||||
assert len(provider._transforms) == 1
|
||||
assert isinstance(provider._transforms[0], Namespace)
|
||||
# Inner provider is FastMCPProvider
|
||||
assert isinstance(provider._inner, FastMCPProvider)
|
||||
assert provider._inner.server is sub_proxy
|
||||
|
||||
async def test_as_proxy_mounts_still_have_live_link(self):
|
||||
async def test_mounts_have_live_link(self):
|
||||
mcp = FastMCP("Main")
|
||||
sub = FastMCP("Sub")
|
||||
|
||||
mcp.mount(sub, "sub", as_proxy=True)
|
||||
mcp.mount(sub, "sub")
|
||||
|
||||
assert len(await mcp.list_tools()) == 0
|
||||
|
||||
|
|
@ -294,7 +213,7 @@ class TestAsProxyKwarg:
|
|||
def hello():
|
||||
return "hi"
|
||||
|
||||
mcp.mount(sub, as_proxy=True)
|
||||
mcp.mount(sub)
|
||||
|
||||
assert lifespan_check == []
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from mcp_types import (
|
|||
from pydantic import BaseModel, Field
|
||||
|
||||
from fastmcp import Client, Context, FastMCP
|
||||
from fastmcp.server import create_proxy
|
||||
from fastmcp.client.elicitation import ElicitRequestParams, ElicitResult
|
||||
from fastmcp.client.logging import LogMessage
|
||||
from fastmcp.client.sampling import RequestContext, SamplingMessage, SamplingParams
|
||||
|
|
@ -88,7 +89,7 @@ async def proxy_server(fastmcp_server: FastMCP):
|
|||
"""
|
||||
A proxy server that forwards interactions with the proxy client to the given fastmcp server.
|
||||
"""
|
||||
return FastMCP.as_proxy(ProxyClient(fastmcp_server))
|
||||
return create_proxy(ProxyClient(fastmcp_server))
|
||||
|
||||
|
||||
class TestProxyClient:
|
||||
|
|
@ -377,7 +378,7 @@ class TestProxyClient:
|
|||
else:
|
||||
return f"Elicitation {result.action}"
|
||||
|
||||
proxy_server = FastMCP.as_proxy(ProxyClient(fastmcp_server))
|
||||
proxy_server = create_proxy(ProxyClient(fastmcp_server))
|
||||
|
||||
# Test that elicitation works correctly through the proxy
|
||||
async def elicitation_handler(
|
||||
|
|
@ -407,16 +408,16 @@ class TestProxyClient:
|
|||
# Create a disconnected client (should use fresh sessions per request)
|
||||
base_client = Client(fastmcp_server)
|
||||
|
||||
# Test both as_proxy convenience method and direct client_factory usage
|
||||
proxy_via_as_proxy = FastMCP.as_proxy(base_client)
|
||||
# Test both create_proxy convenience function and direct client_factory usage
|
||||
proxy_via_create_proxy = create_proxy(base_client)
|
||||
proxy_via_factory = FastMCPProxy(client_factory=base_client.new)
|
||||
|
||||
# Verify the proxies are created successfully - this tests the client factory pattern
|
||||
assert proxy_via_as_proxy is not None
|
||||
assert proxy_via_create_proxy is not None
|
||||
assert proxy_via_factory is not None
|
||||
|
||||
# Verify they have the expected client factory behavior
|
||||
assert hasattr(proxy_via_as_proxy, "_local_provider")
|
||||
assert hasattr(proxy_via_create_proxy, "_local_provider")
|
||||
assert hasattr(proxy_via_factory, "_local_provider")
|
||||
|
||||
async def test_connected_proxy_client_uses_fresh_sessions(
|
||||
|
|
@ -464,7 +465,7 @@ def roots_backend_server():
|
|||
|
||||
@pytest.fixture
|
||||
async def roots_proxy_server(roots_backend_server: FastMCP):
|
||||
return FastMCP.as_proxy(ProxyClient(roots_backend_server))
|
||||
return create_proxy(ProxyClient(roots_backend_server))
|
||||
|
||||
|
||||
class TestProxyServerInitiatedForwardingNonTool:
|
||||
|
|
|
|||
|
|
@ -198,38 +198,6 @@ def test_create_proxy_with_url():
|
|||
assert client.transport.url == "http://example.com/mcp/"
|
||||
|
||||
|
||||
# --- Deprecated as_proxy tests (verify backwards compatibility) ---
|
||||
|
||||
|
||||
async def test_as_proxy_deprecated_with_server(fastmcp_server):
|
||||
"""FastMCP.as_proxy should work but emit deprecation warning."""
|
||||
import warnings
|
||||
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter("always")
|
||||
proxy = FastMCP.as_proxy(fastmcp_server)
|
||||
assert len(w) == 1
|
||||
assert issubclass(w[0].category, DeprecationWarning)
|
||||
assert "create_proxy" in str(w[0].message)
|
||||
|
||||
async with Client(proxy) as client:
|
||||
result = await client.call_tool("greet", {"name": "Test"})
|
||||
assert result.data == "Hello, Test!"
|
||||
|
||||
|
||||
def test_as_proxy_deprecated_with_url():
|
||||
"""FastMCP.as_proxy should work but emit deprecation warning."""
|
||||
import warnings
|
||||
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter("always")
|
||||
proxy = FastMCP.as_proxy("http://example.com/mcp/")
|
||||
assert len(w) == 1
|
||||
assert issubclass(w[0].category, DeprecationWarning)
|
||||
|
||||
assert isinstance(proxy, FastMCPProxy)
|
||||
|
||||
|
||||
async def test_proxy_with_async_client_factory():
|
||||
"""FastMCPProxy should accept an async client_factory."""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import os
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from textwrap import dedent
|
||||
|
|
@ -161,37 +160,6 @@ class TestLocalProviderProperty:
|
|||
assert not any(p.name == "my_prompt" for p in prompts)
|
||||
|
||||
|
||||
class TestRemoveToolDeprecation:
|
||||
async def test_remove_tool_emits_deprecation_warning(self):
|
||||
mcp = FastMCP()
|
||||
|
||||
@mcp.tool
|
||||
def my_tool() -> str:
|
||||
return "result"
|
||||
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter("always")
|
||||
mcp.remove_tool("my_tool")
|
||||
|
||||
assert len(w) == 1
|
||||
assert issubclass(w[0].category, DeprecationWarning)
|
||||
assert "local_provider" in str(w[0].message)
|
||||
|
||||
async def test_remove_tool_still_works(self):
|
||||
mcp = FastMCP()
|
||||
|
||||
@mcp.tool
|
||||
def my_tool() -> str:
|
||||
return "result"
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", DeprecationWarning)
|
||||
mcp.remove_tool("my_tool")
|
||||
|
||||
tools = await mcp.list_tools()
|
||||
assert not any(t.name == "my_tool" for t in tools)
|
||||
|
||||
|
||||
class TestResourcePrefixMounting:
|
||||
"""Test resource prefixing in mounted servers."""
|
||||
|
||||
|
|
|
|||
|
|
@ -719,8 +719,9 @@ class TestProxy:
|
|||
@pytest.fixture
|
||||
def proxy_server(self, mcp_server: FastMCP) -> FastMCP:
|
||||
from fastmcp.client.transports import FastMCPTransport
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
proxy = FastMCP.as_proxy(FastMCPTransport(mcp_server))
|
||||
proxy = create_proxy(FastMCPTransport(mcp_server))
|
||||
return proxy
|
||||
|
||||
async def test_transform_proxy(self, proxy_server: FastMCP):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue