fastmcp 2.10.1__py3-none-any.whl → 2.10.3__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.
@@ -9,7 +9,7 @@ from typing import Any, Literal
9
9
  from mcp.types import ToolAnnotations
10
10
  from pydantic import ConfigDict
11
11
 
12
- from fastmcp.tools.tool import ParsedFunction, Tool, ToolResult
12
+ from fastmcp.tools.tool import ParsedFunction, Tool, ToolResult, _convert_to_content
13
13
  from fastmcp.utilities.logging import get_logger
14
14
  from fastmcp.utilities.types import NotSet, NotSetT, get_cached_typeadapter
15
15
 
@@ -233,7 +233,6 @@ class TransformedTool(Tool):
233
233
  Returns:
234
234
  ToolResult object containing content and optional structured output.
235
235
  """
236
- from fastmcp.tools.tool import _convert_to_content
237
236
 
238
237
  # Fill in missing arguments with schema defaults to ensure
239
238
  # ArgTransform defaults take precedence over function defaults
@@ -274,7 +273,6 @@ class TransformedTool(Tool):
274
273
  if isinstance(result, ToolResult):
275
274
  if self.output_schema is None:
276
275
  # Check if this is from a custom function that returns ToolResult
277
- import inspect
278
276
 
279
277
  return_annotation = inspect.signature(self.fn).return_annotation
280
278
  if return_annotation is ToolResult:
@@ -298,7 +296,6 @@ class TransformedTool(Tool):
298
296
  return result
299
297
 
300
298
  # Otherwise convert to content and create ToolResult with proper structured content
301
- from fastmcp.tools.tool import _convert_to_content
302
299
 
303
300
  unstructured_result = _convert_to_content(
304
301
  result, serializer=self.serializer
@@ -433,8 +430,6 @@ class TransformedTool(Tool):
433
430
  final_output_schema = parsed_fn.output_schema
434
431
  if final_output_schema is None:
435
432
  # Check if function returns ToolResult - if so, don't fall back to parent
436
- import inspect
437
-
438
433
  return_annotation = inspect.signature(
439
434
  transform_fn
440
435
  ).return_annotation
@@ -553,6 +548,7 @@ class TransformedTool(Tool):
553
548
  """
554
549
 
555
550
  # Build transformed schema and mapping
551
+ parent_defs = parent_tool.parameters.get("$defs", {})
556
552
  parent_props = parent_tool.parameters.get("properties", {}).copy()
557
553
  parent_required = set(parent_tool.parameters.get("required", []))
558
554
 
@@ -608,6 +604,9 @@ class TransformedTool(Tool):
608
604
  "required": list(new_required),
609
605
  }
610
606
 
607
+ if parent_defs:
608
+ schema["$defs"] = parent_defs
609
+
611
610
  # Create forwarding function that closes over everything it needs
612
611
  async def _forward(**kwargs):
613
612
  # Validate arguments
