mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 15:19:10 +02:00
Replace FastMCP.as_proxy() with create_proxy() function (#2829)
This commit is contained in:
parent
d8351993f7
commit
3163e61ee4
16 changed files with 341 additions and 124 deletions
|
|
@ -46,13 +46,13 @@ server2 = FastMCP("Server2", providers=[provider])
|
|||
|
||||
### ProxyProvider
|
||||
|
||||
`ProxyProvider` (`src/fastmcp/server/providers/proxy.py`) proxies components from remote MCP servers via a client factory. Used by `FastMCP.as_proxy()` and `FastMCP.mount()` for remote server integration.
|
||||
`ProxyProvider` (`src/fastmcp/server/providers/proxy.py`) proxies components from remote MCP servers via a client factory. Used by `create_proxy()` and `FastMCP.mount()` for remote server integration.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
# Create proxy to remote server
|
||||
server = FastMCP.as_proxy("http://remote-server/mcp")
|
||||
server = create_proxy("http://remote-server/mcp")
|
||||
```
|
||||
|
||||
### OpenAPIProvider
|
||||
|
|
|
|||
|
|
@ -263,11 +263,11 @@ Users on Claude Pro, Max, Team, and Enterprise plans have first-class remote ser
|
|||
Create a proxy server that connects to a remote HTTP server:
|
||||
|
||||
```python proxy_server.py
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
# Create a proxy to a remote server
|
||||
proxy = FastMCP.as_proxy(
|
||||
"https://example.com/mcp/sse",
|
||||
proxy = create_proxy(
|
||||
"https://example.com/mcp/sse",
|
||||
name="Remote Server Proxy"
|
||||
)
|
||||
|
||||
|
|
@ -280,8 +280,9 @@ if __name__ == "__main__":
|
|||
For authenticated remote servers, create an authenticated client following the guidance in the [client auth documentation](/clients/auth/bearer) and pass it to the proxy:
|
||||
|
||||
```python auth_proxy_server.py {7}
|
||||
from fastmcp import FastMCP, Client
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.auth import BearerAuth
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
# Create authenticated client
|
||||
client = Client(
|
||||
|
|
@ -290,7 +291,7 @@ client = Client(
|
|||
)
|
||||
|
||||
# Create proxy using the authenticated client
|
||||
proxy = FastMCP.as_proxy(client, name="Authenticated Proxy")
|
||||
proxy = create_proxy(client, name="Authenticated Proxy")
|
||||
|
||||
if __name__ == "__main__":
|
||||
proxy.run()
|
||||
|
|
|
|||
|
|
@ -47,6 +47,77 @@ main.mount(weather_server)
|
|||
# Now main has access to get_forecast and data://cities
|
||||
```
|
||||
|
||||
## Mounting External Servers
|
||||
|
||||
Mount remote HTTP servers or subprocess-based MCP servers using `create_proxy()`:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
mcp = FastMCP("Orchestrator")
|
||||
|
||||
# Mount a remote HTTP server (URLs work directly)
|
||||
mcp.mount(create_proxy("http://api.example.com/mcp"), namespace="api")
|
||||
|
||||
# Mount local Python scripts (file paths work directly)
|
||||
mcp.mount(create_proxy("./my_server.py"), namespace="local")
|
||||
```
|
||||
|
||||
### Mounting npm/uvx Packages
|
||||
|
||||
For npm packages or Python tools, use the config dict format:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
mcp = FastMCP("Orchestrator")
|
||||
|
||||
# Mount npm package via config
|
||||
github_config = {
|
||||
"mcpServers": {
|
||||
"default": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-github"]
|
||||
}
|
||||
}
|
||||
}
|
||||
mcp.mount(create_proxy(github_config), namespace="github")
|
||||
|
||||
# Mount Python tool via config
|
||||
sqlite_config = {
|
||||
"mcpServers": {
|
||||
"default": {
|
||||
"command": "uvx",
|
||||
"args": ["mcp-server-sqlite", "--db", "data.db"]
|
||||
}
|
||||
}
|
||||
}
|
||||
mcp.mount(create_proxy(sqlite_config), namespace="db")
|
||||
```
|
||||
|
||||
Or use explicit transport classes:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server import create_proxy
|
||||
from fastmcp.client.transports import NpxStdioTransport, UvxStdioTransport
|
||||
|
||||
mcp = FastMCP("Orchestrator")
|
||||
|
||||
mcp.mount(
|
||||
create_proxy(NpxStdioTransport(package="@modelcontextprotocol/server-github")),
|
||||
namespace="github"
|
||||
)
|
||||
mcp.mount(
|
||||
create_proxy(UvxStdioTransport(tool_name="mcp-server-sqlite", tool_args=["--db", "data.db"])),
|
||||
namespace="db"
|
||||
)
|
||||
```
|
||||
|
||||
For advanced configuration, see [Proxying](/servers/providers/proxy).
|
||||
|
||||
## Namespacing
|
||||
|
||||
<VersionBadge version="3.0.0" />
|
||||
|
|
@ -145,18 +216,16 @@ main.mount(subserver, namespace="api")
|
|||
|
||||
### Proxy Mounting
|
||||
|
||||
The parent server treats the mounted server as a separate entity:
|
||||
<Warning>
|
||||
The `as_proxy` parameter is deprecated. Mounted servers now always have their lifespan and middleware invoked. To create a proxy server explicitly, use `create_proxy()` from `fastmcp.server`.
|
||||
</Warning>
|
||||
|
||||
```python
|
||||
main.mount(subserver, namespace="api", as_proxy=True)
|
||||
```
|
||||
Previously, the parent server could treat the mounted server as a separate entity with its own lifecycle. This behavior is now the default for all mounted servers:
|
||||
|
||||
- Full client lifecycle events on mounted server
|
||||
- Mounted server's lifespan is executed
|
||||
- Communication via in-memory Client transport
|
||||
|
||||
FastMCP automatically uses proxy mounting when the mounted server has a custom lifespan. Override with `as_proxy=True` or `as_proxy=False`.
|
||||
|
||||
## Tag Filtering
|
||||
|
||||
<VersionBadge version="3.0.0" />
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ FastMCP includes providers for common patterns:
|
|||
|----------|--------------|----------------|
|
||||
| `LocalProvider` | Stores components you define in code | `@mcp.tool`, `mcp.add_tool()` |
|
||||
| `FastMCPProvider` | Wraps another FastMCP server | `mcp.mount(server)` |
|
||||
| `ProxyProvider` | Connects to remote MCP servers | `FastMCP.as_proxy(client)` |
|
||||
| `ProxyProvider` | Connects to remote MCP servers | `create_proxy(client)` |
|
||||
| `TransformingProvider` | Adds prefixes to avoid name collisions | `mcp.mount(server, namespace="api")` |
|
||||
|
||||
Most users only interact with `LocalProvider` (through decorators) and occasionally mount or proxy other servers. The provider abstraction stays invisible until you need it.
|
||||
|
|
|
|||
|
|
@ -36,17 +36,13 @@ sequenceDiagram
|
|||
|
||||
<VersionBadge version="2.10.3" />
|
||||
|
||||
Create a proxy using `FastMCP.as_proxy()`:
|
||||
Create a proxy using `create_proxy()`:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.proxy import ProxyClient
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
# Create a proxy to a remote server
|
||||
proxy = FastMCP.as_proxy(
|
||||
ProxyClient("http://example.com/mcp"),
|
||||
name="MyProxy"
|
||||
)
|
||||
# create_proxy() accepts URLs, file paths, and transports directly
|
||||
proxy = create_proxy("http://example.com/mcp", name="MyProxy")
|
||||
|
||||
if __name__ == "__main__":
|
||||
proxy.run()
|
||||
|
|
@ -57,19 +53,19 @@ This gives you:
|
|||
- Automatic forwarding of MCP features (sampling, elicitation, etc.)
|
||||
- Session isolation to prevent context mixing
|
||||
|
||||
<Tip>
|
||||
To mount a proxy inside another FastMCP server, see [Mounting External Servers](/servers/providers/mounting#mounting-external-servers).
|
||||
</Tip>
|
||||
|
||||
## Transport Bridging
|
||||
|
||||
A common use case is bridging transports - making a remote server available locally:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server.proxy import ProxyClient
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
# Bridge remote HTTP to local stdio
|
||||
remote_proxy = FastMCP.as_proxy(
|
||||
ProxyClient("http://example.com/mcp/sse"),
|
||||
name="Remote-to-Local"
|
||||
)
|
||||
remote_proxy = create_proxy("http://example.com/mcp/sse", name="Remote-to-Local")
|
||||
|
||||
# Run locally via stdio for Claude Desktop
|
||||
if __name__ == "__main__":
|
||||
|
|
@ -79,11 +75,10 @@ if __name__ == "__main__":
|
|||
Or expose a local server via HTTP:
|
||||
|
||||
```python
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
# Bridge local server to HTTP
|
||||
local_proxy = FastMCP.as_proxy(
|
||||
ProxyClient("local_server.py"),
|
||||
name="Local-to-HTTP"
|
||||
)
|
||||
local_proxy = create_proxy("local_server.py", name="Local-to-HTTP")
|
||||
|
||||
if __name__ == "__main__":
|
||||
local_proxy.run(transport="http", host="0.0.0.0", port=8080)
|
||||
|
|
@ -93,13 +88,13 @@ if __name__ == "__main__":
|
|||
|
||||
<VersionBadge version="2.10.3" />
|
||||
|
||||
ProxyClient provides session isolation - each request gets its own isolated backend session:
|
||||
`create_proxy()` provides session isolation - each request gets its own isolated backend session:
|
||||
|
||||
```python
|
||||
from fastmcp.server.proxy import ProxyClient
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
# Each request creates a fresh backend session (recommended)
|
||||
proxy = FastMCP.as_proxy(ProxyClient("backend_server.py"))
|
||||
proxy = create_proxy("backend_server.py")
|
||||
|
||||
# Multiple clients can use this proxy simultaneously:
|
||||
# - Client A calls a tool → gets isolated session
|
||||
|
|
@ -113,10 +108,11 @@ If you pass an already-connected client, the proxy reuses that session:
|
|||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
async with Client("backend_server.py") as connected_client:
|
||||
# This proxy reuses the connected session
|
||||
proxy = FastMCP.as_proxy(connected_client)
|
||||
proxy = create_proxy(connected_client)
|
||||
|
||||
# ⚠️ Warning: All requests share the same session
|
||||
```
|
||||
|
|
@ -129,7 +125,7 @@ Shared sessions may cause context mixing in concurrent scenarios. Use only in si
|
|||
|
||||
<VersionBadge version="2.10.3" />
|
||||
|
||||
ProxyClient automatically forwards MCP protocol features:
|
||||
Proxies automatically forward MCP protocol features:
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
|
|
@ -140,11 +136,10 @@ ProxyClient automatically forwards MCP protocol features:
|
|||
| Progress | Progress notifications |
|
||||
|
||||
```python
|
||||
from fastmcp.server.proxy import ProxyClient
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
# All features forwarded automatically
|
||||
backend = ProxyClient("advanced_backend.py")
|
||||
proxy = FastMCP.as_proxy(backend)
|
||||
proxy = create_proxy("advanced_backend.py")
|
||||
|
||||
# When the backend:
|
||||
# - Requests LLM sampling → forwarded to your client
|
||||
|
|
@ -157,6 +152,8 @@ proxy = FastMCP.as_proxy(backend)
|
|||
Selectively disable forwarding:
|
||||
|
||||
```python
|
||||
from fastmcp.server.providers.proxy import ProxyClient
|
||||
|
||||
backend = ProxyClient(
|
||||
"backend_server.py",
|
||||
sampling_handler=None, # Disable LLM sampling
|
||||
|
|
@ -171,7 +168,7 @@ backend = ProxyClient(
|
|||
Create proxies from configuration dictionaries:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
config = {
|
||||
"mcpServers": {
|
||||
|
|
@ -182,7 +179,7 @@ config = {
|
|||
}
|
||||
}
|
||||
|
||||
proxy = FastMCP.as_proxy(config, name="Config-Based Proxy")
|
||||
proxy = create_proxy(config, name="Config-Based Proxy")
|
||||
```
|
||||
|
||||
### Multi-Server Proxies
|
||||
|
|
@ -190,6 +187,8 @@ proxy = FastMCP.as_proxy(config, name="Config-Based Proxy")
|
|||
Combine multiple servers with automatic namespacing:
|
||||
|
||||
```python
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
config = {
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
|
|
@ -206,7 +205,7 @@ config = {
|
|||
# Creates unified proxy with prefixed components:
|
||||
# - weather_get_forecast
|
||||
# - calendar_add_event
|
||||
composite = FastMCP.as_proxy(config, name="Composite")
|
||||
composite = create_proxy(config, name="Composite")
|
||||
```
|
||||
|
||||
## Component Prefixing
|
||||
|
|
@ -229,7 +228,10 @@ Components from a proxy server are "mirrored" - they reflect the remote server's
|
|||
To modify a proxied component (like disabling it), create a local copy:
|
||||
|
||||
```python
|
||||
proxy = FastMCP.as_proxy("backend_server.py")
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
proxy = create_proxy("backend_server.py")
|
||||
|
||||
# Get mirrored tool
|
||||
mirrored_tool = await proxy.get_tool("useful_tool")
|
||||
|
|
@ -258,12 +260,14 @@ When mounting proxy servers, this latency affects all operations on the parent s
|
|||
|
||||
For low-latency requirements, consider using [`import_server()`](/servers/providers/mounting#static-importing) to copy tools at startup.
|
||||
|
||||
## Advanced: FastMCPProxy Class
|
||||
## Advanced Usage
|
||||
|
||||
### FastMCPProxy Class
|
||||
|
||||
For explicit session control, use `FastMCPProxy` directly:
|
||||
|
||||
```python
|
||||
from fastmcp.server.proxy import FastMCPProxy, ProxyClient
|
||||
from fastmcp.server.providers.proxy import FastMCPProxy, ProxyClient
|
||||
|
||||
# Custom session factory
|
||||
def create_client():
|
||||
|
|
@ -273,3 +277,25 @@ proxy = FastMCPProxy(client_factory=create_client)
|
|||
```
|
||||
|
||||
This gives you full control over session creation and reuse strategies.
|
||||
|
||||
### Adding Proxied Components to Existing Server
|
||||
|
||||
Mount a proxy to add remote components to an existing server:
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
server = FastMCP("My Server")
|
||||
|
||||
# Add local tools
|
||||
@server.tool
|
||||
def local_tool() -> str:
|
||||
return "Local result"
|
||||
|
||||
# Mount proxied tools from remote server
|
||||
remote = create_proxy("http://remote-server/mcp")
|
||||
server.mount(remote)
|
||||
|
||||
# Now server has both local and proxied tools
|
||||
```
|
||||
|
|
|
|||
|
|
@ -288,17 +288,18 @@ main.mount(sub, namespace="sub")
|
|||
|
||||
<VersionBadge version="2.0.0" />
|
||||
|
||||
FastMCP can act as a proxy for any MCP server (local or remote) using `FastMCP.as_proxy`, letting you bridge transports or add a frontend to existing servers. For example, you can expose a remote SSE server locally via stdio, or vice versa.
|
||||
FastMCP can act as a proxy for any MCP server (local or remote) using `create_proxy`, letting you bridge transports or add a frontend to existing servers. For example, you can expose a remote SSE server locally via stdio, or vice versa.
|
||||
|
||||
Proxies automatically handle concurrent operations safely by creating fresh sessions for each request when using disconnected clients.
|
||||
|
||||
See the [Remote Proxies](/servers/providers/proxy) guide for details and advanced usage.
|
||||
|
||||
```python
|
||||
from fastmcp import FastMCP, Client
|
||||
from fastmcp import Client
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
backend = Client("http://example.com/mcp/sse")
|
||||
proxy = FastMCP.as_proxy(backend, name="ProxyServer")
|
||||
proxy = create_proxy(backend, name="ProxyServer")
|
||||
# Now use the proxy like any FastMCP server
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from typing import Any, Literal
|
|||
from mcp.server.fastmcp import FastMCP as FastMCP1x
|
||||
from watchfiles import Change, awatch
|
||||
|
||||
from fastmcp.server.server import FastMCP
|
||||
from fastmcp.server.server import FastMCP, create_proxy
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
from fastmcp.utilities.mcp_server_config import (
|
||||
MCPServerConfig,
|
||||
|
|
@ -45,7 +45,7 @@ def create_client_server(url: str) -> Any:
|
|||
import fastmcp
|
||||
|
||||
client = fastmcp.Client(url)
|
||||
server = fastmcp.FastMCP.as_proxy(client)
|
||||
server = create_proxy(client)
|
||||
return server
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create client for URL {url}: {e}")
|
||||
|
|
@ -54,12 +54,10 @@ def create_client_server(url: str) -> Any:
|
|||
|
||||
def create_mcp_config_server(mcp_config_path: Path) -> FastMCP[None]:
|
||||
"""Create a FastMCP server from a MCPConfig."""
|
||||
from fastmcp import FastMCP
|
||||
|
||||
with mcp_config_path.open() as src:
|
||||
mcp_config = json.load(src)
|
||||
|
||||
server = FastMCP.as_proxy(mcp_config)
|
||||
server = create_proxy(mcp_config)
|
||||
return server
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ from fastmcp.mcp_config import (
|
|||
infer_transport_type_from_url,
|
||||
)
|
||||
from fastmcp.server.dependencies import get_http_headers
|
||||
from fastmcp.server.server import FastMCP
|
||||
from fastmcp.server.server import FastMCP, create_proxy
|
||||
from fastmcp.server.tasks.capabilities import get_task_capabilities
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
from fastmcp.utilities.mcp_server_config.v1.environments.uv import UVEnvironment
|
||||
|
|
@ -1035,9 +1035,9 @@ class MCPConfigTransport(ClientTransport):
|
|||
transport = config.to_transport()
|
||||
|
||||
client = ProxyClient(transport=transport, timeout=timeout)
|
||||
proxy = FastMCP.as_proxy(
|
||||
proxy = create_proxy(
|
||||
client,
|
||||
name=f"Proxy-{name}",
|
||||
backend=client,
|
||||
tool_transformations=tool_transforms,
|
||||
include_tags=include_tags,
|
||||
exclude_tags=exclude_tags,
|
||||
|
|
|
|||
|
|
@ -95,20 +95,20 @@ class _TransformingMCPServerMixin(FastMCPBaseModel):
|
|||
client_name: str | None = None,
|
||||
) -> tuple[FastMCP[Any], ClientTransport]:
|
||||
"""Turn the Transforming MCPServer into a FastMCP Server and also return the underlying transport."""
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.client import Client
|
||||
from fastmcp.client.transports import (
|
||||
ClientTransport, # pyright: ignore[reportUnusedImport]
|
||||
)
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
transport: ClientTransport = super().to_transport() # pyright: ignore[reportUnknownMemberType, reportAttributeAccessIssue, reportUnknownVariableType] # ty: ignore[unresolved-attribute]
|
||||
transport = cast(ClientTransport, transport)
|
||||
|
||||
client: Client[ClientTransport] = Client(transport=transport, name=client_name)
|
||||
|
||||
wrapped_mcp_server = FastMCP.as_proxy(
|
||||
wrapped_mcp_server = create_proxy(
|
||||
client,
|
||||
name=server_name,
|
||||
backend=client,
|
||||
tool_transformations=self.tools,
|
||||
include_tags=self.include_tags,
|
||||
exclude_tags=self.exclude_tags,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
from .server import FastMCP
|
||||
from .context import Context
|
||||
from .server import FastMCP, create_proxy
|
||||
from . import dependencies
|
||||
|
||||
|
||||
__all__ = ["Context", "FastMCP"]
|
||||
__all__ = ["Context", "FastMCP", "create_proxy"]
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ from fastmcp.utilities.logging import get_logger
|
|||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
from fastmcp.client.transports import ClientTransport
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# Type alias for client factory functions
|
||||
|
|
@ -574,6 +576,59 @@ class ProxyProvider(Provider):
|
|||
# because client cleanup is handled per-request
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Factory Functions
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _create_client_factory(
|
||||
target: (
|
||||
Client[ClientTransportT]
|
||||
| ClientTransport
|
||||
| FastMCP[Any]
|
||||
| FastMCP1Server
|
||||
| AnyUrl
|
||||
| Path
|
||||
| MCPConfig
|
||||
| dict[str, Any]
|
||||
| str
|
||||
),
|
||||
) -> ClientFactoryT:
|
||||
"""Create a client factory from the given target.
|
||||
|
||||
Internal helper that handles the session strategy based on the target type:
|
||||
- Connected Client: reuses existing session (with warning about context mixing)
|
||||
- Disconnected Client: creates fresh sessions per request
|
||||
- Other targets: creates ProxyClient and fresh sessions per request
|
||||
"""
|
||||
if isinstance(target, Client):
|
||||
client = target
|
||||
if client.is_connected():
|
||||
logger.info(
|
||||
"Proxy detected connected client - reusing existing session for all requests. "
|
||||
"This may cause context mixing in concurrent scenarios."
|
||||
)
|
||||
|
||||
def reuse_client_factory() -> Client:
|
||||
return client
|
||||
|
||||
return reuse_client_factory
|
||||
else:
|
||||
|
||||
def fresh_client_factory() -> Client:
|
||||
return client.new()
|
||||
|
||||
return fresh_client_factory
|
||||
else:
|
||||
# target is not a Client, so it's compatible with ProxyClient.__init__
|
||||
base_client = ProxyClient(cast(Any, target))
|
||||
|
||||
def proxy_client_factory() -> Client:
|
||||
return base_client.new()
|
||||
|
||||
return proxy_client_factory
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# FastMCPProxy - Convenience Wrapper
|
||||
# -----------------------------------------------------------------------------
|
||||
|
|
@ -587,14 +642,14 @@ class FastMCPProxy(FastMCP):
|
|||
|
||||
Example:
|
||||
```python
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.server import create_proxy
|
||||
from fastmcp.server.providers.proxy import FastMCPProxy, ProxyClient
|
||||
|
||||
# Create a proxy server
|
||||
proxy = FastMCPProxy(client_factory=lambda: ProxyClient("http://localhost:8000/mcp"))
|
||||
# Create a proxy server using create_proxy (recommended)
|
||||
proxy = create_proxy("http://localhost:8000/mcp")
|
||||
|
||||
# Or use the convenience method
|
||||
proxy = FastMCP.as_proxy("http://localhost:8000/mcp")
|
||||
# Or use FastMCPProxy directly with explicit client factory
|
||||
proxy = FastMCPProxy(client_factory=lambda: ProxyClient("http://localhost:8000/mcp"))
|
||||
```
|
||||
"""
|
||||
|
||||
|
|
@ -607,7 +662,7 @@ class FastMCPProxy(FastMCP):
|
|||
"""Initialize the proxy server.
|
||||
|
||||
FastMCPProxy requires explicit session management via client_factory.
|
||||
Use FastMCP.as_proxy() for convenience with automatic session strategy.
|
||||
Use create_proxy() for convenience with automatic session strategy.
|
||||
|
||||
Args:
|
||||
client_factory: A callable that returns a Client instance when called.
|
||||
|
|
|
|||
|
|
@ -2283,7 +2283,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
namespace: Optional namespace to use for the mounted server's objects. If None,
|
||||
the server's objects are accessible with their original names.
|
||||
as_proxy: Deprecated. Mounted servers now always have their lifespan and
|
||||
middleware invoked. To create a proxy server, use FastMCP.as_proxy()
|
||||
middleware invoked. To create a proxy server, use create_proxy()
|
||||
explicitly before mounting.
|
||||
tool_names: Optional mapping of original tool names to custom names. Use this
|
||||
to override namespaced names. Keys are the original tool names from the
|
||||
|
|
@ -2310,7 +2310,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
warnings.warn(
|
||||
"as_proxy is deprecated and will be removed in a future version. "
|
||||
"Mounted servers now always have their lifespan and middleware invoked. "
|
||||
"To create a proxy server, use FastMCP.as_proxy() explicitly.",
|
||||
"To create a proxy server, use create_proxy() explicitly.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
|
@ -2549,48 +2549,24 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
) -> FastMCPProxy:
|
||||
"""Create a FastMCP proxy server for the given backend.
|
||||
|
||||
.. deprecated::
|
||||
Use :func:`fastmcp.server.create_proxy` instead.
|
||||
This method will be removed in a future version.
|
||||
|
||||
The `backend` argument can be either an existing `fastmcp.client.Client`
|
||||
instance or any value accepted as the `transport` argument of
|
||||
`fastmcp.client.Client`. This mirrors the convenience of the
|
||||
`fastmcp.client.Client` constructor.
|
||||
"""
|
||||
from fastmcp.client.client import Client
|
||||
from fastmcp.server.providers.proxy import FastMCPProxy, ProxyClient
|
||||
|
||||
if isinstance(backend, Client):
|
||||
client = backend
|
||||
# Session strategy based on client connection state:
|
||||
# - Connected clients: reuse existing session for all requests
|
||||
# - Disconnected clients: create fresh sessions per request for isolation
|
||||
if client.is_connected():
|
||||
proxy_logger = get_logger(__name__)
|
||||
proxy_logger.info(
|
||||
"Proxy detected connected client - reusing existing session for all requests. "
|
||||
"This may cause context mixing in concurrent scenarios."
|
||||
)
|
||||
|
||||
# Reuse sessions - return the same client instance
|
||||
def reuse_client_factory():
|
||||
return client
|
||||
|
||||
client_factory = reuse_client_factory
|
||||
else:
|
||||
# Fresh sessions per request
|
||||
def fresh_client_factory():
|
||||
return client.new()
|
||||
|
||||
client_factory = fresh_client_factory
|
||||
else:
|
||||
# backend is not a Client, so it's compatible with ProxyClient.__init__
|
||||
base_client = ProxyClient(cast(Any, backend))
|
||||
|
||||
# Fresh client created from transport - use fresh sessions per request
|
||||
def proxy_client_factory():
|
||||
return base_client.new()
|
||||
|
||||
client_factory = proxy_client_factory
|
||||
|
||||
return FastMCPProxy(client_factory=client_factory, **settings)
|
||||
if fastmcp.settings.deprecation_warnings:
|
||||
warnings.warn(
|
||||
"FastMCP.as_proxy() is deprecated. Use create_proxy() from "
|
||||
"fastmcp.server instead: `from fastmcp.server import create_proxy`",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
# Call the module-level create_proxy function directly
|
||||
return create_proxy(backend, **settings)
|
||||
|
||||
@classmethod
|
||||
def generate_name(cls, name: str | None = None) -> str:
|
||||
|
|
@ -2600,3 +2576,60 @@ class FastMCP(Generic[LifespanResultT]):
|
|||
return f"{class_name}-{secrets.token_hex(2)}"
|
||||
else:
|
||||
return f"{class_name}-{name}-{secrets.token_hex(2)}"
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Module-level Factory Functions
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def create_proxy(
|
||||
target: (
|
||||
Client[ClientTransportT]
|
||||
| ClientTransport
|
||||
| FastMCP[Any]
|
||||
| FastMCP1Server
|
||||
| AnyUrl
|
||||
| Path
|
||||
| MCPConfig
|
||||
| dict[str, Any]
|
||||
| str
|
||||
),
|
||||
**settings: Any,
|
||||
) -> FastMCPProxy:
|
||||
"""Create a FastMCP proxy server for the given target.
|
||||
|
||||
This is the recommended way to create a proxy server. For lower-level control,
|
||||
use `FastMCPProxy` or `ProxyProvider` directly from `fastmcp.server.providers.proxy`.
|
||||
|
||||
Args:
|
||||
target: The backend to proxy to. Can be:
|
||||
- A Client instance (connected or disconnected)
|
||||
- A ClientTransport
|
||||
- A FastMCP server instance
|
||||
- A URL string or AnyUrl
|
||||
- A Path to a server script
|
||||
- An MCPConfig or dict
|
||||
**settings: Additional settings passed to FastMCPProxy (name, etc.)
|
||||
|
||||
Returns:
|
||||
A FastMCPProxy server that proxies to the target.
|
||||
|
||||
Example:
|
||||
```python
|
||||
from fastmcp.server import create_proxy
|
||||
|
||||
# Create a proxy to a remote server
|
||||
proxy = create_proxy("http://remote-server/mcp")
|
||||
|
||||
# Create a proxy to another FastMCP server
|
||||
proxy = create_proxy(other_server)
|
||||
```
|
||||
"""
|
||||
from fastmcp.server.providers.proxy import (
|
||||
FastMCPProxy,
|
||||
_create_client_factory,
|
||||
)
|
||||
|
||||
client_factory = _create_client_factory(target)
|
||||
return FastMCPProxy(client_factory=client_factory, **settings)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,11 @@ from fastmcp.client import Client
|
|||
from fastmcp.client.transports import FastMCPTransport, StreamableHttpTransport
|
||||
from fastmcp.exceptions import ToolError
|
||||
from fastmcp.resources import ResourceContent, ResourceResult
|
||||
from fastmcp.server.providers.proxy import FastMCPProxy, ProxyClient
|
||||
from fastmcp.server import create_proxy
|
||||
from fastmcp.server.providers.proxy import (
|
||||
FastMCPProxy,
|
||||
ProxyClient,
|
||||
)
|
||||
from fastmcp.tools.tool import ToolResult
|
||||
from fastmcp.tools.tool_transform import (
|
||||
ToolTransformConfig,
|
||||
|
|
@ -131,46 +135,76 @@ def fastmcp_server():
|
|||
@pytest.fixture
|
||||
async def proxy_server(fastmcp_server):
|
||||
"""Fixture that creates a FastMCP proxy server."""
|
||||
return FastMCP.as_proxy(ProxyClient(transport=FastMCPTransport(fastmcp_server)))
|
||||
return create_proxy(ProxyClient(transport=FastMCPTransport(fastmcp_server)))
|
||||
|
||||
|
||||
async def test_create_proxy(fastmcp_server):
|
||||
"""Test that the proxy server properly forwards requests to the original server."""
|
||||
# Create a client
|
||||
async def test_create_proxy_with_client(fastmcp_server):
|
||||
"""Test create_proxy with a Client."""
|
||||
client = ProxyClient(transport=FastMCPTransport(fastmcp_server))
|
||||
|
||||
server = FastMCPProxy.as_proxy(client)
|
||||
server = create_proxy(client)
|
||||
|
||||
assert isinstance(server, FastMCPProxy)
|
||||
assert isinstance(server, FastMCP)
|
||||
assert server.name.startswith("FastMCPProxy-")
|
||||
|
||||
|
||||
async def test_as_proxy_with_server(fastmcp_server):
|
||||
"""FastMCP.as_proxy should accept a FastMCP instance."""
|
||||
proxy = FastMCP.as_proxy(fastmcp_server)
|
||||
async def test_create_proxy_with_server(fastmcp_server):
|
||||
"""create_proxy should accept a FastMCP instance."""
|
||||
proxy = create_proxy(fastmcp_server)
|
||||
async with Client(proxy) as client:
|
||||
result = await client.call_tool("greet", {"name": "Test"})
|
||||
assert result.data == "Hello, Test!"
|
||||
|
||||
|
||||
async def test_as_proxy_with_transport(fastmcp_server):
|
||||
"""FastMCP.as_proxy should accept a ClientTransport."""
|
||||
proxy = FastMCP.as_proxy(FastMCPTransport(fastmcp_server))
|
||||
async def test_create_proxy_with_transport(fastmcp_server):
|
||||
"""create_proxy should accept a ClientTransport."""
|
||||
proxy = create_proxy(FastMCPTransport(fastmcp_server))
|
||||
async with Client(proxy) as client:
|
||||
result = await client.call_tool("greet", {"name": "Test"})
|
||||
assert result.data == "Hello, Test!"
|
||||
|
||||
|
||||
def test_as_proxy_with_url():
|
||||
"""FastMCP.as_proxy should accept a URL without connecting."""
|
||||
proxy = FastMCP.as_proxy("http://example.com/mcp/")
|
||||
def test_create_proxy_with_url():
|
||||
"""create_proxy should accept a URL without connecting."""
|
||||
proxy = create_proxy("http://example.com/mcp/")
|
||||
assert isinstance(proxy, FastMCPProxy)
|
||||
client = cast(Client, proxy.client_factory())
|
||||
assert isinstance(client.transport, StreamableHttpTransport)
|
||||
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."""
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue