fastmcp 2.13.3__py3-none-any.whl → 2.14.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastmcp/__init__.py +0 -21
- fastmcp/cli/__init__.py +0 -3
- fastmcp/cli/__main__.py +5 -0
- fastmcp/cli/cli.py +8 -22
- fastmcp/cli/install/shared.py +0 -15
- fastmcp/cli/tasks.py +110 -0
- fastmcp/client/auth/oauth.py +9 -9
- fastmcp/client/client.py +739 -136
- fastmcp/client/elicitation.py +11 -5
- fastmcp/client/messages.py +7 -5
- fastmcp/client/roots.py +2 -1
- fastmcp/client/sampling/__init__.py +69 -0
- fastmcp/client/sampling/handlers/__init__.py +0 -0
- fastmcp/client/sampling/handlers/anthropic.py +387 -0
- fastmcp/client/sampling/handlers/openai.py +399 -0
- fastmcp/client/tasks.py +551 -0
- fastmcp/client/transports.py +72 -21
- fastmcp/contrib/component_manager/component_service.py +4 -20
- fastmcp/dependencies.py +25 -0
- fastmcp/experimental/sampling/handlers/__init__.py +5 -0
- fastmcp/experimental/sampling/handlers/openai.py +4 -169
- fastmcp/experimental/server/openapi/__init__.py +15 -13
- fastmcp/experimental/utilities/openapi/__init__.py +12 -38
- fastmcp/prompts/prompt.py +38 -38
- fastmcp/resources/resource.py +33 -16
- fastmcp/resources/template.py +69 -59
- fastmcp/server/auth/__init__.py +0 -9
- fastmcp/server/auth/auth.py +127 -3
- fastmcp/server/auth/oauth_proxy.py +47 -97
- fastmcp/server/auth/oidc_proxy.py +7 -0
- fastmcp/server/auth/providers/in_memory.py +2 -2
- fastmcp/server/auth/providers/oci.py +2 -2
- fastmcp/server/context.py +509 -180
- fastmcp/server/dependencies.py +464 -6
- fastmcp/server/elicitation.py +285 -47
- fastmcp/server/event_store.py +177 -0
- fastmcp/server/http.py +15 -3
- fastmcp/server/low_level.py +56 -12
- fastmcp/server/middleware/middleware.py +2 -2
- fastmcp/server/openapi/__init__.py +35 -0
- fastmcp/{experimental/server → server}/openapi/components.py +4 -3
- fastmcp/{experimental/server → server}/openapi/routing.py +1 -1
- fastmcp/{experimental/server → server}/openapi/server.py +6 -5
- fastmcp/server/proxy.py +53 -40
- fastmcp/server/sampling/__init__.py +10 -0
- fastmcp/server/sampling/run.py +301 -0
- fastmcp/server/sampling/sampling_tool.py +108 -0
- fastmcp/server/server.py +793 -552
- fastmcp/server/tasks/__init__.py +21 -0
- fastmcp/server/tasks/capabilities.py +22 -0
- fastmcp/server/tasks/config.py +89 -0
- fastmcp/server/tasks/converters.py +206 -0
- fastmcp/server/tasks/handlers.py +356 -0
- fastmcp/server/tasks/keys.py +93 -0
- fastmcp/server/tasks/protocol.py +355 -0
- fastmcp/server/tasks/subscriptions.py +205 -0
- fastmcp/settings.py +101 -103
- fastmcp/tools/tool.py +83 -49
- fastmcp/tools/tool_transform.py +1 -12
- fastmcp/utilities/components.py +3 -3
- fastmcp/utilities/json_schema_type.py +4 -4
- fastmcp/utilities/mcp_config.py +1 -2
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +1 -1
- fastmcp/{experimental/utilities → utilities}/openapi/README.md +7 -35
- fastmcp/utilities/openapi/__init__.py +63 -0
- fastmcp/{experimental/utilities → utilities}/openapi/formatters.py +5 -5
- fastmcp/{experimental/utilities → utilities}/openapi/json_schema_converter.py +1 -1
- fastmcp/utilities/tests.py +11 -5
- fastmcp/utilities/types.py +8 -0
- {fastmcp-2.13.3.dist-info → fastmcp-2.14.1.dist-info}/METADATA +7 -4
- {fastmcp-2.13.3.dist-info → fastmcp-2.14.1.dist-info}/RECORD +79 -63
- fastmcp/client/sampling.py +0 -56
- fastmcp/experimental/sampling/handlers/base.py +0 -21
- fastmcp/server/auth/providers/bearer.py +0 -25
- fastmcp/server/openapi.py +0 -1087
- fastmcp/server/sampling/handler.py +0 -19
- fastmcp/utilities/openapi.py +0 -1568
- /fastmcp/{experimental/server → server}/openapi/README.md +0 -0
- /fastmcp/{experimental/utilities → utilities}/openapi/director.py +0 -0
- /fastmcp/{experimental/utilities → utilities}/openapi/models.py +0 -0
- /fastmcp/{experimental/utilities → utilities}/openapi/parser.py +0 -0
- /fastmcp/{experimental/utilities → utilities}/openapi/schemas.py +0 -0
- {fastmcp-2.13.3.dist-info → fastmcp-2.14.1.dist-info}/WHEEL +0 -0
- {fastmcp-2.13.3.dist-info → fastmcp-2.14.1.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.13.3.dist-info → fastmcp-2.14.1.dist-info}/licenses/LICENSE +0 -0
fastmcp/server/low_level.py
CHANGED
|
@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
7
7
|
import anyio
|
|
8
8
|
import mcp.types
|
|
9
9
|
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
|
10
|
+
from mcp import McpError
|
|
10
11
|
from mcp.server.lowlevel.server import (
|
|
11
12
|
LifespanResultT,
|
|
12
13
|
NotificationOptions,
|
|
@@ -35,6 +36,8 @@ class MiddlewareServerSession(ServerSession):
|
|
|
35
36
|
def __init__(self, fastmcp: FastMCP, *args, **kwargs):
|
|
36
37
|
super().__init__(*args, **kwargs)
|
|
37
38
|
self._fastmcp_ref: weakref.ref[FastMCP] = weakref.ref(fastmcp)
|
|
39
|
+
# Task group for subscription tasks (set during session run)
|
|
40
|
+
self._subscription_task_group: anyio.TaskGroup | None = None # type: ignore[valid-type]
|
|
38
41
|
|
|
39
42
|
@property
|
|
40
43
|
def fastmcp(self) -> FastMCP:
|
|
@@ -49,23 +52,46 @@ class MiddlewareServerSession(ServerSession):
|
|
|
49
52
|
responder: RequestResponder[mcp.types.ClientRequest, mcp.types.ServerResult],
|
|
50
53
|
):
|
|
51
54
|
"""
|
|
52
|
-
Override the _received_request method to route
|
|
55
|
+
Override the _received_request method to route special requests
|
|
53
56
|
through FastMCP middleware.
|
|
54
57
|
|
|
55
|
-
|
|
56
|
-
require special handling.
|
|
58
|
+
Handles initialization requests and SEP-1686 task methods.
|
|
57
59
|
"""
|
|
58
60
|
import fastmcp.server.context
|
|
59
61
|
from fastmcp.server.middleware.middleware import MiddlewareContext
|
|
60
62
|
|
|
61
63
|
if isinstance(responder.request.root, mcp.types.InitializeRequest):
|
|
64
|
+
# The MCP SDK's ServerSession._received_request() handles the
|
|
65
|
+
# initialize request internally by calling responder.respond()
|
|
66
|
+
# to send the InitializeResult directly to the write stream, then
|
|
67
|
+
# returning None. This bypasses the middleware return path entirely,
|
|
68
|
+
# so middleware would only see the request, never the response.
|
|
69
|
+
#
|
|
70
|
+
# To expose the response to middleware (e.g., for logging server
|
|
71
|
+
# capabilities), we wrap responder.respond() to capture the
|
|
72
|
+
# InitializeResult before it's sent, then return it from
|
|
73
|
+
# call_original_handler so it flows back through the middleware chain.
|
|
74
|
+
captured_response: mcp.types.ServerResult | None = None
|
|
75
|
+
original_respond = responder.respond
|
|
76
|
+
|
|
77
|
+
async def capturing_respond(
|
|
78
|
+
response: mcp.types.ServerResult,
|
|
79
|
+
) -> None:
|
|
80
|
+
nonlocal captured_response
|
|
81
|
+
captured_response = response
|
|
82
|
+
return await original_respond(response)
|
|
83
|
+
|
|
84
|
+
responder.respond = capturing_respond # type: ignore[method-assign]
|
|
62
85
|
|
|
63
86
|
async def call_original_handler(
|
|
64
87
|
ctx: MiddlewareContext,
|
|
65
|
-
) -> None:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
88
|
+
) -> mcp.types.InitializeResult | None:
|
|
89
|
+
await super(MiddlewareServerSession, self)._received_request(responder)
|
|
90
|
+
if captured_response is not None and isinstance(
|
|
91
|
+
captured_response.root, mcp.types.InitializeResult
|
|
92
|
+
):
|
|
93
|
+
return captured_response.root
|
|
94
|
+
return None
|
|
69
95
|
|
|
70
96
|
async with fastmcp.server.context.Context(
|
|
71
97
|
fastmcp=self.fastmcp
|
|
@@ -79,11 +105,26 @@ class MiddlewareServerSession(ServerSession):
|
|
|
79
105
|
fastmcp_context=fastmcp_ctx,
|
|
80
106
|
)
|
|
81
107
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
108
|
+
try:
|
|
109
|
+
return await self.fastmcp._apply_middleware(
|
|
110
|
+
mw_context, call_original_handler
|
|
111
|
+
)
|
|
112
|
+
except McpError as e:
|
|
113
|
+
# McpError can be thrown from middleware in `on_initialize`
|
|
114
|
+
# send the error to responder.
|
|
115
|
+
if not responder._completed:
|
|
116
|
+
with responder:
|
|
117
|
+
await responder.respond(e.error)
|
|
118
|
+
else:
|
|
119
|
+
# Don't re-raise: prevents responding to initialize request twice
|
|
120
|
+
logger.warning(
|
|
121
|
+
"Received McpError but responder is already completed. "
|
|
122
|
+
"Cannot send error response as response was already sent.",
|
|
123
|
+
exc_info=e,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Fall through to default handling (task methods now handled via registered handlers)
|
|
127
|
+
return await super()._received_request(responder)
|
|
87
128
|
|
|
88
129
|
|
|
89
130
|
class LowLevelServer(_Server[LifespanResultT, RequestT]):
|
|
@@ -146,6 +187,9 @@ class LowLevelServer(_Server[LifespanResultT, RequestT]):
|
|
|
146
187
|
)
|
|
147
188
|
|
|
148
189
|
async with anyio.create_task_group() as tg:
|
|
190
|
+
# Store task group on session for subscription tasks (SEP-1686)
|
|
191
|
+
session._subscription_task_group = tg
|
|
192
|
+
|
|
149
193
|
async for message in session.incoming_messages:
|
|
150
194
|
tg.start_soon(
|
|
151
195
|
self._handle_message,
|
|
@@ -150,8 +150,8 @@ class Middleware:
|
|
|
150
150
|
async def on_initialize(
|
|
151
151
|
self,
|
|
152
152
|
context: MiddlewareContext[mt.InitializeRequest],
|
|
153
|
-
call_next: CallNext[mt.InitializeRequest, None],
|
|
154
|
-
) -> None:
|
|
153
|
+
call_next: CallNext[mt.InitializeRequest, mt.InitializeResult | None],
|
|
154
|
+
) -> mt.InitializeResult | None:
|
|
155
155
|
return await call_next(context)
|
|
156
156
|
|
|
157
157
|
async def on_call_tool(
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""OpenAPI server implementation for FastMCP - refactored for better maintainability."""
|
|
2
|
+
|
|
3
|
+
# Import from server
|
|
4
|
+
from .server import FastMCPOpenAPI
|
|
5
|
+
|
|
6
|
+
# Import from routing
|
|
7
|
+
from .routing import (
|
|
8
|
+
MCPType,
|
|
9
|
+
RouteMap,
|
|
10
|
+
RouteMapFn,
|
|
11
|
+
ComponentFn,
|
|
12
|
+
DEFAULT_ROUTE_MAPPINGS,
|
|
13
|
+
_determine_route_type,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Import from components
|
|
17
|
+
from .components import (
|
|
18
|
+
OpenAPITool,
|
|
19
|
+
OpenAPIResource,
|
|
20
|
+
OpenAPIResourceTemplate,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
# Export public symbols - maintaining backward compatibility
|
|
24
|
+
__all__ = [
|
|
25
|
+
"DEFAULT_ROUTE_MAPPINGS",
|
|
26
|
+
"ComponentFn",
|
|
27
|
+
"FastMCPOpenAPI",
|
|
28
|
+
"MCPType",
|
|
29
|
+
"OpenAPIResource",
|
|
30
|
+
"OpenAPIResourceTemplate",
|
|
31
|
+
"OpenAPITool",
|
|
32
|
+
"RouteMap",
|
|
33
|
+
"RouteMapFn",
|
|
34
|
+
"_determine_route_type",
|
|
35
|
+
]
|
|
@@ -9,14 +9,15 @@ import httpx
|
|
|
9
9
|
from mcp.types import ToolAnnotations
|
|
10
10
|
from pydantic.networks import AnyUrl
|
|
11
11
|
|
|
12
|
-
# Import from our new utilities
|
|
13
|
-
from fastmcp.experimental.utilities.openapi import HTTPRoute
|
|
14
|
-
from fastmcp.experimental.utilities.openapi.director import RequestDirector
|
|
15
12
|
from fastmcp.resources import Resource, ResourceTemplate
|
|
16
13
|
from fastmcp.server.dependencies import get_http_headers
|
|
17
14
|
from fastmcp.tools.tool import Tool, ToolResult
|
|
18
15
|
from fastmcp.utilities.logging import get_logger
|
|
19
16
|
|
|
17
|
+
# Import from our new utilities
|
|
18
|
+
from fastmcp.utilities.openapi import HTTPRoute
|
|
19
|
+
from fastmcp.utilities.openapi.director import RequestDirector
|
|
20
|
+
|
|
20
21
|
if TYPE_CHECKING:
|
|
21
22
|
from fastmcp.server import Context
|
|
22
23
|
|
|
@@ -14,8 +14,8 @@ if TYPE_CHECKING:
|
|
|
14
14
|
OpenAPITool,
|
|
15
15
|
)
|
|
16
16
|
# Import from our new utilities
|
|
17
|
-
from fastmcp.experimental.utilities.openapi import HttpMethod, HTTPRoute
|
|
18
17
|
from fastmcp.utilities.logging import get_logger
|
|
18
|
+
from fastmcp.utilities.openapi import HttpMethod, HTTPRoute
|
|
19
19
|
|
|
20
20
|
logger = get_logger(__name__)
|
|
21
21
|
|
|
@@ -7,16 +7,17 @@ from typing import Any, Literal
|
|
|
7
7
|
import httpx
|
|
8
8
|
from jsonschema_path import SchemaPath
|
|
9
9
|
|
|
10
|
+
from fastmcp.server.server import FastMCP
|
|
11
|
+
from fastmcp.utilities.logging import get_logger
|
|
12
|
+
|
|
10
13
|
# Import from our new utilities and components
|
|
11
|
-
from fastmcp.
|
|
14
|
+
from fastmcp.utilities.openapi import (
|
|
12
15
|
HTTPRoute,
|
|
13
16
|
extract_output_schema_from_responses,
|
|
14
17
|
format_simple_description,
|
|
15
18
|
parse_openapi_to_http_routes,
|
|
16
19
|
)
|
|
17
|
-
from fastmcp.
|
|
18
|
-
from fastmcp.server.server import FastMCP
|
|
19
|
-
from fastmcp.utilities.logging import get_logger
|
|
20
|
+
from fastmcp.utilities.openapi.director import RequestDirector
|
|
20
21
|
|
|
21
22
|
from .components import (
|
|
22
23
|
OpenAPIResource,
|
|
@@ -247,7 +248,7 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
247
248
|
# Create the new name
|
|
248
249
|
new_name = f"{name}_{self._used_names[component_type][name]}"
|
|
249
250
|
logger.debug(
|
|
250
|
-
f"Name collision detected: '{name}' already exists as a {component_type
|
|
251
|
+
f"Name collision detected: '{name}' already exists as a {component_type}. "
|
|
251
252
|
f"Using '{new_name}' instead."
|
|
252
253
|
)
|
|
253
254
|
|
fastmcp/server/proxy.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
-
import warnings
|
|
5
4
|
from collections.abc import Awaitable, Callable
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
from typing import TYPE_CHECKING, Any, cast
|
|
@@ -15,12 +14,12 @@ from mcp.shared.exceptions import McpError
|
|
|
15
14
|
from mcp.types import (
|
|
16
15
|
METHOD_NOT_FOUND,
|
|
17
16
|
BlobResourceContents,
|
|
17
|
+
ElicitRequestFormParams,
|
|
18
18
|
GetPromptResult,
|
|
19
19
|
TextResourceContents,
|
|
20
20
|
)
|
|
21
21
|
from pydantic.networks import AnyUrl
|
|
22
22
|
|
|
23
|
-
import fastmcp
|
|
24
23
|
from fastmcp.client.client import Client, FastMCP1Server
|
|
25
24
|
from fastmcp.client.elicitation import ElicitResult
|
|
26
25
|
from fastmcp.client.logging import LogMessage
|
|
@@ -36,6 +35,7 @@ from fastmcp.resources.resource_manager import ResourceManager
|
|
|
36
35
|
from fastmcp.server.context import Context
|
|
37
36
|
from fastmcp.server.dependencies import get_context
|
|
38
37
|
from fastmcp.server.server import FastMCP
|
|
38
|
+
from fastmcp.server.tasks.config import TaskConfig
|
|
39
39
|
from fastmcp.tools.tool import Tool, ToolResult
|
|
40
40
|
from fastmcp.tools.tool_manager import ToolManager
|
|
41
41
|
from fastmcp.tools.tool_transform import (
|
|
@@ -117,6 +117,7 @@ class ProxyToolManager(ToolManager, ProxyManagerMixin):
|
|
|
117
117
|
return ToolResult(
|
|
118
118
|
content=result.content,
|
|
119
119
|
structured_content=result.structured_content,
|
|
120
|
+
meta=result.meta,
|
|
120
121
|
)
|
|
121
122
|
|
|
122
123
|
|
|
@@ -260,6 +261,8 @@ class ProxyTool(Tool, MirroredComponent):
|
|
|
260
261
|
A Tool that represents and executes a tool on a remote server.
|
|
261
262
|
"""
|
|
262
263
|
|
|
264
|
+
task_config: TaskConfig = TaskConfig(mode="forbidden")
|
|
265
|
+
|
|
263
266
|
def __init__(self, client: Client, **kwargs: Any):
|
|
264
267
|
super().__init__(**kwargs)
|
|
265
268
|
self._client = client
|
|
@@ -288,15 +291,37 @@ class ProxyTool(Tool, MirroredComponent):
|
|
|
288
291
|
) -> ToolResult:
|
|
289
292
|
"""Executes the tool by making a call through the client."""
|
|
290
293
|
async with self._client:
|
|
294
|
+
context = get_context()
|
|
295
|
+
# Build meta dict from request context
|
|
296
|
+
meta: dict[str, Any] | None = None
|
|
297
|
+
if hasattr(context, "request_context"):
|
|
298
|
+
req_ctx = context.request_context
|
|
299
|
+
# Start with existing meta if present
|
|
300
|
+
if hasattr(req_ctx, "meta") and req_ctx.meta:
|
|
301
|
+
meta = dict(req_ctx.meta)
|
|
302
|
+
# Add task metadata if this is a task request
|
|
303
|
+
if (
|
|
304
|
+
hasattr(req_ctx, "experimental")
|
|
305
|
+
and hasattr(req_ctx.experimental, "is_task")
|
|
306
|
+
and req_ctx.experimental.is_task
|
|
307
|
+
):
|
|
308
|
+
task_metadata = req_ctx.experimental.task_metadata
|
|
309
|
+
if task_metadata:
|
|
310
|
+
meta = meta or {}
|
|
311
|
+
meta["modelcontextprotocol.io/task"] = task_metadata.model_dump(
|
|
312
|
+
exclude_none=True
|
|
313
|
+
)
|
|
314
|
+
|
|
291
315
|
result = await self._client.call_tool_mcp(
|
|
292
|
-
name=self.name,
|
|
293
|
-
arguments=arguments,
|
|
316
|
+
name=self.name, arguments=arguments, meta=meta
|
|
294
317
|
)
|
|
295
318
|
if result.isError:
|
|
296
319
|
raise ToolError(cast(mcp.types.TextContent, result.content[0]).text)
|
|
320
|
+
# Preserve backend's meta (includes task metadata for background tasks)
|
|
297
321
|
return ToolResult(
|
|
298
322
|
content=result.content,
|
|
299
323
|
structured_content=result.structuredContent,
|
|
324
|
+
meta=result.meta,
|
|
300
325
|
)
|
|
301
326
|
|
|
302
327
|
|
|
@@ -305,6 +330,7 @@ class ProxyResource(Resource, MirroredComponent):
|
|
|
305
330
|
A Resource that represents and reads a resource from a remote server.
|
|
306
331
|
"""
|
|
307
332
|
|
|
333
|
+
task_config: TaskConfig = TaskConfig(mode="forbidden")
|
|
308
334
|
_client: Client
|
|
309
335
|
_value: str | bytes | None = None
|
|
310
336
|
|
|
@@ -337,6 +363,7 @@ class ProxyResource(Resource, MirroredComponent):
|
|
|
337
363
|
icons=mcp_resource.icons,
|
|
338
364
|
meta=mcp_resource.meta,
|
|
339
365
|
tags=(mcp_resource.meta or {}).get("_fastmcp", {}).get("tags", []),
|
|
366
|
+
task_config=TaskConfig(mode="forbidden"),
|
|
340
367
|
_mirrored=True,
|
|
341
368
|
)
|
|
342
369
|
|
|
@@ -360,12 +387,14 @@ class ProxyTemplate(ResourceTemplate, MirroredComponent):
|
|
|
360
387
|
A ResourceTemplate that represents and creates resources from a remote server template.
|
|
361
388
|
"""
|
|
362
389
|
|
|
390
|
+
task_config: TaskConfig = TaskConfig(mode="forbidden")
|
|
391
|
+
|
|
363
392
|
def __init__(self, client: Client, **kwargs: Any):
|
|
364
393
|
super().__init__(**kwargs)
|
|
365
394
|
self._client = client
|
|
366
395
|
|
|
367
396
|
@classmethod
|
|
368
|
-
def from_mcp_template(
|
|
397
|
+
def from_mcp_template( # type: ignore[override]
|
|
369
398
|
cls, client: Client, mcp_template: mcp.types.ResourceTemplate
|
|
370
399
|
) -> ProxyTemplate:
|
|
371
400
|
"""Factory method to create a ProxyTemplate from a raw MCP template schema."""
|
|
@@ -380,6 +409,7 @@ class ProxyTemplate(ResourceTemplate, MirroredComponent):
|
|
|
380
409
|
parameters={}, # Remote templates don't have local parameters
|
|
381
410
|
meta=mcp_template.meta,
|
|
382
411
|
tags=(mcp_template.meta or {}).get("_fastmcp", {}).get("tags", []),
|
|
412
|
+
task_config=TaskConfig(mode="forbidden"),
|
|
383
413
|
_mirrored=True,
|
|
384
414
|
)
|
|
385
415
|
|
|
@@ -425,6 +455,7 @@ class ProxyPrompt(Prompt, MirroredComponent):
|
|
|
425
455
|
A Prompt that represents and renders a prompt from a remote server.
|
|
426
456
|
"""
|
|
427
457
|
|
|
458
|
+
task_config: TaskConfig = TaskConfig(mode="forbidden")
|
|
428
459
|
_client: Client
|
|
429
460
|
|
|
430
461
|
def __init__(self, client: Client, **kwargs):
|
|
@@ -453,10 +484,11 @@ class ProxyPrompt(Prompt, MirroredComponent):
|
|
|
453
484
|
icons=mcp_prompt.icons,
|
|
454
485
|
meta=mcp_prompt.meta,
|
|
455
486
|
tags=(mcp_prompt.meta or {}).get("_fastmcp", {}).get("tags", []),
|
|
487
|
+
task_config=TaskConfig(mode="forbidden"),
|
|
456
488
|
_mirrored=True,
|
|
457
489
|
)
|
|
458
490
|
|
|
459
|
-
async def render(self, arguments: dict[str, Any]) -> list[PromptMessage]:
|
|
491
|
+
async def render(self, arguments: dict[str, Any]) -> list[PromptMessage]: # type: ignore[override]
|
|
460
492
|
"""Render the prompt by making a call through the client."""
|
|
461
493
|
async with self._client:
|
|
462
494
|
result = await self._client.get_prompt(self.name, arguments)
|
|
@@ -471,9 +503,8 @@ class FastMCPProxy(FastMCP):
|
|
|
471
503
|
|
|
472
504
|
def __init__(
|
|
473
505
|
self,
|
|
474
|
-
client: Client | None = None,
|
|
475
506
|
*,
|
|
476
|
-
client_factory: ClientFactoryT
|
|
507
|
+
client_factory: ClientFactoryT,
|
|
477
508
|
**kwargs,
|
|
478
509
|
):
|
|
479
510
|
"""
|
|
@@ -483,9 +514,6 @@ class FastMCPProxy(FastMCP):
|
|
|
483
514
|
Use FastMCP.as_proxy() for convenience with automatic session strategy.
|
|
484
515
|
|
|
485
516
|
Args:
|
|
486
|
-
client: [DEPRECATED] A Client instance. Use client_factory instead for explicit
|
|
487
|
-
session management. When provided, a client_factory will be automatically
|
|
488
|
-
created that provides session isolation for backwards compatibility.
|
|
489
517
|
client_factory: A callable that returns a Client instance when called.
|
|
490
518
|
This gives you full control over session creation and reuse.
|
|
491
519
|
Can be either a synchronous or asynchronous function.
|
|
@@ -494,29 +522,7 @@ class FastMCPProxy(FastMCP):
|
|
|
494
522
|
|
|
495
523
|
super().__init__(**kwargs)
|
|
496
524
|
|
|
497
|
-
|
|
498
|
-
if client is not None and client_factory is not None:
|
|
499
|
-
raise ValueError("Cannot specify both 'client' and 'client_factory'")
|
|
500
|
-
|
|
501
|
-
if client is not None:
|
|
502
|
-
# Deprecated in 2.10.3
|
|
503
|
-
if fastmcp.settings.deprecation_warnings:
|
|
504
|
-
warnings.warn(
|
|
505
|
-
"Passing 'client' to FastMCPProxy is deprecated. Use 'client_factory' instead for explicit session management. "
|
|
506
|
-
"For automatic session strategy, use FastMCP.as_proxy().",
|
|
507
|
-
DeprecationWarning,
|
|
508
|
-
stacklevel=2,
|
|
509
|
-
)
|
|
510
|
-
|
|
511
|
-
# Create a factory that provides session isolation for backwards compatibility
|
|
512
|
-
def deprecated_client_factory():
|
|
513
|
-
return client.new()
|
|
514
|
-
|
|
515
|
-
self.client_factory = deprecated_client_factory
|
|
516
|
-
elif client_factory is not None:
|
|
517
|
-
self.client_factory = client_factory
|
|
518
|
-
else:
|
|
519
|
-
raise ValueError("Must specify 'client_factory'")
|
|
525
|
+
self.client_factory = client_factory
|
|
520
526
|
|
|
521
527
|
# Replace the default managers with our specialized proxy managers.
|
|
522
528
|
self._tool_manager = ProxyToolManager(
|
|
@@ -583,19 +589,20 @@ class ProxyClient(Client[ClientTransportT]):
|
|
|
583
589
|
A handler that forwards the sampling request from the remote server to the proxy's connected clients and relays the response back to the remote server.
|
|
584
590
|
"""
|
|
585
591
|
ctx = get_context()
|
|
586
|
-
|
|
592
|
+
result = await ctx.sample(
|
|
587
593
|
list(messages),
|
|
588
594
|
system_prompt=params.systemPrompt,
|
|
589
595
|
temperature=params.temperature,
|
|
590
596
|
max_tokens=params.maxTokens,
|
|
591
597
|
model_preferences=params.modelPreferences,
|
|
592
598
|
)
|
|
593
|
-
|
|
594
|
-
|
|
599
|
+
# Create TextContent from the result text
|
|
600
|
+
content = mcp.types.TextContent(type="text", text=result.text or "")
|
|
595
601
|
return mcp.types.CreateMessageResult(
|
|
596
602
|
role="assistant",
|
|
597
603
|
model="fastmcp-client",
|
|
598
|
-
|
|
604
|
+
# TODO(ty): remove when ty supports isinstance exclusion narrowing
|
|
605
|
+
content=content, # type: ignore[arg-type]
|
|
599
606
|
)
|
|
600
607
|
|
|
601
608
|
@classmethod
|
|
@@ -610,9 +617,15 @@ class ProxyClient(Client[ClientTransportT]):
|
|
|
610
617
|
A handler that forwards the elicitation request from the remote server to the proxy's connected clients and relays the response back to the remote server.
|
|
611
618
|
"""
|
|
612
619
|
ctx = get_context()
|
|
620
|
+
# requestedSchema only exists on ElicitRequestFormParams, not ElicitRequestURLParams
|
|
621
|
+
requested_schema = (
|
|
622
|
+
params.requestedSchema
|
|
623
|
+
if isinstance(params, ElicitRequestFormParams)
|
|
624
|
+
else {"type": "object", "properties": {}}
|
|
625
|
+
)
|
|
613
626
|
result = await ctx.session.elicit(
|
|
614
627
|
message=message,
|
|
615
|
-
requestedSchema=
|
|
628
|
+
requestedSchema=requested_schema,
|
|
616
629
|
related_request_id=ctx.request_id,
|
|
617
630
|
)
|
|
618
631
|
return ElicitResult(action=result.action, content=result.content)
|
|
@@ -656,7 +669,7 @@ class StatefulProxyClient(ProxyClient[ClientTransportT]):
|
|
|
656
669
|
super().__init__(*args, **kwargs)
|
|
657
670
|
self._caches: dict[ServerSession, Client[ClientTransportT]] = {}
|
|
658
671
|
|
|
659
|
-
async def __aexit__(self, exc_type, exc_value, traceback) -> None:
|
|
672
|
+
async def __aexit__(self, exc_type, exc_value, traceback) -> None: # type: ignore[override]
|
|
660
673
|
"""
|
|
661
674
|
The stateful proxy client will be forced disconnected when the session is exited.
|
|
662
675
|
So we do nothing here.
|