@@ -0,0 +1,102 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib.metadata import version
4
+ from typing import TYPE_CHECKING, Any, Literal
5
+
6
+ from rich.console import Console, Group
7
+ from rich.panel import Panel
8
+ from rich.table import Table
9
+ from rich.text import Text
10
+
11
+ import fastmcp
12
+
13
+ if TYPE_CHECKING:
14
+ from fastmcp import FastMCP
15
+
16
+ LOGO_ASCII = r"""
17
+ _ __ ___ ______ __ __ _____________ ____ ____
18
+ _ __ ___ / ____/___ ______/ /_/ |/ / ____/ __ \ |___ \ / __ \
19
+ _ __ ___ / /_ / __ `/ ___/ __/ /|_/ / / / /_/ / ___/ / / / / /
20
+ _ __ ___ / __/ / /_/ (__ ) /_/ / / / /___/ ____/ / __/_/ /_/ /
21
+ _ __ ___ /_/ \__,_/____/\__/_/ /_/\____/_/ /_____(_)____/
22
+
23
+ """.lstrip("\n")
24
+
25
+
26
+ def log_server_banner(
27
+ server: FastMCP[Any],
28
+ transport: Literal["stdio", "http", "sse", "streamable-http"],
29
+ *,
30
+ host: str | None = None,
31
+ port: int | None = None,
32
+ path: str | None = None,
33
+ ) -> None:
34
+ """Creates and logs a formatted banner with server information and logo.
35
+
36
+ Args:
37
+ transport: The transport protocol being used
38
+ server_name: Optional server name to display
39
+ host: Host address (for HTTP transports)
40
+ port: Port number (for HTTP transports)
41
+ path: Server path (for HTTP transports)
42
+ """
43
+
44
+ # Create the logo text
45
+ logo_text = Text(LOGO_ASCII, style="bold green")
46
+
47
+ # Create the information table
48
+ info_table = Table.grid(padding=(0, 1))
49
+ info_table.add_column(style="bold", justify="center") # Emoji column
50
+ info_table.add_column(style="bold cyan", justify="left") # Label column
51
+ info_table.add_column(style="white", justify="left") # Value column
52
+
53
+ match transport:
54
+ case "http" | "streamable-http":
55
+ display_transport = "Streamable-HTTP"
56
+ case "sse":
57
+ display_transport = "SSE"
58
+ case "stdio":
59
+ display_transport = "STDIO"
60
+
61
+ info_table.add_row("🖥️", "Server name:", server.name)
62
+ info_table.add_row("📦", "Transport:", display_transport)
63
+
64
+ # Show connection info based on transport
65
+ if transport in ("http", "streamable-http", "sse"):
66
+ if host and port:
67
+ server_url = f"http://{host}:{port}"
68
+ if path:
69
+ server_url += f"/{path.lstrip('/')}"
70
+ info_table.add_row("🔗", "Server URL:", server_url)
71
+
72
+ # Add documentation link
73
+ info_table.add_row("", "", "")
74
+ info_table.add_row("📚", "Docs:", "https://gofastmcp.com")
75
+ info_table.add_row("🚀", "Deploy:", "https://fastmcp.cloud")
76
+
77
+ # Add version information with explicit style overrides
78
+ info_table.add_row("", "", "")
79
+ info_table.add_row(
80
+ "🏎️",
81
+ "FastMCP version:",
82
+ Text(fastmcp.__version__, style="dim white", no_wrap=True),
83
+ )
84
+ info_table.add_row(
85
+ "🤝",
86
+ "MCP version:",
87
+ Text(version("mcp"), style="dim white", no_wrap=True),
88
+ )
89
+ # Create panel with logo and information using Group
90
+ panel_content = Group(logo_text, "", info_table)
91
+
92
+ panel = Panel(
93
+ panel_content,
94
+ title="FastMCP 2.0",
95
+ title_align="left",
96
+ border_style="dim",
97
+ padding=(2, 5),
98
+ expand=False,
99
+ )
100
+
101
+ console = Console(stderr=True)
102
+ console.print(Group("\n", panel, "\n"))
@@ -67,13 +67,24 @@ def _prune_unused_defs(schema: dict) -> dict:
67
67
  walk(value, current_def=def_name)
68
68
 
69
69
  # Figure out what defs were referenced directly or recursively
70
- def def_is_referenced(def_name):
70
+ def def_is_referenced(def_name, parent_def_names: set[str] | None = None):
71
71
  if def_name in root_defs:
72
72
  return True
73
73
  references = referenced_by.get(def_name)
74
74
  if references:
75
- for reference in references:
76
- if def_is_referenced(reference):
75
+ if parent_def_names is None:
76
+ parent_def_names = set()
77
+
78
+ # Handle recursion by excluding references already present in parent references
79
+ parent_def_names = parent_def_names | {def_name}
80
+ valid_references = [
81
+ reference
82
+ for reference in references
83
+ if reference not in parent_def_names
84
+ ]
85
+
86
+ for reference in valid_references:
87
+ if def_is_referenced(reference, parent_def_names):
77
88
  return True
78
89
  return False
79
90
 
