fastmcp 2.12.5__py3-none-any.whl → 2.13.2__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 +2 -2
- fastmcp/cli/cli.py +11 -11
- fastmcp/cli/install/claude_code.py +6 -6
- fastmcp/cli/install/claude_desktop.py +3 -3
- fastmcp/cli/install/cursor.py +18 -12
- fastmcp/cli/install/gemini_cli.py +3 -3
- fastmcp/cli/install/mcp_json.py +3 -3
- fastmcp/cli/run.py +13 -8
- fastmcp/client/__init__.py +9 -9
- fastmcp/client/auth/oauth.py +115 -217
- fastmcp/client/client.py +105 -39
- fastmcp/client/logging.py +18 -14
- fastmcp/client/oauth_callback.py +85 -171
- fastmcp/client/sampling.py +1 -1
- fastmcp/client/transports.py +80 -25
- fastmcp/contrib/component_manager/__init__.py +1 -1
- fastmcp/contrib/component_manager/component_manager.py +2 -2
- fastmcp/contrib/component_manager/component_service.py +6 -6
- fastmcp/contrib/mcp_mixin/README.md +32 -1
- fastmcp/contrib/mcp_mixin/__init__.py +2 -2
- fastmcp/contrib/mcp_mixin/mcp_mixin.py +14 -2
- fastmcp/experimental/sampling/handlers/openai.py +2 -2
- fastmcp/experimental/server/openapi/__init__.py +5 -8
- fastmcp/experimental/server/openapi/components.py +11 -7
- fastmcp/experimental/server/openapi/routing.py +2 -2
- fastmcp/experimental/utilities/openapi/__init__.py +10 -15
- fastmcp/experimental/utilities/openapi/director.py +14 -15
- fastmcp/experimental/utilities/openapi/json_schema_converter.py +6 -2
- fastmcp/experimental/utilities/openapi/models.py +3 -3
- fastmcp/experimental/utilities/openapi/parser.py +37 -16
- fastmcp/experimental/utilities/openapi/schemas.py +2 -2
- fastmcp/mcp_config.py +3 -4
- fastmcp/prompts/__init__.py +1 -1
- fastmcp/prompts/prompt.py +22 -19
- fastmcp/prompts/prompt_manager.py +16 -101
- fastmcp/resources/__init__.py +5 -5
- fastmcp/resources/resource.py +14 -9
- fastmcp/resources/resource_manager.py +9 -168
- fastmcp/resources/template.py +107 -17
- fastmcp/resources/types.py +30 -24
- fastmcp/server/__init__.py +1 -1
- fastmcp/server/auth/__init__.py +9 -5
- fastmcp/server/auth/auth.py +70 -43
- fastmcp/server/auth/handlers/authorize.py +326 -0
- fastmcp/server/auth/jwt_issuer.py +236 -0
- fastmcp/server/auth/middleware.py +96 -0
- fastmcp/server/auth/oauth_proxy.py +1510 -289
- fastmcp/server/auth/oidc_proxy.py +84 -20
- fastmcp/server/auth/providers/auth0.py +40 -21
- fastmcp/server/auth/providers/aws.py +29 -3
- fastmcp/server/auth/providers/azure.py +312 -131
- fastmcp/server/auth/providers/bearer.py +1 -1
- fastmcp/server/auth/providers/debug.py +114 -0
- fastmcp/server/auth/providers/descope.py +86 -29
- fastmcp/server/auth/providers/discord.py +308 -0
- fastmcp/server/auth/providers/github.py +29 -8
- fastmcp/server/auth/providers/google.py +48 -9
- fastmcp/server/auth/providers/in_memory.py +27 -3
- fastmcp/server/auth/providers/introspection.py +281 -0
- fastmcp/server/auth/providers/jwt.py +48 -31
- fastmcp/server/auth/providers/oci.py +233 -0
- fastmcp/server/auth/providers/scalekit.py +238 -0
- fastmcp/server/auth/providers/supabase.py +188 -0
- fastmcp/server/auth/providers/workos.py +35 -17
- fastmcp/server/context.py +177 -51
- fastmcp/server/dependencies.py +39 -12
- fastmcp/server/elicitation.py +1 -1
- fastmcp/server/http.py +56 -17
- fastmcp/server/low_level.py +121 -2
- fastmcp/server/middleware/__init__.py +1 -1
- fastmcp/server/middleware/caching.py +476 -0
- fastmcp/server/middleware/error_handling.py +14 -10
- fastmcp/server/middleware/logging.py +50 -39
- fastmcp/server/middleware/middleware.py +29 -16
- fastmcp/server/middleware/rate_limiting.py +3 -3
- fastmcp/server/middleware/tool_injection.py +116 -0
- fastmcp/server/openapi.py +10 -6
- fastmcp/server/proxy.py +22 -11
- fastmcp/server/server.py +725 -242
- fastmcp/settings.py +24 -10
- fastmcp/tools/__init__.py +1 -1
- fastmcp/tools/tool.py +70 -23
- fastmcp/tools/tool_manager.py +30 -112
- fastmcp/tools/tool_transform.py +12 -10
- fastmcp/utilities/cli.py +67 -28
- fastmcp/utilities/components.py +7 -2
- fastmcp/utilities/inspect.py +79 -23
- fastmcp/utilities/json_schema.py +4 -4
- fastmcp/utilities/json_schema_type.py +4 -4
- fastmcp/utilities/logging.py +118 -8
- fastmcp/utilities/mcp_server_config/__init__.py +3 -3
- fastmcp/utilities/mcp_server_config/v1/environments/base.py +1 -2
- fastmcp/utilities/mcp_server_config/v1/environments/uv.py +6 -6
- fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py +4 -4
- fastmcp/utilities/mcp_server_config/v1/schema.json +3 -0
- fastmcp/utilities/mcp_server_config/v1/sources/base.py +0 -1
- fastmcp/utilities/openapi.py +11 -11
- fastmcp/utilities/tests.py +85 -4
- fastmcp/utilities/types.py +78 -16
- fastmcp/utilities/ui.py +626 -0
- {fastmcp-2.12.5.dist-info → fastmcp-2.13.2.dist-info}/METADATA +22 -14
- fastmcp-2.13.2.dist-info/RECORD +144 -0
- {fastmcp-2.12.5.dist-info → fastmcp-2.13.2.dist-info}/WHEEL +1 -1
- fastmcp/cli/claude.py +0 -135
- fastmcp/utilities/storage.py +0 -204
- fastmcp-2.12.5.dist-info/RECORD +0 -134
- {fastmcp-2.12.5.dist-info → fastmcp-2.13.2.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.12.5.dist-info → fastmcp-2.13.2.dist-info}/licenses/LICENSE +0 -0
fastmcp/client/client.py
CHANGED
|
@@ -61,15 +61,15 @@ from .transports import (
|
|
|
61
61
|
|
|
62
62
|
__all__ = [
|
|
63
63
|
"Client",
|
|
64
|
-
"SessionKwargs",
|
|
65
|
-
"RootsHandler",
|
|
66
|
-
"RootsList",
|
|
67
|
-
"LogHandler",
|
|
68
|
-
"MessageHandler",
|
|
69
64
|
"ClientSamplingHandler",
|
|
70
|
-
"SamplingHandler",
|
|
71
65
|
"ElicitationHandler",
|
|
66
|
+
"LogHandler",
|
|
67
|
+
"MessageHandler",
|
|
72
68
|
"ProgressHandler",
|
|
69
|
+
"RootsHandler",
|
|
70
|
+
"RootsList",
|
|
71
|
+
"SamplingHandler",
|
|
72
|
+
"SessionKwargs",
|
|
73
73
|
]
|
|
74
74
|
|
|
75
75
|
logger = get_logger(__name__)
|
|
@@ -77,6 +77,16 @@ logger = get_logger(__name__)
|
|
|
77
77
|
T = TypeVar("T", bound="ClientTransport")
|
|
78
78
|
|
|
79
79
|
|
|
80
|
+
def _timeout_to_seconds(
|
|
81
|
+
timeout: datetime.timedelta | float | int | None,
|
|
82
|
+
) -> float | None:
|
|
83
|
+
if timeout is None:
|
|
84
|
+
return None
|
|
85
|
+
if isinstance(timeout, datetime.timedelta):
|
|
86
|
+
return timeout.total_seconds()
|
|
87
|
+
return float(timeout)
|
|
88
|
+
|
|
89
|
+
|
|
80
90
|
@dataclass
|
|
81
91
|
class ClientSessionState:
|
|
82
92
|
"""Holds all session-related state for a Client instance.
|
|
@@ -155,38 +165,38 @@ class Client(Generic[ClientTransportT]):
|
|
|
155
165
|
"""
|
|
156
166
|
|
|
157
167
|
@overload
|
|
158
|
-
def __init__(self: Client[T], transport: T, *args, **kwargs) -> None: ...
|
|
168
|
+
def __init__(self: Client[T], transport: T, *args: Any, **kwargs: Any) -> None: ...
|
|
159
169
|
|
|
160
170
|
@overload
|
|
161
171
|
def __init__(
|
|
162
172
|
self: Client[SSETransport | StreamableHttpTransport],
|
|
163
173
|
transport: AnyUrl,
|
|
164
|
-
*args,
|
|
165
|
-
**kwargs,
|
|
174
|
+
*args: Any,
|
|
175
|
+
**kwargs: Any,
|
|
166
176
|
) -> None: ...
|
|
167
177
|
|
|
168
178
|
@overload
|
|
169
179
|
def __init__(
|
|
170
180
|
self: Client[FastMCPTransport],
|
|
171
181
|
transport: FastMCP | FastMCP1Server,
|
|
172
|
-
*args,
|
|
173
|
-
**kwargs,
|
|
182
|
+
*args: Any,
|
|
183
|
+
**kwargs: Any,
|
|
174
184
|
) -> None: ...
|
|
175
185
|
|
|
176
186
|
@overload
|
|
177
187
|
def __init__(
|
|
178
188
|
self: Client[PythonStdioTransport | NodeStdioTransport],
|
|
179
189
|
transport: Path,
|
|
180
|
-
*args,
|
|
181
|
-
**kwargs,
|
|
190
|
+
*args: Any,
|
|
191
|
+
**kwargs: Any,
|
|
182
192
|
) -> None: ...
|
|
183
193
|
|
|
184
194
|
@overload
|
|
185
195
|
def __init__(
|
|
186
196
|
self: Client[MCPConfigTransport],
|
|
187
197
|
transport: MCPConfig | dict[str, Any],
|
|
188
|
-
*args,
|
|
189
|
-
**kwargs,
|
|
198
|
+
*args: Any,
|
|
199
|
+
**kwargs: Any,
|
|
190
200
|
) -> None: ...
|
|
191
201
|
|
|
192
202
|
@overload
|
|
@@ -198,8 +208,8 @@ class Client(Generic[ClientTransportT]):
|
|
|
198
208
|
| StreamableHttpTransport
|
|
199
209
|
],
|
|
200
210
|
transport: str,
|
|
201
|
-
*args,
|
|
202
|
-
**kwargs,
|
|
211
|
+
*args: Any,
|
|
212
|
+
**kwargs: Any,
|
|
203
213
|
) -> None: ...
|
|
204
214
|
|
|
205
215
|
def __init__(
|
|
@@ -222,6 +232,7 @@ class Client(Generic[ClientTransportT]):
|
|
|
222
232
|
message_handler: MessageHandlerT | MessageHandler | None = None,
|
|
223
233
|
progress_handler: ProgressHandler | None = None,
|
|
224
234
|
timeout: datetime.timedelta | float | int | None = None,
|
|
235
|
+
auto_initialize: bool = True,
|
|
225
236
|
init_timeout: datetime.timedelta | float | int | None = None,
|
|
226
237
|
client_info: mcp.types.Implementation | None = None,
|
|
227
238
|
auth: httpx.Auth | Literal["oauth"] | str | None = None,
|
|
@@ -240,26 +251,23 @@ class Client(Generic[ClientTransportT]):
|
|
|
240
251
|
|
|
241
252
|
self._progress_handler = progress_handler
|
|
242
253
|
|
|
254
|
+
# Convert timeout to timedelta if needed
|
|
243
255
|
if isinstance(timeout, int | float):
|
|
244
256
|
timeout = datetime.timedelta(seconds=float(timeout))
|
|
245
257
|
|
|
246
258
|
# handle init handshake timeout
|
|
247
259
|
if init_timeout is None:
|
|
248
260
|
init_timeout = fastmcp.settings.client_init_timeout
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
init_timeout = None
|
|
253
|
-
else:
|
|
254
|
-
init_timeout = float(init_timeout)
|
|
255
|
-
self._init_timeout = init_timeout
|
|
261
|
+
self._init_timeout = _timeout_to_seconds(init_timeout)
|
|
262
|
+
|
|
263
|
+
self.auto_initialize = auto_initialize
|
|
256
264
|
|
|
257
265
|
self._session_kwargs: SessionKwargs = {
|
|
258
266
|
"sampling_callback": None,
|
|
259
267
|
"list_roots_callback": None,
|
|
260
268
|
"logging_callback": create_log_callback(log_handler),
|
|
261
269
|
"message_handler": message_handler,
|
|
262
|
-
"read_timeout_seconds": timeout,
|
|
270
|
+
"read_timeout_seconds": timeout, # ty: ignore[invalid-argument-type]
|
|
263
271
|
"client_info": client_info,
|
|
264
272
|
}
|
|
265
273
|
|
|
@@ -290,12 +298,8 @@ class Client(Generic[ClientTransportT]):
|
|
|
290
298
|
return self._session_state.session
|
|
291
299
|
|
|
292
300
|
@property
|
|
293
|
-
def initialize_result(self) -> mcp.types.InitializeResult:
|
|
301
|
+
def initialize_result(self) -> mcp.types.InitializeResult | None:
|
|
294
302
|
"""Get the result of the initialization request."""
|
|
295
|
-
if self._session_state.initialize_result is None:
|
|
296
|
-
raise RuntimeError(
|
|
297
|
-
"Client is not connected. Use the 'async with client:' context manager first."
|
|
298
|
-
)
|
|
299
303
|
return self._session_state.initialize_result
|
|
300
304
|
|
|
301
305
|
def set_roots(self, roots: RootsList | RootsHandler) -> None:
|
|
@@ -357,15 +361,11 @@ class Client(Generic[ClientTransportT]):
|
|
|
357
361
|
self._session_state.session = session
|
|
358
362
|
# Initialize the session
|
|
359
363
|
try:
|
|
360
|
-
|
|
361
|
-
self.
|
|
362
|
-
await self._session_state.session.initialize()
|
|
363
|
-
)
|
|
364
|
+
if self.auto_initialize:
|
|
365
|
+
await self.initialize()
|
|
364
366
|
yield
|
|
365
|
-
except anyio.ClosedResourceError:
|
|
366
|
-
raise RuntimeError("Server session was closed unexpectedly")
|
|
367
|
-
except TimeoutError:
|
|
368
|
-
raise RuntimeError("Failed to initialize server session")
|
|
367
|
+
except anyio.ClosedResourceError as e:
|
|
368
|
+
raise RuntimeError("Server session was closed unexpectedly") from e
|
|
369
369
|
finally:
|
|
370
370
|
self._session_state.session = None
|
|
371
371
|
self._session_state.initialize_result = None
|
|
@@ -493,6 +493,55 @@ class Client(Generic[ClientTransportT]):
|
|
|
493
493
|
|
|
494
494
|
# --- MCP Client Methods ---
|
|
495
495
|
|
|
496
|
+
async def initialize(
|
|
497
|
+
self,
|
|
498
|
+
timeout: datetime.timedelta | float | int | None = None,
|
|
499
|
+
) -> mcp.types.InitializeResult:
|
|
500
|
+
"""Send an initialize request to the server.
|
|
501
|
+
|
|
502
|
+
This method performs the MCP initialization handshake with the server,
|
|
503
|
+
exchanging capabilities and server information. It is idempotent - calling
|
|
504
|
+
it multiple times returns the cached result from the first call.
|
|
505
|
+
|
|
506
|
+
The initialization happens automatically when entering the client context
|
|
507
|
+
manager unless `auto_initialize=False` was set during client construction.
|
|
508
|
+
Manual calls to this method are only needed when auto-initialization is disabled.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
timeout: Optional timeout for the initialization request (seconds or timedelta).
|
|
512
|
+
If None, uses the client's init_timeout setting.
|
|
513
|
+
|
|
514
|
+
Returns:
|
|
515
|
+
InitializeResult: The server's initialization response containing server info,
|
|
516
|
+
capabilities, protocol version, and optional instructions.
|
|
517
|
+
|
|
518
|
+
Raises:
|
|
519
|
+
RuntimeError: If the client is not connected or initialization times out.
|
|
520
|
+
|
|
521
|
+
Example:
|
|
522
|
+
```python
|
|
523
|
+
# With auto-initialization disabled
|
|
524
|
+
client = Client(server, auto_initialize=False)
|
|
525
|
+
async with client:
|
|
526
|
+
result = await client.initialize()
|
|
527
|
+
print(f"Server: {result.serverInfo.name}")
|
|
528
|
+
print(f"Instructions: {result.instructions}")
|
|
529
|
+
```
|
|
530
|
+
"""
|
|
531
|
+
|
|
532
|
+
if self.initialize_result is not None:
|
|
533
|
+
return self.initialize_result
|
|
534
|
+
|
|
535
|
+
if timeout is None:
|
|
536
|
+
timeout = self._init_timeout
|
|
537
|
+
try:
|
|
538
|
+
with anyio.fail_after(_timeout_to_seconds(timeout)):
|
|
539
|
+
initialize_result = await self.session.initialize()
|
|
540
|
+
self._session_state.initialize_result = initialize_result
|
|
541
|
+
return initialize_result
|
|
542
|
+
except TimeoutError as e:
|
|
543
|
+
raise RuntimeError("Failed to initialize server session") from e
|
|
544
|
+
|
|
496
545
|
async def ping(self) -> bool:
|
|
497
546
|
"""Send a ping request."""
|
|
498
547
|
result = await self.session.send_ping()
|
|
@@ -831,6 +880,7 @@ class Client(Generic[ClientTransportT]):
|
|
|
831
880
|
arguments: dict[str, Any],
|
|
832
881
|
progress_handler: ProgressHandler | None = None,
|
|
833
882
|
timeout: datetime.timedelta | float | int | None = None,
|
|
883
|
+
meta: dict[str, Any] | None = None,
|
|
834
884
|
) -> mcp.types.CallToolResult:
|
|
835
885
|
"""Send a tools/call request and return the complete MCP protocol result.
|
|
836
886
|
|
|
@@ -842,6 +892,10 @@ class Client(Generic[ClientTransportT]):
|
|
|
842
892
|
arguments (dict[str, Any]): Arguments to pass to the tool.
|
|
843
893
|
timeout (datetime.timedelta | float | int | None, optional): The timeout for the tool call. Defaults to None.
|
|
844
894
|
progress_handler (ProgressHandler | None, optional): The progress handler to use for the tool call. Defaults to None.
|
|
895
|
+
meta (dict[str, Any] | None, optional): Additional metadata to include with the request.
|
|
896
|
+
This is useful for passing contextual information (like user IDs, trace IDs, or preferences)
|
|
897
|
+
that shouldn't be tool arguments but may influence server-side processing. The server
|
|
898
|
+
can access this via `context.request_context.meta`. Defaults to None.
|
|
845
899
|
|
|
846
900
|
Returns:
|
|
847
901
|
mcp.types.CallToolResult: The complete response object from the protocol,
|
|
@@ -852,13 +906,16 @@ class Client(Generic[ClientTransportT]):
|
|
|
852
906
|
"""
|
|
853
907
|
logger.debug(f"[{self.name}] called call_tool: {name}")
|
|
854
908
|
|
|
909
|
+
# Convert timeout to timedelta if needed
|
|
855
910
|
if isinstance(timeout, int | float):
|
|
856
911
|
timeout = datetime.timedelta(seconds=float(timeout))
|
|
912
|
+
|
|
857
913
|
result = await self.session.call_tool(
|
|
858
914
|
name=name,
|
|
859
915
|
arguments=arguments,
|
|
860
|
-
read_timeout_seconds=timeout,
|
|
916
|
+
read_timeout_seconds=timeout, # ty: ignore[invalid-argument-type]
|
|
861
917
|
progress_callback=progress_handler or self._progress_handler,
|
|
918
|
+
meta=meta,
|
|
862
919
|
)
|
|
863
920
|
return result
|
|
864
921
|
|
|
@@ -869,6 +926,7 @@ class Client(Generic[ClientTransportT]):
|
|
|
869
926
|
timeout: datetime.timedelta | float | int | None = None,
|
|
870
927
|
progress_handler: ProgressHandler | None = None,
|
|
871
928
|
raise_on_error: bool = True,
|
|
929
|
+
meta: dict[str, Any] | None = None,
|
|
872
930
|
) -> CallToolResult:
|
|
873
931
|
"""Call a tool on the server.
|
|
874
932
|
|
|
@@ -879,6 +937,11 @@ class Client(Generic[ClientTransportT]):
|
|
|
879
937
|
arguments (dict[str, Any] | None, optional): Arguments to pass to the tool. Defaults to None.
|
|
880
938
|
timeout (datetime.timedelta | float | int | None, optional): The timeout for the tool call. Defaults to None.
|
|
881
939
|
progress_handler (ProgressHandler | None, optional): The progress handler to use for the tool call. Defaults to None.
|
|
940
|
+
raise_on_error (bool, optional): Whether to raise a ToolError if the tool call results in an error. Defaults to True.
|
|
941
|
+
meta (dict[str, Any] | None, optional): Additional metadata to include with the request.
|
|
942
|
+
This is useful for passing contextual information (like user IDs, trace IDs, or preferences)
|
|
943
|
+
that shouldn't be tool arguments but may influence server-side processing. The server
|
|
944
|
+
can access this via `context.request_context.meta`. Defaults to None.
|
|
882
945
|
|
|
883
946
|
Returns:
|
|
884
947
|
CallToolResult:
|
|
@@ -898,6 +961,7 @@ class Client(Generic[ClientTransportT]):
|
|
|
898
961
|
arguments=arguments or {},
|
|
899
962
|
timeout=timeout,
|
|
900
963
|
progress_handler=progress_handler,
|
|
964
|
+
meta=meta,
|
|
901
965
|
)
|
|
902
966
|
data = None
|
|
903
967
|
if result.isError and raise_on_error:
|
|
@@ -928,6 +992,7 @@ class Client(Generic[ClientTransportT]):
|
|
|
928
992
|
return CallToolResult(
|
|
929
993
|
content=result.content,
|
|
930
994
|
structured_content=result.structuredContent,
|
|
995
|
+
meta=result.meta,
|
|
931
996
|
data=data,
|
|
932
997
|
is_error=result.isError,
|
|
933
998
|
)
|
|
@@ -945,5 +1010,6 @@ class Client(Generic[ClientTransportT]):
|
|
|
945
1010
|
class CallToolResult:
|
|
946
1011
|
content: list[mcp.types.ContentBlock]
|
|
947
1012
|
structured_content: dict[str, Any] | None
|
|
1013
|
+
meta: dict[str, Any] | None
|
|
948
1014
|
data: Any = None
|
|
949
1015
|
is_error: bool = False
|
fastmcp/client/logging.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from collections.abc import Awaitable, Callable
|
|
2
|
+
from logging import Logger
|
|
2
3
|
from typing import TypeAlias
|
|
3
4
|
|
|
4
5
|
from mcp.client.session import LoggingFnT
|
|
@@ -6,7 +7,8 @@ from mcp.types import LoggingMessageNotificationParams
|
|
|
6
7
|
|
|
7
8
|
from fastmcp.utilities.logging import get_logger
|
|
8
9
|
|
|
9
|
-
logger = get_logger(__name__)
|
|
10
|
+
logger: Logger = get_logger(name=__name__)
|
|
11
|
+
from_server_logger: Logger = get_logger(name="fastmcp.client.from_server")
|
|
10
12
|
|
|
11
13
|
LogMessage: TypeAlias = LoggingMessageNotificationParams
|
|
12
14
|
LogHandler: TypeAlias = Callable[[LogMessage], Awaitable[None]]
|
|
@@ -14,30 +16,32 @@ LogHandler: TypeAlias = Callable[[LogMessage], Awaitable[None]]
|
|
|
14
16
|
|
|
15
17
|
async def default_log_handler(message: LogMessage) -> None:
|
|
16
18
|
"""Default handler that properly routes server log messages to appropriate log levels."""
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
# data can be any JSON-serializable type, not just a dict
|
|
20
|
+
data = message.data
|
|
19
21
|
|
|
20
22
|
# Map MCP log levels to Python logging levels
|
|
21
23
|
level_map = {
|
|
22
|
-
"debug":
|
|
23
|
-
"info":
|
|
24
|
-
"notice":
|
|
25
|
-
"warning":
|
|
26
|
-
"error":
|
|
27
|
-
"critical":
|
|
28
|
-
"alert":
|
|
29
|
-
"emergency":
|
|
24
|
+
"debug": from_server_logger.debug,
|
|
25
|
+
"info": from_server_logger.info,
|
|
26
|
+
"notice": from_server_logger.info, # Python doesn't have 'notice', map to info
|
|
27
|
+
"warning": from_server_logger.warning,
|
|
28
|
+
"error": from_server_logger.error,
|
|
29
|
+
"critical": from_server_logger.critical,
|
|
30
|
+
"alert": from_server_logger.critical, # Map alert to critical
|
|
31
|
+
"emergency": from_server_logger.critical, # Map emergency to critical
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
# Get the appropriate logging function based on the message level
|
|
33
35
|
log_fn = level_map.get(message.level.lower(), logger.info)
|
|
34
36
|
|
|
35
37
|
# Include logger name if available
|
|
38
|
+
msg_prefix: str = f"Received {message.level.upper()} from server"
|
|
39
|
+
|
|
36
40
|
if message.logger:
|
|
37
|
-
|
|
41
|
+
msg_prefix += f" ({message.logger})"
|
|
38
42
|
|
|
39
|
-
# Log with appropriate level and
|
|
40
|
-
log_fn(f"
|
|
43
|
+
# Log with appropriate level and data
|
|
44
|
+
log_fn(msg=f"{msg_prefix}: {data}")
|
|
41
45
|
|
|
42
46
|
|
|
43
47
|
def create_log_callback(handler: LogHandler | None = None) -> LoggingFnT:
|