fastmcp 2.2.3__py3-none-any.whl → 2.2.4__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/cli.py +36 -3
- fastmcp/resources/template.py +1 -1
- fastmcp/server/openapi.py +13 -59
- fastmcp/utilities/func_metadata.py +16 -4
- {fastmcp-2.2.3.dist-info → fastmcp-2.2.4.dist-info}/METADATA +2 -2
- {fastmcp-2.2.3.dist-info → fastmcp-2.2.4.dist-info}/RECORD +9 -9
- {fastmcp-2.2.3.dist-info → fastmcp-2.2.4.dist-info}/WHEEL +0 -0
- {fastmcp-2.2.3.dist-info → fastmcp-2.2.4.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.2.3.dist-info → fastmcp-2.2.4.dist-info}/licenses/LICENSE +0 -0
fastmcp/cli/cli.py
CHANGED
|
@@ -223,6 +223,27 @@ def dev(
|
|
|
223
223
|
help="Additional packages to install",
|
|
224
224
|
),
|
|
225
225
|
] = [],
|
|
226
|
+
inspector_version: Annotated[
|
|
227
|
+
str | None,
|
|
228
|
+
typer.Option(
|
|
229
|
+
"--inspector-version",
|
|
230
|
+
help="Version of the MCP Inspector to use",
|
|
231
|
+
),
|
|
232
|
+
] = None,
|
|
233
|
+
ui_port: Annotated[
|
|
234
|
+
int | None,
|
|
235
|
+
typer.Option(
|
|
236
|
+
"--ui-port",
|
|
237
|
+
help="Port for the MCP Inspector UI",
|
|
238
|
+
),
|
|
239
|
+
] = None,
|
|
240
|
+
server_port: Annotated[
|
|
241
|
+
int | None,
|
|
242
|
+
typer.Option(
|
|
243
|
+
"--server-port",
|
|
244
|
+
help="Port for the MCP Inspector Proxy server",
|
|
245
|
+
),
|
|
246
|
+
] = None,
|
|
226
247
|
) -> None:
|
|
227
248
|
"""Run a MCP server with the MCP Inspector."""
|
|
228
249
|
file, server_object = _parse_file_path(file_spec)
|
|
@@ -234,6 +255,8 @@ def dev(
|
|
|
234
255
|
"server_object": server_object,
|
|
235
256
|
"with_editable": str(with_editable) if with_editable else None,
|
|
236
257
|
"with_packages": with_packages,
|
|
258
|
+
"ui_port": ui_port,
|
|
259
|
+
"server_port": server_port,
|
|
237
260
|
},
|
|
238
261
|
)
|
|
239
262
|
|
|
@@ -243,7 +266,11 @@ def dev(
|
|
|
243
266
|
if hasattr(server, "dependencies"):
|
|
244
267
|
with_packages = list(set(with_packages + server.dependencies))
|
|
245
268
|
|
|
246
|
-
|
|
269
|
+
env_vars = {}
|
|
270
|
+
if ui_port:
|
|
271
|
+
env_vars["CLIENT_PORT"] = str(ui_port)
|
|
272
|
+
if server_port:
|
|
273
|
+
env_vars["SERVER_PORT"] = str(server_port)
|
|
247
274
|
|
|
248
275
|
# Get the correct npx command
|
|
249
276
|
npx_cmd = _get_npx_command()
|
|
@@ -254,13 +281,19 @@ def dev(
|
|
|
254
281
|
)
|
|
255
282
|
sys.exit(1)
|
|
256
283
|
|
|
284
|
+
inspector_cmd = "@modelcontextprotocol/inspector"
|
|
285
|
+
if inspector_version:
|
|
286
|
+
inspector_cmd += f"@{inspector_version}"
|
|
287
|
+
|
|
288
|
+
uv_cmd = _build_uv_command(file_spec, with_editable, with_packages)
|
|
289
|
+
|
|
257
290
|
# Run the MCP Inspector command with shell=True on Windows
|
|
258
291
|
shell = sys.platform == "win32"
|
|
259
292
|
process = subprocess.run(
|
|
260
|
-
[npx_cmd,
|
|
293
|
+
[npx_cmd, inspector_cmd] + uv_cmd,
|
|
261
294
|
check=True,
|
|
262
295
|
shell=shell,
|
|
263
|
-
env=dict(os.environ.items())
|
|
296
|
+
env=dict(os.environ.items()) | env_vars,
|
|
264
297
|
)
|
|
265
298
|
sys.exit(process.returncode)
|
|
266
299
|
except subprocess.CalledProcessError as e:
|
fastmcp/resources/template.py
CHANGED
|
@@ -95,7 +95,7 @@ class ResourceTemplate(BaseModel):
|
|
|
95
95
|
raise ValueError("You must provide a name for lambda functions")
|
|
96
96
|
|
|
97
97
|
# Validate that URI params match function params
|
|
98
|
-
uri_params = set(re.findall(r"{(\w+)}", uri_template))
|
|
98
|
+
uri_params = set(re.findall(r"{(\w+)(?:\*)?}", uri_template))
|
|
99
99
|
if not uri_params:
|
|
100
100
|
raise ValueError("URI template must contain at least one parameter")
|
|
101
101
|
|
fastmcp/server/openapi.py
CHANGED
|
@@ -257,7 +257,7 @@ class OpenAPIResource(Resource):
|
|
|
257
257
|
self._client = client
|
|
258
258
|
self._route = route
|
|
259
259
|
|
|
260
|
-
async def read(self) -> str:
|
|
260
|
+
async def read(self) -> str | bytes:
|
|
261
261
|
"""Fetch the resource data by making an HTTP request."""
|
|
262
262
|
try:
|
|
263
263
|
# Extract path parameters from the URI if present
|
|
@@ -297,15 +297,16 @@ class OpenAPIResource(Resource):
|
|
|
297
297
|
# Raise for 4xx/5xx responses
|
|
298
298
|
response.raise_for_status()
|
|
299
299
|
|
|
300
|
-
#
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
else:
|
|
300
|
+
# Determine content type and return appropriate format
|
|
301
|
+
content_type = response.headers.get("content-type", "").lower()
|
|
302
|
+
|
|
303
|
+
if "application/json" in content_type:
|
|
304
|
+
result = response.json()
|
|
305
|
+
return json.dumps(result)
|
|
306
|
+
elif any(ct in content_type for ct in ["text/", "application/xml"]):
|
|
308
307
|
return response.text
|
|
308
|
+
else:
|
|
309
|
+
return response.content
|
|
309
310
|
|
|
310
311
|
except httpx.HTTPStatusError as e:
|
|
311
312
|
# Handle HTTP errors (4xx, 5xx)
|
|
@@ -343,59 +344,13 @@ class OpenAPIResourceTemplate(ResourceTemplate):
|
|
|
343
344
|
uri_template=uri_template,
|
|
344
345
|
name=name,
|
|
345
346
|
description=description,
|
|
346
|
-
fn=
|
|
347
|
+
fn=lambda **kwargs: None,
|
|
347
348
|
parameters=parameters,
|
|
348
349
|
tags=tags,
|
|
349
350
|
)
|
|
350
351
|
self._client = client
|
|
351
352
|
self._route = route
|
|
352
353
|
|
|
353
|
-
async def _create_resource_fn(self, **kwargs):
|
|
354
|
-
"""Create a resource with parameters."""
|
|
355
|
-
# Prepare the path with parameters
|
|
356
|
-
path = self._route.path
|
|
357
|
-
for param_name, param_value in kwargs.items():
|
|
358
|
-
path = path.replace(f"{{{param_name}}}", str(param_value))
|
|
359
|
-
|
|
360
|
-
try:
|
|
361
|
-
response = await self._client.request(
|
|
362
|
-
method=self._route.method,
|
|
363
|
-
url=path,
|
|
364
|
-
timeout=30.0, # Default timeout
|
|
365
|
-
)
|
|
366
|
-
|
|
367
|
-
# Raise for 4xx/5xx responses
|
|
368
|
-
response.raise_for_status()
|
|
369
|
-
|
|
370
|
-
# Determine the mime type from the response
|
|
371
|
-
content_type = response.headers.get("content-type", "application/json")
|
|
372
|
-
mime_type = content_type.split(";")[0].strip()
|
|
373
|
-
|
|
374
|
-
# Return the appropriate data
|
|
375
|
-
if mime_type == "application/json":
|
|
376
|
-
try:
|
|
377
|
-
return response.json()
|
|
378
|
-
except (json.JSONDecodeError, ValueError):
|
|
379
|
-
return response.text
|
|
380
|
-
else:
|
|
381
|
-
return response.text
|
|
382
|
-
|
|
383
|
-
except httpx.HTTPStatusError as e:
|
|
384
|
-
error_message = (
|
|
385
|
-
f"HTTP error {e.response.status_code}: {e.response.reason_phrase}"
|
|
386
|
-
)
|
|
387
|
-
try:
|
|
388
|
-
error_data = e.response.json()
|
|
389
|
-
error_message += f" - {error_data}"
|
|
390
|
-
except (json.JSONDecodeError, ValueError):
|
|
391
|
-
if e.response.text:
|
|
392
|
-
error_message += f" - {e.response.text}"
|
|
393
|
-
|
|
394
|
-
raise ValueError(error_message)
|
|
395
|
-
|
|
396
|
-
except httpx.RequestError as e:
|
|
397
|
-
raise ValueError(f"Request error: {str(e)}")
|
|
398
|
-
|
|
399
354
|
async def create_resource(self, uri: str, params: dict[str, Any]) -> Resource:
|
|
400
355
|
"""Create a resource with the given parameters."""
|
|
401
356
|
# Generate a URI for this resource instance
|
|
@@ -409,9 +364,8 @@ class OpenAPIResourceTemplate(ResourceTemplate):
|
|
|
409
364
|
route=self._route,
|
|
410
365
|
uri=uri,
|
|
411
366
|
name=f"{self.name}-{'-'.join(uri_parts)}",
|
|
412
|
-
description=self.description
|
|
413
|
-
|
|
414
|
-
mime_type="application/json", # Default, will be updated when read
|
|
367
|
+
description=self.description or f"Resource for {self._route.path}",
|
|
368
|
+
mime_type="application/json",
|
|
415
369
|
tags=set(self._route.tags or []),
|
|
416
370
|
)
|
|
417
371
|
|
|
@@ -7,7 +7,15 @@ from typing import (
|
|
|
7
7
|
ForwardRef,
|
|
8
8
|
)
|
|
9
9
|
|
|
10
|
-
from pydantic import
|
|
10
|
+
from pydantic import (
|
|
11
|
+
BaseModel,
|
|
12
|
+
ConfigDict,
|
|
13
|
+
Field,
|
|
14
|
+
TypeAdapter,
|
|
15
|
+
ValidationError,
|
|
16
|
+
WithJsonSchema,
|
|
17
|
+
create_model,
|
|
18
|
+
)
|
|
11
19
|
from pydantic._internal._typing_extra import eval_type_backport
|
|
12
20
|
from pydantic.fields import FieldInfo
|
|
13
21
|
from pydantic_core import PydanticUndefined
|
|
@@ -80,14 +88,18 @@ class FuncMetadata(BaseModel):
|
|
|
80
88
|
dicts (JSON objects) as JSON strings, which can be pre-parsed here.
|
|
81
89
|
"""
|
|
82
90
|
new_data = data.copy() # Shallow copy
|
|
83
|
-
for field_name,
|
|
91
|
+
for field_name, field_info in self.arg_model.model_fields.items():
|
|
84
92
|
if field_name not in data.keys():
|
|
85
93
|
continue
|
|
86
94
|
if isinstance(data[field_name], str):
|
|
87
95
|
try:
|
|
88
96
|
pre_parsed = json.loads(data[field_name])
|
|
89
|
-
|
|
90
|
-
|
|
97
|
+
|
|
98
|
+
# Check if the pre_parsed value is valid for the field
|
|
99
|
+
validator = TypeAdapter(field_info.annotation)
|
|
100
|
+
validator.validate_python(pre_parsed)
|
|
101
|
+
except (json.JSONDecodeError, ValidationError):
|
|
102
|
+
continue # Not JSON or invalid for the field
|
|
91
103
|
if isinstance(pre_parsed, str | int | float):
|
|
92
104
|
# This is likely that the raw value is e.g. `"hello"` which we
|
|
93
105
|
# Should really be parsed as '"hello"' in Python - but if we parse
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastmcp
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.4
|
|
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
|
|
@@ -141,7 +141,7 @@ FastMCP aims to be:
|
|
|
141
141
|
### Servers
|
|
142
142
|
- **Create** servers with minimal boilerplate using intuitive decorators
|
|
143
143
|
- **Proxy** existing servers to modify configuration or transport
|
|
144
|
-
- **Compose**
|
|
144
|
+
- **Compose** servers into complex applications
|
|
145
145
|
- **Generate** servers from OpenAPI specs or FastAPI objects
|
|
146
146
|
|
|
147
147
|
### Clients
|
|
@@ -4,7 +4,7 @@ fastmcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
4
4
|
fastmcp/settings.py,sha256=VCjc-3011pKRYjt2h9rZ68XhVEekbpyLyVUREVBTSrg,1955
|
|
5
5
|
fastmcp/cli/__init__.py,sha256=Ii284TNoG5lxTP40ETMGhHEq3lQZWxu9m9JuU57kUpQ,87
|
|
6
6
|
fastmcp/cli/claude.py,sha256=IAlcZ4qZKBBj09jZUMEx7EANZE_IR3vcu7zOBJmMOuU,4567
|
|
7
|
-
fastmcp/cli/cli.py,sha256=
|
|
7
|
+
fastmcp/cli/cli.py,sha256=wsFIYTv48_nr0mcW8vjI1l7_thr4cOrRxzo9g-qr2l8,15726
|
|
8
8
|
fastmcp/client/__init__.py,sha256=BXO9NUhntZ5GnUACfaRCzDJ5IzxqFJs8qKG-CRMSco4,490
|
|
9
9
|
fastmcp/client/base.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
10
10
|
fastmcp/client/client.py,sha256=XXpN28epV9N5w-keQSDPBCdLFtZ5EPK2msxuQ6PxTmo,7732
|
|
@@ -26,11 +26,11 @@ fastmcp/prompts/prompt_manager.py,sha256=tMob9a-igjuzf6oTPLPGidFpJdg5JaPJVlYgyNk
|
|
|
26
26
|
fastmcp/resources/__init__.py,sha256=t0x1j8lc74rjUKtXe9H5Gs4fpQt82K4NgBK6Y7A0xTg,467
|
|
27
27
|
fastmcp/resources/resource.py,sha256=5FN2a7dpNwf7FSEYTNvQvkTxtodu1OPxSlJL-U-8yrM,2413
|
|
28
28
|
fastmcp/resources/resource_manager.py,sha256=_0itubfjYvfkA_wXKa4DQN5YpE7ejXhsE1hdt7m8XwU,9072
|
|
29
|
-
fastmcp/resources/template.py,sha256=
|
|
29
|
+
fastmcp/resources/template.py,sha256=3JL3I_pommv-K8Q0NLVMfbGAXtL7iuBaV-2yyUMxgnE,5832
|
|
30
30
|
fastmcp/resources/types.py,sha256=tigil7z-SUJMakGXzDLIGSqTepPrAsRpwqwtBA4yoUY,6168
|
|
31
31
|
fastmcp/server/__init__.py,sha256=pdkghG11VLMZiluQ-4_rl2JK1LMWmV003m9dDRUN8W4,92
|
|
32
32
|
fastmcp/server/context.py,sha256=s1885AZRipKB3VltfaO3VEtMxGefKs8fdZByj-4tbNI,7120
|
|
33
|
-
fastmcp/server/openapi.py,sha256=
|
|
33
|
+
fastmcp/server/openapi.py,sha256=44jqx5Y08BiQX4Ftd2ZCncOnt8wt9aNuBzLUXBYMc3A,20750
|
|
34
34
|
fastmcp/server/proxy.py,sha256=JHbxnOKbxyD5Jg2M_zSlNGKVBSZ5NUlVhQoKf442wxo,9619
|
|
35
35
|
fastmcp/server/server.py,sha256=PFhnwa24diSKCz8KO39q43yuSHSbqYrzgnSspc-SPfg,31721
|
|
36
36
|
fastmcp/tools/__init__.py,sha256=ocw-SFTtN6vQ8fgnlF8iNAOflRmh79xS1xdO0Bc3QPE,96
|
|
@@ -38,12 +38,12 @@ fastmcp/tools/tool.py,sha256=hAdeQaJ-1AgPyVZPvCQKYFkK0opccJWa39xWGFAWlzA,5975
|
|
|
38
38
|
fastmcp/tools/tool_manager.py,sha256=hClv7fwj0cQSSwW0i-Swt7xiVqR4T9LVmr1Tp704nW4,3283
|
|
39
39
|
fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
|
|
40
40
|
fastmcp/utilities/decorators.py,sha256=AjhjsetQZF4YOPV5MTZmIxO21iFp_4fDIS3O2_KNCEg,2990
|
|
41
|
-
fastmcp/utilities/func_metadata.py,sha256=
|
|
41
|
+
fastmcp/utilities/func_metadata.py,sha256=iYXnx7MILOSL8mUQ6Rtq_6U7qA08OkoEN2APY802hJg,8141
|
|
42
42
|
fastmcp/utilities/logging.py,sha256=zav8pnFxG_fvGJHUV2XpobmT9WVrmv1mlQBSCz-CPx4,1159
|
|
43
43
|
fastmcp/utilities/openapi.py,sha256=PrH3usbTblaVC6jIH1UGiPEfgB2sSCLj33zA5dH7o_s,45193
|
|
44
44
|
fastmcp/utilities/types.py,sha256=m2rPYMzO-ZFvvZ46N-1-Xqyw693K7yq9Z2xR4pVELyk,2091
|
|
45
|
-
fastmcp-2.2.
|
|
46
|
-
fastmcp-2.2.
|
|
47
|
-
fastmcp-2.2.
|
|
48
|
-
fastmcp-2.2.
|
|
49
|
-
fastmcp-2.2.
|
|
45
|
+
fastmcp-2.2.4.dist-info/METADATA,sha256=3qgwiPo7x3pPDatCPYyvKwPfRRmtd_INFQclHkfNxDY,27769
|
|
46
|
+
fastmcp-2.2.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
47
|
+
fastmcp-2.2.4.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
|
|
48
|
+
fastmcp-2.2.4.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
49
|
+
fastmcp-2.2.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|