fastmcp 2.2.5__py3-none-any.whl → 2.2.6__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 CHANGED
@@ -14,6 +14,7 @@ __all__ = [
14
14
  "FastMCP",
15
15
  "Context",
16
16
  "client",
17
+ "Client",
17
18
  "settings",
18
19
  "Image",
19
20
  ]
fastmcp/client/base.py CHANGED
@@ -1 +0,0 @@
1
-
fastmcp/client/client.py CHANGED
@@ -5,12 +5,9 @@ from typing import Any, Literal, cast, overload
5
5
 
6
6
  import mcp.types
7
7
  from mcp import ClientSession
8
- from mcp.client.session import (
9
- LoggingFnT,
10
- MessageHandlerFnT,
11
- )
12
8
  from pydantic import AnyUrl
13
9
 
10
+ from fastmcp.client.logging import LogHandler, MessageHandler
14
11
  from fastmcp.client.roots import (
15
12
  RootsHandler,
16
13
  RootsList,
@@ -22,7 +19,14 @@ from fastmcp.server import FastMCP
22
19
 
23
20
  from .transports import ClientTransport, SessionKwargs, infer_transport
24
21
 
25
- __all__ = ["Client", "RootsHandler", "RootsList"]
22
+ __all__ = [
23
+ "Client",
24
+ "RootsHandler",
25
+ "RootsList",
26
+ "LogHandler",
27
+ "MessageHandler",
28
+ "SamplingHandler",
29
+ ]
26
30
 
27
31
 
28
32
  class Client:
@@ -35,12 +39,12 @@ class Client:
35
39
 
36
40
  def __init__(
37
41
  self,
38
- transport: ClientTransport | FastMCP | AnyUrl | Path | str,
42
+ transport: ClientTransport | FastMCP | AnyUrl | Path | dict[str, Any] | str,
39
43
  # Common args
40
44
  roots: RootsList | RootsHandler | None = None,
41
45
  sampling_handler: SamplingHandler | None = None,
42
- log_handler: LoggingFnT | None = None,
43
- message_handler: MessageHandlerFnT | None = None,
46
+ log_handler: LogHandler | None = None,
47
+ message_handler: MessageHandler | None = None,
44
48
  read_timeout_seconds: datetime.timedelta | None = None,
45
49
  ):
46
50
  self.transport = infer_transport(transport)
@@ -0,0 +1,13 @@
1
+ from typing import TypeAlias
2
+
3
+ from mcp.client.session import (
4
+ LoggingFnT,
5
+ MessageHandlerFnT,
6
+ )
7
+ from mcp.types import LoggingMessageNotificationParams
8
+
9
+ LogMessage: TypeAlias = LoggingMessageNotificationParams
10
+ LogHandler: TypeAlias = LoggingFnT
11
+ MessageHandler: TypeAlias = MessageHandlerFnT
12
+
13
+ __all__ = ["LogMessage", "LogHandler", "MessageHandler"]
@@ -9,6 +9,8 @@ from mcp.shared.context import LifespanContextT, RequestContext
9
9
  from mcp.types import CreateMessageRequestParams as SamplingParams
10
10
  from mcp.types import SamplingMessage
11
11
 
12
+ __all__ = ["SamplingMessage", "SamplingParams", "MessageResult", "SamplingHandler"]
13
+
12
14
 
13
15
  class MessageResult(CreateMessageResult):
14
16
  role: mcp.types.Role = "assistant"
@@ -6,9 +6,7 @@ import shutil
6
6
  import sys
7
7
  from collections.abc import AsyncIterator
8
8
  from pathlib import Path
9
- from typing import (
10
- TypedDict,
11
- )
9
+ from typing import Any, TypedDict
12
10
 
13
11
  from exceptiongroup import BaseExceptionGroup, catch
14
12
  from mcp import ClientSession, McpError, StdioServerParameters
@@ -416,7 +414,7 @@ class FastMCPTransport(ClientTransport):
416
414
 
417
415
 
418
416
  def infer_transport(
419
- transport: ClientTransport | FastMCPServer | AnyUrl | Path | str,
417
+ transport: ClientTransport | FastMCPServer | AnyUrl | Path | dict[str, Any] | str,
420
418
  ) -> ClientTransport:
421
419
  """
422
420
  Infer the appropriate transport type from the given transport argument.
@@ -450,6 +448,41 @@ def infer_transport(
450
448
  elif isinstance(transport, AnyUrl | str) and str(transport).startswith("ws"):
451
449
  return WSTransport(url=transport)
452
450
 
451
+ ## if the transport is a config dict
452
+ elif isinstance(transport, dict):
453
+ if "mcpServers" not in transport:
454
+ raise ValueError("Invalid transport dictionary: missing 'mcpServers' key")
455
+ else:
456
+ server = transport["mcpServers"]
457
+ if len(list(server.keys())) > 1:
458
+ raise ValueError(
459
+ "Invalid transport dictionary: multiple servers found - only one expected"
460
+ )
461
+ server_name = list(server.keys())[0]
462
+ # Stdio transport
463
+ if "command" in server[server_name] and "args" in server[server_name]:
464
+ return StdioTransport(
465
+ command=server[server_name]["command"],
466
+ args=server[server_name]["args"],
467
+ env=server[server_name].get("env", None),
468
+ cwd=server[server_name].get("cwd", None),
469
+ )
470
+
471
+ # HTTP transport
472
+ elif "url" in server:
473
+ return SSETransport(
474
+ url=server["url"],
475
+ headers=server.get("headers", None),
476
+ )
477
+
478
+ # WebSocket transport
479
+ elif "ws_url" in server:
480
+ return WSTransport(
481
+ url=server["ws_url"],
482
+ )
483
+
484
+ raise ValueError("Cannot determine transport type from dictionary")
485
+
453
486
  # the transport is an unknown type
454
487
  else:
455
488
  raise ValueError(f"Could not infer a valid transport from: {transport}")
fastmcp/server/context.py CHANGED
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations as _annotations
2
2
 
3
- from typing import Any, Generic, Literal
3
+ from typing import Any, Generic
4
4
 
5
+ from mcp import LoggingLevel
5
6
  from mcp.server.lowlevel.helper_types import ReadResourceContents
6
7
  from mcp.server.session import ServerSessionT
7
8
  from mcp.shared.context import LifespanContextT, RequestContext
@@ -122,19 +123,20 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT]):
122
123
 
123
124
  async def log(
124
125
  self,
125
- level: Literal["debug", "info", "warning", "error"],
126
126
  message: str,
127
- *,
127
+ level: LoggingLevel | None = None,
128
128
  logger_name: str | None = None,
129
129
  ) -> None:
130
130
  """Send a log message to the client.
131
131
 
132
132
  Args:
133
- level: Log level (debug, info, warning, error)
134
133
  message: Log message
134
+ level: Optional log level. One of "debug", "info", "notice", "warning", "error", "critical",
135
+ "alert", or "emergency". Default is "info".
135
136
  logger_name: Optional logger name
136
- **extra: Additional structured data to include
137
137
  """
138
+ if level is None:
139
+ level = "info"
138
140
  await self.request_context.session.send_log_message(
139
141
  level=level, data=message, logger=logger_name
140
142
  )
@@ -159,21 +161,21 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT]):
159
161
  return self.request_context.session
160
162
 
161
163
  # Convenience methods for common log levels
162
- async def debug(self, message: str, **extra: Any) -> None:
164
+ async def debug(self, message: str, logger_name: str | None = None) -> None:
163
165
  """Send a debug log message."""
164
- await self.log("debug", message, **extra)
166
+ await self.log(level="debug", message=message, logger_name=logger_name)
165
167
 
166
- async def info(self, message: str, **extra: Any) -> None:
168
+ async def info(self, message: str, logger_name: str | None = None) -> None:
167
169
  """Send an info log message."""
168
- await self.log("info", message, **extra)
170
+ await self.log(level="info", message=message, logger_name=logger_name)
169
171
 
170
- async def warning(self, message: str, **extra: Any) -> None:
172
+ async def warning(self, message: str, logger_name: str | None = None) -> None:
171
173
  """Send a warning log message."""
172
- await self.log("warning", message, **extra)
174
+ await self.log(level="warning", message=message, logger_name=logger_name)
173
175
 
174
- async def error(self, message: str, **extra: Any) -> None:
176
+ async def error(self, message: str, logger_name: str | None = None) -> None:
175
177
  """Send an error log message."""
176
- await self.log("error", message, **extra)
178
+ await self.log(level="error", message=message, logger_name=logger_name)
177
179
 
178
180
  async def list_roots(self) -> list[Root]:
179
181
  """List the roots available to the server, as indicated by the client."""
fastmcp/server/openapi.py CHANGED
@@ -10,12 +10,12 @@ from re import Pattern
10
10
  from typing import TYPE_CHECKING, Any, Literal
11
11
 
12
12
  import httpx
13
- from mcp.types import TextContent
13
+ from mcp.types import EmbeddedResource, ImageContent, TextContent
14
14
  from pydantic.networks import AnyUrl
15
15
 
16
16
  from fastmcp.resources import Resource, ResourceTemplate
17
17
  from fastmcp.server.server import FastMCP
18
- from fastmcp.tools.tool import Tool
18
+ from fastmcp.tools.tool import Tool, _convert_to_content
19
19
  from fastmcp.utilities import openapi
20
20
  from fastmcp.utilities.func_metadata import func_metadata
21
21
  from fastmcp.utilities.logging import get_logger
@@ -125,6 +125,7 @@ class OpenAPITool(Tool):
125
125
  fn_metadata: Any,
126
126
  is_async: bool = True,
127
127
  tags: set[str] = set(),
128
+ timeout: float | None = None,
128
129
  ):
129
130
  super().__init__(
130
131
  name=name,
@@ -138,6 +139,7 @@ class OpenAPITool(Tool):
138
139
  )
139
140
  self._client = client
140
141
  self._route = route
142
+ self._timeout = timeout
141
143
 
142
144
  async def _execute_request(self, *args, **kwargs):
143
145
  """Execute the HTTP request based on the route configuration."""
@@ -147,19 +149,37 @@ class OpenAPITool(Tool):
147
149
  path = self._route.path
148
150
 
149
151
  # Replace path parameters with values from kwargs
152
+ # Path parameters should never be None as they're typically required
153
+ # but we'll handle that case anyway
150
154
  path_params = {
151
155
  p.name: kwargs.get(p.name)
152
156
  for p in self._route.parameters
153
157
  if p.location == "path"
158
+ and p.name in kwargs
159
+ and kwargs.get(p.name) is not None
154
160
  }
161
+
162
+ # Ensure all path parameters are provided
163
+ required_path_params = {
164
+ p.name
165
+ for p in self._route.parameters
166
+ if p.location == "path" and p.required
167
+ }
168
+ missing_params = required_path_params - path_params.keys()
169
+ if missing_params:
170
+ raise ValueError(f"Missing required path parameters: {missing_params}")
171
+
155
172
  for param_name, param_value in path_params.items():
156
173
  path = path.replace(f"{{{param_name}}}", str(param_value))
157
174
 
158
- # Prepare query parameters
175
+ # Prepare query parameters - filter out None and empty strings
159
176
  query_params = {
160
177
  p.name: kwargs.get(p.name)
161
178
  for p in self._route.parameters
162
- if p.location == "query" and p.name in kwargs
179
+ if p.location == "query"
180
+ and p.name in kwargs
181
+ and kwargs.get(p.name) is not None
182
+ and kwargs.get(p.name) != ""
163
183
  }
164
184
 
165
185
  # Prepare headers - fix typing by ensuring all values are strings
@@ -206,7 +226,7 @@ class OpenAPITool(Tool):
206
226
  params=query_params,
207
227
  headers=headers,
208
228
  json=json_data,
209
- timeout=30.0, # Default timeout
229
+ timeout=self._timeout,
210
230
  )
211
231
 
212
232
  # Raise for 4xx/5xx responses
@@ -237,9 +257,14 @@ class OpenAPITool(Tool):
237
257
  # Handle request errors (connection, timeout, etc.)
238
258
  raise ValueError(f"Request error: {str(e)}")
239
259
 
240
- async def run(self, arguments: dict[str, Any], context: Any = None) -> Any:
260
+ async def run(
261
+ self,
262
+ arguments: dict[str, Any],
263
+ context: Context[ServerSessionT, LifespanContextT] | None = None,
264
+ ) -> list[TextContent | ImageContent | EmbeddedResource]:
241
265
  """Run the tool with arguments and optional context."""
242
- return await self._execute_request(**arguments, context=context)
266
+ response = await self._execute_request(**arguments, context=context)
267
+ return _convert_to_content(response)
243
268
 
244
269
 
245
270
  class OpenAPIResource(Resource):
@@ -254,6 +279,7 @@ class OpenAPIResource(Resource):
254
279
  description: str,
255
280
  mime_type: str = "application/json",
256
281
  tags: set[str] = set(),
282
+ timeout: float | None = None,
257
283
  ):
258
284
  super().__init__(
259
285
  uri=AnyUrl(uri), # Convert string to AnyUrl
@@ -264,6 +290,7 @@ class OpenAPIResource(Resource):
264
290
  )
265
291
  self._client = client
266
292
  self._route = route
293
+ self._timeout = timeout
267
294
 
268
295
  async def read(
269
296
  self, context: Context[ServerSessionT, LifespanContextT] | None = None
@@ -278,30 +305,44 @@ class OpenAPIResource(Resource):
278
305
  if "{" in path and "}" in path:
279
306
  # Extract the resource ID from the URI (the last part after the last slash)
280
307
  parts = resource_uri.split("/")
308
+
281
309
  if len(parts) > 1:
282
310
  # Find all path parameters in the route path
283
311
  path_params = {}
284
312
 
285
- # Extract parameters from the URI
286
- param_value = parts[
287
- -1
288
- ] # The last part contains the parameter value
289
-
290
- # Find the path parameter name from the route path
313
+ # Find the path parameter names from the route path
291
314
  param_matches = re.findall(r"\{([^}]+)\}", path)
292
315
  if param_matches:
293
- # Assume the last parameter in the URI is for the first path parameter in the route
294
- path_param_name = param_matches[0]
295
- path_params[path_param_name] = param_value
316
+ # Reverse sorting from creation order (traversal is backwards)
317
+ param_matches.sort(reverse=True)
318
+ # Number of sent parameters is number of parts -1 (assuming first part is resource identifier)
319
+ expected_param_count = len(parts) - 1
320
+ # Map parameters from the end of the URI to the parameters in the path
321
+ # Last parameter in URI (parts[-1]) maps to last parameter in path, and so on
322
+ for i, param_name in enumerate(param_matches):
323
+ # Ensure we don't use resource identifier as parameter
324
+ if i < expected_param_count:
325
+ # Get values from the end of parts
326
+ param_value = parts[-1 - i]
327
+ path_params[param_name] = param_value
296
328
 
297
329
  # Replace path parameters with their values
298
330
  for param_name, param_value in path_params.items():
299
331
  path = path.replace(f"{{{param_name}}}", str(param_value))
300
332
 
333
+ # Filter any query parameters - get query parameters and filter out None/empty values
334
+ query_params = {}
335
+ for param in self._route.parameters:
336
+ if param.location == "query" and hasattr(self, f"_{param.name}"):
337
+ value = getattr(self, f"_{param.name}")
338
+ if value is not None and value != "":
339
+ query_params[param.name] = value
340
+
301
341
  response = await self._client.request(
302
342
  method=self._route.method,
303
343
  url=path,
304
- timeout=30.0, # Default timeout
344
+ params=query_params,
345
+ timeout=self._timeout,
305
346
  )
306
347
 
307
348
  # Raise for 4xx/5xx responses
@@ -349,6 +390,7 @@ class OpenAPIResourceTemplate(ResourceTemplate):
349
390
  description: str,
350
391
  parameters: dict[str, Any],
351
392
  tags: set[str] = set(),
393
+ timeout: float | None = None,
352
394
  ):
353
395
  super().__init__(
354
396
  uri_template=uri_template,
@@ -361,6 +403,7 @@ class OpenAPIResourceTemplate(ResourceTemplate):
361
403
  )
362
404
  self._client = client
363
405
  self._route = route
406
+ self._timeout = timeout
364
407
 
365
408
  async def create_resource(
366
409
  self,
@@ -383,6 +426,7 @@ class OpenAPIResourceTemplate(ResourceTemplate):
383
426
  description=self.description or f"Resource for {self._route.path}",
384
427
  mime_type="application/json",
385
428
  tags=set(self._route.tags or []),
429
+ timeout=self._timeout,
386
430
  )
387
431
 
388
432
 
@@ -430,6 +474,7 @@ class FastMCPOpenAPI(FastMCP):
430
474
  client: httpx.AsyncClient,
431
475
  name: str | None = None,
432
476
  route_maps: list[RouteMap] | None = None,
477
+ timeout: float | None = None,
433
478
  **settings: Any,
434
479
  ):
435
480
  """
@@ -440,13 +485,13 @@ class FastMCPOpenAPI(FastMCP):
440
485
  client: httpx AsyncClient for making HTTP requests
441
486
  name: Optional name for the server
442
487
  route_maps: Optional list of RouteMap objects defining route mappings
443
- default_mime_type: Default MIME type for resources
488
+ timeout: Optional timeout (in seconds) for all requests
444
489
  **settings: Additional settings for FastMCP
445
490
  """
446
491
  super().__init__(name=name or "OpenAPI FastMCP", **settings)
447
492
 
448
493
  self._client = client
449
-
494
+ self._timeout = timeout
450
495
  http_routes = openapi.parse_openapi_to_http_routes(openapi_spec)
451
496
 
452
497
  # Process routes
@@ -504,6 +549,7 @@ class FastMCPOpenAPI(FastMCP):
504
549
  fn_metadata=func_metadata(_openapi_passthrough),
505
550
  is_async=True,
506
551
  tags=set(route.tags or []),
552
+ timeout=self._timeout,
507
553
  )
508
554
  # Register the tool by directly assigning to the tools dictionary
509
555
  self._tool_manager._tools[tool_name] = tool
@@ -532,6 +578,7 @@ class FastMCPOpenAPI(FastMCP):
532
578
  name=resource_name,
533
579
  description=enhanced_description,
534
580
  tags=set(route.tags or []),
581
+ timeout=self._timeout,
535
582
  )
536
583
  # Register the resource by directly assigning to the resources dictionary
537
584
  self._resource_manager._resources[str(resource.uri)] = resource
@@ -577,6 +624,7 @@ class FastMCPOpenAPI(FastMCP):
577
624
  description=enhanced_description,
578
625
  parameters=template_params_schema,
579
626
  tags=set(route.tags or []),
627
+ timeout=self._timeout,
580
628
  )
581
629
  # Register the template by directly assigning to the templates dictionary
582
630
  self._resource_manager._templates[uri_template_str] = template
@@ -589,13 +637,4 @@ class FastMCPOpenAPI(FastMCP):
589
637
 
590
638
  context = self.get_context()
591
639
  result = await self._tool_manager.call_tool(name, arguments, context=context)
592
-
593
- # For other tools, ensure the response is wrapped in TextContent
594
- if isinstance(result, dict | str):
595
- if isinstance(result, dict):
596
- result_text = json.dumps(result)
597
- else:
598
- result_text = result
599
- return [TextContent(text=result_text, type="text")]
600
-
601
640
  return result
fastmcp/server/proxy.py CHANGED
@@ -61,7 +61,7 @@ class ProxyTool(Tool):
61
61
  self,
62
62
  arguments: dict[str, Any],
63
63
  context: Context[ServerSessionT, LifespanContextT] | None = None,
64
- ) -> Any:
64
+ ) -> list[TextContent | ImageContent | EmbeddedResource]:
65
65
  # the client context manager will swallow any exceptions inside a TaskGroup
66
66
  # so we return the raw result and raise an exception ourselves
67
67
  async with self._client:
fastmcp/server/server.py CHANGED
@@ -1,5 +1,7 @@
1
1
  """FastMCP - A more ergonomic interface for MCP servers."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import datetime
4
6
  from collections.abc import AsyncIterator, Awaitable, Callable
5
7
  from contextlib import (
@@ -63,7 +65,7 @@ class MountedServer:
63
65
  def __init__(
64
66
  self,
65
67
  prefix: str,
66
- server: "FastMCP",
68
+ server: FastMCP,
67
69
  tool_separator: str | None = None,
68
70
  resource_separator: str | None = None,
69
71
  prompt_separator: str | None = None,
@@ -149,7 +151,7 @@ class TimedCache:
149
151
 
150
152
 
151
153
  @asynccontextmanager
152
- async def default_lifespan(server: "FastMCP") -> AsyncIterator[Any]:
154
+ async def default_lifespan(server: FastMCP) -> AsyncIterator[Any]:
153
155
  """Default lifespan context manager that does nothing.
154
156
 
155
157
  Args:
@@ -162,8 +164,8 @@ async def default_lifespan(server: "FastMCP") -> AsyncIterator[Any]:
162
164
 
163
165
 
164
166
  def _lifespan_wrapper(
165
- app: "FastMCP",
166
- lifespan: Callable[["FastMCP"], AbstractAsyncContextManager[LifespanResultT]],
167
+ app: FastMCP,
168
+ lifespan: Callable[[FastMCP], AbstractAsyncContextManager[LifespanResultT]],
167
169
  ) -> Callable[
168
170
  [MCPServer[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]
169
171
  ]:
@@ -182,7 +184,11 @@ class FastMCP(Generic[LifespanResultT]):
182
184
  name: str | None = None,
183
185
  instructions: str | None = None,
184
186
  lifespan: (
185
- Callable[["FastMCP"], AbstractAsyncContextManager[LifespanResultT]] | None
187
+ Callable[
188
+ [FastMCP[LifespanResultT]],
189
+ AbstractAsyncContextManager[LifespanResultT],
190
+ ]
191
+ | None
186
192
  ) = None,
187
193
  tags: set[str] | None = None,
188
194
  **settings: Any,
@@ -273,7 +279,7 @@ class FastMCP(Generic[LifespanResultT]):
273
279
  self._mcp_server.get_prompt()(self._mcp_get_prompt)
274
280
  self._mcp_server.list_resource_templates()(self._mcp_list_resource_templates)
275
281
 
276
- def get_context(self) -> "Context[ServerSession, LifespanResultT]":
282
+ def get_context(self) -> Context[ServerSession, LifespanResultT]:
277
283
  """
278
284
  Returns a Context object. Note that the context will only be valid
279
285
  during a request; outside a request, most methods will error.
@@ -766,7 +772,7 @@ class FastMCP(Generic[LifespanResultT]):
766
772
  def mount(
767
773
  self,
768
774
  prefix: str,
769
- server: "FastMCP",
775
+ server: FastMCP[LifespanResultT],
770
776
  tool_separator: str | None = None,
771
777
  resource_separator: str | None = None,
772
778
  prompt_separator: str | None = None,
@@ -791,7 +797,7 @@ class FastMCP(Generic[LifespanResultT]):
791
797
  async def import_server(
792
798
  self,
793
799
  prefix: str,
794
- server: "FastMCP",
800
+ server: FastMCP[LifespanResultT],
795
801
  tool_separator: str | None = None,
796
802
  resource_separator: str | None = None,
797
803
  prompt_separator: str | None = None,
@@ -865,7 +871,7 @@ class FastMCP(Generic[LifespanResultT]):
865
871
  @classmethod
866
872
  def from_openapi(
867
873
  cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, **settings: Any
868
- ) -> "FastMCPOpenAPI":
874
+ ) -> FastMCPOpenAPI:
869
875
  """
870
876
  Create a FastMCP server from an OpenAPI specification.
871
877
  """
@@ -875,8 +881,8 @@ class FastMCP(Generic[LifespanResultT]):
875
881
 
876
882
  @classmethod
877
883
  def from_fastapi(
878
- cls, app: "Any", name: str | None = None, **settings: Any
879
- ) -> "FastMCPOpenAPI":
884
+ cls, app: Any, name: str | None = None, **settings: Any
885
+ ) -> FastMCPOpenAPI:
880
886
  """
881
887
  Create a FastMCP server from a FastAPI application.
882
888
  """
@@ -894,7 +900,7 @@ class FastMCP(Generic[LifespanResultT]):
894
900
  )
895
901
 
896
902
  @classmethod
897
- def from_client(cls, client: "Client", **settings: Any) -> "FastMCPProxy":
903
+ def from_client(cls, client: Client, **settings: Any) -> FastMCPProxy:
898
904
  """
899
905
  Create a FastMCP proxy server from a FastMCP client.
900
906
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastmcp
3
- Version: 2.2.5
3
+ Version: 2.2.6
4
4
  Summary: The fast, Pythonic way to build MCP servers.
5
5
  Project-URL: Homepage, https://gofastmcp.com
6
6
  Project-URL: Repository, https://github.com/jlowin/fastmcp
@@ -24,7 +24,7 @@ Requires-Dist: openapi-pydantic>=0.5.1
24
24
  Requires-Dist: python-dotenv>=1.1.0
25
25
  Requires-Dist: rich>=13.9.4
26
26
  Requires-Dist: typer>=0.15.2
27
- Requires-Dist: websockets>=15.0.1
27
+ Requires-Dist: websockets>=14.0
28
28
  Description-Content-Type: text/markdown
29
29
 
30
30
  <div align="center">
@@ -1,4 +1,4 @@
1
- fastmcp/__init__.py,sha256=2bwhjiyLJisyobp1O9tVYMjriHZAx_f4bIKJYOL-Rpk,399
1
+ fastmcp/__init__.py,sha256=e-wu5UQpgduOauB-H8lzbnxv_H9K90fCJVnc1qgaAhM,413
2
2
  fastmcp/exceptions.py,sha256=QKVHbftoZp4YZQ2NxA-t1SjztqspFdX95YTFOAmr5EE,640
3
3
  fastmcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  fastmcp/settings.py,sha256=VCjc-3011pKRYjt2h9rZ68XhVEekbpyLyVUREVBTSrg,1955
@@ -6,11 +6,12 @@ fastmcp/cli/__init__.py,sha256=Ii284TNoG5lxTP40ETMGhHEq3lQZWxu9m9JuU57kUpQ,87
6
6
  fastmcp/cli/claude.py,sha256=IAlcZ4qZKBBj09jZUMEx7EANZE_IR3vcu7zOBJmMOuU,4567
7
7
  fastmcp/cli/cli.py,sha256=wsFIYTv48_nr0mcW8vjI1l7_thr4cOrRxzo9g-qr2l8,15726
8
8
  fastmcp/client/__init__.py,sha256=BXO9NUhntZ5GnUACfaRCzDJ5IzxqFJs8qKG-CRMSco4,490
9
- fastmcp/client/base.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
10
- fastmcp/client/client.py,sha256=XXpN28epV9N5w-keQSDPBCdLFtZ5EPK2msxuQ6PxTmo,7732
9
+ fastmcp/client/base.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ fastmcp/client/client.py,sha256=cGVy066XGBtwl4Bwa_Prx-Vy5NMZHrDH8jxiq1oYuBo,7812
11
+ fastmcp/client/logging.py,sha256=Q8jYcZj4KA15Yiz3RP8tBXj8sd9IxL3VThF_Y0O4Upc,356
11
12
  fastmcp/client/roots.py,sha256=IxI_bHwHTmg6c2H-s1av1ZgrRnNDieHtYwdGFbzXT5c,2471
12
- fastmcp/client/sampling.py,sha256=WdRhIZbWv54rXYI8lWHv0thXmGCloZYPFpwJK9El_sQ,1613
13
- fastmcp/client/transports.py,sha256=7dJUQemdxj6UHNThizPzSJbHTGiJHlM77vLf4X9g11M,15491
13
+ fastmcp/client/sampling.py,sha256=UlDHxnd6k_HoU8RA3ob0g8-e6haJBc9u27N_v291QoI,1698
14
+ fastmcp/client/transports.py,sha256=FMMniyxnaaHyWeFHXXP_ayOpb4jtCoyjaiqLobgYb_M,16885
14
15
  fastmcp/contrib/README.md,sha256=rKknYSI1T192UvSszqwwDlQ2eYQpxywrNTLoj177SYU,878
15
16
  fastmcp/contrib/bulk_tool_caller/README.md,sha256=5aUUY1TSFKtz1pvTLSDqkUCkGkuqMfMZNsLeaNqEgAc,1960
16
17
  fastmcp/contrib/bulk_tool_caller/__init__.py,sha256=xvGSSaUXTQrc31erBoi1Gh7BikgOliETDiYVTP3rLxY,75
@@ -29,10 +30,10 @@ fastmcp/resources/resource_manager.py,sha256=yfQNCEUooZiQ8LNslnAzjQp4Vh-y1YOlFyG
29
30
  fastmcp/resources/template.py,sha256=oa85KiuTjh3C7aZvMwmO4fwbTi6IvwlQ3fxizJuv3dk,7261
30
31
  fastmcp/resources/types.py,sha256=c1z6BQSosgrlPQ3v67DuXCvDjCJMq9Xl45npEpyk0ik,7710
31
32
  fastmcp/server/__init__.py,sha256=pdkghG11VLMZiluQ-4_rl2JK1LMWmV003m9dDRUN8W4,92
32
- fastmcp/server/context.py,sha256=s1885AZRipKB3VltfaO3VEtMxGefKs8fdZByj-4tbNI,7120
33
- fastmcp/server/openapi.py,sha256=hFMOVe-bzudxP8SE-CqQhUWlUCVF5inGfMVL28HlqDs,21179
34
- fastmcp/server/proxy.py,sha256=xOufto2gIfLk2BZfjhpLdZOlKDlJk5Rn6hCP0pzvaCU,10110
35
- fastmcp/server/server.py,sha256=89RreIOw0siZmc6SlVlYWm6d6cFvjfVPY7mczXCNGFM,33032
33
+ fastmcp/server/context.py,sha256=_eaL_6QtlQC9yDvHYjntmMzG1aNQZSnli5bIIpCLIwc,7403
34
+ fastmcp/server/openapi.py,sha256=bMmBfgNGzkbweM5g_64NpqvJ6cPtzhSWrzgRvUqP3ec,23185
35
+ fastmcp/server/proxy.py,sha256=pOY6XRvU_GDVv6hd3BttG5W5D1bOtjs9u6iygUMHPsw,10158
36
+ fastmcp/server/server.py,sha256=vQDVo7M8JwltqbprsE7Bd_lTxlnXyrc5jQ4C1uQA1lA,33152
36
37
  fastmcp/tools/__init__.py,sha256=ocw-SFTtN6vQ8fgnlF8iNAOflRmh79xS1xdO0Bc3QPE,96
37
38
  fastmcp/tools/tool.py,sha256=k797XAeXdcUuBfeuvxkEy8xckXi7xSzQVgkzL876rBQ,6755
38
39
  fastmcp/tools/tool_manager.py,sha256=hClv7fwj0cQSSwW0i-Swt7xiVqR4T9LVmr1Tp704nW4,3283
@@ -42,8 +43,8 @@ fastmcp/utilities/func_metadata.py,sha256=iYXnx7MILOSL8mUQ6Rtq_6U7qA08OkoEN2APY8
42
43
  fastmcp/utilities/logging.py,sha256=zav8pnFxG_fvGJHUV2XpobmT9WVrmv1mlQBSCz-CPx4,1159
43
44
  fastmcp/utilities/openapi.py,sha256=PrH3usbTblaVC6jIH1UGiPEfgB2sSCLj33zA5dH7o_s,45193
44
45
  fastmcp/utilities/types.py,sha256=m2rPYMzO-ZFvvZ46N-1-Xqyw693K7yq9Z2xR4pVELyk,2091
45
- fastmcp-2.2.5.dist-info/METADATA,sha256=JKwmnn7QiN_KbNkC3ZyTpTEoUOTIDHPoCZFraw3eMR8,27769
46
- fastmcp-2.2.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
47
- fastmcp-2.2.5.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
48
- fastmcp-2.2.5.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
49
- fastmcp-2.2.5.dist-info/RECORD,,
46
+ fastmcp-2.2.6.dist-info/METADATA,sha256=gjgxlV6gTth_za73JXr1kzU9LgD9ELMoqAMX1vaaUDY,27767
47
+ fastmcp-2.2.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
48
+ fastmcp-2.2.6.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
49
+ fastmcp-2.2.6.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
50
+ fastmcp-2.2.6.dist-info/RECORD,,