@@ -50,3 +50,6 @@ def configure_logging(
50
50
  logger.removeHandler(hdlr)
51
51
 
52
52
  logger.addHandler(handler)
53
+
54
+ # Don't propagate to the root logger
55
+ logger.propagate = False
@@ -152,6 +152,7 @@ __all__ = [
152
152
  "ParameterLocation",
153
153
  "JsonSchema",
154
154
  "parse_openapi_to_http_routes",
155
+ "extract_output_schema_from_responses",
155
156
  ]
156
157
 
157
158
  # Type variables for generic parser
@@ -1107,3 +1108,94 @@ def _combine_schemas(route: HTTPRoute) -> dict[str, Any]:
1107
1108
  result = compress_schema(result)
1108
1109
 
1109
1110
  return result
1111
+
1112
+
1113
+ def extract_output_schema_from_responses(
1114
+ responses: dict[str, ResponseInfo], schema_definitions: dict[str, Any] | None = None
1115
+ ) -> dict[str, Any] | None:
1116
+ """
1117
+ Extract output schema from OpenAPI responses for use as MCP tool output schema.
1118
+
1119
+ This function finds the first successful response (200, 201, 202, 204) with a
1120
+ JSON-compatible content type and extracts its schema. If the schema is not an
1121
+ object type, it wraps it to comply with MCP requirements.
1122
+
1123
+ Args:
1124
+ responses: Dictionary of ResponseInfo objects keyed by status code
1125
+ schema_definitions: Optional schema definitions to include in the output schema
1126
+
1127
+ Returns:
1128
+ dict: MCP-compliant output schema with potential wrapping, or None if no suitable schema found
1129
+ """
1130
+ if not responses:
1131
+ return None
1132
+
1133
+ # Priority order for success status codes
1134
+ success_codes = ["200", "201", "202", "204"]
1135
+
1136
+ # Find the first successful response
1137
+ response_info = None
1138
+ for status_code in success_codes:
1139
+ if status_code in responses:
1140
+ response_info = responses[status_code]
1141
+ break
1142
+
1143
+ # If no explicit success codes, try any 2xx response
1144
+ if response_info is None:
1145
+ for status_code, resp_info in responses.items():
1146
+ if status_code.startswith("2"):
1147
+ response_info = resp_info
1148
+ break
1149
+
1150
+ if response_info is None or not response_info.content_schema:
1151
+ return None
1152
+
1153
+ # Prefer application/json, then fall back to other JSON-compatible types
1154
+ json_compatible_types = [
1155
+ "application/json",
1156
+ "application/vnd.api+json",
1157
+ "application/hal+json",
1158
+ "application/ld+json",
1159
+ "text/json",
1160
+ ]
1161
+
1162
+ schema = None
1163
+ for content_type in json_compatible_types:
1164
+ if content_type in response_info.content_schema:
1165
+ schema = response_info.content_schema[content_type]
1166
+ break
1167
+
1168
+ # If no JSON-compatible type found, try the first available content type
1169
+ if schema is None and response_info.content_schema:
1170
+ first_content_type = next(iter(response_info.content_schema))
1171
+ schema = response_info.content_schema[first_content_type]
1172
+ logger.debug(
1173
+ f"Using non-JSON content type for output schema: {first_content_type}"
1174
+ )
1175
+
1176
+ if not schema or not isinstance(schema, dict):
1177
+ return None
1178
+
1179
+ # Clean and copy the schema
1180
+ output_schema = schema.copy()
1181
+
1182
+ # MCP requires output schemas to be objects. If this schema is not an object,
1183
+ # we need to wrap it similar to how ParsedFunction.from_function() does it
1184
+ if output_schema.get("type") != "object":
1185
+ # Create a wrapped schema that contains the original schema under a "result" key
1186
+ wrapped_schema = {
1187
+ "type": "object",
1188
+ "properties": {"result": output_schema},
1189
+ "required": ["result"],
1190
+ "x-fastmcp-wrap-result": True,
1191
+ }
1192
+ output_schema = wrapped_schema
1193
+
1194
+ # Add schema definitions if available
1195
+ if schema_definitions:
1196
+ output_schema["$defs"] = schema_definitions
1197
+
1198
+ # Use compress_schema to remove unused definitions
1199
+ output_schema = compress_schema(output_schema)
1200
+
1201
+ return output_schema
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import copy
4
+ import logging
4
5
  import multiprocessing
5
6
  import socket
6
7
  import time
@@ -129,3 +130,15 @@ def run_server_in_process(
129
130
  proc.join(timeout=2)
130
131
  if proc.is_alive():
131
132
  raise RuntimeError("Server process failed to terminate even after kill")
133
+
134
+
135
+ @contextmanager
136
+ def caplog_for_fastmcp(caplog):
137
+ """Context manager to capture logs from FastMCP loggers even when propagation is disabled."""
138
+ caplog.clear()
139
+ logger = logging.getLogger("FastMCP")
140
+ logger.addHandler(caplog.handler)
141
+ try:
142
+ yield
143
+ finally:
144
+ logger.removeHandler(caplog.handler)
@@ -3,6 +3,7 @@
3
3
  import base64
4
4
  import inspect
5
5
  import mimetypes
6
+ import os
6
7
  from collections.abc import Callable
7
8
  from functools import lru_cache
8
9
  from pathlib import Path
@@ -101,7 +102,7 @@ class Image:
101
102
  if path is not None and data is not None:
102
103
  raise ValueError("Only one of path or data can be provided")
103
104
 
104
- self.path = Path(path) if path else None
105
+ self.path = Path(os.path.expandvars(str(path))).expanduser() if path else None
105
106
  self.data = data
106
107
  self._format = format
107
108
  self._mime_type = self._get_mime_type()
@@ -160,7 +161,7 @@ class Audio:
160
161
  if path is not None and data is not None:
161
162
  raise ValueError("Only one of path or data can be provided")
162
163
 
163
- self.path = Path(path) if path else None
164
+ self.path = Path(os.path.expandvars(str(path))).expanduser() if path else None
164
165
  self.data = data
165
166
  self._format = format
166
167
  self._mime_type = self._get_mime_type()
@@ -219,7 +220,7 @@ class File:
219
220
  if path is not None and data is not None:
220
221
  raise ValueError("Only one of path or data can be provided")
221
222
 
222
- self.path = Path(path) if path else None
223
+ self.path = Path(os.path.expandvars(str(path))).expanduser() if path else None
223
224
  self.data = data
224
225
  self._format = format
225
226
  self._mime_type = self._get_mime_type()
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastmcp
3
- Version: 2.10.1
4
- Summary: The fast, Pythonic way to build MCP servers.
3
+ Version: 2.10.3
4
+ Summary: The fast, Pythonic way to build MCP servers and clients.
5
5
  Project-URL: Homepage, https://gofastmcp.com
6
6
  Project-URL: Repository, https://github.com/jlowin/fastmcp
7
7
  Project-URL: Documentation, https://gofastmcp.com
@@ -18,14 +18,15 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
18
  Classifier: Typing :: Typed
19
19
  Requires-Python: >=3.10
20
20
  Requires-Dist: authlib>=1.5.2
21
+ Requires-Dist: cyclopts>=3.0.0
21
22
  Requires-Dist: exceptiongroup>=1.2.2
22
23
  Requires-Dist: httpx>=0.28.1
23
24
  Requires-Dist: mcp>=1.10.0
24
25
  Requires-Dist: openapi-pydantic>=0.5.1
25
26
  Requires-Dist: pydantic[email]>=2.11.7
27
+ Requires-Dist: pyperclip>=1.9.0
26
28
  Requires-Dist: python-dotenv>=1.1.0
27
29
  Requires-Dist: rich>=13.9.4
28
- Requires-Dist: typer>=0.15.2
29
30
  Provides-Extra: websockets
30
31
  Requires-Dist: websockets>=15.0.1; extra == 'websockets'
31
32
  Description-Content-Type: text/markdown
@@ -1,21 +1,28 @@
1
1
  fastmcp/__init__.py,sha256=5ChT4kg3srdFl0-9dZekGqpzCESlpc6ohrfPbWf1aTo,1300
2
2
  fastmcp/exceptions.py,sha256=-krEavxwddQau6T7MESCR4VjKNLfP9KHJrU1p3y72FU,744
3
+ fastmcp/mcp_config.py,sha256=hOF_NO7F9LbrbmRTj99TVSqHIul4sExNpf1T1HRLpzo,9606
3
4
  fastmcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- fastmcp/settings.py,sha256=yC3f0ITEWYeit37-wnF0G12klTw_hjrWjCr6oZtUT8o,8329
5
+ fastmcp/settings.py,sha256=Ta0TKA75xda9sNkIOpPVIEEk4W9jf_2gwcmO26uDQpg,8946
5
6
  fastmcp/cli/__init__.py,sha256=Ii284TNoG5lxTP40ETMGhHEq3lQZWxu9m9JuU57kUpQ,87
6
7
  fastmcp/cli/claude.py,sha256=IAlcZ4qZKBBj09jZUMEx7EANZE_IR3vcu7zOBJmMOuU,4567
7
- fastmcp/cli/cli.py,sha256=598s1S-KdqIB85mwqvXJaBVen6xYY65ek4v-q3lmY4k,15933
8
- fastmcp/cli/run.py,sha256=Pw3vH5wKRHfbmHRn0saIbC4l450KPOzeQbzPFqG4AbY,6208
8
+ fastmcp/cli/cli.py,sha256=mr8uKvFINEs4eh-v3vSOmncFCaUS4G0CCk8YVBh_jfk,12679
9
+ fastmcp/cli/run.py,sha256=h2WjJtrmhHQN1kM8CYa5i177hBuHoPsbycSMiOr_8uw,6696
10
+ fastmcp/cli/install/__init__.py,sha256=afaNANIhivfKi4QhJdYUP56Q2XZ4da4dUbDq9HAMJqo,701
11
+ fastmcp/cli/install/claude_code.py,sha256=eH6Pdre-FtzGuEatWYIFVTNtEEjHb7m-bgX4ARIFG2g,5384
12
+ fastmcp/cli/install/claude_desktop.py,sha256=o3p3KiW0QzsrzOEa3OU_gkvzTuFSkabDwEqwwgp1mYU,5590
13
+ fastmcp/cli/install/cursor.py,sha256=296a22T3fdaC8deCwh3TreVFoJ0QpjLxRF1gNsp3Wjg,5555
14
+ fastmcp/cli/install/mcp_config.py,sha256=D1vkZj13ofNFbFhdn8eAuEx-CHTr3nGiVkjSZTdnetY,4559
15
+ fastmcp/cli/install/shared.py,sha256=Y0YZei1YemVCkg0ieUgfRse-lqSlIn5Ho8t6pB9nDa4,2683
9
16
  fastmcp/client/__init__.py,sha256=kd2hhSuD8rZuF87c9zlPJP_icJ-Rx3exyNoK0EzfOtE,617
10
- fastmcp/client/client.py,sha256=8Sx7NxnnF5IMv9E-J0MXjTI_TfhlM3j0mD1Rh-LXQJI,29213
11
- fastmcp/client/elicitation.py,sha256=8eWZpXbRFctZGgulyJ_GGI4GYXqAFf5Zp47vYTRD4fc,2155
17
+ fastmcp/client/client.py,sha256=GniETS28L8B-ahzztU2ZLI_XSCcEib-miGzE2ZnG4Xc,34054
18
+ fastmcp/client/elicitation.py,sha256=Jf9yqna8R7r1hqedXAyh9a2-QNVzbCSKUDZhkFHqHqg,2403
12
19
  fastmcp/client/logging.py,sha256=7GJ-BLFW16_IOJPlGTNEWPP0P-yqqRpmsLdiKrlVsw8,757
13
20
  fastmcp/client/messages.py,sha256=NIPjt-5js_DkI5BD4OVdTf6pz-nGjc2dtbgt-vAY234,4329
14
21
  fastmcp/client/oauth_callback.py,sha256=ODAnVX-ettL82RuI5KpfkKf8iDtYMDue3Tnab5sjQtM,10071
15
22
  fastmcp/client/progress.py,sha256=WjLLDbUKMsx8DK-fqO7AGsXb83ak-6BMrLvzzznGmcI,1043
16
23
  fastmcp/client/roots.py,sha256=IxI_bHwHTmg6c2H-s1av1ZgrRnNDieHtYwdGFbzXT5c,2471
17
24
  fastmcp/client/sampling.py,sha256=Q8PzYCERa1W3xGGI9I9QOhhDM-M4i3P5lESb0cp2iI8,1595
18
- fastmcp/client/transports.py,sha256=liHLuOcgKotdlhTJBOiSn73WX0v7mnFa853hJBdu-2E,33898
25
+ fastmcp/client/transports.py,sha256=AIQ0ni3lI0MoewgPNlkLHvym1iEnc8vCa513lHKCIqA,33803
19
26
  fastmcp/client/auth/__init__.py,sha256=4DNsfp4iaQeBcpds0JDdMn6Mmfud44stWLsret0sVKY,91
20
27
  fastmcp/client/auth/bearer.py,sha256=MFEFqcH6u_V86msYiOsEFKN5ks1V9BnBNiPsPLHUTqo,399
21
28
  fastmcp/client/auth/oauth.py,sha256=pSyuI0FlRK1qkBA6mvq-bxKzl2B-pMCvPPzIByTJBEo,11745
@@ -34,12 +41,12 @@ fastmcp/contrib/mcp_mixin/__init__.py,sha256=aw9IQ1ssNjCgws4ZNt8bkdpossAAGVAwwjB
34
41
  fastmcp/contrib/mcp_mixin/example.py,sha256=GnunkXmtG5hLLTUsM8aW5ZURU52Z8vI4tNLl-fK7Dg0,1228
35
42
  fastmcp/contrib/mcp_mixin/mcp_mixin.py,sha256=sUSJ2o0sTsb061MyPN2xuYP0oI4W6YVQXupY3nnjD50,8687
36
43
  fastmcp/prompts/__init__.py,sha256=An8uMBUh9Hrb7qqcn_5_Hent7IOeSh7EA2IUVsIrtHc,179
37
- fastmcp/prompts/prompt.py,sha256=ee7DT32pw0H0k0ON8a63Eu0kQ3G1zMGlWXIFRDV8p7E,13925
44
+ fastmcp/prompts/prompt.py,sha256=Ux_FT8GTnejvjDWEgf5nSAIwbhV-9otgCJm-EqnzxvY,13861
38
45
  fastmcp/prompts/prompt_manager.py,sha256=Pt9cg_c9FR2EA0ITIHJT5Utihkd383JzhqhT-y2VnKo,7677
39
46
  fastmcp/resources/__init__.py,sha256=y1iAuqx-GIrS1NqIYzKezIDiYyjNEzzHD35epHpMnXE,463
40
- fastmcp/resources/resource.py,sha256=fN_ck9fgFG_ciHV9reXDNe-dWzqEZ7XV4p3U7z-yf4k,6050
47
+ fastmcp/resources/resource.py,sha256=GGCUHUQe4ALrVJc-Ng1uz8-mtQSg_vuVA71H_qJkko0,6031
41
48
  fastmcp/resources/resource_manager.py,sha256=zjhso9ZP0EK_beTpUz_amqJ7XSABqU8mqPSk-JturOE,19777
42
- fastmcp/resources/template.py,sha256=rYEfwNWVJZnlTp6uSKbGNL0OlaUqOoKRjmg4hesLXVU,10346
49
+ fastmcp/resources/template.py,sha256=mYsw3xPWRq1AgOVFkG_A1otY3NXYw5oUJnVzMC1Qy80,10346
43
50
  fastmcp/resources/types.py,sha256=SiYNLnpXT-mHgNUrzqKUvXYUsY-V3gwJIrYdJfFwDDo,4868
44
51
  fastmcp/server/__init__.py,sha256=bMD4aQD4yJqLz7-mudoNsyeV8UgQfRAg3PRwPvwTEds,119
45
52
  fastmcp/server/context.py,sha256=OFkdgT53NkN_VYwwHjdR2C_28CWtPsFln36X0QLM23g,20258
@@ -47,13 +54,13 @@ fastmcp/server/dependencies.py,sha256=iKJdz1XsVJcrfHo_reXj9ZSldw-HeAwsp9S6lAgfGA
47
54
  fastmcp/server/elicitation.py,sha256=jZIHjV4NjhYbT-w8pBArwd0vNzP8OYwzmsnWDdk6Bd0,6136
48
55
  fastmcp/server/http.py,sha256=d0Jij4HVTaAohluRXArSniXLb1HcHP3ytbe-mMHg6nE,11678
49
56
  fastmcp/server/low_level.py,sha256=LNmc_nU_wx-fRG8OEHdLPKopZpovcrWlyAxJzKss3TA,1239
50
- fastmcp/server/openapi.py,sha256=ALIbl0r2T1cvbSeFwz6HpIzut2akdngAtKDdWGyIWHs,36221
51
- fastmcp/server/proxy.py,sha256=Ofx7P5rU7mKVPDjvvIKjQS7_C1w4-1UiKc78x7AdjN8,15211
52
- fastmcp/server/server.py,sha256=XKt0vGwQNopBnYvciJtbJ_FVDt1uuwwuxrAcrVBJGa8,79703
57
+ fastmcp/server/openapi.py,sha256=YoARNzEZQs6MfnGfl5-V9kULCDp1LluJL2yeH9m62Os,37595
58
+ fastmcp/server/proxy.py,sha256=YxlU4lNPGb5rk4oyUySYiOMz3GIAywrQP1rMcVYSih4,21689
59
+ fastmcp/server/server.py,sha256=Zk77BcHyr0lSwf2LmNOQXiDJNFeQKkZ_p20dg81P0IU,82383
53
60
  fastmcp/server/auth/__init__.py,sha256=doHCLwOIElvH1NrTdpeP9JKfnNf3MDYPSpQfdsQ-uI0,84
54
61
  fastmcp/server/auth/auth.py,sha256=A00OKxglEMrGMMIiMbc6UmpGc2VoWDkEVU5g2pIzDIg,2119
55
62
  fastmcp/server/auth/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
- fastmcp/server/auth/providers/bearer.py,sha256=iRExt5hRKmrwr7MjsUN-UdQDe-iCSjH7sNCURaK7HBs,16456
63
+ fastmcp/server/auth/providers/bearer.py,sha256=rjwuKFgCm4fLsAQzUaIaE8OD6bml3sBaO7NYq3Wgs24,16718
57
64
  fastmcp/server/auth/providers/bearer_env.py,sha256=NYPCW363Q8u8BdiPPz1FdB3_kwmbCaWT5yKdAO-ZgwA,2081
58
65
  fastmcp/server/auth/providers/in_memory.py,sha256=Sb3GOtLL2bWbm8z-T8cEsMz1qcQUSHpPEEgYRvTOQi4,14251
59
66
  fastmcp/server/middleware/__init__.py,sha256=m1QJFQ7JW_2JHpJp1FurBNYaxbBUa_HyDn1Bw9mtyvc,367
@@ -63,24 +70,24 @@ fastmcp/server/middleware/middleware.py,sha256=1Zy5KdBCrGzqFIpYZqWlZq9rLK3NDlRiX
63
70
  fastmcp/server/middleware/rate_limiting.py,sha256=VTrCoQFmWCm0BxwOrNfG21CBFDDOKJT7IiSEjpJgmPA,7921
64
71
  fastmcp/server/middleware/timing.py,sha256=lL_xc-ErLD5lplfvd5-HIyWEbZhgNBYkcQ74KFXAMkA,5591
65
72
  fastmcp/tools/__init__.py,sha256=vzqb-Y7Kf0d5T0aOsld-O-FA8kD7-4uFExChewFHEzY,201
66
- fastmcp/tools/tool.py,sha256=DsoF0QNTl4i_w0FqqmdbK_1poZtfj1j6mv5kuf8zH1Y,17524
73
+ fastmcp/tools/tool.py,sha256=fW-xZQCLN2qSZrlAo2eQ1aEFa-VN5jYEHx5H936-_50,17466
67
74
  fastmcp/tools/tool_manager.py,sha256=Sm_tOO-SY0m7tEN_dofP-tvBnC2HroPRKLU6sp8gnUw,7739
68
- fastmcp/tools/tool_transform.py,sha256=YY2DZdJZ6fEGtgEP1Djrc49F8rAEyx6fgRGEyIGaxPE,32601
75
+ fastmcp/tools/tool_transform.py,sha256=nGvxxKsyfp3gYl_nkYFHlnb8Fc4Jtw6t7WL291S4Vh4,32558
69
76
  fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
70
77
  fastmcp/utilities/cache.py,sha256=aV3oZ-ZhMgLSM9iAotlUlEy5jFvGXrVo0Y5Bj4PBtqY,707
78
+ fastmcp/utilities/cli.py,sha256=n3HA8IXg7hKaizuM3SRCW65UMXlqjUDf5USRSuscTdY,3287
71
79
  fastmcp/utilities/components.py,sha256=WIxNVZ7YxCLpdIm_pbTYeP0lAxikvgptVYhIL0LVmCc,2535
72
80
  fastmcp/utilities/exceptions.py,sha256=7Z9j5IzM5rT27BC1Mcn8tkS-bjqCYqMKwb2MMTaxJYU,1350
73
81
  fastmcp/utilities/http.py,sha256=1ns1ymBS-WSxbZjGP6JYjSO52Wa_ls4j4WbnXiupoa4,245
74
82
  fastmcp/utilities/inspect.py,sha256=XNA0dfYM5G-FVbJaVJO8loSUUCNypyLA-QjqTOneJyU,10833
75
- fastmcp/utilities/json_schema.py,sha256=K0QH5UazBD_tweBi-TguWYjUu5Lgp9wcM-wT42Fet5w,5022
83
+ fastmcp/utilities/json_schema.py,sha256=8qyb3qOvk1gc3p63uP6LGyKdOANkNdD9YA32OiBxyNw,5495
76
84
  fastmcp/utilities/json_schema_type.py,sha256=Sml03nJGOnUfxCGrHWRMwZMultV0X5JThMepUnHIUiA,22377
77
- fastmcp/utilities/logging.py,sha256=B1WNO-ZWFjd9wiFSh13YtW1hAKaNmbpscDZleIAhr-g,1317
78
- fastmcp/utilities/mcp_config.py,sha256=ryjAfJUPquDSoKdSymPH4M2B0WvuM3pWUGI3cOgAX80,2782
79
- fastmcp/utilities/openapi.py,sha256=neeaXwwn1OWdUp0Gawhx4SJHLfV78gXU5OMMTFGeD24,45235
80
- fastmcp/utilities/tests.py,sha256=O9hRSjnyaYQqu1RJ-CFBw1cIjezlwSQtS-Ea_iqO4sY,3899
81
- fastmcp/utilities/types.py,sha256=SWtzKpIr9TMeOE6TyPgqSi-SBXpWBPUnA5QPiP4nDzw,10512
82
- fastmcp-2.10.1.dist-info/METADATA,sha256=I0_44sR-kc7BcTHdA1mLy4f9zASLk6hCiOWjrcxBfk8,17796
83
- fastmcp-2.10.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
84
- fastmcp-2.10.1.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
85
- fastmcp-2.10.1.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
86
- fastmcp-2.10.1.dist-info/RECORD,,
85
+ fastmcp/utilities/logging.py,sha256=1y7oNmy8WrR0NsfNVw1LPoKu92OFdmzIO65syOKi_BI,1388
86
+ fastmcp/utilities/openapi.py,sha256=YWDFjT9ng4xi_kzNCoeBCgwnlep14IQ7PnCSQt_lVyU,48528
87
+ fastmcp/utilities/tests.py,sha256=kZH8HQAC702a5vNJb4K0tO1ll9CZADWQ_P-5ERWSvSA,4242
88
+ fastmcp/utilities/types.py,sha256=c6HPvHCpkq8EXh0hWjaUlj9aCZklmxzAQHCXZy7llNo,10636
89
+ fastmcp-2.10.3.dist-info/METADATA,sha256=ZQWYX4dWpwwDXmCBG4EiiXACSF8Xv6qag6WKuqcnY2s,17842
90
+ fastmcp-2.10.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
91
+ fastmcp-2.10.3.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
92
+ fastmcp-2.10.3.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
93
+ fastmcp-2.10.3.dist-info/RECORD,,
@@ -1,93 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import re
4
- from typing import TYPE_CHECKING, Annotated, Any, Literal
5
- from urllib.parse import urlparse
6
-
7
- import httpx
8
- from pydantic import AnyUrl, ConfigDict, Field
9
-
10
- from fastmcp.utilities.types import FastMCPBaseModel
11
-
12
- if TYPE_CHECKING:
13
- from fastmcp.client.transports import (
14
- SSETransport,
15
- StdioTransport,
16
- StreamableHttpTransport,
17
- )
18
-
19
-
20
- def infer_transport_type_from_url(
21
- url: str | AnyUrl,
22
- ) -> Literal["http", "sse"]:
23
- """
24
- Infer the appropriate transport type from the given URL.
25
- """
26
- url = str(url)
27
- if not url.startswith("http"):
28
- raise ValueError(f"Invalid URL: {url}")
29
-
30
- parsed_url = urlparse(url)
31
- path = parsed_url.path
32
-
33
- # Match /sse followed by /, ?, &, or end of string
34
- if re.search(r"/sse(/|\?|&|$)", path):
35
- return "sse"
36
- else:
37
- return "http"
38
-
39
-
40
- class StdioMCPServer(FastMCPBaseModel):
41
- command: str
42
- args: list[str] = Field(default_factory=list)
43
- env: dict[str, Any] = Field(default_factory=dict)
44
- cwd: str | None = None
45
- transport: Literal["stdio"] = "stdio"
46
-
47
- def to_transport(self) -> StdioTransport:
48
- from fastmcp.client.transports import StdioTransport
49
-
50
- return StdioTransport(
51
- command=self.command,
52
- args=self.args,
53
- env=self.env,
54
- cwd=self.cwd,
55
- )
56
-
57
-
58
- class RemoteMCPServer(FastMCPBaseModel):
59
- url: str
60
- headers: dict[str, str] = Field(default_factory=dict)
61
- transport: Literal["http", "streamable-http", "sse"] | None = None
62
- auth: Annotated[
63
- str | Literal["oauth"] | httpx.Auth | None,
64
- Field(
65
- description='Either a string representing a Bearer token, the literal "oauth" to use OAuth authentication, or an httpx.Auth instance for custom authentication.',
66
- ),
67
- ] = None
68
-
69
- model_config = ConfigDict(arbitrary_types_allowed=True)
70
-
71
- def to_transport(self) -> StreamableHttpTransport | SSETransport:
72
- from fastmcp.client.transports import SSETransport, StreamableHttpTransport
73
-
74
- if self.transport is None:
75
- transport = infer_transport_type_from_url(self.url)
76
- else:
77
- transport = self.transport
78
-
79
- if transport == "sse":
80
- return SSETransport(self.url, headers=self.headers, auth=self.auth)
81
- else:
82
- # Both "http" and "streamable-http" map to StreamableHttpTransport
83
- return StreamableHttpTransport(
84
- self.url, headers=self.headers, auth=self.auth
85
- )
86
-
87
-
88
- class MCPConfig(FastMCPBaseModel):
89
- mcpServers: dict[str, StdioMCPServer | RemoteMCPServer]
90
-
91
- @classmethod
92
- def from_dict(cls, config: dict[str, Any]) -> MCPConfig:
93
- return cls(mcpServers=config.get("mcpServers", config))