fastmcp 2.11.1__py3-none-any.whl → 2.11.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.
- fastmcp/cli/cli.py +5 -5
- fastmcp/cli/install/claude_code.py +2 -2
- fastmcp/cli/install/claude_desktop.py +2 -2
- fastmcp/cli/install/cursor.py +2 -2
- fastmcp/cli/install/mcp_json.py +2 -2
- fastmcp/cli/install/shared.py +2 -2
- fastmcp/cli/run.py +74 -24
- fastmcp/client/logging.py +25 -1
- fastmcp/client/transports.py +4 -3
- fastmcp/experimental/server/openapi/routing.py +1 -1
- fastmcp/experimental/server/openapi/server.py +10 -23
- fastmcp/experimental/utilities/openapi/__init__.py +2 -2
- fastmcp/experimental/utilities/openapi/formatters.py +34 -0
- fastmcp/experimental/utilities/openapi/models.py +5 -2
- fastmcp/experimental/utilities/openapi/parser.py +248 -70
- fastmcp/experimental/utilities/openapi/schemas.py +135 -106
- fastmcp/prompts/prompt_manager.py +2 -2
- fastmcp/resources/resource_manager.py +12 -6
- fastmcp/server/auth/__init__.py +9 -1
- fastmcp/server/auth/auth.py +17 -1
- fastmcp/server/auth/providers/jwt.py +3 -4
- fastmcp/server/auth/registry.py +1 -1
- fastmcp/server/dependencies.py +32 -2
- fastmcp/server/http.py +41 -34
- fastmcp/server/proxy.py +33 -15
- fastmcp/server/server.py +18 -11
- fastmcp/settings.py +6 -9
- fastmcp/tools/tool.py +7 -7
- fastmcp/tools/tool_manager.py +3 -1
- fastmcp/tools/tool_transform.py +41 -27
- fastmcp/utilities/components.py +19 -4
- fastmcp/utilities/inspect.py +12 -17
- fastmcp/utilities/openapi.py +4 -4
- {fastmcp-2.11.1.dist-info → fastmcp-2.11.3.dist-info}/METADATA +2 -2
- {fastmcp-2.11.1.dist-info → fastmcp-2.11.3.dist-info}/RECORD +38 -38
- {fastmcp-2.11.1.dist-info → fastmcp-2.11.3.dist-info}/WHEEL +0 -0
- {fastmcp-2.11.1.dist-info → fastmcp-2.11.3.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.11.1.dist-info → fastmcp-2.11.3.dist-info}/licenses/LICENSE +0 -0
fastmcp/cli/cli.py
CHANGED
|
@@ -138,7 +138,7 @@ def version(
|
|
|
138
138
|
|
|
139
139
|
|
|
140
140
|
@app.command
|
|
141
|
-
def dev(
|
|
141
|
+
async def dev(
|
|
142
142
|
server_spec: str,
|
|
143
143
|
*,
|
|
144
144
|
with_editable: Annotated[
|
|
@@ -220,7 +220,7 @@ def dev(
|
|
|
220
220
|
|
|
221
221
|
try:
|
|
222
222
|
# Import server to get dependencies
|
|
223
|
-
server: FastMCP = run_module.import_server(file, server_object)
|
|
223
|
+
server: FastMCP = await run_module.import_server(file, server_object)
|
|
224
224
|
if server.dependencies is not None:
|
|
225
225
|
with_packages = list(set(with_packages + server.dependencies))
|
|
226
226
|
|
|
@@ -283,7 +283,7 @@ def dev(
|
|
|
283
283
|
|
|
284
284
|
|
|
285
285
|
@app.command
|
|
286
|
-
def run(
|
|
286
|
+
async def run(
|
|
287
287
|
server_spec: str,
|
|
288
288
|
*server_args: str,
|
|
289
289
|
transport: Annotated[
|
|
@@ -414,7 +414,7 @@ def run(
|
|
|
414
414
|
else:
|
|
415
415
|
# Use direct import for backwards compatibility
|
|
416
416
|
try:
|
|
417
|
-
run_module.run_command(
|
|
417
|
+
await run_module.run_command(
|
|
418
418
|
server_spec=server_spec,
|
|
419
419
|
transport=transport,
|
|
420
420
|
host=host,
|
|
@@ -476,7 +476,7 @@ async def inspect(
|
|
|
476
476
|
|
|
477
477
|
try:
|
|
478
478
|
# Import the server
|
|
479
|
-
server = run_module.import_server(file, server_object)
|
|
479
|
+
server = await run_module.import_server(file, server_object)
|
|
480
480
|
|
|
481
481
|
# Get server information - using native async support
|
|
482
482
|
info = await inspect_fastmcp(server)
|
|
@@ -167,7 +167,7 @@ def install_claude_code(
|
|
|
167
167
|
return False
|
|
168
168
|
|
|
169
169
|
|
|
170
|
-
def claude_code_command(
|
|
170
|
+
async def claude_code_command(
|
|
171
171
|
server_spec: str,
|
|
172
172
|
*,
|
|
173
173
|
server_name: Annotated[
|
|
@@ -234,7 +234,7 @@ def claude_code_command(
|
|
|
234
234
|
Args:
|
|
235
235
|
server_spec: Python file to install, optionally with :object suffix
|
|
236
236
|
"""
|
|
237
|
-
file, server_object, name, packages, env_dict = process_common_args(
|
|
237
|
+
file, server_object, name, packages, env_dict = await process_common_args(
|
|
238
238
|
server_spec, server_name, with_packages, env_vars, env_file
|
|
239
239
|
)
|
|
240
240
|
|
|
@@ -140,7 +140,7 @@ def install_claude_desktop(
|
|
|
140
140
|
return False
|
|
141
141
|
|
|
142
142
|
|
|
143
|
-
def claude_desktop_command(
|
|
143
|
+
async def claude_desktop_command(
|
|
144
144
|
server_spec: str,
|
|
145
145
|
*,
|
|
146
146
|
server_name: Annotated[
|
|
@@ -207,7 +207,7 @@ def claude_desktop_command(
|
|
|
207
207
|
Args:
|
|
208
208
|
server_spec: Python file to install, optionally with :object suffix
|
|
209
209
|
"""
|
|
210
|
-
file, server_object, name, with_packages, env_dict = process_common_args(
|
|
210
|
+
file, server_object, name, with_packages, env_dict = await process_common_args(
|
|
211
211
|
server_spec, server_name, with_packages, env_vars, env_file
|
|
212
212
|
)
|
|
213
213
|
|
fastmcp/cli/install/cursor.py
CHANGED
|
@@ -150,7 +150,7 @@ def install_cursor(
|
|
|
150
150
|
return False
|
|
151
151
|
|
|
152
152
|
|
|
153
|
-
def cursor_command(
|
|
153
|
+
async def cursor_command(
|
|
154
154
|
server_spec: str,
|
|
155
155
|
*,
|
|
156
156
|
server_name: Annotated[
|
|
@@ -217,7 +217,7 @@ def cursor_command(
|
|
|
217
217
|
Args:
|
|
218
218
|
server_spec: Python file to install, optionally with :object suffix
|
|
219
219
|
"""
|
|
220
|
-
file, server_object, name, with_packages, env_dict = process_common_args(
|
|
220
|
+
file, server_object, name, with_packages, env_dict = await process_common_args(
|
|
221
221
|
server_spec, server_name, with_packages, env_vars, env_file
|
|
222
222
|
)
|
|
223
223
|
|
fastmcp/cli/install/mcp_json.py
CHANGED
|
@@ -113,7 +113,7 @@ def install_mcp_json(
|
|
|
113
113
|
return False
|
|
114
114
|
|
|
115
115
|
|
|
116
|
-
def mcp_json_command(
|
|
116
|
+
async def mcp_json_command(
|
|
117
117
|
server_spec: str,
|
|
118
118
|
*,
|
|
119
119
|
server_name: Annotated[
|
|
@@ -188,7 +188,7 @@ def mcp_json_command(
|
|
|
188
188
|
Args:
|
|
189
189
|
server_spec: Python file to install, optionally with :object suffix
|
|
190
190
|
"""
|
|
191
|
-
file, server_object, name, packages, env_dict = process_common_args(
|
|
191
|
+
file, server_object, name, packages, env_dict = await process_common_args(
|
|
192
192
|
server_spec, server_name, with_packages, env_vars, env_file
|
|
193
193
|
)
|
|
194
194
|
|
fastmcp/cli/install/shared.py
CHANGED
|
@@ -23,7 +23,7 @@ def parse_env_var(env_var: str) -> tuple[str, str]:
|
|
|
23
23
|
return key.strip(), value.strip()
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def process_common_args(
|
|
26
|
+
async def process_common_args(
|
|
27
27
|
server_spec: str,
|
|
28
28
|
server_name: str | None,
|
|
29
29
|
with_packages: list[str],
|
|
@@ -49,7 +49,7 @@ def process_common_args(
|
|
|
49
49
|
server = None
|
|
50
50
|
if not name:
|
|
51
51
|
try:
|
|
52
|
-
server = import_server(file, server_object)
|
|
52
|
+
server = await import_server(file, server_object)
|
|
53
53
|
name = server.name
|
|
54
54
|
except (ImportError, ModuleNotFoundError) as e:
|
|
55
55
|
logger.debug(
|
fastmcp/cli/run.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""FastMCP run command implementation with enhanced type hints."""
|
|
2
2
|
|
|
3
3
|
import importlib.util
|
|
4
|
+
import inspect
|
|
4
5
|
import json
|
|
5
6
|
import re
|
|
6
7
|
import subprocess
|
|
@@ -58,15 +59,15 @@ def parse_file_path(server_spec: str) -> tuple[Path, str | None]:
|
|
|
58
59
|
return file_path, server_object
|
|
59
60
|
|
|
60
61
|
|
|
61
|
-
def import_server(file: Path,
|
|
62
|
+
async def import_server(file: Path, server_or_factory: str | None = None) -> Any:
|
|
62
63
|
"""Import a MCP server from a file.
|
|
63
64
|
|
|
64
65
|
Args:
|
|
65
66
|
file: Path to the file
|
|
66
|
-
|
|
67
|
+
server_or_factory: Optional object name in format "module:object" or just "object"
|
|
67
68
|
|
|
68
69
|
Returns:
|
|
69
|
-
The server object
|
|
70
|
+
The server object (or result of calling a factory function)
|
|
70
71
|
"""
|
|
71
72
|
# Add parent directory to Python path so imports can be resolved
|
|
72
73
|
file_dir = str(file.parent)
|
|
@@ -86,11 +87,12 @@ def import_server(file: Path, server_object: str | None = None) -> Any:
|
|
|
86
87
|
spec.loader.exec_module(module)
|
|
87
88
|
|
|
88
89
|
# If no object specified, try common server names
|
|
89
|
-
if not
|
|
90
|
-
# Look for
|
|
90
|
+
if not server_or_factory:
|
|
91
|
+
# Look for common server instance names
|
|
91
92
|
for name in ["mcp", "server", "app"]:
|
|
92
93
|
if hasattr(module, name):
|
|
93
|
-
|
|
94
|
+
obj = getattr(module, name)
|
|
95
|
+
return await _resolve_server_or_factory(obj, file, name)
|
|
94
96
|
|
|
95
97
|
logger.error(
|
|
96
98
|
f"No server object found in {file}. Please either:\n"
|
|
@@ -100,14 +102,14 @@ def import_server(file: Path, server_object: str | None = None) -> Any:
|
|
|
100
102
|
)
|
|
101
103
|
sys.exit(1)
|
|
102
104
|
|
|
103
|
-
assert
|
|
105
|
+
assert server_or_factory is not None
|
|
104
106
|
|
|
105
107
|
# Handle module:object syntax
|
|
106
|
-
if ":" in
|
|
107
|
-
module_name, object_name =
|
|
108
|
+
if ":" in server_or_factory:
|
|
109
|
+
module_name, object_name = server_or_factory.split(":", 1)
|
|
108
110
|
try:
|
|
109
111
|
server_module = importlib.import_module(module_name)
|
|
110
|
-
|
|
112
|
+
obj = getattr(server_module, object_name, None)
|
|
111
113
|
except ImportError:
|
|
112
114
|
logger.error(
|
|
113
115
|
f"Could not import module '{module_name}'",
|
|
@@ -116,16 +118,62 @@ def import_server(file: Path, server_object: str | None = None) -> Any:
|
|
|
116
118
|
sys.exit(1)
|
|
117
119
|
else:
|
|
118
120
|
# Just object name
|
|
119
|
-
|
|
121
|
+
obj = getattr(module, server_or_factory, None)
|
|
120
122
|
|
|
121
|
-
if
|
|
123
|
+
if obj is None:
|
|
122
124
|
logger.error(
|
|
123
|
-
f"Server object '{
|
|
125
|
+
f"Server object '{server_or_factory}' not found",
|
|
124
126
|
extra={"file": str(file)},
|
|
125
127
|
)
|
|
126
128
|
sys.exit(1)
|
|
127
129
|
|
|
128
|
-
return
|
|
130
|
+
return await _resolve_server_or_factory(obj, file, server_or_factory)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
async def _resolve_server_or_factory(obj: Any, file: Path, name: str) -> Any:
|
|
134
|
+
"""Resolve a server object or factory function to a server instance.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
obj: The object that might be a server or factory function
|
|
138
|
+
file: Path to the file for error messages
|
|
139
|
+
name: Name of the object for error messages
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
A server instance
|
|
143
|
+
"""
|
|
144
|
+
# Check if it's a function or coroutine function
|
|
145
|
+
if inspect.isfunction(obj) or inspect.iscoroutinefunction(obj):
|
|
146
|
+
logger.debug(f"Found factory function '{name}' in {file}")
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
if inspect.iscoroutinefunction(obj):
|
|
150
|
+
# Async factory function
|
|
151
|
+
server = await obj()
|
|
152
|
+
else:
|
|
153
|
+
# Sync factory function
|
|
154
|
+
server = obj()
|
|
155
|
+
|
|
156
|
+
# Validate the result is a FastMCP server
|
|
157
|
+
if not isinstance(server, FastMCP | FastMCP1x):
|
|
158
|
+
logger.error(
|
|
159
|
+
f"Factory function '{name}' must return a FastMCP server instance, "
|
|
160
|
+
f"got {type(server).__name__}",
|
|
161
|
+
extra={"file": str(file)},
|
|
162
|
+
)
|
|
163
|
+
sys.exit(1)
|
|
164
|
+
|
|
165
|
+
logger.debug(f"Factory function '{name}' created server: {server.name}")
|
|
166
|
+
return server
|
|
167
|
+
|
|
168
|
+
except Exception as e:
|
|
169
|
+
logger.error(
|
|
170
|
+
f"Failed to call factory function '{name}': {e}",
|
|
171
|
+
extra={"file": str(file)},
|
|
172
|
+
)
|
|
173
|
+
sys.exit(1)
|
|
174
|
+
|
|
175
|
+
# Not a function, return as-is (should be a server instance)
|
|
176
|
+
return obj
|
|
129
177
|
|
|
130
178
|
|
|
131
179
|
def run_with_uv(
|
|
@@ -219,7 +267,7 @@ def create_client_server(url: str) -> Any:
|
|
|
219
267
|
import fastmcp
|
|
220
268
|
|
|
221
269
|
client = fastmcp.Client(url)
|
|
222
|
-
server = fastmcp.FastMCP.
|
|
270
|
+
server = fastmcp.FastMCP.as_proxy(client)
|
|
223
271
|
return server
|
|
224
272
|
except Exception as e:
|
|
225
273
|
logger.error(f"Failed to create client for URL {url}: {e}")
|
|
@@ -237,14 +285,16 @@ def create_mcp_config_server(mcp_config_path: Path) -> FastMCP[None]:
|
|
|
237
285
|
return server
|
|
238
286
|
|
|
239
287
|
|
|
240
|
-
def import_server_with_args(
|
|
241
|
-
file: Path,
|
|
288
|
+
async def import_server_with_args(
|
|
289
|
+
file: Path,
|
|
290
|
+
server_or_factory: str | None = None,
|
|
291
|
+
server_args: list[str] | None = None,
|
|
242
292
|
) -> Any:
|
|
243
293
|
"""Import a server with optional command line arguments.
|
|
244
294
|
|
|
245
295
|
Args:
|
|
246
296
|
file: Path to the server file
|
|
247
|
-
|
|
297
|
+
server_or_factory: Optional server object or factory function name
|
|
248
298
|
server_args: Optional command line arguments to inject
|
|
249
299
|
|
|
250
300
|
Returns:
|
|
@@ -254,14 +304,14 @@ def import_server_with_args(
|
|
|
254
304
|
original_argv = sys.argv[:]
|
|
255
305
|
try:
|
|
256
306
|
sys.argv = [str(file)] + server_args
|
|
257
|
-
return import_server(file,
|
|
307
|
+
return await import_server(file, server_or_factory)
|
|
258
308
|
finally:
|
|
259
309
|
sys.argv = original_argv
|
|
260
310
|
else:
|
|
261
|
-
return import_server(file,
|
|
311
|
+
return await import_server(file, server_or_factory)
|
|
262
312
|
|
|
263
313
|
|
|
264
|
-
def run_command(
|
|
314
|
+
async def run_command(
|
|
265
315
|
server_spec: str,
|
|
266
316
|
transport: TransportType | None = None,
|
|
267
317
|
host: str | None = None,
|
|
@@ -293,8 +343,8 @@ def run_command(
|
|
|
293
343
|
server = create_mcp_config_server(Path(server_spec))
|
|
294
344
|
else:
|
|
295
345
|
# Handle file case
|
|
296
|
-
file,
|
|
297
|
-
server = import_server_with_args(file,
|
|
346
|
+
file, server_or_factory = parse_file_path(server_spec)
|
|
347
|
+
server = await import_server_with_args(file, server_or_factory, server_args)
|
|
298
348
|
logger.debug(f'Found server "{server.name}" in {file}')
|
|
299
349
|
|
|
300
350
|
# Run the server
|
|
@@ -320,7 +370,7 @@ def run_command(
|
|
|
320
370
|
kwargs["show_banner"] = False
|
|
321
371
|
|
|
322
372
|
try:
|
|
323
|
-
server.
|
|
373
|
+
await server.run_async(**kwargs)
|
|
324
374
|
except Exception as e:
|
|
325
375
|
logger.error(f"Failed to run server: {e}")
|
|
326
376
|
sys.exit(1)
|
fastmcp/client/logging.py
CHANGED
|
@@ -13,7 +13,31 @@ LogHandler: TypeAlias = Callable[[LogMessage], Awaitable[None]]
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
async def default_log_handler(message: LogMessage) -> None:
|
|
16
|
-
|
|
16
|
+
"""Default handler that properly routes server log messages to appropriate log levels."""
|
|
17
|
+
msg = message.data.get("msg", str(message))
|
|
18
|
+
extra = message.data.get("extra", {})
|
|
19
|
+
|
|
20
|
+
# Map MCP log levels to Python logging levels
|
|
21
|
+
level_map = {
|
|
22
|
+
"debug": logger.debug,
|
|
23
|
+
"info": logger.info,
|
|
24
|
+
"notice": logger.info, # Python doesn't have 'notice', map to info
|
|
25
|
+
"warning": logger.warning,
|
|
26
|
+
"error": logger.error,
|
|
27
|
+
"critical": logger.critical,
|
|
28
|
+
"alert": logger.critical, # Map alert to critical
|
|
29
|
+
"emergency": logger.critical, # Map emergency to critical
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Get the appropriate logging function based on the message level
|
|
33
|
+
log_fn = level_map.get(message.level.lower(), logger.info)
|
|
34
|
+
|
|
35
|
+
# Include logger name if available
|
|
36
|
+
if message.logger:
|
|
37
|
+
msg = f"[{message.logger}] {msg}"
|
|
38
|
+
|
|
39
|
+
# Log with appropriate level and extra data
|
|
40
|
+
log_fn(f"Server log: {msg}", extra=extra)
|
|
17
41
|
|
|
18
42
|
|
|
19
43
|
def create_log_callback(handler: LogHandler | None = None) -> LoggingFnT:
|
fastmcp/client/transports.py
CHANGED
|
@@ -6,7 +6,7 @@ import os
|
|
|
6
6
|
import shutil
|
|
7
7
|
import sys
|
|
8
8
|
import warnings
|
|
9
|
-
from collections.abc import AsyncIterator
|
|
9
|
+
from collections.abc import AsyncIterator
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import Any, Literal, TypeVar, cast, overload
|
|
12
12
|
|
|
@@ -22,6 +22,7 @@ from mcp.client.session import (
|
|
|
22
22
|
SamplingFnT,
|
|
23
23
|
)
|
|
24
24
|
from mcp.server.fastmcp import FastMCP as FastMCP1Server
|
|
25
|
+
from mcp.shared._httpx_utils import McpHttpClientFactory
|
|
25
26
|
from mcp.shared.memory import create_client_server_memory_streams
|
|
26
27
|
from pydantic import AnyUrl
|
|
27
28
|
from typing_extensions import TypedDict, Unpack
|
|
@@ -161,7 +162,7 @@ class SSETransport(ClientTransport):
|
|
|
161
162
|
headers: dict[str, str] | None = None,
|
|
162
163
|
auth: httpx.Auth | Literal["oauth"] | str | None = None,
|
|
163
164
|
sse_read_timeout: datetime.timedelta | float | int | None = None,
|
|
164
|
-
httpx_client_factory:
|
|
165
|
+
httpx_client_factory: McpHttpClientFactory | None = None,
|
|
165
166
|
):
|
|
166
167
|
if isinstance(url, AnyUrl):
|
|
167
168
|
url = str(url)
|
|
@@ -233,7 +234,7 @@ class StreamableHttpTransport(ClientTransport):
|
|
|
233
234
|
headers: dict[str, str] | None = None,
|
|
234
235
|
auth: httpx.Auth | Literal["oauth"] | str | None = None,
|
|
235
236
|
sse_read_timeout: datetime.timedelta | float | int | None = None,
|
|
236
|
-
httpx_client_factory:
|
|
237
|
+
httpx_client_factory: McpHttpClientFactory | None = None,
|
|
237
238
|
):
|
|
238
239
|
if isinstance(url, AnyUrl):
|
|
239
240
|
url = str(url)
|
|
@@ -113,7 +113,7 @@ def _determine_route_type(
|
|
|
113
113
|
# We know mcp_type is not None here due to post_init validation
|
|
114
114
|
assert route_map.mcp_type is not None
|
|
115
115
|
logger.debug(
|
|
116
|
-
f"Route {route.method} {route.path}
|
|
116
|
+
f"Route {route.method} {route.path} mapped to {route_map.mcp_type.name}"
|
|
117
117
|
)
|
|
118
118
|
return route_map
|
|
119
119
|
|
|
@@ -11,7 +11,7 @@ from jsonschema_path import SchemaPath
|
|
|
11
11
|
from fastmcp.experimental.utilities.openapi import (
|
|
12
12
|
HTTPRoute,
|
|
13
13
|
extract_output_schema_from_responses,
|
|
14
|
-
|
|
14
|
+
format_simple_description,
|
|
15
15
|
parse_openapi_to_http_routes,
|
|
16
16
|
)
|
|
17
17
|
from fastmcp.experimental.utilities.openapi.director import RequestDirector
|
|
@@ -151,9 +151,6 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
151
151
|
try:
|
|
152
152
|
self._spec = SchemaPath.from_dict(openapi_spec) # type: ignore[arg-type]
|
|
153
153
|
self._director = RequestDirector(self._spec)
|
|
154
|
-
logger.debug(
|
|
155
|
-
"Initialized OpenAPI RequestDirector for stateless request building"
|
|
156
|
-
)
|
|
157
154
|
except Exception as e:
|
|
158
155
|
logger.error(f"Failed to initialize RequestDirector: {e}")
|
|
159
156
|
raise ValueError(f"Invalid OpenAPI specification: {e}") from e
|
|
@@ -270,7 +267,9 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
270
267
|
|
|
271
268
|
# Extract output schema from OpenAPI responses
|
|
272
269
|
output_schema = extract_output_schema_from_responses(
|
|
273
|
-
route.responses,
|
|
270
|
+
route.responses,
|
|
271
|
+
route.response_schemas,
|
|
272
|
+
route.openapi_version,
|
|
274
273
|
)
|
|
275
274
|
|
|
276
275
|
# Get a unique tool name
|
|
@@ -282,10 +281,9 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
282
281
|
or f"Executes {route.method} {route.path}"
|
|
283
282
|
)
|
|
284
283
|
|
|
285
|
-
#
|
|
286
|
-
enhanced_description =
|
|
284
|
+
# Use simplified description formatter for tools
|
|
285
|
+
enhanced_description = format_simple_description(
|
|
287
286
|
base_description=base_description,
|
|
288
|
-
responses=route.responses,
|
|
289
287
|
parameters=route.parameters,
|
|
290
288
|
request_body=route.request_body,
|
|
291
289
|
)
|
|
@@ -318,9 +316,6 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
318
316
|
|
|
319
317
|
# Register the tool by directly assigning to the tools dictionary
|
|
320
318
|
self._tool_manager._tools[final_tool_name] = tool
|
|
321
|
-
logger.debug(
|
|
322
|
-
f"Registered TOOL: {final_tool_name} ({route.method} {route.path}) with tags: {route.tags}"
|
|
323
|
-
)
|
|
324
319
|
|
|
325
320
|
def _create_openapi_resource(
|
|
326
321
|
self,
|
|
@@ -337,10 +332,9 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
337
332
|
route.description or route.summary or f"Represents {route.path}"
|
|
338
333
|
)
|
|
339
334
|
|
|
340
|
-
#
|
|
341
|
-
enhanced_description =
|
|
335
|
+
# Use simplified description for resources
|
|
336
|
+
enhanced_description = format_simple_description(
|
|
342
337
|
base_description=base_description,
|
|
343
|
-
responses=route.responses,
|
|
344
338
|
parameters=route.parameters,
|
|
345
339
|
request_body=route.request_body,
|
|
346
340
|
)
|
|
@@ -372,9 +366,6 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
372
366
|
|
|
373
367
|
# Register the resource by directly assigning to the resources dictionary
|
|
374
368
|
self._resource_manager._resources[final_resource_uri] = resource
|
|
375
|
-
logger.debug(
|
|
376
|
-
f"Registered RESOURCE: {final_resource_uri} ({route.method} {route.path}) with tags: {route.tags}"
|
|
377
|
-
)
|
|
378
369
|
|
|
379
370
|
def _create_openapi_template(
|
|
380
371
|
self,
|
|
@@ -397,10 +388,9 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
397
388
|
route.description or route.summary or f"Template for {route.path}"
|
|
398
389
|
)
|
|
399
390
|
|
|
400
|
-
#
|
|
401
|
-
enhanced_description =
|
|
391
|
+
# Use simplified description for resource templates
|
|
392
|
+
enhanced_description = format_simple_description(
|
|
402
393
|
base_description=base_description,
|
|
403
|
-
responses=route.responses,
|
|
404
394
|
parameters=route.parameters,
|
|
405
395
|
request_body=route.request_body,
|
|
406
396
|
)
|
|
@@ -455,9 +445,6 @@ class FastMCPOpenAPI(FastMCP):
|
|
|
455
445
|
|
|
456
446
|
# Register the template by directly assigning to the templates dictionary
|
|
457
447
|
self._resource_manager._templates[final_template_uri] = template
|
|
458
|
-
logger.debug(
|
|
459
|
-
f"Registered TEMPLATE: {final_template_uri} ({route.method} {route.path}) with tags: {route.tags}"
|
|
460
|
-
)
|
|
461
448
|
|
|
462
449
|
|
|
463
450
|
# Export public symbols
|
|
@@ -20,6 +20,7 @@ from .formatters import (
|
|
|
20
20
|
format_deep_object_parameter,
|
|
21
21
|
format_description_with_responses,
|
|
22
22
|
format_json_for_description,
|
|
23
|
+
format_simple_description,
|
|
23
24
|
generate_example_from_schema,
|
|
24
25
|
)
|
|
25
26
|
|
|
@@ -28,7 +29,6 @@ from .schemas import (
|
|
|
28
29
|
_combine_schemas,
|
|
29
30
|
extract_output_schema_from_responses,
|
|
30
31
|
clean_schema_for_display,
|
|
31
|
-
_replace_ref_with_defs,
|
|
32
32
|
_make_optional_parameter_nullable,
|
|
33
33
|
)
|
|
34
34
|
|
|
@@ -55,12 +55,12 @@ __all__ = [
|
|
|
55
55
|
"format_deep_object_parameter",
|
|
56
56
|
"format_description_with_responses",
|
|
57
57
|
"format_json_for_description",
|
|
58
|
+
"format_simple_description",
|
|
58
59
|
"generate_example_from_schema",
|
|
59
60
|
# Schemas
|
|
60
61
|
"_combine_schemas",
|
|
61
62
|
"extract_output_schema_from_responses",
|
|
62
63
|
"clean_schema_for_display",
|
|
63
|
-
"_replace_ref_with_defs",
|
|
64
64
|
"_make_optional_parameter_nullable",
|
|
65
65
|
# JSON Schema Converter
|
|
66
66
|
"convert_openapi_schema_to_json_schema",
|
|
@@ -189,6 +189,39 @@ def format_json_for_description(data: Any, indent: int = 2) -> str:
|
|
|
189
189
|
return f"```\nCould not serialize to JSON: {data}\n```"
|
|
190
190
|
|
|
191
191
|
|
|
192
|
+
def format_simple_description(
|
|
193
|
+
base_description: str,
|
|
194
|
+
parameters: list[ParameterInfo] | None = None,
|
|
195
|
+
request_body: RequestBodyInfo | None = None,
|
|
196
|
+
) -> str:
|
|
197
|
+
"""
|
|
198
|
+
Formats a simple description for MCP objects (tools, resources, prompts).
|
|
199
|
+
Excludes response details, examples, and verbose status codes.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
base_description (str): The initial description to be formatted.
|
|
203
|
+
parameters (list[ParameterInfo] | None, optional): A list of parameter information.
|
|
204
|
+
request_body (RequestBodyInfo | None, optional): Information about the request body.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
str: The formatted description string with minimal details.
|
|
208
|
+
"""
|
|
209
|
+
desc_parts = [base_description]
|
|
210
|
+
|
|
211
|
+
# Only add critical parameter information if they have descriptions
|
|
212
|
+
if parameters:
|
|
213
|
+
path_params = [p for p in parameters if p.location == "path" and p.description]
|
|
214
|
+
if path_params:
|
|
215
|
+
desc_parts.append("\n\n**Path Parameters:**")
|
|
216
|
+
for param in path_params:
|
|
217
|
+
desc_parts.append(f"\n- **{param.name}**: {param.description}")
|
|
218
|
+
|
|
219
|
+
# Skip query parameters, request body details, and all response information
|
|
220
|
+
# These are already captured in the inputSchema
|
|
221
|
+
|
|
222
|
+
return "\n".join(desc_parts)
|
|
223
|
+
|
|
224
|
+
|
|
192
225
|
def format_description_with_responses(
|
|
193
226
|
base_description: str,
|
|
194
227
|
responses: dict[
|
|
@@ -351,5 +384,6 @@ __all__ = [
|
|
|
351
384
|
"format_deep_object_parameter",
|
|
352
385
|
"format_description_with_responses",
|
|
353
386
|
"format_json_for_description",
|
|
387
|
+
"format_simple_description",
|
|
354
388
|
"generate_example_from_schema",
|
|
355
389
|
]
|
|
@@ -58,9 +58,12 @@ class HTTPRoute(FastMCPBaseModel):
|
|
|
58
58
|
responses: dict[str, ResponseInfo] = Field(
|
|
59
59
|
default_factory=dict
|
|
60
60
|
) # Key: status code str
|
|
61
|
-
|
|
61
|
+
request_schemas: dict[str, JsonSchema] = Field(
|
|
62
62
|
default_factory=dict
|
|
63
|
-
) # Store
|
|
63
|
+
) # Store schemas needed for input (parameters/request body)
|
|
64
|
+
response_schemas: dict[str, JsonSchema] = Field(
|
|
65
|
+
default_factory=dict
|
|
66
|
+
) # Store schemas needed for output (responses)
|
|
64
67
|
extensions: dict[str, Any] = Field(default_factory=dict)
|
|
65
68
|
openapi_version: str | None = None
|
|
66
69
|
|