fastmcp 2.1.1__py3-none-any.whl → 2.1.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/prompts/prompt.py CHANGED
@@ -8,7 +8,6 @@ from typing import Annotated, Any, Literal
8
8
  import pydantic_core
9
9
  from mcp.types import EmbeddedResource, ImageContent, TextContent
10
10
  from pydantic import BaseModel, BeforeValidator, Field, TypeAdapter, validate_call
11
- from typing_extensions import Self
12
11
 
13
12
  from fastmcp.utilities.types import _convert_set_defaults
14
13
 
@@ -163,13 +162,6 @@ class Prompt(BaseModel):
163
162
  except Exception as e:
164
163
  raise ValueError(f"Error rendering prompt {self.name}: {e}")
165
164
 
166
- def copy(self, updates: dict[str, Any] | None = None) -> Self:
167
- """Copy the prompt with optional updates."""
168
- data = self.model_dump()
169
- if updates:
170
- data.update(updates)
171
- return type(self)(**data)
172
-
173
165
  def __eq__(self, other: object) -> bool:
174
166
  if not isinstance(other, Prompt):
175
167
  return False
@@ -1,5 +1,6 @@
1
1
  """Prompt management functionality."""
2
2
 
3
+ import copy
3
4
  from collections.abc import Awaitable, Callable
4
5
  from typing import Any
5
6
 
@@ -84,7 +85,8 @@ class PromptManager:
84
85
  # Create prefixed name
85
86
  prefixed_name = f"{prefix}{name}" if prefix else name
86
87
 
87
- new_prompt = prompt.copy(updates=dict(name=prefixed_name))
88
+ new_prompt = copy.copy(prompt)
89
+ new_prompt.name = prefixed_name
88
90
 
89
91
  # Store the prompt with the prefixed name
90
92
  self.add_prompt(new_prompt)
@@ -1,7 +1,7 @@
1
1
  """Base classes and interfaces for FastMCP resources."""
2
2
 
3
3
  import abc
4
- from typing import Annotated, Any
4
+ from typing import Annotated
5
5
 
6
6
  from pydantic import (
7
7
  AnyUrl,
@@ -13,7 +13,6 @@ from pydantic import (
13
13
  ValidationInfo,
14
14
  field_validator,
15
15
  )
16
- from typing_extensions import Self
17
16
 
18
17
  from fastmcp.utilities.types import _convert_set_defaults
19
18
 
@@ -54,13 +53,6 @@ class Resource(BaseModel, abc.ABC):
54
53
  """Read the resource content."""
55
54
  pass
56
55
 
57
- def copy(self, updates: dict[str, Any] | None = None) -> Self:
58
- """Copy the resource with optional updates."""
59
- data = self.model_dump()
60
- if updates:
61
- data.update(updates)
62
- return type(self)(**data)
63
-
64
56
  def __eq__(self, other: object) -> bool:
65
57
  if not isinstance(other, Resource):
66
58
  return False
@@ -1,5 +1,6 @@
1
1
  """Resource manager functionality."""
2
2
 
3
+ import copy
3
4
  import inspect
4
5
  import re
5
6
  from collections.abc import Callable
@@ -238,7 +239,8 @@ class ResourceManager:
238
239
  # Create prefixed URI and copy the resource with the new URI
239
240
  prefixed_uri = f"{prefix}{uri}" if prefix else uri
240
241
 
241
- new_resource = resource.copy(updates=dict(uri=prefixed_uri))
242
+ new_resource = copy.copy(resource)
243
+ new_resource.uri = AnyUrl(prefixed_uri)
242
244
 
243
245
  # Store directly in resources dictionary
244
246
  self.add_resource(new_resource)
@@ -266,9 +268,8 @@ class ResourceManager:
266
268
  f"{prefix}{uri_template}" if prefix else uri_template
267
269
  )
268
270
 
269
- new_template = template.copy(
270
- updates=dict(uri_template=prefixed_uri_template)
271
- )
271
+ new_template = copy.copy(template)
272
+ new_template.uri_template = prefixed_uri_template
272
273
 
273
274
  # Store directly in templates dictionary
