fastmcp 0.3.3__py3-none-any.whl → 0.3.5__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/cli/claude.py CHANGED
@@ -41,14 +41,31 @@ def update_claude_config(
41
41
  with_packages: Optional list of additional packages to install
42
42
  env_vars: Optional dictionary of environment variables. These are merged with
43
43
  any existing variables, with new values taking precedence.
44
+
45
+ Raises:
46
+ RuntimeError: If Claude Desktop's config directory is not found, indicating
47
+ Claude Desktop may not be installed or properly set up.
44
48
  """
45
49
  config_dir = get_claude_config_path()
46
50
  if not config_dir:
47
- return False
51
+ raise RuntimeError(
52
+ "Claude Desktop config directory not found. Please ensure Claude Desktop "
53
+ "is installed and has been run at least once to initialize its configuration."
54
+ )
48
55
 
49
56
  config_file = config_dir / "claude_desktop_config.json"
50
57
  if not config_file.exists():
51
- return False
58
+ try:
59
+ config_file.write_text("{}")
60
+ except Exception as e:
61
+ logger.error(
62
+ "Failed to create Claude config file",
63
+ extra={
64
+ "error": str(e),
65
+ "config_file": str(config_file),
66
+ },
67
+ )
68
+ return False
52
69
 
53
70
  try:
54
71
  config = json.loads(config_file.read_text())
fastmcp/cli/cli.py CHANGED
@@ -11,8 +11,8 @@ import typer
11
11
  from typing_extensions import Annotated
12
12
  import dotenv
13
13
 
14
- from ..utilities.logging import get_logger
15
- from . import claude
14
+ from fastmcp.cli import claude
15
+ from fastmcp.utilities.logging import get_logger
16
16
 
17
17
  logger = get_logger("cli")
18
18
 
@@ -24,6 +24,22 @@ app = typer.Typer(
24
24
  )
25
25
 
26
26
 
27
+ def _get_npx_command():
28
+ """Get the correct npx command for the current platform."""
29
+ if sys.platform == "win32":
30
+ # Try both npx.cmd and npx.exe on Windows
31
+ for cmd in ["npx.cmd", "npx.exe", "npx"]:
32
+ try:
33
+ subprocess.run(
34
+ [cmd, "--version"], check=True, capture_output=True, shell=True
35
+ )
36
+ return cmd
37
+ except subprocess.CalledProcessError:
38
+ continue
39
+ return None
40
+ return "npx" # On Unix-like systems, just use npx
41
+
42
+
27
43
  def _parse_env_var(env_var: str) -> Tuple[str, str]:
28
44
  """Parse environment variable string in format KEY=VALUE."""
29
45
  if "=" not in env_var:
@@ -67,11 +83,17 @@ def _parse_file_path(file_spec: str) -> Tuple[Path, Optional[str]]:
67
83
  Returns:
68
84
  Tuple of (file_path, server_object)
69
85
  """
70
- if ":" in file_spec:
86
+ # First check if we have a Windows path (e.g., C:\...)
87
+ has_windows_drive = len(file_spec) > 1 and file_spec[1] == ":"
88
+
89
+ # Split on the last colon, but only if it's not part of the Windows drive letter
90
+ # and there's actually another colon in the string after the drive letter
91
+ if ":" in (file_spec[2:] if has_windows_drive else file_spec):
71
92
  file_str, server_object = file_spec.rsplit(":", 1)
72
93
  else:
73
94
  file_str, server_object = file_spec, None
74
95
 
96
+ # Resolve the file path
75
97
  file_path = Path(file_str).expanduser().resolve()
76
98
  if not file_path.exists():
77
99
  logger.error(f"File not found: {file_path}")
@@ -93,6 +115,11 @@ def _import_server(file: Path, server_object: Optional[str] = None):
93
115
  Returns:
94
116
  The server object
95
117
  """
118
+ # Add parent directory to Python path so imports can be resolved
119
+ file_dir = str(file.parent)
120
+ if file_dir not in sys.path:
121
+ sys.path.insert(0, file_dir)
122
+
96
123
  # Import the module
97
124
  spec = importlib.util.spec_from_file_location("server_module", file)
98
125
  if not spec or not spec.loader:
@@ -199,10 +226,22 @@ def dev(
199
226
  with_packages = list(set(with_packages + server.dependencies))
200
227
 
201
228
  uv_cmd = _build_uv_command(file_spec, with_editable, with_packages)
202
- # Run the MCP Inspector command
229
+
230
+ # Get the correct npx command
231
+ npx_cmd = _get_npx_command()
232
+ if not npx_cmd:
233
+ logger.error(
234
+ "npx not found. Please ensure Node.js and npm are properly installed "
235
+ "and added to your system PATH."
236
+ )
237
+ sys.exit(1)
238
+
239
+ # Run the MCP Inspector command with shell=True on Windows
240
+ shell = sys.platform == "win32"
203
241
  process = subprocess.run(
204
- ["npx", "@modelcontextprotocol/inspector"] + uv_cmd,
242
+ [npx_cmd, "@modelcontextprotocol/inspector"] + uv_cmd,
205
243
  check=True,
244
+ shell=shell,
206
245
  )
207
246
  sys.exit(process.returncode)
208
247
  except subprocess.CalledProcessError as e:
@@ -217,7 +256,9 @@ def dev(
217
256
  sys.exit(e.returncode)
218
257
  except FileNotFoundError:
219
258
  logger.error(
220
- "npx not found. Please install Node.js and npm.",
259
+ "npx not found. Please ensure Node.js and npm are properly installed "
260
+ "and added to your system PATH. You may need to restart your terminal "
261
+ "after installation.",
221
262
  extra={"file": str(file)},
222
263
  )
223
264
  sys.exit(1)
fastmcp/exceptions.py CHANGED
@@ -15,3 +15,7 @@ class ResourceError(FastMCPError):
15
15
 
16
16
  class ToolError(FastMCPError):
17
17
  """Error in tool operations."""
18
+
19
+
20
+ class InvalidSignature(Exception):
21
+ """Invalid signature for use with FastMCP."""
fastmcp/resources/base.py CHANGED
@@ -1,34 +1,17 @@
1
1
  """Base classes and interfaces for FastMCP resources."""
2
2
 
3
3
  import abc
4
- from typing import Annotated, Union
4
+ from typing import Union
5
5
 
6
6
  from pydantic import (
7
7
  AnyUrl,
8
8
  BaseModel,
9
- BeforeValidator,
10
9
  ConfigDict,
11
10
  Field,
12
11
  FileUrl,
13
12
  ValidationInfo,
14
13
  field_validator,
15
14
  )
16
- from pydantic.networks import _BaseUrl # TODO: remove this once pydantic is updated
17
-
18
-
19
- def maybe_cast_str_to_any_url(x) -> AnyUrl:
20
- if isinstance(x, FileUrl):
21
- return x
22
- elif isinstance(x, AnyUrl):
23
- return x
24
- elif isinstance(x, str):
25
- if x.startswith("file://"):
26
- return FileUrl(x)
27
- return AnyUrl(x)
28
- raise ValueError(f"Expected str or AnyUrl, got {type(x)}")
29
-
30
-
31
- LaxAnyUrl = Annotated[_BaseUrl | str, BeforeValidator(maybe_cast_str_to_any_url)]
32
15
 
33
16
 
34
17
  class Resource(BaseModel, abc.ABC):
@@ -36,7 +19,8 @@ class Resource(BaseModel, abc.ABC):
36
19
 
37
20
  model_config = ConfigDict(validate_default=True)
38
21
 
39
- uri: LaxAnyUrl = Field(default=..., description="URI of the resource")
22
+ # uri: Annotated[AnyUrl, BeforeValidator(maybe_cast_str_to_any_url)] = Field(
23
+ uri: AnyUrl = Field(default=..., description="URI of the resource")
40
24
  name: str | None = Field(description="Name of the resource", default=None)
41
25
  description: str | None = Field(
42
26
  description="Description of the resource", default=None
@@ -47,6 +31,15 @@ class Resource(BaseModel, abc.ABC):
47
31
  pattern=r"^[a-zA-Z0-9]+/[a-zA-Z0-9\-+.]+$",
48
32
  )
49
33
 
34
+ @field_validator("uri", mode="before")
35
+ def validate_uri(cls, uri: AnyUrl | str) -> AnyUrl:
36
+ if isinstance(uri, str):
37
+ # AnyUrl doesn't support triple-slashes, but files do ("file:///absolute/path")
38
+ if uri.startswith("file://"):
39
+ return FileUrl(uri)
40
+ return AnyUrl(uri)
41
+ return uri
42
+
50
43
  @field_validator("name", mode="before")
51
44
  @classmethod
52
45
  def set_default_name(cls, name: str | None, info: ValidationInfo) -> str:
fastmcp/tools/base.py CHANGED
@@ -1,8 +1,8 @@
1
1
  import fastmcp
2
2
  from fastmcp.exceptions import ToolError
3
3
 
4
-
5
- from pydantic import BaseModel, Field, TypeAdapter, validate_call
4
+ from fastmcp.utilities.func_metadata import func_metadata, FuncMetadata
5
+ from pydantic import BaseModel, Field
6
6
 
7
7
 
8
8
  import inspect
@@ -19,6 +19,9 @@ class Tool(BaseModel):
19
19
  name: str = Field(description="Name of the tool")
20
20
  description: str = Field(description="Description of what the tool does")
21
21
  parameters: dict = Field(description="JSON schema for tool parameters")
22
+ fn_metadata: FuncMetadata = Field(
23
+ description="Metadata about the function including a pydantic model for tool arguments"
24
+ )
22
25
  is_async: bool = Field(description="Whether the tool is async")
23
26
  context_kwarg: Optional[str] = Field(
24
27
  None, description="Name of the kwarg that should receive context"
@@ -41,9 +44,6 @@ class Tool(BaseModel):
41
44
  func_doc = description or fn.__doc__ or ""
42
45
  is_async = inspect.iscoroutinefunction(fn)
43
46
 
44
- # Get schema from TypeAdapter - will fail if function isn't properly typed
45
- parameters = TypeAdapter(fn).json_schema()
46
-
47
47
  # Find context parameter if it exists
48
48
  if context_kwarg is None:
49
49
  sig = inspect.signature(fn)
@@ -52,14 +52,18 @@ class Tool(BaseModel):
52
52
  context_kwarg = param_name
53
53
  break
54
54
 
55
- # ensure the arguments are properly cast
56
- fn = validate_call(fn)
55
+ func_arg_metadata = func_metadata(
56
+ fn,
57
+ skip_names=[context_kwarg] if context_kwarg is not None else [],
58
+ )
59
+ parameters = func_arg_metadata.arg_model.model_json_schema()
57
60
 
58
61
  return cls(
59
62
  fn=fn,
60
63
  name=func_name,
61
64
  description=func_doc,
62
65
  parameters=parameters,
66
+ fn_metadata=func_arg_metadata,
63
67
  is_async=is_async,
64
68
  context_kwarg=context_kwarg,
65
69
  )
@@ -67,13 +71,13 @@ class Tool(BaseModel):
67
71
  async def run(self, arguments: dict, context: Optional["Context"] = None) -> Any:
68
72
  """Run the tool with arguments."""
69
73
  try:
70
- # Inject context if needed
71
- if self.context_kwarg:
72
- arguments[self.context_kwarg] = context
73
-
74
- # Call function with proper async handling
75
- if self.is_async:
76
- return await self.fn(**arguments)
77
- return self.fn(**arguments)
74
+ return await self.fn_metadata.call_fn_with_arg_validation(
75
+ self.fn,
76
+ self.is_async,
77
+ arguments,
78
+ {self.context_kwarg: context}
79
+ if self.context_kwarg is not None
80
+ else None,
81
+ )
78
82
  except Exception as e:
79
83
  raise ToolError(f"Error executing tool {self.name}: {e}") from e
@@ -0,0 +1,200 @@
1
+ import inspect
2
+ from collections.abc import Callable, Sequence, Awaitable
3
+ from typing import (
4
+ Annotated,
5
+ Any,
6
+ Dict,
7
+ ForwardRef,
8
+ )
9
+ from pydantic import Field
10
+ from fastmcp.exceptions import InvalidSignature
11
+ from pydantic._internal._typing_extra import try_eval_type
12
+ import json
13
+ from pydantic import BaseModel
14
+ from pydantic.fields import FieldInfo
15
+ from pydantic import ConfigDict, create_model
16
+ from pydantic import WithJsonSchema
17
+ from pydantic_core import PydanticUndefined
18
+ from fastmcp.utilities.logging import get_logger
19
+
20
+
21
+ logger = get_logger(__name__)
22
+
23
+
24
+ class ArgModelBase(BaseModel):
25
+ """A model representing the arguments to a function."""
26
+
27
+ def model_dump_one_level(self) -> dict[str, Any]:
28
+ """Return a dict of the model's fields, one level deep.
29
+
30
+ That is, sub-models etc are not dumped - they are kept as pydantic models.
31
+ """
32
+ kwargs: dict[str, Any] = {}
33
+ for field_name in self.model_fields.keys():
34
+ kwargs[field_name] = getattr(self, field_name)
35
+ return kwargs
36
+
37
+ model_config = ConfigDict(
38
+ arbitrary_types_allowed=True,
39
+ )
40
+
41
+
42
+ class FuncMetadata(BaseModel):
43
+ arg_model: Annotated[type[ArgModelBase], WithJsonSchema(None)]
44
+ # We can add things in the future like
45
+ # - Maybe some args are excluded from attempting to parse from JSON
46
+ # - Maybe some args are special (like context) for dependency injection
47
+
48
+ async def call_fn_with_arg_validation(
49
+ self,
50
+ fn: Callable | Awaitable,
51
+ fn_is_async: bool,
52
+ arguments_to_validate: dict[str, Any],
53
+ arguments_to_pass_directly: dict[str, Any] | None,
54
+ ) -> Any:
55
+ """Call the given function with arguments validated and injected.
56
+
57
+ Arguments are first attempted to be parsed from JSON, then validated against
58
+ the argument model, before being passed to the function.
59
+ """
60
+ arguments_pre_parsed = self.pre_parse_json(arguments_to_validate)
61
+ arguments_parsed_model = self.arg_model.model_validate(arguments_pre_parsed)
62
+ arguments_parsed_dict = arguments_parsed_model.model_dump_one_level()
63
+
64
+ arguments_parsed_dict |= arguments_to_pass_directly or {}
65
+
66
+ if fn_is_async:
67
+ return await fn(**arguments_parsed_dict)
68
+ return fn(**arguments_parsed_dict)
69
+
70
+ def pre_parse_json(self, data: dict[str, Any]) -> dict[str, Any]:
71
+ """Pre-parse data from JSON.
72
+
73
+ Return a dict with same keys as input but with values parsed from JSON
74
+ if appropriate.
75
+
76
+ This is to handle cases like `["a", "b", "c"]` being passed in as JSON inside
77
+ a string rather than an actual list. Claude desktop is prone to this - in fact
78
+ it seems incapable of NOT doing this. For sub-models, it tends to pass
79
+ dicts (JSON objects) as JSON strings, which can be pre-parsed here.
80
+ """
81
+ new_data = data.copy() # Shallow copy
82
+ for field_name, field_info in self.arg_model.model_fields.items():
83
+ if field_name not in data.keys():
84
+ continue
85
+ if isinstance(data[field_name], str):
86
+ try:
87
+ pre_parsed = json.loads(data[field_name])
88
+ except json.JSONDecodeError:
89
+ continue # Not JSON - skip
90
+ if isinstance(pre_parsed, str):
91
+ # This is likely that the raw value is e.g. `"hello"` which we
92
+ # Should really be parsed as '"hello"' in Python - but if we parse
93
+ # it as JSON it'll turn into just 'hello'. So we skip it.
94
+ continue
95
+ new_data[field_name] = pre_parsed
96
+ assert new_data.keys() == data.keys()
97
+ return new_data
98
+
99
+ model_config = ConfigDict(
100
+ arbitrary_types_allowed=True,
101
+ )
102
+
103
+
104
+ def func_metadata(func: Callable, skip_names: Sequence[str] = ()) -> FuncMetadata:
105
+ """Given a function, return metadata including a pydantic model representing its signature.
106
+
107
+ The use case for this is
108
+ ```
109
+ meta = func_to_pyd(func)
110
+ validated_args = meta.arg_model.model_validate(some_raw_data_dict)
111
+ return func(**validated_args.model_dump_one_level())
112
+ ```
113
+
114
+ **critically** it also provides pre-parse helper to attempt to parse things from JSON.
115
+
116
+ Args:
117
+ func: The function to convert to a pydantic model
118
+ skip_names: A list of parameter names to skip. These will not be included in
119
+ the model.
120
+ Returns:
121
+ A pydantic model representing the function's signature.
122
+ """
123
+ sig = _get_typed_signature(func)
124
+ params = sig.parameters
125
+ dynamic_pydantic_model_params: dict[str, Any] = {}
126
+ for param in params.values():
127
+ if param.name.startswith("_"):
128
+ raise InvalidSignature(
129
+ f"Parameter {param.name} of {func.__name__} may not start with an underscore"
130
+ )
131
+ if param.name in skip_names:
132
+ continue
133
+ annotation = param.annotation
134
+
135
+ # `x: None` / `x: None = None`
136
+ if annotation is None:
137
+ annotation = Annotated[
138
+ None,
139
+ Field(
140
+ default=param.default
141
+ if param.default is not inspect.Parameter.empty
142
+ else PydanticUndefined
143
+ ),
144
+ ]
145
+
146
+ # Untyped field
147
+ if annotation is inspect.Parameter.empty:
148
+ annotation = Annotated[
149
+ Any,
150
+ Field(),
151
+ # 🤷
152
+ WithJsonSchema({"title": param.name, "type": "string"}),
153
+ ]
154
+
155
+ field_info = FieldInfo.from_annotated_attribute(
156
+ annotation,
157
+ param.default
158
+ if param.default is not inspect.Parameter.empty
159
+ else PydanticUndefined,
160
+ )
161
+ dynamic_pydantic_model_params[param.name] = (field_info.annotation, field_info)
162
+ continue
163
+
164
+ arguments_model = create_model(
165
+ f"{func.__name__}Arguments",
166
+ **dynamic_pydantic_model_params,
167
+ __base__=ArgModelBase,
168
+ )
169
+ resp = FuncMetadata(arg_model=arguments_model)
170
+ return resp
171
+
172
+
173
+ def _get_typed_annotation(annotation: Any, globalns: Dict[str, Any]) -> Any:
174
+ if isinstance(annotation, str):
175
+ annotation = ForwardRef(annotation)
176
+ annotation, status = try_eval_type(annotation, globalns, globalns)
177
+
178
+ # This check and raise could perhaps be skipped, and we (FastMCP) just call
179
+ # model_rebuild right before using it 🤷
180
+ if status is False:
181
+ raise InvalidSignature(f"Unable to evaluate type annotation {annotation}")
182
+
183
+ return annotation
184
+
185
+
186
+ def _get_typed_signature(call: Callable[..., Any]) -> inspect.Signature:
187
+ """Get function signature while evaluating forward references"""
188
+ signature = inspect.signature(call)
189
+ globalns = getattr(call, "__globals__", {})
190
+ typed_params = [
191
+ inspect.Parameter(
192
+ name=param.name,
193
+ kind=param.kind,
194
+ default=param.default,
195
+ annotation=_get_typed_annotation(param.annotation, globalns),
196
+ )
197
+ for param in signature.parameters.values()
198
+ ]
199
+ typed_signature = inspect.Signature(typed_params)
200
+ return typed_signature
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: fastmcp
3
- Version: 0.3.3
3
+ Version: 0.3.5
4
4
  Summary: A more ergonomic interface for MCP servers
5
5
  Author: Jeremiah Lowin
6
6
  License: Apache-2.0
@@ -20,6 +20,12 @@ Requires-Dist: pytest-asyncio>=0.23.5; extra == 'dev'
20
20
  Requires-Dist: pytest-xdist>=3.6.1; extra == 'dev'
21
21
  Requires-Dist: pytest>=8.3.3; extra == 'dev'
22
22
  Requires-Dist: ruff; extra == 'dev'
23
+ Provides-Extra: tests
24
+ Requires-Dist: pre-commit; extra == 'tests'
25
+ Requires-Dist: pytest-asyncio>=0.23.5; extra == 'tests'
26
+ Requires-Dist: pytest-xdist>=3.6.1; extra == 'tests'
27
+ Requires-Dist: pytest>=8.3.3; extra == 'tests'
28
+ Requires-Dist: ruff; extra == 'tests'
23
29
  Description-Content-Type: text/markdown
24
30
 
25
31
  <!-- omit in toc -->
@@ -122,6 +128,8 @@ pip install fastmcp
122
128
  Let's create a simple MCP server that exposes a calculator tool and some data:
123
129
 
124
130
  ```python
131
+ # server.py
132
+
125
133
  from fastmcp import FastMCP
126
134
 
127
135
 
@@ -143,14 +151,12 @@ def get_greeting(name: str) -> str:
143
151
  return f"Hello, {name}!"
144
152
  ```
145
153
 
146
- To use this server, you have two options:
147
-
148
- 1. Install it in [Claude Desktop](https://claude.ai/download):
154
+ You can install this server in [Claude Desktop](https://claude.ai/download) and interact with it right away by running:
149
155
  ```bash
150
156
  fastmcp install server.py
151
157
  ```
152
158
 
153
- 2. Test it with the MCP Inspector:
159
+ Alternatively, you can test it with the MCP Inspector:
154
160
  ```bash
155
161
  fastmcp dev server.py
156
162
  ```
@@ -181,9 +187,6 @@ from fastmcp import FastMCP
181
187
  # Create a named server
182
188
  mcp = FastMCP("My App")
183
189
 
184
- # Configure host/port for HTTP transport (optional)
185
- mcp = FastMCP("My App", host="localhost", port=8000)
186
-
187
190
  # Specify dependencies for deployment and development
188
191
  mcp = FastMCP("My App", dependencies=["pandas", "numpy"])
189
192
  ```
@@ -239,6 +242,27 @@ async def fetch_weather(city: str) -> str:
239
242
  return response.text
240
243
  ```
241
244
 
245
+ Complex input handling example:
246
+ ```python
247
+ from pydantic import BaseModel, Field
248
+ from typing import Annotated
249
+
250
+ class ShrimpTank(BaseModel):
251
+ class Shrimp(BaseModel):
252
+ name: Annotated[str, Field(max_length=10)]
253
+
254
+ shrimp: list[Shrimp]
255
+
256
+ @mcp.tool()
257
+ def name_shrimp(
258
+ tank: ShrimpTank,
259
+ # You can use pydantic Field in function signatures for validation.
260
+ extra_names: Annotated[list[str], Field(max_length=10)],
261
+ ) -> list[str]:
262
+ """List all shrimp names in the tank"""
263
+ return [shrimp.name for shrimp in tank.shrimp] + extra_names
264
+ ```
265
+
242
266
  ### Prompts
243
267
 
244
268
  Prompts are reusable templates that help LLMs interact with your server effectively. They're like "best practices" encoded into your server. A prompt can be as simple as a string:
@@ -491,23 +515,20 @@ FastMCP requires Python 3.10+ and [uv](https://docs.astral.sh/uv/).
491
515
 
492
516
  ### Installation
493
517
 
494
- Create a fork of this repository, then clone it:
518
+ For development, we recommend installing FastMCP with development dependencies, which includes various utilities the maintainers find useful.
495
519
 
496
520
  ```bash
497
- git clone https://github.com/YouFancyUserYou/fastmcp.git
521
+ git clone https://github.com/jlowin/fastmcp.git
498
522
  cd fastmcp
523
+ uv sync --frozen --extra dev
499
524
  ```
500
525
 
501
- Next, create a virtual environment and install FastMCP:
526
+ For running tests only (e.g., in CI), you only need the testing dependencies:
502
527
 
503
528
  ```bash
504
- uv venv
505
- source .venv/bin/activate
506
- uv sync --frozen --all-extras --dev
529
+ uv sync --frozen --extra tests
507
530
  ```
508
531
 
509
-
510
-
511
532
  ### Testing
512
533
 
513
534
  Please make sure to test any new functionality. Your tests should be simple and atomic and anticipate change rather than cement complex patterns.
@@ -1,26 +1,27 @@
1
1
  fastmcp/__init__.py,sha256=Y5dHGBwyQPgNP5gzOyNIItefvMZ3vJLdom1oV8A1u_k,248
2
- fastmcp/exceptions.py,sha256=K0rCgXsUVlws39hz98Tb4BBf_BzIql_zXFZgqbkNTiE,348
2
+ fastmcp/exceptions.py,sha256=q9djUDmpwmGEWcHI8q4UzJBtf7s7UtgL--OB7OaGzyQ,435
3
3
  fastmcp/server.py,sha256=V6BaciC5u9g_XzbvPkafjGO8OtEBPGy_KlV535Au0do,22234
4
4
  fastmcp/cli/__init__.py,sha256=7hrwtCHX9nMd9qcz7R_JFSoqbL71fC35cBLXBS430mg,88
5
- fastmcp/cli/claude.py,sha256=APgTMQm7qa68Fz4EObZYmrgY-8-wjn6vCJZD7Se6XlU,3765
6
- fastmcp/cli/cli.py,sha256=Ub2iNwnCVRO4TbC6Rr_1xOeTKnDN-9XE1z1O1yZatbg,11664
5
+ fastmcp/cli/claude.py,sha256=5SoVEsA_PnOyOe2bcItvfcCwuhfX6W99TP1nXahLIJE,4442
6
+ fastmcp/cli/cli.py,sha256=sb8RC8YnKg9dlH9NHzX3owLYKVC1D8Mt6OhQj_G8sGU,13297
7
7
  fastmcp/prompts/__init__.py,sha256=4BsMxoYolpoxg74xkkkzCFL8vvdkLVJ5cIPNs1ND1Jo,99
8
8
  fastmcp/prompts/base.py,sha256=WaSsfyFSsUPUbcApkGy3Pm-Ne-Gk-5ZwU3efqRYn1mQ,4996
9
9
  fastmcp/prompts/manager.py,sha256=EkexOB_N4QNtC-UlZmIcWcau91ceO2O1K4_kD75pA_A,1485
10
10
  fastmcp/prompts/prompt_manager.py,sha256=5uR14gsi7l0YHwbxFH7N5b_ACHHRWyTtBAH3R0-G5rk,1129
11
11
  fastmcp/resources/__init__.py,sha256=9QShop6ckX3Khh3BQLZNkB6R2ZhwskAcnh7-sIuX-W8,464
12
- fastmcp/resources/base.py,sha256=glGLpHp8Eq-LKq7YLJx3LRDiCktLlLaTTmltNcdWzHg,1818
12
+ fastmcp/resources/base.py,sha256=inm2uhaE6KPQEsMd4SyDLbRIOjQrXahSn84RNT3Y6MY,1723
13
13
  fastmcp/resources/resource_manager.py,sha256=b0PKpG-pKi7x2Yx-qaeknjv0mqL17zixSIYOz2V5G6o,3271
14
14
  fastmcp/resources/templates.py,sha256=EmLlI-ddBBzSTAUiA6-knFnHCE3MPMW2ZoH9WswPKvI,2868
15
15
  fastmcp/resources/types.py,sha256=ofE6bfeQQfPSmaWrLGDf3qjCP0kGjKmvupsHDYkSrj0,5658
16
16
  fastmcp/tools/__init__.py,sha256=ZboxhyMJDl87Svjov8YwNYwNZi55P9VhmpTjBZLesnk,96
17
- fastmcp/tools/base.py,sha256=JPdTx8SAl5pKsHyIVxnsLG88f3fbjnopDTOAZ_PoVQE,2585
17
+ fastmcp/tools/base.py,sha256=aJEOnNUs5KiTC16Szr8AEZyozTtpp0vZXtF5wMIA2mM,2797
18
18
  fastmcp/tools/tool_manager.py,sha256=PT6XHcQWzhdC6kfdsJaddRn7VLps4nAs5FMG8l1j8Zc,1617
19
19
  fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
20
+ fastmcp/utilities/func_metadata.py,sha256=TWxO9dVqdcJp4STGlm-WMekwpENqaGUIETstw_GTJ7U,7225
20
21
  fastmcp/utilities/logging.py,sha256=VLJdNc0tIYoQZmpobehLUnWrQz7NXnuwSqrDlFt2RF0,738
21
22
  fastmcp/utilities/types.py,sha256=jFlZMZsKrJg4NWc1vTBIILLoHpTVwSd-vxO7ycoRuig,1718
22
- fastmcp-0.3.3.dist-info/METADATA,sha256=pDlwjd5p0ErFBvpK2D6u9jAWb4rsGzL9f3l95IWJToc,15319
23
- fastmcp-0.3.3.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
24
- fastmcp-0.3.3.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
25
- fastmcp-0.3.3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
26
- fastmcp-0.3.3.dist-info/RECORD,,
23
+ fastmcp-0.3.5.dist-info/METADATA,sha256=bOVsMw4T9aVzb0AgpyCNIQibIYQPkJIM8Hb_ETfwue8,16174
24
+ fastmcp-0.3.5.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
25
+ fastmcp-0.3.5.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
26
+ fastmcp-0.3.5.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
27
+ fastmcp-0.3.5.dist-info/RECORD,,