fastmcp 2.11.0__py3-none-any.whl → 2.11.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/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/server/auth/__init__.py +2 -1
- fastmcp/server/auth/auth.py +122 -41
- fastmcp/server/auth/providers/jwt.py +1 -1
- fastmcp/server/auth/providers/workos.py +27 -46
- fastmcp/server/http.py +32 -73
- fastmcp/server/proxy.py +33 -15
- fastmcp/server/server.py +9 -6
- fastmcp/settings.py +4 -7
- fastmcp/utilities/components.py +6 -1
- fastmcp/utilities/inspect.py +12 -17
- fastmcp/utilities/openapi.py +3 -7
- fastmcp/utilities/types.py +6 -1
- {fastmcp-2.11.0.dist-info → fastmcp-2.11.2.dist-info}/METADATA +1 -1
- {fastmcp-2.11.0.dist-info → fastmcp-2.11.2.dist-info}/RECORD +24 -24
- {fastmcp-2.11.0.dist-info → fastmcp-2.11.2.dist-info}/WHEEL +0 -0
- {fastmcp-2.11.0.dist-info → fastmcp-2.11.2.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.11.0.dist-info → fastmcp-2.11.2.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/server/auth/__init__.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from .auth import OAuthProvider, TokenVerifier
|
|
1
|
+
from .auth import OAuthProvider, TokenVerifier, RemoteAuthProvider
|
|
2
2
|
from .providers.jwt import JWTVerifier, StaticTokenVerifier
|
|
3
3
|
|
|
4
4
|
|
|
@@ -7,6 +7,7 @@ __all__ = [
|
|
|
7
7
|
"TokenVerifier",
|
|
8
8
|
"JWTVerifier",
|
|
9
9
|
"StaticTokenVerifier",
|
|
10
|
+
"RemoteAuthProvider",
|
|
10
11
|
]
|
|
11
12
|
|
|
12
13
|
|
fastmcp/server/auth/auth.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
4
|
-
|
|
5
3
|
from mcp.server.auth.provider import (
|
|
6
4
|
AccessToken,
|
|
7
5
|
AuthorizationCode,
|
|
@@ -11,6 +9,10 @@ from mcp.server.auth.provider import (
|
|
|
11
9
|
from mcp.server.auth.provider import (
|
|
12
10
|
TokenVerifier as TokenVerifierProtocol,
|
|
13
11
|
)
|
|
12
|
+
from mcp.server.auth.routes import (
|
|
13
|
+
create_auth_routes,
|
|
14
|
+
create_protected_resource_routes,
|
|
15
|
+
)
|
|
14
16
|
from mcp.server.auth.settings import (
|
|
15
17
|
ClientRegistrationOptions,
|
|
16
18
|
RevocationOptions,
|
|
@@ -18,11 +20,8 @@ from mcp.server.auth.settings import (
|
|
|
18
20
|
from pydantic import AnyHttpUrl
|
|
19
21
|
from starlette.routing import Route
|
|
20
22
|
|
|
21
|
-
if TYPE_CHECKING:
|
|
22
|
-
pass
|
|
23
|
-
|
|
24
23
|
|
|
25
|
-
class AuthProvider:
|
|
24
|
+
class AuthProvider(TokenVerifierProtocol):
|
|
26
25
|
"""Base class for all FastMCP authentication providers.
|
|
27
26
|
|
|
28
27
|
This class provides a unified interface for all authentication providers,
|
|
@@ -31,9 +30,18 @@ class AuthProvider:
|
|
|
31
30
|
custom authentication routes.
|
|
32
31
|
"""
|
|
33
32
|
|
|
34
|
-
def __init__(self,
|
|
35
|
-
"""
|
|
36
|
-
|
|
33
|
+
def __init__(self, resource_server_url: AnyHttpUrl | str | None = None):
|
|
34
|
+
"""
|
|
35
|
+
Initialize the auth provider.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
resource_server_url: The URL of this resource server. This is used
|
|
39
|
+
for RFC 8707 resource indicators, including creating the WWW-Authenticate
|
|
40
|
+
header.
|
|
41
|
+
"""
|
|
42
|
+
if isinstance(resource_server_url, str):
|
|
43
|
+
resource_server_url = AnyHttpUrl(resource_server_url)
|
|
44
|
+
self.resource_server_url = resource_server_url
|
|
37
45
|
|
|
38
46
|
async def verify_token(self, token: str) -> AccessToken | None:
|
|
39
47
|
"""Verify a bearer token and return access info if valid.
|
|
@@ -48,22 +56,34 @@ class AuthProvider:
|
|
|
48
56
|
"""
|
|
49
57
|
raise NotImplementedError("Subclasses must implement verify_token")
|
|
50
58
|
|
|
51
|
-
def
|
|
52
|
-
"""
|
|
59
|
+
def get_routes(self) -> list[Route]:
|
|
60
|
+
"""Get the routes for this authentication provider.
|
|
53
61
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
Each provider is responsible for creating whatever routes it needs:
|
|
63
|
+
- TokenVerifier: typically no routes (default implementation)
|
|
64
|
+
- RemoteAuthProvider: protected resource metadata routes
|
|
65
|
+
- OAuthProvider: full OAuth authorization server routes
|
|
66
|
+
- Custom providers: whatever routes they need
|
|
59
67
|
|
|
60
68
|
Returns:
|
|
61
|
-
List of routes
|
|
69
|
+
List of routes for this provider
|
|
62
70
|
"""
|
|
63
|
-
return
|
|
71
|
+
return []
|
|
72
|
+
|
|
73
|
+
def get_resource_metadata_url(self) -> AnyHttpUrl | None:
|
|
74
|
+
"""Get the resource metadata URL for RFC 9728 compliance."""
|
|
75
|
+
if self.resource_server_url is None:
|
|
76
|
+
return None
|
|
64
77
|
|
|
78
|
+
# Add .well-known path for RFC 9728 compliance
|
|
79
|
+
resource_metadata_url = AnyHttpUrl(
|
|
80
|
+
str(self.resource_server_url).rstrip("/")
|
|
81
|
+
+ "/.well-known/oauth-protected-resource"
|
|
82
|
+
)
|
|
83
|
+
return resource_metadata_url
|
|
65
84
|
|
|
66
|
-
|
|
85
|
+
|
|
86
|
+
class TokenVerifier(AuthProvider):
|
|
67
87
|
"""Base class for token verifiers (Resource Servers).
|
|
68
88
|
|
|
69
89
|
This class provides token verification capability without OAuth server functionality.
|
|
@@ -79,26 +99,71 @@ class TokenVerifier(AuthProvider, TokenVerifierProtocol):
|
|
|
79
99
|
Initialize the token verifier.
|
|
80
100
|
|
|
81
101
|
Args:
|
|
82
|
-
resource_server_url: The URL of this resource server
|
|
102
|
+
resource_server_url: The URL of this resource server. This is used
|
|
103
|
+
for RFC 8707 resource indicators, including creating the WWW-Authenticate
|
|
104
|
+
header.
|
|
83
105
|
required_scopes: Scopes that are required for all requests
|
|
84
106
|
"""
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
# Handle our own resource_server_url and required_scopes
|
|
89
|
-
self.resource_server_url: AnyHttpUrl | None
|
|
90
|
-
if resource_server_url is None:
|
|
91
|
-
self.resource_server_url = None
|
|
92
|
-
elif isinstance(resource_server_url, str):
|
|
93
|
-
self.resource_server_url = AnyHttpUrl(resource_server_url)
|
|
94
|
-
else:
|
|
95
|
-
self.resource_server_url = resource_server_url
|
|
107
|
+
super().__init__(resource_server_url=resource_server_url)
|
|
108
|
+
self.required_scopes = required_scopes or []
|
|
96
109
|
|
|
97
110
|
async def verify_token(self, token: str) -> AccessToken | None:
|
|
98
111
|
"""Verify a bearer token and return access info if valid."""
|
|
99
112
|
raise NotImplementedError("Subclasses must implement verify_token")
|
|
100
113
|
|
|
101
114
|
|
|
115
|
+
class RemoteAuthProvider(AuthProvider):
|
|
116
|
+
"""Authentication provider for resource servers that verify tokens from known authorization servers.
|
|
117
|
+
|
|
118
|
+
This provider composes a TokenVerifier with authorization server metadata to create
|
|
119
|
+
standardized OAuth 2.0 Protected Resource endpoints (RFC 9728). Perfect for:
|
|
120
|
+
- JWT verification with known issuers
|
|
121
|
+
- Remote token introspection services
|
|
122
|
+
- Any resource server that knows where its tokens come from
|
|
123
|
+
|
|
124
|
+
Use this when you have token verification logic and want to advertise
|
|
125
|
+
the authorization servers that issue valid tokens.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
def __init__(
|
|
129
|
+
self,
|
|
130
|
+
token_verifier: TokenVerifier,
|
|
131
|
+
authorization_servers: list[AnyHttpUrl],
|
|
132
|
+
resource_server_url: AnyHttpUrl | str,
|
|
133
|
+
):
|
|
134
|
+
"""Initialize the remote auth provider.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
token_verifier: TokenVerifier instance for token validation
|
|
138
|
+
authorization_servers: List of authorization servers that issue valid tokens
|
|
139
|
+
resource_server_url: URL of this resource server. This is used
|
|
140
|
+
for RFC 8707 resource indicators, including creating the WWW-Authenticate
|
|
141
|
+
header.
|
|
142
|
+
"""
|
|
143
|
+
super().__init__(resource_server_url=resource_server_url)
|
|
144
|
+
self.token_verifier = token_verifier
|
|
145
|
+
self.authorization_servers = authorization_servers
|
|
146
|
+
|
|
147
|
+
async def verify_token(self, token: str) -> AccessToken | None:
|
|
148
|
+
"""Verify token using the configured token verifier."""
|
|
149
|
+
return await self.token_verifier.verify_token(token)
|
|
150
|
+
|
|
151
|
+
def get_routes(self) -> list[Route]:
|
|
152
|
+
"""Get OAuth routes for this provider.
|
|
153
|
+
|
|
154
|
+
By default, returns only the standardized OAuth 2.0 Protected Resource routes.
|
|
155
|
+
Subclasses can override this method to add additional routes by calling
|
|
156
|
+
super().get_routes() and extending the returned list.
|
|
157
|
+
"""
|
|
158
|
+
assert self.resource_server_url is not None
|
|
159
|
+
|
|
160
|
+
return create_protected_resource_routes(
|
|
161
|
+
resource_url=self.resource_server_url,
|
|
162
|
+
authorization_servers=self.authorization_servers,
|
|
163
|
+
scopes_supported=self.token_verifier.required_scopes,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
|
|
102
167
|
class OAuthProvider(
|
|
103
168
|
AuthProvider,
|
|
104
169
|
OAuthAuthorizationServerProvider[AuthorizationCode, RefreshToken, AccessToken],
|
|
@@ -181,17 +246,33 @@ class OAuthProvider(
|
|
|
181
246
|
"""
|
|
182
247
|
return await self.load_access_token(token)
|
|
183
248
|
|
|
184
|
-
def
|
|
185
|
-
"""
|
|
249
|
+
def get_routes(self) -> list[Route]:
|
|
250
|
+
"""Get OAuth authorization server routes and optional protected resource routes.
|
|
186
251
|
|
|
187
|
-
This method
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
Args:
|
|
192
|
-
routes: List of standard OAuth routes from create_auth_routes
|
|
252
|
+
This method creates the full set of OAuth routes including:
|
|
253
|
+
- Standard OAuth authorization server routes (/.well-known/oauth-authorization-server, /authorize, /token, etc.)
|
|
254
|
+
- Optional protected resource routes if resource_server_url is configured
|
|
193
255
|
|
|
194
256
|
Returns:
|
|
195
|
-
List of routes
|
|
257
|
+
List of OAuth routes
|
|
196
258
|
"""
|
|
197
|
-
|
|
259
|
+
|
|
260
|
+
# Create standard OAuth authorization server routes
|
|
261
|
+
oauth_routes = create_auth_routes(
|
|
262
|
+
provider=self,
|
|
263
|
+
issuer_url=self.issuer_url,
|
|
264
|
+
service_documentation_url=self.service_documentation_url,
|
|
265
|
+
client_registration_options=self.client_registration_options,
|
|
266
|
+
revocation_options=self.revocation_options,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
# Add protected resource routes if this server is also acting as a resource server
|
|
270
|
+
if self.resource_server_url:
|
|
271
|
+
protected_routes = create_protected_resource_routes(
|
|
272
|
+
resource_url=self.resource_server_url,
|
|
273
|
+
authorization_servers=[self.issuer_url],
|
|
274
|
+
scopes_supported=self.required_scopes,
|
|
275
|
+
)
|
|
276
|
+
oauth_routes.extend(protected_routes)
|
|
277
|
+
|
|
278
|
+
return oauth_routes
|
|
@@ -16,7 +16,7 @@ from pydantic import AnyHttpUrl, SecretStr
|
|
|
16
16
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
17
17
|
from typing_extensions import TypedDict
|
|
18
18
|
|
|
19
|
-
from fastmcp.server.auth
|
|
19
|
+
from fastmcp.server.auth import TokenVerifier
|
|
20
20
|
from fastmcp.server.auth.registry import register_provider
|
|
21
21
|
from fastmcp.utilities.logging import get_logger
|
|
22
22
|
from fastmcp.utilities.types import NotSet, NotSetT
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import httpx
|
|
4
|
-
from mcp.server.auth.provider import (
|
|
5
|
-
AccessToken,
|
|
6
|
-
)
|
|
7
4
|
from pydantic import AnyHttpUrl
|
|
8
5
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
9
6
|
from starlette.responses import JSONResponse
|
|
10
|
-
from starlette.routing import
|
|
7
|
+
from starlette.routing import Route
|
|
11
8
|
|
|
12
|
-
from fastmcp.server.auth
|
|
9
|
+
from fastmcp.server.auth import RemoteAuthProvider, TokenVerifier
|
|
13
10
|
from fastmcp.server.auth.providers.jwt import JWTVerifier
|
|
14
11
|
from fastmcp.server.auth.registry import register_provider
|
|
15
12
|
from fastmcp.utilities.logging import get_logger
|
|
@@ -31,10 +28,10 @@ class AuthKitProviderSettings(BaseSettings):
|
|
|
31
28
|
|
|
32
29
|
|
|
33
30
|
@register_provider("AUTHKIT")
|
|
34
|
-
class AuthKitProvider(
|
|
35
|
-
"""
|
|
31
|
+
class AuthKitProvider(RemoteAuthProvider):
|
|
32
|
+
"""AuthKit metadata provider for DCR (Dynamic Client Registration).
|
|
36
33
|
|
|
37
|
-
This provider implements
|
|
34
|
+
This provider implements AuthKit integration using metadata forwarding
|
|
38
35
|
instead of OAuth proxying. This is the recommended approach for WorkOS DCR
|
|
39
36
|
as it allows WorkOS to handle the OAuth flow directly while FastMCP acts
|
|
40
37
|
as a resource server.
|
|
@@ -56,7 +53,7 @@ class AuthKitProvider(AuthProvider):
|
|
|
56
53
|
```python
|
|
57
54
|
from fastmcp.server.auth.providers.workos import AuthKitProvider
|
|
58
55
|
|
|
59
|
-
# Create
|
|
56
|
+
# Create AuthKit metadata provider (JWT verifier created automatically)
|
|
60
57
|
workos_auth = AuthKitProvider(
|
|
61
58
|
authkit_domain="https://your-workos-domain.authkit.app",
|
|
62
59
|
base_url="https://your-fastmcp-server.com",
|
|
@@ -75,16 +72,14 @@ class AuthKitProvider(AuthProvider):
|
|
|
75
72
|
required_scopes: list[str] | None | NotSetT = NotSet,
|
|
76
73
|
token_verifier: TokenVerifier | None = None,
|
|
77
74
|
):
|
|
78
|
-
"""Initialize
|
|
75
|
+
"""Initialize AuthKit metadata provider.
|
|
79
76
|
|
|
80
77
|
Args:
|
|
81
|
-
authkit_domain: Your
|
|
78
|
+
authkit_domain: Your AuthKit domain (e.g., "https://your-app.authkit.app")
|
|
82
79
|
base_url: Public URL of this FastMCP server
|
|
83
80
|
required_scopes: Optional list of scopes to require for all requests
|
|
84
|
-
token_verifier: Optional token verifier. If None, creates JWT verifier for
|
|
81
|
+
token_verifier: Optional token verifier. If None, creates JWT verifier for AuthKit
|
|
85
82
|
"""
|
|
86
|
-
super().__init__()
|
|
87
|
-
|
|
88
83
|
settings = AuthKitProviderSettings.model_validate(
|
|
89
84
|
{
|
|
90
85
|
k: v
|
|
@@ -109,19 +104,21 @@ class AuthKitProvider(AuthProvider):
|
|
|
109
104
|
required_scopes=settings.required_scopes,
|
|
110
105
|
)
|
|
111
106
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
107
|
+
# Initialize RemoteAuthProvider with AuthKit as the authorization server
|
|
108
|
+
super().__init__(
|
|
109
|
+
token_verifier=token_verifier,
|
|
110
|
+
authorization_servers=[AnyHttpUrl(self.authkit_domain)],
|
|
111
|
+
resource_server_url=self.base_url,
|
|
112
|
+
)
|
|
117
113
|
|
|
118
|
-
def
|
|
119
|
-
"""
|
|
114
|
+
def get_routes(self) -> list[Route]:
|
|
115
|
+
"""Get OAuth routes including AuthKit authorization server metadata forwarding.
|
|
120
116
|
|
|
121
|
-
This
|
|
122
|
-
|
|
123
|
-
- /.well-known/oauth-protected-resource (returns FastMCP resource info)
|
|
117
|
+
This returns the standard protected resource routes plus an authorization server
|
|
118
|
+
metadata endpoint that forwards AuthKit's OAuth metadata to clients.
|
|
124
119
|
"""
|
|
120
|
+
# Get the standard protected resource routes from RemoteAuthProvider
|
|
121
|
+
routes = super().get_routes()
|
|
125
122
|
|
|
126
123
|
async def oauth_authorization_server_metadata(request):
|
|
127
124
|
"""Forward AuthKit OAuth authorization server metadata with FastMCP customizations."""
|
|
@@ -142,29 +139,13 @@ class AuthKitProvider(AuthProvider):
|
|
|
142
139
|
status_code=500,
|
|
143
140
|
)
|
|
144
141
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
"bearer_methods_supported": ["header"],
|
|
152
|
-
}
|
|
142
|
+
# Add AuthKit authorization server metadata forwarding
|
|
143
|
+
routes.append(
|
|
144
|
+
Route(
|
|
145
|
+
"/.well-known/oauth-authorization-server",
|
|
146
|
+
endpoint=oauth_authorization_server_metadata,
|
|
147
|
+
methods=["GET"],
|
|
153
148
|
)
|
|
154
|
-
|
|
155
|
-
routes.extend(
|
|
156
|
-
[
|
|
157
|
-
Route(
|
|
158
|
-
"/.well-known/oauth-authorization-server",
|
|
159
|
-
endpoint=oauth_authorization_server_metadata,
|
|
160
|
-
methods=["GET"],
|
|
161
|
-
),
|
|
162
|
-
Route(
|
|
163
|
-
"/.well-known/oauth-protected-resource",
|
|
164
|
-
endpoint=oauth_protected_resource_metadata,
|
|
165
|
-
methods=["GET"],
|
|
166
|
-
),
|
|
167
|
-
]
|
|
168
149
|
)
|
|
169
150
|
|
|
170
151
|
return routes
|
fastmcp/server/http.py
CHANGED
|
@@ -11,12 +11,10 @@ from mcp.server.auth.middleware.bearer_auth import (
|
|
|
11
11
|
RequireAuthMiddleware,
|
|
12
12
|
)
|
|
13
13
|
from mcp.server.auth.provider import TokenVerifier as TokenVerifierProtocol
|
|
14
|
-
from mcp.server.auth.routes import create_auth_routes
|
|
15
14
|
from mcp.server.lowlevel.server import LifespanResultT
|
|
16
15
|
from mcp.server.sse import SseServerTransport
|
|
17
16
|
from mcp.server.streamable_http import EventStore
|
|
18
17
|
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
|
|
19
|
-
from pydantic import AnyHttpUrl
|
|
20
18
|
from starlette.applications import Starlette
|
|
21
19
|
from starlette.middleware import Middleware
|
|
22
20
|
from starlette.middleware.authentication import AuthenticationMiddleware
|
|
@@ -25,7 +23,7 @@ from starlette.responses import Response
|
|
|
25
23
|
from starlette.routing import BaseRoute, Mount, Route
|
|
26
24
|
from starlette.types import Lifespan, Receive, Scope, Send
|
|
27
25
|
|
|
28
|
-
from fastmcp.server.auth.auth import AuthProvider
|
|
26
|
+
from fastmcp.server.auth.auth import AuthProvider
|
|
29
27
|
from fastmcp.utilities.logging import get_logger
|
|
30
28
|
|
|
31
29
|
if TYPE_CHECKING:
|
|
@@ -71,51 +69,6 @@ class RequestContextMiddleware:
|
|
|
71
69
|
await self.app(scope, receive, send)
|
|
72
70
|
|
|
73
71
|
|
|
74
|
-
def setup_auth_middleware_and_routes(
|
|
75
|
-
auth: AuthProvider,
|
|
76
|
-
) -> tuple[list[Middleware], list[Route], list[str]]:
|
|
77
|
-
"""Set up authentication middleware and routes if auth is enabled.
|
|
78
|
-
|
|
79
|
-
Args:
|
|
80
|
-
auth: An AuthProvider for authentication (TokenVerifier or OAuthProvider)
|
|
81
|
-
|
|
82
|
-
Returns:
|
|
83
|
-
Tuple of (middleware, auth_routes, required_scopes)
|
|
84
|
-
"""
|
|
85
|
-
middleware: list[Middleware] = [
|
|
86
|
-
Middleware(
|
|
87
|
-
AuthenticationMiddleware,
|
|
88
|
-
backend=BearerAuthBackend(cast(TokenVerifierProtocol, auth)),
|
|
89
|
-
),
|
|
90
|
-
Middleware(AuthContextMiddleware),
|
|
91
|
-
]
|
|
92
|
-
|
|
93
|
-
auth_routes: list[Route] = []
|
|
94
|
-
required_scopes: list[str] = auth.required_scopes or []
|
|
95
|
-
|
|
96
|
-
# Check if it's an OAuthProvider (has OAuth server capability)
|
|
97
|
-
if isinstance(auth, OAuthProvider):
|
|
98
|
-
# OAuthProvider: create standard OAuth routes first
|
|
99
|
-
standard_routes = list(
|
|
100
|
-
create_auth_routes(
|
|
101
|
-
provider=auth,
|
|
102
|
-
issuer_url=auth.issuer_url,
|
|
103
|
-
service_documentation_url=auth.service_documentation_url,
|
|
104
|
-
client_registration_options=auth.client_registration_options,
|
|
105
|
-
revocation_options=auth.revocation_options,
|
|
106
|
-
)
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
# Allow provider to customize routes (e.g., for proxy behavior or metadata endpoints)
|
|
110
|
-
auth_routes = auth.customize_auth_routes(standard_routes)
|
|
111
|
-
else:
|
|
112
|
-
# Simple AuthProvider or TokenVerifier: start with empty routes
|
|
113
|
-
# Allow provider to add custom routes (e.g., metadata endpoints)
|
|
114
|
-
auth_routes = auth.customize_auth_routes([])
|
|
115
|
-
|
|
116
|
-
return middleware, auth_routes, required_scopes
|
|
117
|
-
|
|
118
|
-
|
|
119
72
|
def create_base_app(
|
|
120
73
|
routes: list[BaseRoute],
|
|
121
74
|
middleware: list[Middleware],
|
|
@@ -183,24 +136,27 @@ def create_sse_app(
|
|
|
183
136
|
)
|
|
184
137
|
return Response()
|
|
185
138
|
|
|
186
|
-
#
|
|
139
|
+
# Set up auth if enabled
|
|
187
140
|
if auth:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
141
|
+
# Create auth middleware
|
|
142
|
+
auth_middleware = [
|
|
143
|
+
Middleware(
|
|
144
|
+
AuthenticationMiddleware,
|
|
145
|
+
backend=BearerAuthBackend(auth),
|
|
146
|
+
),
|
|
147
|
+
Middleware(AuthContextMiddleware),
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
# Get auth routes and scopes
|
|
151
|
+
auth_routes = auth.get_routes()
|
|
152
|
+
required_scopes = getattr(auth, "required_scopes", None) or []
|
|
153
|
+
|
|
154
|
+
# Get resource metadata URL for WWW-Authenticate header
|
|
155
|
+
resource_metadata_url = auth.get_resource_metadata_url()
|
|
191
156
|
|
|
192
157
|
server_routes.extend(auth_routes)
|
|
193
158
|
server_middleware.extend(auth_middleware)
|
|
194
159
|
|
|
195
|
-
# Determine resource_metadata_url for TokenVerifier
|
|
196
|
-
resource_metadata_url = None
|
|
197
|
-
if isinstance(auth, TokenVerifier) and auth.resource_server_url:
|
|
198
|
-
# Add .well-known path for RFC 9728 compliance
|
|
199
|
-
resource_metadata_url = AnyHttpUrl(
|
|
200
|
-
str(auth.resource_server_url).rstrip("/")
|
|
201
|
-
+ "/.well-known/oauth-protected-resource"
|
|
202
|
-
)
|
|
203
|
-
|
|
204
160
|
# Auth is enabled, wrap endpoints with RequireAuthMiddleware
|
|
205
161
|
server_routes.append(
|
|
206
162
|
Route(
|
|
@@ -328,22 +284,25 @@ def create_streamable_http_app(
|
|
|
328
284
|
|
|
329
285
|
# Add StreamableHTTP routes with or without auth
|
|
330
286
|
if auth:
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
287
|
+
# Create auth middleware
|
|
288
|
+
auth_middleware = [
|
|
289
|
+
Middleware(
|
|
290
|
+
AuthenticationMiddleware,
|
|
291
|
+
backend=BearerAuthBackend(cast(TokenVerifierProtocol, auth)),
|
|
292
|
+
),
|
|
293
|
+
Middleware(AuthContextMiddleware),
|
|
294
|
+
]
|
|
295
|
+
|
|
296
|
+
# Get auth routes and scopes
|
|
297
|
+
auth_routes = auth.get_routes()
|
|
298
|
+
required_scopes = getattr(auth, "required_scopes", None) or []
|
|
299
|
+
|
|
300
|
+
# Get resource metadata URL for WWW-Authenticate header
|
|
301
|
+
resource_metadata_url = auth.get_resource_metadata_url()
|
|
334
302
|
|
|
335
303
|
server_routes.extend(auth_routes)
|
|
336
304
|
server_middleware.extend(auth_middleware)
|
|
337
305
|
|
|
338
|
-
# Determine resource_metadata_url for TokenVerifier
|
|
339
|
-
resource_metadata_url = None
|
|
340
|
-
if isinstance(auth, TokenVerifier) and auth.resource_server_url:
|
|
341
|
-
# Add .well-known path for RFC 9728 compliance
|
|
342
|
-
resource_metadata_url = AnyHttpUrl(
|
|
343
|
-
str(auth.resource_server_url).rstrip("/")
|
|
344
|
-
+ "/.well-known/oauth-protected-resource"
|
|
345
|
-
)
|
|
346
|
-
|
|
347
306
|
# Auth is enabled, wrap endpoint with RequireAuthMiddleware
|
|
348
307
|
server_routes.append(
|
|
349
308
|
Mount(
|
fastmcp/server/proxy.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import inspect
|
|
3
4
|
import warnings
|
|
4
|
-
from collections.abc import Callable
|
|
5
|
+
from collections.abc import Awaitable, Callable
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import TYPE_CHECKING, Any, cast
|
|
7
8
|
from urllib.parse import quote
|
|
@@ -48,11 +49,27 @@ if TYPE_CHECKING:
|
|
|
48
49
|
|
|
49
50
|
logger = get_logger(__name__)
|
|
50
51
|
|
|
52
|
+
# Type alias for client factory functions
|
|
53
|
+
ClientFactoryT = Callable[[], Client] | Callable[[], Awaitable[Client]]
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
|
|
56
|
+
class ProxyManagerMixin:
|
|
57
|
+
"""A mixin for proxy managers to provide a unified client retrieval method."""
|
|
58
|
+
|
|
59
|
+
client_factory: ClientFactoryT
|
|
60
|
+
|
|
61
|
+
async def _get_client(self) -> Client:
|
|
62
|
+
"""Gets a client instance by calling the sync or async factory."""
|
|
63
|
+
client = self.client_factory()
|
|
64
|
+
if inspect.isawaitable(client):
|
|
65
|
+
client = await client
|
|
66
|
+
return client
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ProxyToolManager(ToolManager, ProxyManagerMixin):
|
|
53
70
|
"""A ToolManager that sources its tools from a remote client in addition to local and mounted tools."""
|
|
54
71
|
|
|
55
|
-
def __init__(self, client_factory:
|
|
72
|
+
def __init__(self, client_factory: ClientFactoryT, **kwargs):
|
|
56
73
|
super().__init__(**kwargs)
|
|
57
74
|
self.client_factory = client_factory
|
|
58
75
|
|
|
@@ -63,7 +80,7 @@ class ProxyToolManager(ToolManager):
|
|
|
63
80
|
|
|
64
81
|
# Then add proxy tools, but don't overwrite existing ones
|
|
65
82
|
try:
|
|
66
|
-
client = self.
|
|
83
|
+
client = await self._get_client()
|
|
67
84
|
async with client:
|
|
68
85
|
client_tools = await client.list_tools()
|
|
69
86
|
for tool in client_tools:
|
|
@@ -94,7 +111,7 @@ class ProxyToolManager(ToolManager):
|
|
|
94
111
|
return await super().call_tool(key, arguments)
|
|
95
112
|
except NotFoundError:
|
|
96
113
|
# If not found locally, try proxy
|
|
97
|
-
client = self.
|
|
114
|
+
client = await self._get_client()
|
|
98
115
|
async with client:
|
|
99
116
|
result = await client.call_tool(key, arguments)
|
|
100
117
|
return ToolResult(
|
|
@@ -103,10 +120,10 @@ class ProxyToolManager(ToolManager):
|
|
|
103
120
|
)
|
|
104
121
|
|
|
105
122
|
|
|
106
|
-
class ProxyResourceManager(ResourceManager):
|
|
123
|
+
class ProxyResourceManager(ResourceManager, ProxyManagerMixin):
|
|
107
124
|
"""A ResourceManager that sources its resources from a remote client in addition to local and mounted resources."""
|
|
108
125
|
|
|
109
|
-
def __init__(self, client_factory:
|
|
126
|
+
def __init__(self, client_factory: ClientFactoryT, **kwargs):
|
|
110
127
|
super().__init__(**kwargs)
|
|
111
128
|
self.client_factory = client_factory
|
|
112
129
|
|
|
@@ -117,7 +134,7 @@ class ProxyResourceManager(ResourceManager):
|
|
|
117
134
|
|
|
118
135
|
# Then add proxy resources, but don't overwrite existing ones
|
|
119
136
|
try:
|
|
120
|
-
client = self.
|
|
137
|
+
client = await self._get_client()
|
|
121
138
|
async with client:
|
|
122
139
|
client_resources = await client.list_resources()
|
|
123
140
|
for resource in client_resources:
|
|
@@ -140,7 +157,7 @@ class ProxyResourceManager(ResourceManager):
|
|
|
140
157
|
|
|
141
158
|
# Then add proxy templates, but don't overwrite existing ones
|
|
142
159
|
try:
|
|
143
|
-
client = self.
|
|
160
|
+
client = await self._get_client()
|
|
144
161
|
async with client:
|
|
145
162
|
client_templates = await client.list_resource_templates()
|
|
146
163
|
for template in client_templates:
|
|
@@ -173,7 +190,7 @@ class ProxyResourceManager(ResourceManager):
|
|
|
173
190
|
return await super().read_resource(uri)
|
|
174
191
|
except NotFoundError:
|
|
175
192
|
# If not found locally, try proxy
|
|
176
|
-
client = self.
|
|
193
|
+
client = await self._get_client()
|
|
177
194
|
async with client:
|
|
178
195
|
result = await client.read_resource(uri)
|
|
179
196
|
if isinstance(result[0], TextResourceContents):
|
|
@@ -184,10 +201,10 @@ class ProxyResourceManager(ResourceManager):
|
|
|
184
201
|
raise ResourceError(f"Unsupported content type: {type(result[0])}")
|
|
185
202
|
|
|
186
203
|
|
|
187
|
-
class ProxyPromptManager(PromptManager):
|
|
204
|
+
class ProxyPromptManager(PromptManager, ProxyManagerMixin):
|
|
188
205
|
"""A PromptManager that sources its prompts from a remote client in addition to local and mounted prompts."""
|
|
189
206
|
|
|
190
|
-
def __init__(self, client_factory:
|
|
207
|
+
def __init__(self, client_factory: ClientFactoryT, **kwargs):
|
|
191
208
|
super().__init__(**kwargs)
|
|
192
209
|
self.client_factory = client_factory
|
|
193
210
|
|
|
@@ -198,7 +215,7 @@ class ProxyPromptManager(PromptManager):
|
|
|
198
215
|
|
|
199
216
|
# Then add proxy prompts, but don't overwrite existing ones
|
|
200
217
|
try:
|
|
201
|
-
client = self.
|
|
218
|
+
client = await self._get_client()
|
|
202
219
|
async with client:
|
|
203
220
|
client_prompts = await client.list_prompts()
|
|
204
221
|
for prompt in client_prompts:
|
|
@@ -230,7 +247,7 @@ class ProxyPromptManager(PromptManager):
|
|
|
230
247
|
return await super().render_prompt(name, arguments)
|
|
231
248
|
except NotFoundError:
|
|
232
249
|
# If not found locally, try proxy
|
|
233
|
-
client = self.
|
|
250
|
+
client = await self._get_client()
|
|
234
251
|
async with client:
|
|
235
252
|
result = await client.get_prompt(name, arguments)
|
|
236
253
|
return result
|
|
@@ -444,7 +461,7 @@ class FastMCPProxy(FastMCP):
|
|
|
444
461
|
self,
|
|
445
462
|
client: Client | None = None,
|
|
446
463
|
*,
|
|
447
|
-
client_factory:
|
|
464
|
+
client_factory: ClientFactoryT | None = None,
|
|
448
465
|
**kwargs,
|
|
449
466
|
):
|
|
450
467
|
"""
|
|
@@ -459,6 +476,7 @@ class FastMCPProxy(FastMCP):
|
|
|
459
476
|
created that provides session isolation for backwards compatibility.
|
|
460
477
|
client_factory: A callable that returns a Client instance when called.
|
|
461
478
|
This gives you full control over session creation and reuse.
|
|
479
|
+
Can be either a synchronous or asynchronous function.
|
|
462
480
|
**kwargs: Additional settings for the FastMCP server.
|
|
463
481
|
"""
|
|
464
482
|
|
fastmcp/server/server.py
CHANGED
|
@@ -140,19 +140,18 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
140
140
|
]
|
|
141
141
|
| None
|
|
142
142
|
) = None,
|
|
143
|
-
|
|
144
|
-
cache_expiration_seconds: float | None = None,
|
|
145
|
-
on_duplicate_tools: DuplicateBehavior | None = None,
|
|
146
|
-
on_duplicate_resources: DuplicateBehavior | None = None,
|
|
147
|
-
on_duplicate_prompts: DuplicateBehavior | None = None,
|
|
143
|
+
dependencies: list[str] | None = None,
|
|
148
144
|
resource_prefix_format: Literal["protocol", "path"] | None = None,
|
|
149
145
|
mask_error_details: bool | None = None,
|
|
150
146
|
tools: list[Tool | Callable[..., Any]] | None = None,
|
|
151
147
|
tool_transformations: dict[str, ToolTransformConfig] | None = None,
|
|
152
|
-
|
|
148
|
+
tool_serializer: Callable[[Any], str] | None = None,
|
|
153
149
|
include_tags: set[str] | None = None,
|
|
154
150
|
exclude_tags: set[str] | None = None,
|
|
155
151
|
include_fastmcp_meta: bool | None = None,
|
|
152
|
+
on_duplicate_tools: DuplicateBehavior | None = None,
|
|
153
|
+
on_duplicate_resources: DuplicateBehavior | None = None,
|
|
154
|
+
on_duplicate_prompts: DuplicateBehavior | None = None,
|
|
156
155
|
# ---
|
|
157
156
|
# ---
|
|
158
157
|
# --- The following arguments are DEPRECATED ---
|
|
@@ -304,6 +303,10 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
304
303
|
def instructions(self) -> str | None:
|
|
305
304
|
return self._mcp_server.instructions
|
|
306
305
|
|
|
306
|
+
@property
|
|
307
|
+
def version(self) -> str | None:
|
|
308
|
+
return self._mcp_server.version
|
|
309
|
+
|
|
307
310
|
async def run_async(
|
|
308
311
|
self,
|
|
309
312
|
transport: Transport | None = None,
|
fastmcp/settings.py
CHANGED
|
@@ -244,13 +244,10 @@ class Settings(BaseSettings):
|
|
|
244
244
|
),
|
|
245
245
|
] = False
|
|
246
246
|
|
|
247
|
-
server_dependencies:
|
|
248
|
-
list
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
description="List of dependencies to install in the server environment",
|
|
252
|
-
),
|
|
253
|
-
] = []
|
|
247
|
+
server_dependencies: list[str] = Field(
|
|
248
|
+
default_factory=list,
|
|
249
|
+
description="List of dependencies to install in the server environment",
|
|
250
|
+
)
|
|
254
251
|
|
|
255
252
|
# StreamableHTTP settings
|
|
256
253
|
json_response: bool = False
|
fastmcp/utilities/components.py
CHANGED
|
@@ -92,7 +92,12 @@ class FastMCPComponent(FastMCPBaseModel):
|
|
|
92
92
|
return meta or None
|
|
93
93
|
|
|
94
94
|
def with_key(self, key: str) -> Self:
|
|
95
|
-
|
|
95
|
+
# `model_copy` has an `update` parameter but it doesn't work for certain private attributes
|
|
96
|
+
# https://github.com/pydantic/pydantic/issues/12116
|
|
97
|
+
# So we manually set the private attribute here instead
|
|
98
|
+
copy = self.model_copy()
|
|
99
|
+
copy._key = key
|
|
100
|
+
return copy
|
|
96
101
|
|
|
97
102
|
def __eq__(self, other: object) -> bool:
|
|
98
103
|
if type(self) is not type(other):
|
fastmcp/utilities/inspect.py
CHANGED
|
@@ -9,6 +9,7 @@ from typing import Any
|
|
|
9
9
|
from mcp.server.fastmcp import FastMCP as FastMCP1x
|
|
10
10
|
|
|
11
11
|
import fastmcp
|
|
12
|
+
from fastmcp import Client
|
|
12
13
|
from fastmcp.server.server import FastMCP
|
|
13
14
|
|
|
14
15
|
|
|
@@ -71,7 +72,7 @@ class FastMCPInfo:
|
|
|
71
72
|
instructions: str | None
|
|
72
73
|
fastmcp_version: str
|
|
73
74
|
mcp_version: str
|
|
74
|
-
server_version: str
|
|
75
|
+
server_version: str | None
|
|
75
76
|
tools: list[ToolInfo]
|
|
76
77
|
prompts: list[PromptInfo]
|
|
77
78
|
resources: list[ResourceInfo]
|
|
@@ -170,7 +171,9 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
|
|
|
170
171
|
instructions=mcp.instructions,
|
|
171
172
|
fastmcp_version=fastmcp.__version__,
|
|
172
173
|
mcp_version=importlib.metadata.version("mcp"),
|
|
173
|
-
server_version=
|
|
174
|
+
server_version=(
|
|
175
|
+
mcp.version if hasattr(mcp, "version") else mcp._mcp_server.version
|
|
176
|
+
),
|
|
174
177
|
tools=tool_infos,
|
|
175
178
|
prompts=prompt_infos,
|
|
176
179
|
resources=resource_infos,
|
|
@@ -179,7 +182,7 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
|
|
|
179
182
|
)
|
|
180
183
|
|
|
181
184
|
|
|
182
|
-
async def inspect_fastmcp_v1(mcp:
|
|
185
|
+
async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
|
|
183
186
|
"""Extract information from a FastMCP v1.x instance using a Client.
|
|
184
187
|
|
|
185
188
|
Args:
|
|
@@ -188,7 +191,6 @@ async def inspect_fastmcp_v1(mcp: Any) -> FastMCPInfo:
|
|
|
188
191
|
Returns:
|
|
189
192
|
FastMCPInfo dataclass containing the extracted information
|
|
190
193
|
"""
|
|
191
|
-
from fastmcp import Client
|
|
192
194
|
|
|
193
195
|
# Use a client to interact with the FastMCP1x server
|
|
194
196
|
async with Client(mcp) as client:
|
|
@@ -288,11 +290,11 @@ async def inspect_fastmcp_v1(mcp: Any) -> FastMCPInfo:
|
|
|
288
290
|
}
|
|
289
291
|
|
|
290
292
|
return FastMCPInfo(
|
|
291
|
-
name=mcp.name,
|
|
292
|
-
instructions=
|
|
293
|
-
fastmcp_version=
|
|
293
|
+
name=mcp._mcp_server.name,
|
|
294
|
+
instructions=mcp._mcp_server.instructions,
|
|
295
|
+
fastmcp_version=importlib.metadata.version("mcp"),
|
|
294
296
|
mcp_version=importlib.metadata.version("mcp"),
|
|
295
|
-
server_version=
|
|
297
|
+
server_version=mcp._mcp_server.version,
|
|
296
298
|
tools=tool_infos,
|
|
297
299
|
prompts=prompt_infos,
|
|
298
300
|
resources=resource_infos,
|
|
@@ -301,14 +303,7 @@ async def inspect_fastmcp_v1(mcp: Any) -> FastMCPInfo:
|
|
|
301
303
|
)
|
|
302
304
|
|
|
303
305
|
|
|
304
|
-
def
|
|
305
|
-
"""Check if the given instance is a FastMCP v1.x instance."""
|
|
306
|
-
|
|
307
|
-
# Check if it's an instance of FastMCP1x and not FastMCP2
|
|
308
|
-
return isinstance(mcp, FastMCP1x) and not isinstance(mcp, FastMCP)
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
async def inspect_fastmcp(mcp: FastMCP[Any] | Any) -> FastMCPInfo:
|
|
306
|
+
async def inspect_fastmcp(mcp: FastMCP[Any] | FastMCP1x) -> FastMCPInfo:
|
|
312
307
|
"""Extract information from a FastMCP instance into a dataclass.
|
|
313
308
|
|
|
314
309
|
This function automatically detects whether the instance is FastMCP v1.x or v2.x
|
|
@@ -320,7 +315,7 @@ async def inspect_fastmcp(mcp: FastMCP[Any] | Any) -> FastMCPInfo:
|
|
|
320
315
|
Returns:
|
|
321
316
|
FastMCPInfo dataclass containing the extracted information
|
|
322
317
|
"""
|
|
323
|
-
if
|
|
318
|
+
if isinstance(mcp, FastMCP1x):
|
|
324
319
|
return await inspect_fastmcp_v1(mcp)
|
|
325
320
|
else:
|
|
326
321
|
return await inspect_fastmcp_v2(mcp)
|
fastmcp/utilities/openapi.py
CHANGED
|
@@ -82,13 +82,9 @@ def format_array_parameter(
|
|
|
82
82
|
return values
|
|
83
83
|
else:
|
|
84
84
|
# For path parameters, fallback to string representation without Python syntax
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
.replace("]", "")
|
|
89
|
-
.replace("'", "")
|
|
90
|
-
.replace('"', "")
|
|
91
|
-
)
|
|
85
|
+
# Use str.translate() for efficient character removal
|
|
86
|
+
translation_table = str.maketrans("", "", "[]'\"")
|
|
87
|
+
str_value = str(values).translate(translation_table)
|
|
92
88
|
return str_value
|
|
93
89
|
|
|
94
90
|
|
fastmcp/utilities/types.py
CHANGED
|
@@ -101,7 +101,12 @@ def get_cached_typeadapter(cls: T) -> TypeAdapter[T]:
|
|
|
101
101
|
new_func.__module__ = cls.__module__
|
|
102
102
|
new_func.__qualname__ = getattr(cls, "__qualname__", cls.__name__)
|
|
103
103
|
new_func.__annotations__ = processed_hints
|
|
104
|
-
|
|
104
|
+
|
|
105
|
+
if inspect.ismethod(cls):
|
|
106
|
+
new_method = types.MethodType(new_func, cls.__self__)
|
|
107
|
+
return TypeAdapter(new_method)
|
|
108
|
+
else:
|
|
109
|
+
return TypeAdapter(new_func)
|
|
105
110
|
|
|
106
111
|
return TypeAdapter(cls)
|
|
107
112
|
|
|
@@ -2,17 +2,17 @@ fastmcp/__init__.py,sha256=B_FAqsxbTJmwvJKyIDMOZWpUUdmO806bKo8RR32oZL0,1503
|
|
|
2
2
|
fastmcp/exceptions.py,sha256=-krEavxwddQau6T7MESCR4VjKNLfP9KHJrU1p3y72FU,744
|
|
3
3
|
fastmcp/mcp_config.py,sha256=jf6VyGHli3GcZNg4spdV1L_lPOeNCKTJh-nGIBaMgn4,10324
|
|
4
4
|
fastmcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
fastmcp/settings.py,sha256=
|
|
5
|
+
fastmcp/settings.py,sha256=LxSDlUmWX2SJLZRjX5ih07jAVIHaFZFRK7RCQPPKAGI,11081
|
|
6
6
|
fastmcp/cli/__init__.py,sha256=Ii284TNoG5lxTP40ETMGhHEq3lQZWxu9m9JuU57kUpQ,87
|
|
7
7
|
fastmcp/cli/claude.py,sha256=IAlcZ4qZKBBj09jZUMEx7EANZE_IR3vcu7zOBJmMOuU,4567
|
|
8
|
-
fastmcp/cli/cli.py,sha256=
|
|
9
|
-
fastmcp/cli/run.py,sha256=
|
|
8
|
+
fastmcp/cli/cli.py,sha256=32OKZ49JKq3INqIlHXnZfFNTxQ2XYE1UkW3x5iTvtlI,15699
|
|
9
|
+
fastmcp/cli/run.py,sha256=12I_JP4NSbmNqfQT2u14wvl4Uq2Rr_RvlK1z6bgAfOQ,12383
|
|
10
10
|
fastmcp/cli/install/__init__.py,sha256=cDEc0hhuf_xwGpI6ghpqlvlMdBMJUGHq_rs-tgmOJZ8,695
|
|
11
|
-
fastmcp/cli/install/claude_code.py,sha256=
|
|
12
|
-
fastmcp/cli/install/claude_desktop.py,sha256=
|
|
13
|
-
fastmcp/cli/install/cursor.py,sha256=
|
|
14
|
-
fastmcp/cli/install/mcp_json.py,sha256=
|
|
15
|
-
fastmcp/cli/install/shared.py,sha256=
|
|
11
|
+
fastmcp/cli/install/claude_code.py,sha256=1aSO02cJGGWAVl_iVY_dWpShz9Df5ndrAGA_qEj5rbc,7724
|
|
12
|
+
fastmcp/cli/install/claude_desktop.py,sha256=4ErROs-wxdLWr-bCMvL3r9-UIYMETfAghzHTNzhLx5E,6884
|
|
13
|
+
fastmcp/cli/install/cursor.py,sha256=KkRzIBiqaJhmT1wfl3IzprgTnIiAIioTcveq-oDHELE,6849
|
|
14
|
+
fastmcp/cli/install/mcp_json.py,sha256=RUlg7rQiwBysgTOoJfjvVwgz9MFXT-u7aleBDzK1eVQ,5935
|
|
15
|
+
fastmcp/cli/install/shared.py,sha256=T-0R3-WMlmq5uRHYMEVbdItvm6zThzFET4AlI_foG7s,2695
|
|
16
16
|
fastmcp/client/__init__.py,sha256=J-RcLU2WcnYnstXWoW01itGtAg7DEjvCsWyqQKQljoo,663
|
|
17
17
|
fastmcp/client/client.py,sha256=GniETS28L8B-ahzztU2ZLI_XSCcEib-miGzE2ZnG4Xc,34054
|
|
18
18
|
fastmcp/client/elicitation.py,sha256=Jf9yqna8R7r1hqedXAyh9a2-QNVzbCSKUDZhkFHqHqg,2403
|
|
@@ -65,19 +65,19 @@ fastmcp/server/__init__.py,sha256=bMD4aQD4yJqLz7-mudoNsyeV8UgQfRAg3PRwPvwTEds,11
|
|
|
65
65
|
fastmcp/server/context.py,sha256=ooVwF4RoIbLB2S043E5HtK_dCyAWQkDm1YWCkqfUPzs,22638
|
|
66
66
|
fastmcp/server/dependencies.py,sha256=gfb1l3KD2m3h7BpN8lgKWrHY3_OJdDrlnJXCvzmU5YM,2487
|
|
67
67
|
fastmcp/server/elicitation.py,sha256=jZIHjV4NjhYbT-w8pBArwd0vNzP8OYwzmsnWDdk6Bd0,6136
|
|
68
|
-
fastmcp/server/http.py,sha256=
|
|
68
|
+
fastmcp/server/http.py,sha256=idZGs4kRm_eLMscMrlEBIBpX21aiuTlvon9Fizmjswo,11648
|
|
69
69
|
fastmcp/server/low_level.py,sha256=LNmc_nU_wx-fRG8OEHdLPKopZpovcrWlyAxJzKss3TA,1239
|
|
70
70
|
fastmcp/server/openapi.py,sha256=-7-pKwQ1hT-UV9OnLlWrjbbXXRfZld8YJqa4Duybhtw,42102
|
|
71
|
-
fastmcp/server/proxy.py,sha256=
|
|
72
|
-
fastmcp/server/server.py,sha256=
|
|
73
|
-
fastmcp/server/auth/__init__.py,sha256=
|
|
74
|
-
fastmcp/server/auth/auth.py,sha256=
|
|
71
|
+
fastmcp/server/proxy.py,sha256=soKobVa-c-1Vex7IYnz-jAT526Do2Z4Rs1FaT5ruLro,25651
|
|
72
|
+
fastmcp/server/server.py,sha256=PBDHMx2N2Zr6qVeEs7XEcZrCNObnsAoIrPbK9DlArGk,87634
|
|
73
|
+
fastmcp/server/auth/__init__.py,sha256=ldv13Dsxr2r-VdUV6af6P5qa3wMPQ-MP_yKxsWdlb-4,550
|
|
74
|
+
fastmcp/server/auth/auth.py,sha256=FMkCcht4BMEqKx-SwvfzVC5l7X2moiIzS_ZevaHBq08,10536
|
|
75
75
|
fastmcp/server/auth/registry.py,sha256=4ftVbbuyAi-8zBiJL9-dYIKLU_EOpxY-pFdzHMrmjV8,1306
|
|
76
76
|
fastmcp/server/auth/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
77
77
|
fastmcp/server/auth/providers/bearer.py,sha256=iu4pUj7TF5pT1wPuAGzDuM6lt5WtzenBN3c0otUleQY,923
|
|
78
78
|
fastmcp/server/auth/providers/in_memory.py,sha256=tlUwbUZK5stUC_wjezcDs61PxjaHjykXbd8aH0cnW6k,14359
|
|
79
|
-
fastmcp/server/auth/providers/jwt.py,sha256=
|
|
80
|
-
fastmcp/server/auth/providers/workos.py,sha256=
|
|
79
|
+
fastmcp/server/auth/providers/jwt.py,sha256=7pAH8kuudl_lbY2skeVDlLkxFMhs5-3axpAoGVVp_MU,18882
|
|
80
|
+
fastmcp/server/auth/providers/workos.py,sha256=jedSq4uhttDxYDYSZFrb4u_q1X9JeHgvKupJPpu0tSM,5590
|
|
81
81
|
fastmcp/server/middleware/__init__.py,sha256=vh5C9ubN6q-y5QND32P4mQ4zDT89C7XYK39yqhELNAk,155
|
|
82
82
|
fastmcp/server/middleware/error_handling.py,sha256=SoDatr9i3T2qSIUbSEGWrOnu4WPPyMDymnsF5GR_BiE,7572
|
|
83
83
|
fastmcp/server/middleware/logging.py,sha256=UIAoafnKRGWpQa7OX5nzChep-9EhKdyTDBmmcRcEVdo,6239
|
|
@@ -90,19 +90,19 @@ fastmcp/tools/tool_manager.py,sha256=Pr6BI7IInvhkTw2S4ENWj8qMewdYi2CpzbOBiJIk40c
|
|
|
90
90
|
fastmcp/tools/tool_transform.py,sha256=f8Bt79qdmOM8Zietm7lXb5_5DlJLu02ovGmQBQJ6orw,36671
|
|
91
91
|
fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
|
|
92
92
|
fastmcp/utilities/cli.py,sha256=TXuSyALFAGJwi7tWEBwBmaGhYZBdF1aG6dLgl3zjM1w,3272
|
|
93
|
-
fastmcp/utilities/components.py,sha256=
|
|
93
|
+
fastmcp/utilities/components.py,sha256=wmo1XZBqf_tcOMMN1a-r8d1pT7g--Zmf75f5-vXx5FA,5333
|
|
94
94
|
fastmcp/utilities/exceptions.py,sha256=7Z9j5IzM5rT27BC1Mcn8tkS-bjqCYqMKwb2MMTaxJYU,1350
|
|
95
95
|
fastmcp/utilities/http.py,sha256=1ns1ymBS-WSxbZjGP6JYjSO52Wa_ls4j4WbnXiupoa4,245
|
|
96
|
-
fastmcp/utilities/inspect.py,sha256=
|
|
96
|
+
fastmcp/utilities/inspect.py,sha256=VLCrh8oMnIg49uuwVO899rUcHhxszPJ1nU0RZ8q2HX8,10639
|
|
97
97
|
fastmcp/utilities/json_schema.py,sha256=Nk6qQKtp0-MlMbRQmx8ps8SJ7SW9CpuWhSes1VASFPQ,8141
|
|
98
98
|
fastmcp/utilities/json_schema_type.py,sha256=fSG-af3OPGgOhuhY_xb0-JsTu5tqi275zXlUw4ItjNo,22287
|
|
99
99
|
fastmcp/utilities/logging.py,sha256=1y7oNmy8WrR0NsfNVw1LPoKu92OFdmzIO65syOKi_BI,1388
|
|
100
100
|
fastmcp/utilities/mcp_config.py,sha256=zzs4VWHqG0eWEnEUwVve7mef_JFThwfvqBYt7nx3jXc,871
|
|
101
|
-
fastmcp/utilities/openapi.py,sha256=
|
|
101
|
+
fastmcp/utilities/openapi.py,sha256=Jcxu0s9VdA8RSkx1iyoTQwvsny4YCusuuJahyy-TvE8,63300
|
|
102
102
|
fastmcp/utilities/tests.py,sha256=9FVLmGYfUjqfn0pPH33awlTgvg-JCNbnZnraJHNSYTg,6156
|
|
103
|
-
fastmcp/utilities/types.py,sha256=
|
|
104
|
-
fastmcp-2.11.
|
|
105
|
-
fastmcp-2.11.
|
|
106
|
-
fastmcp-2.11.
|
|
107
|
-
fastmcp-2.11.
|
|
108
|
-
fastmcp-2.11.
|
|
103
|
+
fastmcp/utilities/types.py,sha256=zrF8Oc_L_BdzGj0pqU5ZalM02BisC0_D-d8kcRGccS0,13984
|
|
104
|
+
fastmcp-2.11.2.dist-info/METADATA,sha256=6OE28R9_gx-82MA3st0SR6dCxsfoH4oftu0jhSjbL10,17839
|
|
105
|
+
fastmcp-2.11.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
106
|
+
fastmcp-2.11.2.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
|
|
107
|
+
fastmcp-2.11.2.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
108
|
+
fastmcp-2.11.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|