274
275
  self.add_template(new_template)
@@ -8,7 +8,6 @@ from collections.abc import Callable
8
8
  from typing import Annotated, Any
9
9
 
10
10
  from pydantic import BaseModel, BeforeValidator, Field, TypeAdapter, validate_call
11
- from typing_extensions import Self
12
11
 
13
12
  from fastmcp.resources.types import FunctionResource, Resource
14
13
  from fastmcp.utilities.types import _convert_set_defaults
@@ -92,13 +91,6 @@ class ResourceTemplate(BaseModel):
92
91
  except Exception as e:
93
92
  raise ValueError(f"Error creating resource from template: {e}")
94
93
 
95
- def copy(self, updates: dict[str, Any] | None = None) -> Self:
96
- """Copy the resource template with optional updates."""
97
- data = self.model_dump()
98
- if updates:
99
- data.update(updates)
100
- return type(self)(**data)
101
-
102
94
  def __eq__(self, other: object) -> bool:
103
95
  if not isinstance(other, ResourceTemplate):
104
96
  return False
fastmcp/tools/tool.py CHANGED
@@ -5,7 +5,6 @@ from collections.abc import Callable
5
5
  from typing import TYPE_CHECKING, Annotated, Any
6
6
 
7
7
  from pydantic import BaseModel, BeforeValidator, Field
8
- from typing_extensions import Self
9
8
 
10
9
  from fastmcp.exceptions import ToolError
11
10
  from fastmcp.utilities.func_metadata import FuncMetadata, func_metadata
@@ -102,13 +101,6 @@ class Tool(BaseModel):
102
101
  except Exception as e:
103
102
  raise ToolError(f"Error executing tool {self.name}: {e}") from e
104
103
 
105
- def copy(self, updates: dict[str, Any] | None = None) -> Self:
106
- """Copy the tool with optional updates."""
107
- data = self.model_dump()
108
- if updates:
109
- data.update(updates)
110
- return type(self)(**data)
111
-
112
104
  def __eq__(self, other: object) -> bool:
113
105
  if not isinstance(other, Tool):
114
106
  return False
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations as _annotations
2
2
 
3
+ import copy
3
4
  from collections.abc import Callable
4
5
  from typing import TYPE_CHECKING, Any
5
6
 
@@ -90,7 +91,9 @@ class ToolManager:
90
91
  for name, tool in tool_manager._tools.items():
91
92
  prefixed_name = f"{prefix}{name}" if prefix else name
92
93
 
93
- new_tool = tool.copy(updates=dict(name=prefixed_name))
94
+ new_tool = copy.copy(tool)
95
+ new_tool.name = prefixed_name
96
+
94
97
  # Store the copied tool
95
98
  self.add_tool(new_tool)
96
99
  logger.debug(f'Imported tool "{name}" as "{prefixed_name}"')
@@ -20,15 +20,23 @@ def get_logger(name: str) -> logging.Logger:
20
20
 
21
21
 
22
22
  def configure_logging(
23
- level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO",
23
+ level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | int = "INFO",
24
24
  ) -> None:
25
25
  """Configure logging for FastMCP.
26
26
 
27
27
  Args:
28
28
  level: the log level to use
29
29
  """
30
- logging.basicConfig(
31
- level=level,
32
- format="%(message)s",
33
- handlers=[RichHandler(console=Console(stderr=True), rich_tracebacks=True)],
34
- )
30
+ # Only configure the FastMCP logger namespace
31
+ handler = RichHandler(console=Console(stderr=True), rich_tracebacks=True)
32
+ formatter = logging.Formatter("%(message)s")
33
+ handler.setFormatter(formatter)
34
+
35
+ fastmcp_logger = logging.getLogger("FastMCP")
36
+ fastmcp_logger.setLevel(level)
37
+
38
+ # Remove any existing handlers to avoid duplicates on reconfiguration
39
+ for hdlr in fastmcp_logger.handlers[:]:
40
+ fastmcp_logger.removeHandler(hdlr)
41
+
42
+ fastmcp_logger.addHandler(handler)
@@ -150,93 +150,6 @@ def _resolve_ref(
150
150
  return item
151
151
 
152
152
 
153
- def _extract_schema_as_dict(
154
- schema_obj: Schema | Reference, openapi: OpenAPI
155
- ) -> JsonSchema:
156
- """Resolves a schema/reference and returns it as a dictionary."""
157
- resolved_schema = _resolve_ref(schema_obj, openapi)
158
- if isinstance(resolved_schema, Schema):
159
- # Using exclude_none=True might be better than exclude_unset sometimes
160
- return resolved_schema.model_dump(mode="json", by_alias=True, exclude_none=True)
161
- elif isinstance(resolved_schema, dict):
162
- logger.warning(
163
- "Resolved schema reference resulted in a dict, not a Schema model."
164
- )
165
- return resolved_schema
166
- else:
167
- ref_str = getattr(schema_obj, "ref", "unknown")
168
- logger.warning(
169
- f"Expected Schema after resolving ref '{ref_str}', got {type(resolved_schema)}. Returning empty dict."
170
- )
171
- return {}
172
-
173
-
174
- def _convert_to_parameter_location(param_in: str) -> ParameterLocation:
175
- """Convert string parameter location to our ParameterLocation type."""
176
- if param_in == "path":
177
- return "path"
178
- elif param_in == "query":
179
- return "query"
180
- elif param_in == "header":
181
- return "header"
182
- elif param_in == "cookie":
183
- return "cookie"
184
- else:
185
- logger.warning(f"Unknown parameter location: {param_in}, defaulting to 'query'")
186
- return "query"
187
-
188
-
189
- def _extract_responses(
190
- operation_responses: dict[str, Response | Reference] | None,
191
- openapi: OpenAPI,
192
- ) -> dict[str, ResponseInfo]:
193
- """Extracts and resolves response information for an operation."""
194
- extracted_responses: dict[str, ResponseInfo] = {}
195
- if not operation_responses:
196
- return extracted_responses
197
-
198
- for status_code, resp_or_ref in operation_responses.items():
199
- try:
200
- response = cast(Response, _resolve_ref(resp_or_ref, openapi))
201
- if not isinstance(response, Response):
202
- ref_str = getattr(resp_or_ref, "ref", "unknown")
203
- logger.warning(
204
- f"Expected Response after resolving ref '{ref_str}' for status code {status_code}, got {type(response)}. Skipping."
205
- )
206
- continue
207
-
208
- content_schemas: dict[str, JsonSchema] = {}
209
- if response.content:
210
- for media_type_str, media_type_obj in response.content.items():
211
- if (
212
- isinstance(media_type_obj, MediaType)
213
- and media_type_obj.media_type_schema
214
- ):
215
- try:
216
- schema_dict = _extract_schema_as_dict(
217
- media_type_obj.media_type_schema, openapi
218
- )
219
- content_schemas[media_type_str] = schema_dict
220
- except ValueError as schema_err:
221
- logger.error(
222
- f"Failed to extract schema for media type '{media_type_str}' in response {status_code}: {schema_err}"
223
- )
224
-
225
- resp_info = ResponseInfo(
226
- description=response.description, content_schema=content_schemas
227
- )
228
- extracted_responses[str(status_code)] = resp_info
229
-
230
- except (ValidationError, ValueError, AttributeError) as e:
231
- ref_name = getattr(resp_or_ref, "ref", "unknown")
232
- logger.error(
233
- f"Failed to extract response for status code {status_code} (ref: '{ref_name}'): {e}",
234
- exc_info=False,
235
- )
236
-
237
- return extracted_responses
238
-
239
-
240
153
  # --- Main Parsing Function ---
241
154
  # (No changes needed in the main loop logic, only in the helpers it calls)
242
155
  def parse_openapi_to_http_routes(openapi_dict: dict[str, Any]) -> list[HTTPRoute]:
@@ -1,10 +1,21 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastmcp
3
- Version: 2.1.1
3
+ Version: 2.1.2
4
4
  Summary: The fast, Pythonic way to build MCP servers.
5
+ Project-URL: Homepage, https://gofastmcp.com
6
+ Project-URL: Repository, https://github.com/jlowin/fastmcp
7
+ Project-URL: Documentation, https://gofastmcp.com
5
8
  Author: Jeremiah Lowin
6
- License: Apache-2.0
9
+ License-Expression: Apache-2.0
7
10
  License-File: LICENSE
11
+ Keywords: agent,fastmcp,llm,mcp,mcp client,mcp server,model context protocol
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
18
+ Classifier: Typing :: Typed
8
19
  Requires-Python: >=3.10
9
20
  Requires-Dist: dotenv>=0.9.9
10
21
  Requires-Dist: fastapi>=0.115.12
@@ -26,6 +37,7 @@ Description-Content-Type: text/markdown
26
37
  [![Tests](https://github.com/jlowin/fastmcp/actions/workflows/run-tests.yml/badge.svg)](https://github.com/jlowin/fastmcp/actions/workflows/run-tests.yml)
27
38
  [![License](https://img.shields.io/github/license/jlowin/fastmcp.svg)](https://github.com/jlowin/fastmcp/blob/main/LICENSE)
28
39
 
40
+ <a href="https://trendshift.io/repositories/13266" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13266" alt="jlowin%2Ffastmcp | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
29
41
  </div>
30
42
 
31
43
  The [Model Context Protocol (MCP)](https://modelcontextprotocol.io) is a new, standardized way to provide context and tools to your LLMs, and FastMCP makes building MCP servers and clients simple and intuitive. Create tools, expose resources, define prompts, and connect components with clean, Pythonic code.
@@ -787,4 +799,4 @@ We use `ruff` via `pre-commit`.
787
799
 
788
800
  Please open an issue or discussion for questions or suggestions!
789
801
 
790
- </details>
802
+ </details>
@@ -12,12 +12,12 @@ fastmcp/client/roots.py,sha256=IxI_bHwHTmg6c2H-s1av1ZgrRnNDieHtYwdGFbzXT5c,2471
12
12
  fastmcp/client/sampling.py,sha256=WdRhIZbWv54rXYI8lWHv0thXmGCloZYPFpwJK9El_sQ,1613
13
13
  fastmcp/client/transports.py,sha256=WVChsDV1UF0I3reiefsT3dipIh-P_K262TXpucwH-YY,14602
14
14
  fastmcp/prompts/__init__.py,sha256=LtPAv2JKIu54AwUd3iwv-HUd4DPcwgEqy6itEd3BH_E,194
15
- fastmcp/prompts/prompt.py,sha256=wy5gHHiTeYDsDufrVlfJzej_A7lTT34WNI9jwsEaNn8,6268
16
- fastmcp/prompts/prompt_manager.py,sha256=HGwg8vsx1TVh9hBBWwuDdLm-M-FzBU5oB6Xu8nZC524,3505
15
+ fastmcp/prompts/prompt.py,sha256=TCjIn0RyYsKDKjBYMuzSvmZPzigw4Y05EOvM0u_WBuk,5992
16
+ fastmcp/prompts/prompt_manager.py,sha256=fX8Ckha0byC2VEgwRoM8_w8orNAkcdxEZRCd6oRtvo4,3533
17
17
  fastmcp/resources/__init__.py,sha256=t0x1j8lc74rjUKtXe9H5Gs4fpQt82K4NgBK6Y7A0xTg,467
18
- fastmcp/resources/resource.py,sha256=MrfRdLA2FEglvRJP7KgduG7na_qgkBo-_iXTzRbil6c,2038
19
- fastmcp/resources/resource_manager.py,sha256=fIre1GkXhOyWJNGM36qwYoZQC74aEznV4oXfLfCoKdw,10971
20
- fastmcp/resources/template.py,sha256=Fpjb51_ktWFpS1aQ5CFCt1SFuPe6S7CPuyzQCz7c3Mg,3742
18
+ fastmcp/resources/resource.py,sha256=qTAj6v_NrOhI_Hd1Mc6VfsmgSNFAxsBdCJTF9W-nU_s,1755
19
+ fastmcp/resources/resource_manager.py,sha256=Ei1QthTUgjvSXIN-qXQeBd7aQiBNNsv0SY_lMtV9tfw,10997
20
+ fastmcp/resources/template.py,sha256=_4JaMeYqtQYs1IO-lgBehaHJqp16G1pQ1Qe1f9YYoNg,3455
21
21
  fastmcp/resources/types.py,sha256=tigil7z-SUJMakGXzDLIGSqTepPrAsRpwqwtBA4yoUY,6168
22
22
  fastmcp/server/__init__.py,sha256=pdkghG11VLMZiluQ-4_rl2JK1LMWmV003m9dDRUN8W4,92
23
23
  fastmcp/server/context.py,sha256=s1885AZRipKB3VltfaO3VEtMxGefKs8fdZByj-4tbNI,7120
@@ -25,16 +25,16 @@ fastmcp/server/openapi.py,sha256=J7HrAlRziaB2a6pwB0wStbjRJ1E5Lf818yMqD762s5U,226
25
25
  fastmcp/server/proxy.py,sha256=gYcoQFDIBraqWMOpWSsZLqefKjL_v0v74juLW1SU1AU,8058
26
26
  fastmcp/server/server.py,sha256=ryN7o7G1gNFE1NsAuZVc3WpcmsBtcKOo-mXACN5NCoc,28814
27
27
  fastmcp/tools/__init__.py,sha256=ocw-SFTtN6vQ8fgnlF8iNAOflRmh79xS1xdO0Bc3QPE,96
28
- fastmcp/tools/tool.py,sha256=yPRqEM8sntDIbWPtZBy9evDg3BXupe0BKqnpvMwy7Sc,3872
29
- fastmcp/tools/tool_manager.py,sha256=oUT8ExxIKKpJnFxCUDwThRxdK7WADD40e6yjGBYDPmI,3498
28
+ fastmcp/tools/tool.py,sha256=pD3xOtryldu6lYsM5cOHnS05tJq9EwmQaoNUCZzLcoY,3598
29
+ fastmcp/tools/tool_manager.py,sha256=p-L1KK8ecwP2psoyrcaSwHmeUKQZf-2pLPv6S_qgIcM,3525
30
30
  fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
31
31
  fastmcp/utilities/decorators.py,sha256=AjhjsetQZF4YOPV5MTZmIxO21iFp_4fDIS3O2_KNCEg,2990
32
32
  fastmcp/utilities/func_metadata.py,sha256=uh-u3gAjLD4kCcGf0ZkZZwBTTl-84JuANZTnDqP5ztI,7841
33
- fastmcp/utilities/logging.py,sha256=1ipiOXzgWUp3Vih_JtEiLX7aAFmrUDZNr4KrZbofZTM,818
34
- fastmcp/utilities/openapi.py,sha256=HiGCC5nh1sWtJRJ7DrFNhdjf1m-7SgyXWOE4nsRvyOc,48762
33
+ fastmcp/utilities/logging.py,sha256=zav8pnFxG_fvGJHUV2XpobmT9WVrmv1mlQBSCz-CPx4,1159
34
+ fastmcp/utilities/openapi.py,sha256=PrH3usbTblaVC6jIH1UGiPEfgB2sSCLj33zA5dH7o_s,45193
35
35
  fastmcp/utilities/types.py,sha256=m2rPYMzO-ZFvvZ46N-1-Xqyw693K7yq9Z2xR4pVELyk,2091
36
- fastmcp-2.1.1.dist-info/METADATA,sha256=Db4PK68mC7j-FCLfkUiyAg4hIaFgYho-X465u0c0sOs,26634
37
- fastmcp-2.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
38
- fastmcp-2.1.1.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
39
- fastmcp-2.1.1.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
40
- fastmcp-2.1.1.dist-info/RECORD,,
36
+ fastmcp-2.1.2.dist-info/METADATA,sha256=I3ePFWEbVbqKFh3UG6bnScng4t5WEhpbq3YNR-VET34,27467
37
+ fastmcp-2.1.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
38
+ fastmcp-2.1.2.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
39
+ fastmcp-2.1.2.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
40
+ fastmcp-2.1.2.dist-info/RECORD,,