fastmcp 2.14.2__py3-none-any.whl → 2.14.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 +9 -0
- fastmcp/client/auth/oauth.py +4 -1
- fastmcp/client/transports.py +19 -13
- fastmcp/server/auth/oauth_proxy.py +45 -10
- fastmcp/server/context.py +28 -4
- fastmcp/server/server.py +7 -4
- fastmcp/server/tasks/handlers.py +19 -10
- fastmcp/server/tasks/protocol.py +12 -8
- fastmcp/server/tasks/subscriptions.py +3 -5
- fastmcp/settings.py +15 -0
- fastmcp/utilities/cli.py +31 -4
- fastmcp/utilities/version_check.py +153 -0
- {fastmcp-2.14.2.dist-info → fastmcp-2.14.3.dist-info}/METADATA +2 -2
- {fastmcp-2.14.2.dist-info → fastmcp-2.14.3.dist-info}/RECORD +17 -16
- {fastmcp-2.14.2.dist-info → fastmcp-2.14.3.dist-info}/WHEEL +0 -0
- {fastmcp-2.14.2.dist-info → fastmcp-2.14.3.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.14.2.dist-info → fastmcp-2.14.3.dist-info}/licenses/LICENSE +0 -0
fastmcp/cli/cli.py
CHANGED
|
@@ -28,6 +28,7 @@ from fastmcp.utilities.inspect import (
|
|
|
28
28
|
)
|
|
29
29
|
from fastmcp.utilities.logging import get_logger
|
|
30
30
|
from fastmcp.utilities.mcp_server_config import MCPServerConfig
|
|
31
|
+
from fastmcp.utilities.version_check import check_for_newer_version
|
|
31
32
|
|
|
32
33
|
logger = get_logger("cli")
|
|
33
34
|
console = Console()
|
|
@@ -122,6 +123,14 @@ def version(
|
|
|
122
123
|
else:
|
|
123
124
|
console.print(g)
|
|
124
125
|
|
|
126
|
+
# Check for updates (not included in --copy output)
|
|
127
|
+
if newer_version := check_for_newer_version():
|
|
128
|
+
console.print()
|
|
129
|
+
console.print(
|
|
130
|
+
f"[bold]🎉 FastMCP update available:[/bold] [green]{newer_version}[/green]"
|
|
131
|
+
)
|
|
132
|
+
console.print("[dim]Run: pip install --upgrade fastmcp[/dim]")
|
|
133
|
+
|
|
125
134
|
|
|
126
135
|
@app.command
|
|
127
136
|
async def dev(
|
fastmcp/client/auth/oauth.py
CHANGED
|
@@ -105,10 +105,13 @@ class TokenStorageAdapter(TokenStorage):
|
|
|
105
105
|
|
|
106
106
|
@override
|
|
107
107
|
async def set_tokens(self, tokens: OAuthToken) -> None:
|
|
108
|
+
# Don't set TTL based on access token expiry - the refresh token may be
|
|
109
|
+
# valid much longer. Use 1 year as a reasonable upper bound; the OAuth
|
|
110
|
+
# provider handles actual token expiry/refresh logic.
|
|
108
111
|
await self._storage_oauth_token.put(
|
|
109
112
|
key=self._get_token_cache_key(),
|
|
110
113
|
value=tokens,
|
|
111
|
-
ttl=
|
|
114
|
+
ttl=60 * 60 * 24 * 365, # 1 year
|
|
112
115
|
)
|
|
113
116
|
|
|
114
117
|
@override
|
fastmcp/client/transports.py
CHANGED
|
@@ -25,7 +25,7 @@ from mcp.client.sse import sse_client
|
|
|
25
25
|
from mcp.client.stdio import stdio_client
|
|
26
26
|
from mcp.client.streamable_http import streamable_http_client
|
|
27
27
|
from mcp.server.fastmcp import FastMCP as FastMCP1Server
|
|
28
|
-
from mcp.shared._httpx_utils import McpHttpClientFactory
|
|
28
|
+
from mcp.shared._httpx_utils import McpHttpClientFactory, create_mcp_http_client
|
|
29
29
|
from mcp.shared.memory import create_client_server_memory_streams
|
|
30
30
|
from pydantic import AnyUrl
|
|
31
31
|
from typing_extensions import TypedDict, Unpack
|
|
@@ -284,25 +284,31 @@ class StreamableHttpTransport(ClientTransport):
|
|
|
284
284
|
# need to be forwarded to the remote server.
|
|
285
285
|
headers = get_http_headers() | self.headers
|
|
286
286
|
|
|
287
|
-
#
|
|
288
|
-
|
|
289
|
-
"headers": headers,
|
|
290
|
-
"auth": self.auth,
|
|
291
|
-
"follow_redirects": True,
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
# Configure timeout if provided (convert timedelta to seconds for httpx)
|
|
287
|
+
# Configure timeout if provided, preserving MCP's 30s connect default
|
|
288
|
+
timeout: httpx.Timeout | None = None
|
|
295
289
|
if session_kwargs.get("read_timeout_seconds") is not None:
|
|
296
290
|
read_timeout_seconds = cast(
|
|
297
291
|
datetime.timedelta, session_kwargs.get("read_timeout_seconds")
|
|
298
292
|
)
|
|
299
|
-
|
|
293
|
+
timeout = httpx.Timeout(30.0, read=read_timeout_seconds.total_seconds())
|
|
300
294
|
|
|
301
|
-
# Create httpx client from factory or use default
|
|
295
|
+
# Create httpx client from factory or use default with MCP-appropriate timeouts
|
|
296
|
+
# create_mcp_http_client uses 30s connect/5min read timeout by default,
|
|
297
|
+
# and always enables follow_redirects
|
|
302
298
|
if self.httpx_client_factory is not None:
|
|
303
|
-
|
|
299
|
+
# Factory clients get the full kwargs for backwards compatibility
|
|
300
|
+
http_client = self.httpx_client_factory(
|
|
301
|
+
headers=headers,
|
|
302
|
+
auth=self.auth,
|
|
303
|
+
follow_redirects=True,
|
|
304
|
+
**({"timeout": timeout} if timeout else {}),
|
|
305
|
+
)
|
|
304
306
|
else:
|
|
305
|
-
http_client =
|
|
307
|
+
http_client = create_mcp_http_client(
|
|
308
|
+
headers=headers,
|
|
309
|
+
timeout=timeout,
|
|
310
|
+
auth=self.auth,
|
|
311
|
+
)
|
|
306
312
|
|
|
307
313
|
# Ensure httpx client is closed after use
|
|
308
314
|
async with (
|
|
@@ -1221,12 +1221,24 @@ class OAuthProxy(OAuthProvider):
|
|
|
1221
1221
|
# - 1 year if no refresh token (likely API-key-style token like GitHub OAuth Apps)
|
|
1222
1222
|
if "expires_in" in idp_tokens:
|
|
1223
1223
|
expires_in = int(idp_tokens["expires_in"])
|
|
1224
|
+
logger.debug(
|
|
1225
|
+
"Access token TTL: %d seconds (from IdP expires_in)", expires_in
|
|
1226
|
+
)
|
|
1224
1227
|
elif self._fallback_access_token_expiry_seconds is not None:
|
|
1225
1228
|
expires_in = self._fallback_access_token_expiry_seconds
|
|
1229
|
+
logger.debug(
|
|
1230
|
+
"Access token TTL: %d seconds (using configured fallback)", expires_in
|
|
1231
|
+
)
|
|
1226
1232
|
elif idp_tokens.get("refresh_token"):
|
|
1227
1233
|
expires_in = DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS
|
|
1234
|
+
logger.debug(
|
|
1235
|
+
"Access token TTL: %d seconds (default, has refresh token)", expires_in
|
|
1236
|
+
)
|
|
1228
1237
|
else:
|
|
1229
1238
|
expires_in = DEFAULT_ACCESS_TOKEN_EXPIRY_NO_REFRESH_SECONDS
|
|
1239
|
+
logger.debug(
|
|
1240
|
+
"Access token TTL: %d seconds (default, no refresh token)", expires_in
|
|
1241
|
+
)
|
|
1230
1242
|
|
|
1231
1243
|
# Calculate refresh token expiry if provided by upstream
|
|
1232
1244
|
# Some providers include refresh_expires_in, some don't
|
|
@@ -1266,8 +1278,9 @@ class OAuthProxy(OAuthProvider):
|
|
|
1266
1278
|
await self._upstream_token_store.put(
|
|
1267
1279
|
key=upstream_token_id,
|
|
1268
1280
|
value=upstream_token_set,
|
|
1269
|
-
ttl=
|
|
1270
|
-
|
|
1281
|
+
ttl=max(
|
|
1282
|
+
refresh_expires_in or 0, expires_in, 1
|
|
1283
|
+
), # Keep until longest-lived token expires (min 1s for safety)
|
|
1271
1284
|
)
|
|
1272
1285
|
logger.debug("Stored encrypted upstream tokens (jti=%s)", access_jti[:8])
|
|
1273
1286
|
|
|
@@ -1467,10 +1480,21 @@ class OAuthProxy(OAuthProvider):
|
|
|
1467
1480
|
# (user override still applies if set)
|
|
1468
1481
|
if "expires_in" in token_response:
|
|
1469
1482
|
new_expires_in = int(token_response["expires_in"])
|
|
1483
|
+
logger.debug(
|
|
1484
|
+
"Refreshed access token TTL: %d seconds (from IdP expires_in)",
|
|
1485
|
+
new_expires_in,
|
|
1486
|
+
)
|
|
1470
1487
|
elif self._fallback_access_token_expiry_seconds is not None:
|
|
1471
1488
|
new_expires_in = self._fallback_access_token_expiry_seconds
|
|
1489
|
+
logger.debug(
|
|
1490
|
+
"Refreshed access token TTL: %d seconds (using configured fallback)",
|
|
1491
|
+
new_expires_in,
|
|
1492
|
+
)
|
|
1472
1493
|
else:
|
|
1473
1494
|
new_expires_in = DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS
|
|
1495
|
+
logger.debug(
|
|
1496
|
+
"Refreshed access token TTL: %d seconds (default)", new_expires_in
|
|
1497
|
+
)
|
|
1474
1498
|
upstream_token_set.access_token = token_response["access_token"]
|
|
1475
1499
|
upstream_token_set.expires_at = time.time() + new_expires_in
|
|
1476
1500
|
|
|
@@ -1504,15 +1528,18 @@ class OAuthProxy(OAuthProvider):
|
|
|
1504
1528
|
)
|
|
1505
1529
|
|
|
1506
1530
|
upstream_token_set.raw_token_data = token_response
|
|
1531
|
+
# Calculate refresh TTL for storage
|
|
1532
|
+
refresh_ttl = new_refresh_expires_in or (
|
|
1533
|
+
int(upstream_token_set.refresh_token_expires_at - time.time())
|
|
1534
|
+
if upstream_token_set.refresh_token_expires_at
|
|
1535
|
+
else 60 * 60 * 24 * 30 # Default to 30 days if unknown
|
|
1536
|
+
)
|
|
1507
1537
|
await self._upstream_token_store.put(
|
|
1508
1538
|
key=upstream_token_set.upstream_token_id,
|
|
1509
1539
|
value=upstream_token_set,
|
|
1510
|
-
ttl=
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
if upstream_token_set.refresh_token_expires_at
|
|
1514
|
-
else 60 * 60 * 24 * 30 # Default to 30 days if unknown
|
|
1515
|
-
), # Auto-expire when refresh token expires
|
|
1540
|
+
ttl=max(
|
|
1541
|
+
refresh_ttl, new_expires_in, 1
|
|
1542
|
+
), # Keep until longest-lived token expires (min 1s for safety)
|
|
1516
1543
|
)
|
|
1517
1544
|
|
|
1518
1545
|
# Issue new minimal FastMCP access token (just a reference via JTI)
|
|
@@ -1549,7 +1576,7 @@ class OAuthProxy(OAuthProvider):
|
|
|
1549
1576
|
)
|
|
1550
1577
|
|
|
1551
1578
|
# Store new refresh token JTI mapping with aligned expiry
|
|
1552
|
-
|
|
1579
|
+
# (reuse refresh_ttl calculated above for upstream token store)
|
|
1553
1580
|
await self._jti_mapping_store.put(
|
|
1554
1581
|
key=new_refresh_jti,
|
|
1555
1582
|
value=JTIMapping(
|
|
@@ -1622,7 +1649,10 @@ class OAuthProxy(OAuthProvider):
|
|
|
1622
1649
|
# 2. Look up upstream token via JTI mapping
|
|
1623
1650
|
jti_mapping = await self._jti_mapping_store.get(key=jti)
|
|
1624
1651
|
if not jti_mapping:
|
|
1625
|
-
logger.
|
|
1652
|
+
logger.info(
|
|
1653
|
+
"JTI mapping not found (token may have expired): jti=%s...",
|
|
1654
|
+
jti[:16],
|
|
1655
|
+
)
|
|
1626
1656
|
return None
|
|
1627
1657
|
|
|
1628
1658
|
upstream_token_set = await self._upstream_token_store.get(
|
|
@@ -1865,6 +1895,11 @@ class OAuthProxy(OAuthProvider):
|
|
|
1865
1895
|
logger.debug(
|
|
1866
1896
|
f"Successfully exchanged IdP code for tokens (transaction: {txn_id}, PKCE: {bool(proxy_code_verifier)})"
|
|
1867
1897
|
)
|
|
1898
|
+
logger.debug(
|
|
1899
|
+
"IdP token response: expires_in=%s, has_refresh_token=%s",
|
|
1900
|
+
idp_tokens.get("expires_in"),
|
|
1901
|
+
"refresh_token" in idp_tokens,
|
|
1902
|
+
)
|
|
1868
1903
|
|
|
1869
1904
|
except Exception as e:
|
|
1870
1905
|
logger.error("IdP token exchange failed: %s", e)
|
fastmcp/server/context.py
CHANGED
|
@@ -185,10 +185,24 @@ class Context:
|
|
|
185
185
|
self._tokens.append(token)
|
|
186
186
|
|
|
187
187
|
# Set current server for dependency injection (use weakref to avoid reference cycles)
|
|
188
|
-
from fastmcp.server.dependencies import
|
|
188
|
+
from fastmcp.server.dependencies import (
|
|
189
|
+
_current_docket,
|
|
190
|
+
_current_server,
|
|
191
|
+
_current_worker,
|
|
192
|
+
)
|
|
189
193
|
|
|
190
194
|
self._server_token = _current_server.set(weakref.ref(self.fastmcp))
|
|
191
195
|
|
|
196
|
+
# Set docket/worker from server instance for this request's context.
|
|
197
|
+
# This ensures ContextVars work even in environments (like Lambda) where
|
|
198
|
+
# lifespan ContextVars don't propagate to request handlers.
|
|
199
|
+
server = self.fastmcp
|
|
200
|
+
if server._docket is not None:
|
|
201
|
+
self._docket_token = _current_docket.set(server._docket)
|
|
202
|
+
|
|
203
|
+
if server._worker is not None:
|
|
204
|
+
self._worker_token = _current_worker.set(server._worker)
|
|
205
|
+
|
|
192
206
|
return self
|
|
193
207
|
|
|
194
208
|
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
@@ -196,10 +210,20 @@ class Context:
|
|
|
196
210
|
# Flush any remaining notifications before exiting
|
|
197
211
|
await self._flush_notifications()
|
|
198
212
|
|
|
199
|
-
# Reset server
|
|
200
|
-
|
|
201
|
-
|
|
213
|
+
# Reset server/docket/worker tokens
|
|
214
|
+
from fastmcp.server.dependencies import (
|
|
215
|
+
_current_docket,
|
|
216
|
+
_current_server,
|
|
217
|
+
_current_worker,
|
|
218
|
+
)
|
|
202
219
|
|
|
220
|
+
if hasattr(self, "_worker_token"):
|
|
221
|
+
_current_worker.reset(self._worker_token)
|
|
222
|
+
delattr(self, "_worker_token")
|
|
223
|
+
if hasattr(self, "_docket_token"):
|
|
224
|
+
_current_docket.reset(self._docket_token)
|
|
225
|
+
delattr(self, "_docket_token")
|
|
226
|
+
if hasattr(self, "_server_token"):
|
|
203
227
|
_current_server.reset(self._server_token)
|
|
204
228
|
delattr(self, "_server_token")
|
|
205
229
|
|
fastmcp/server/server.py
CHANGED
|
@@ -197,8 +197,9 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
197
197
|
# Resolve server default for background task support
|
|
198
198
|
self._support_tasks_by_default: bool = tasks if tasks is not None else False
|
|
199
199
|
|
|
200
|
-
# Docket
|
|
200
|
+
# Docket and Worker instances (set during lifespan for cross-task access)
|
|
201
201
|
self._docket = None
|
|
202
|
+
self._worker = None
|
|
202
203
|
|
|
203
204
|
self._additional_http_routes: list[BaseRoute] = []
|
|
204
205
|
self._mounted_servers: list[MountedServer] = []
|
|
@@ -468,6 +469,8 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
468
469
|
|
|
469
470
|
# Create and start Worker
|
|
470
471
|
async with Worker(docket, **worker_kwargs) as worker: # type: ignore[arg-type]
|
|
472
|
+
# Store on server instance for cross-context access
|
|
473
|
+
self._worker = worker
|
|
471
474
|
# Set Worker in ContextVar so CurrentWorker can access it
|
|
472
475
|
worker_token = _current_worker.set(worker)
|
|
473
476
|
try:
|
|
@@ -480,13 +483,11 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
480
483
|
await worker_task
|
|
481
484
|
finally:
|
|
482
485
|
_current_worker.reset(worker_token)
|
|
486
|
+
self._worker = None
|
|
483
487
|
finally:
|
|
484
|
-
# Reset ContextVar
|
|
485
488
|
_current_docket.reset(docket_token)
|
|
486
|
-
# Clear instance attribute
|
|
487
489
|
self._docket = None
|
|
488
490
|
finally:
|
|
489
|
-
# Reset server ContextVar
|
|
490
491
|
_current_server.reset(server_token)
|
|
491
492
|
|
|
492
493
|
async def _register_mounted_server_functions(
|
|
@@ -564,6 +565,8 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
564
565
|
@asynccontextmanager
|
|
565
566
|
async def _lifespan_manager(self) -> AsyncIterator[None]:
|
|
566
567
|
if self._lifespan_result_set:
|
|
568
|
+
# Lifespan already ran - ContextVars will be set by Context.__aenter__
|
|
569
|
+
# at request time, so we just yield here.
|
|
567
570
|
yield
|
|
568
571
|
return
|
|
569
572
|
|
fastmcp/server/tasks/handlers.py
CHANGED
|
@@ -56,6 +56,7 @@ async def handle_tool_as_task(
|
|
|
56
56
|
ctx = get_context()
|
|
57
57
|
session_id = ctx.session_id
|
|
58
58
|
|
|
59
|
+
# Get Docket from ContextVar (set by Context.__aenter__ at request time)
|
|
59
60
|
docket = _current_docket.get()
|
|
60
61
|
if docket is None:
|
|
61
62
|
raise McpError(
|
|
@@ -72,13 +73,15 @@ async def handle_tool_as_task(
|
|
|
72
73
|
tool = await server.get_tool(tool_name)
|
|
73
74
|
|
|
74
75
|
# Store task key mapping and creation timestamp in Redis for protocol handlers
|
|
75
|
-
|
|
76
|
-
created_at_key =
|
|
76
|
+
task_meta_key = docket.key(f"fastmcp:task:{session_id}:{server_task_id}")
|
|
77
|
+
created_at_key = docket.key(
|
|
78
|
+
f"fastmcp:task:{session_id}:{server_task_id}:created_at"
|
|
79
|
+
)
|
|
77
80
|
ttl_seconds = int(
|
|
78
81
|
docket.execution_ttl.total_seconds() + TASK_MAPPING_TTL_BUFFER_SECONDS
|
|
79
82
|
)
|
|
80
83
|
async with docket.redis() as redis:
|
|
81
|
-
await redis.set(
|
|
84
|
+
await redis.set(task_meta_key, task_key, ex=ttl_seconds)
|
|
82
85
|
await redis.set(created_at_key, created_at, ex=ttl_seconds)
|
|
83
86
|
|
|
84
87
|
# Send notifications/tasks/created per SEP-1686 (mandatory)
|
|
@@ -165,6 +168,7 @@ async def handle_prompt_as_task(
|
|
|
165
168
|
ctx = get_context()
|
|
166
169
|
session_id = ctx.session_id
|
|
167
170
|
|
|
171
|
+
# Get Docket from ContextVar (set by Context.__aenter__ at request time)
|
|
168
172
|
docket = _current_docket.get()
|
|
169
173
|
if docket is None:
|
|
170
174
|
raise McpError(
|
|
@@ -181,13 +185,15 @@ async def handle_prompt_as_task(
|
|
|
181
185
|
prompt = await server.get_prompt(prompt_name)
|
|
182
186
|
|
|
183
187
|
# Store task key mapping and creation timestamp in Redis for protocol handlers
|
|
184
|
-
|
|
185
|
-
created_at_key =
|
|
188
|
+
task_meta_key = docket.key(f"fastmcp:task:{session_id}:{server_task_id}")
|
|
189
|
+
created_at_key = docket.key(
|
|
190
|
+
f"fastmcp:task:{session_id}:{server_task_id}:created_at"
|
|
191
|
+
)
|
|
186
192
|
ttl_seconds = int(
|
|
187
193
|
docket.execution_ttl.total_seconds() + TASK_MAPPING_TTL_BUFFER_SECONDS
|
|
188
194
|
)
|
|
189
195
|
async with docket.redis() as redis:
|
|
190
|
-
await redis.set(
|
|
196
|
+
await redis.set(task_meta_key, task_key, ex=ttl_seconds)
|
|
191
197
|
await redis.set(created_at_key, created_at, ex=ttl_seconds)
|
|
192
198
|
|
|
193
199
|
# Send notifications/tasks/created per SEP-1686 (mandatory)
|
|
@@ -272,12 +278,13 @@ async def handle_resource_as_task(
|
|
|
272
278
|
ctx = get_context()
|
|
273
279
|
session_id = ctx.session_id
|
|
274
280
|
|
|
281
|
+
# Get Docket from ContextVar (set by Context.__aenter__ at request time)
|
|
275
282
|
docket = _current_docket.get()
|
|
276
283
|
if docket is None:
|
|
277
284
|
raise McpError(
|
|
278
285
|
ErrorData(
|
|
279
286
|
code=INTERNAL_ERROR,
|
|
280
|
-
message="Background tasks require
|
|
287
|
+
message="Background tasks require a running FastMCP server context",
|
|
281
288
|
)
|
|
282
289
|
)
|
|
283
290
|
|
|
@@ -285,13 +292,15 @@ async def handle_resource_as_task(
|
|
|
285
292
|
task_key = build_task_key(session_id, server_task_id, "resource", str(uri))
|
|
286
293
|
|
|
287
294
|
# Store task key mapping and creation timestamp in Redis for protocol handlers
|
|
288
|
-
|
|
289
|
-
created_at_key =
|
|
295
|
+
task_meta_key = docket.key(f"fastmcp:task:{session_id}:{server_task_id}")
|
|
296
|
+
created_at_key = docket.key(
|
|
297
|
+
f"fastmcp:task:{session_id}:{server_task_id}:created_at"
|
|
298
|
+
)
|
|
290
299
|
ttl_seconds = int(
|
|
291
300
|
docket.execution_ttl.total_seconds() + TASK_MAPPING_TTL_BUFFER_SECONDS
|
|
292
301
|
)
|
|
293
302
|
async with docket.redis() as redis:
|
|
294
|
-
await redis.set(
|
|
303
|
+
await redis.set(task_meta_key, task_key, ex=ttl_seconds)
|
|
295
304
|
await redis.set(created_at_key, created_at, ex=ttl_seconds)
|
|
296
305
|
|
|
297
306
|
# Send notifications/tasks/created per SEP-1686 (mandatory)
|
fastmcp/server/tasks/protocol.py
CHANGED
|
@@ -77,10 +77,12 @@ async def tasks_get_handler(server: FastMCP, params: dict[str, Any]) -> GetTaskR
|
|
|
77
77
|
)
|
|
78
78
|
|
|
79
79
|
# Look up full task key and creation timestamp from Redis
|
|
80
|
-
|
|
81
|
-
created_at_key =
|
|
80
|
+
task_meta_key = docket.key(f"fastmcp:task:{session_id}:{client_task_id}")
|
|
81
|
+
created_at_key = docket.key(
|
|
82
|
+
f"fastmcp:task:{session_id}:{client_task_id}:created_at"
|
|
83
|
+
)
|
|
82
84
|
async with docket.redis() as redis:
|
|
83
|
-
task_key_bytes = await redis.get(
|
|
85
|
+
task_key_bytes = await redis.get(task_meta_key)
|
|
84
86
|
created_at_bytes = await redis.get(created_at_key)
|
|
85
87
|
|
|
86
88
|
task_key = None if task_key_bytes is None else task_key_bytes.decode("utf-8")
|
|
@@ -176,9 +178,9 @@ async def tasks_result_handler(server: FastMCP, params: dict[str, Any]) -> Any:
|
|
|
176
178
|
)
|
|
177
179
|
|
|
178
180
|
# Look up full task key from Redis
|
|
179
|
-
|
|
181
|
+
task_meta_key = docket.key(f"fastmcp:task:{session_id}:{client_task_id}")
|
|
180
182
|
async with docket.redis() as redis:
|
|
181
|
-
task_key_bytes = await redis.get(
|
|
183
|
+
task_key_bytes = await redis.get(task_meta_key)
|
|
182
184
|
|
|
183
185
|
task_key = None if task_key_bytes is None else task_key_bytes.decode("utf-8")
|
|
184
186
|
|
|
@@ -309,10 +311,12 @@ async def tasks_cancel_handler(
|
|
|
309
311
|
)
|
|
310
312
|
|
|
311
313
|
# Look up full task key and creation timestamp from Redis
|
|
312
|
-
|
|
313
|
-
created_at_key =
|
|
314
|
+
task_meta_key = docket.key(f"fastmcp:task:{session_id}:{client_task_id}")
|
|
315
|
+
created_at_key = docket.key(
|
|
316
|
+
f"fastmcp:task:{session_id}:{client_task_id}:created_at"
|
|
317
|
+
)
|
|
314
318
|
async with docket.redis() as redis:
|
|
315
|
-
task_key_bytes = await redis.get(
|
|
319
|
+
task_key_bytes = await redis.get(task_meta_key)
|
|
316
320
|
created_at_bytes = await redis.get(created_at_key)
|
|
317
321
|
|
|
318
322
|
task_key = None if task_key_bytes is None else task_key_bytes.decode("utf-8")
|
|
@@ -70,7 +70,7 @@ async def subscribe_to_task_updates(
|
|
|
70
70
|
)
|
|
71
71
|
|
|
72
72
|
except Exception as e:
|
|
73
|
-
logger.
|
|
73
|
+
logger.error(f"subscribe_to_task_updates failed for {task_id}: {e}")
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
async def _send_status_notification(
|
|
@@ -101,8 +101,7 @@ async def _send_status_notification(
|
|
|
101
101
|
key_parts = parse_task_key(task_key)
|
|
102
102
|
session_id = key_parts["session_id"]
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
created_at_key = f"fastmcp:task:{session_id}:{task_id}:created_at"
|
|
104
|
+
created_at_key = docket.key(f"fastmcp:task:{session_id}:{task_id}:created_at")
|
|
106
105
|
async with docket.redis() as redis:
|
|
107
106
|
created_at_bytes = await redis.get(created_at_key)
|
|
108
107
|
|
|
@@ -175,8 +174,7 @@ async def _send_progress_notification(
|
|
|
175
174
|
key_parts = parse_task_key(task_key)
|
|
176
175
|
session_id = key_parts["session_id"]
|
|
177
176
|
|
|
178
|
-
|
|
179
|
-
created_at_key = f"fastmcp:task:{session_id}:{task_id}:created_at"
|
|
177
|
+
created_at_key = docket.key(f"fastmcp:task:{session_id}:{task_id}:created_at")
|
|
180
178
|
async with docket.redis() as redis:
|
|
181
179
|
created_at_bytes = await redis.get(created_at_key)
|
|
182
180
|
|
fastmcp/settings.py
CHANGED
|
@@ -392,6 +392,21 @@ class Settings(BaseSettings):
|
|
|
392
392
|
),
|
|
393
393
|
] = True
|
|
394
394
|
|
|
395
|
+
check_for_updates: Annotated[
|
|
396
|
+
Literal["stable", "prerelease", "off"],
|
|
397
|
+
Field(
|
|
398
|
+
description=inspect.cleandoc(
|
|
399
|
+
"""
|
|
400
|
+
Controls update checking when displaying the CLI banner.
|
|
401
|
+
- "stable": Check for stable releases only (default)
|
|
402
|
+
- "prerelease": Also check for pre-release versions (alpha, beta, rc)
|
|
403
|
+
- "off": Disable update checking entirely
|
|
404
|
+
Set via FASTMCP_CHECK_FOR_UPDATES environment variable.
|
|
405
|
+
"""
|
|
406
|
+
),
|
|
407
|
+
),
|
|
408
|
+
] = "stable"
|
|
409
|
+
|
|
395
410
|
@property
|
|
396
411
|
def server_auth_class(self) -> AuthProvider | None:
|
|
397
412
|
from fastmcp.utilities.types import get_cached_typeadapter
|
fastmcp/utilities/cli.py
CHANGED
|
@@ -17,6 +17,7 @@ from fastmcp.utilities.logging import get_logger
|
|
|
17
17
|
from fastmcp.utilities.mcp_server_config import MCPServerConfig
|
|
18
18
|
from fastmcp.utilities.mcp_server_config.v1.sources.filesystem import FileSystemSource
|
|
19
19
|
from fastmcp.utilities.types import get_cached_typeadapter
|
|
20
|
+
from fastmcp.utilities.version_check import check_for_newer_version
|
|
20
21
|
|
|
21
22
|
if TYPE_CHECKING:
|
|
22
23
|
from fastmcp import FastMCP
|
|
@@ -200,6 +201,9 @@ LOGO_ASCII_4 = (
|
|
|
200
201
|
def log_server_banner(server: FastMCP[Any]) -> None:
|
|
201
202
|
"""Creates and logs a formatted banner with server information and logo."""
|
|
202
203
|
|
|
204
|
+
# Check for updates (non-blocking, fails silently)
|
|
205
|
+
newer_version = check_for_newer_version()
|
|
206
|
+
|
|
203
207
|
# Create the logo text
|
|
204
208
|
# Use Text with no_wrap and markup disabled to preserve ANSI escape codes
|
|
205
209
|
logo_text = Text.from_ansi(LOGO_ASCII_4, no_wrap=True)
|
|
@@ -231,8 +235,10 @@ def log_server_banner(server: FastMCP[Any]) -> None:
|
|
|
231
235
|
|
|
232
236
|
# v3 notice banner (shown below main panel)
|
|
233
237
|
v3_line1 = Text("✨ FastMCP 3.0 is coming!", style="bold")
|
|
234
|
-
v3_line2 = Text(
|
|
235
|
-
"Pin
|
|
238
|
+
v3_line2 = Text.assemble(
|
|
239
|
+
("Pin ", "dim"),
|
|
240
|
+
("`fastmcp < 3`", "dim bold"),
|
|
241
|
+
(" in production, then upgrade when you're ready.", "dim"),
|
|
236
242
|
)
|
|
237
243
|
v3_notice = Panel(
|
|
238
244
|
Group(Align.center(v3_line1), Align.center(v3_line2)),
|
|
@@ -250,5 +256,26 @@ def log_server_banner(server: FastMCP[Any]) -> None:
|
|
|
250
256
|
)
|
|
251
257
|
|
|
252
258
|
console = Console(stderr=True)
|
|
253
|
-
|
|
254
|
-
|
|
259
|
+
|
|
260
|
+
# Build output elements
|
|
261
|
+
output_elements: list[Align | Panel | str] = ["\n", Align.center(panel)]
|
|
262
|
+
output_elements.append(Align.center(v3_notice))
|
|
263
|
+
|
|
264
|
+
# Add update notice if a newer version is available (shown last for visibility)
|
|
265
|
+
if newer_version:
|
|
266
|
+
update_line1 = Text.assemble(
|
|
267
|
+
("🎉 Update available: ", "bold"),
|
|
268
|
+
(newer_version, "bold green"),
|
|
269
|
+
)
|
|
270
|
+
update_line2 = Text("Run: pip install --upgrade fastmcp", style="dim")
|
|
271
|
+
update_notice = Panel(
|
|
272
|
+
Group(Align.center(update_line1), Align.center(update_line2)),
|
|
273
|
+
border_style="blue",
|
|
274
|
+
padding=(0, 2),
|
|
275
|
+
width=80,
|
|
276
|
+
)
|
|
277
|
+
output_elements.append(Align.center(update_notice))
|
|
278
|
+
|
|
279
|
+
output_elements.append("\n")
|
|
280
|
+
|
|
281
|
+
console.print(Group(*output_elements))
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Version checking utilities for FastMCP."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
from packaging.version import Version
|
|
11
|
+
|
|
12
|
+
from fastmcp.utilities.logging import get_logger
|
|
13
|
+
|
|
14
|
+
logger = get_logger(__name__)
|
|
15
|
+
|
|
16
|
+
PYPI_URL = "https://pypi.org/pypi/fastmcp/json"
|
|
17
|
+
CACHE_TTL_SECONDS = 60 * 60 * 12 # 12 hours
|
|
18
|
+
REQUEST_TIMEOUT_SECONDS = 2.0
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _get_cache_path(include_prereleases: bool = False) -> Path:
|
|
22
|
+
"""Get the path to the version cache file."""
|
|
23
|
+
import fastmcp
|
|
24
|
+
|
|
25
|
+
suffix = "_prerelease" if include_prereleases else ""
|
|
26
|
+
return fastmcp.settings.home / f"version_cache{suffix}.json"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _read_cache(include_prereleases: bool = False) -> tuple[str | None, float]:
|
|
30
|
+
"""Read cached version info.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Tuple of (cached_version, cache_timestamp) or (None, 0) if no cache.
|
|
34
|
+
"""
|
|
35
|
+
cache_path = _get_cache_path(include_prereleases)
|
|
36
|
+
if not cache_path.exists():
|
|
37
|
+
return None, 0
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
data = json.loads(cache_path.read_text())
|
|
41
|
+
return data.get("latest_version"), data.get("timestamp", 0)
|
|
42
|
+
except (json.JSONDecodeError, OSError):
|
|
43
|
+
return None, 0
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _write_cache(latest_version: str, include_prereleases: bool = False) -> None:
|
|
47
|
+
"""Write version info to cache."""
|
|
48
|
+
cache_path = _get_cache_path(include_prereleases)
|
|
49
|
+
try:
|
|
50
|
+
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
|
51
|
+
cache_path.write_text(
|
|
52
|
+
json.dumps({"latest_version": latest_version, "timestamp": time.time()})
|
|
53
|
+
)
|
|
54
|
+
except OSError:
|
|
55
|
+
# Silently ignore cache write failures
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _fetch_latest_version(include_prereleases: bool = False) -> str | None:
|
|
60
|
+
"""Fetch the latest version from PyPI.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
include_prereleases: If True, include pre-release versions (alpha, beta, rc).
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
The latest version string, or None if the fetch failed.
|
|
67
|
+
"""
|
|
68
|
+
try:
|
|
69
|
+
response = httpx.get(PYPI_URL, timeout=REQUEST_TIMEOUT_SECONDS)
|
|
70
|
+
response.raise_for_status()
|
|
71
|
+
data = response.json()
|
|
72
|
+
|
|
73
|
+
releases = data.get("releases", {})
|
|
74
|
+
if not releases:
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
versions = []
|
|
78
|
+
for version_str in releases:
|
|
79
|
+
try:
|
|
80
|
+
v = Version(version_str)
|
|
81
|
+
# Skip prereleases if not requested
|
|
82
|
+
if not include_prereleases and v.is_prerelease:
|
|
83
|
+
continue
|
|
84
|
+
versions.append(v)
|
|
85
|
+
except ValueError:
|
|
86
|
+
logger.debug(f"Skipping invalid version string: {version_str}")
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
if not versions:
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
return str(max(versions))
|
|
93
|
+
|
|
94
|
+
except (httpx.HTTPError, json.JSONDecodeError, KeyError):
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_latest_version(include_prereleases: bool = False) -> str | None:
|
|
99
|
+
"""Get the latest version of FastMCP from PyPI, using cache when available.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
include_prereleases: If True, include pre-release versions.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
The latest version string, or None if unavailable.
|
|
106
|
+
"""
|
|
107
|
+
# Check cache first
|
|
108
|
+
cached_version, cache_timestamp = _read_cache(include_prereleases)
|
|
109
|
+
if cached_version and (time.time() - cache_timestamp) < CACHE_TTL_SECONDS:
|
|
110
|
+
return cached_version
|
|
111
|
+
|
|
112
|
+
# Fetch from PyPI
|
|
113
|
+
latest_version = _fetch_latest_version(include_prereleases)
|
|
114
|
+
|
|
115
|
+
# Update cache if we got a valid version
|
|
116
|
+
if latest_version:
|
|
117
|
+
_write_cache(latest_version, include_prereleases)
|
|
118
|
+
return latest_version
|
|
119
|
+
|
|
120
|
+
# Return stale cache if available
|
|
121
|
+
return cached_version
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def check_for_newer_version() -> str | None:
|
|
125
|
+
"""Check if a newer version of FastMCP is available.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
The latest version string if newer than current, None otherwise.
|
|
129
|
+
"""
|
|
130
|
+
import fastmcp
|
|
131
|
+
|
|
132
|
+
setting = fastmcp.settings.check_for_updates
|
|
133
|
+
if setting == "off":
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
include_prereleases = setting == "prerelease"
|
|
137
|
+
latest_version = get_latest_version(include_prereleases)
|
|
138
|
+
if not latest_version:
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
current = Version(fastmcp.__version__)
|
|
143
|
+
latest = Version(latest_version)
|
|
144
|
+
|
|
145
|
+
if latest > current:
|
|
146
|
+
return latest_version
|
|
147
|
+
except ValueError:
|
|
148
|
+
logger.debug(
|
|
149
|
+
f"Could not compare versions: current={fastmcp.__version__!r}, "
|
|
150
|
+
f"latest={latest_version!r}"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastmcp
|
|
3
|
-
Version: 2.14.
|
|
3
|
+
Version: 2.14.3
|
|
4
4
|
Summary: The fast, Pythonic way to build MCP servers and clients.
|
|
5
5
|
Project-URL: Homepage, https://gofastmcp.com
|
|
6
6
|
Project-URL: Repository, https://github.com/jlowin/fastmcp
|
|
@@ -28,7 +28,7 @@ Requires-Dist: openapi-pydantic>=0.5.1
|
|
|
28
28
|
Requires-Dist: platformdirs>=4.0.0
|
|
29
29
|
Requires-Dist: py-key-value-aio[disk,keyring,memory]<0.4.0,>=0.3.0
|
|
30
30
|
Requires-Dist: pydantic[email]>=2.11.7
|
|
31
|
-
Requires-Dist: pydocket>=0.16.
|
|
31
|
+
Requires-Dist: pydocket>=0.16.6
|
|
32
32
|
Requires-Dist: pyperclip>=1.9.0
|
|
33
33
|
Requires-Dist: python-dotenv>=1.1.0
|
|
34
34
|
Requires-Dist: rich>=13.9.4
|
|
@@ -3,10 +3,10 @@ fastmcp/dependencies.py,sha256=Un5S30WHJbAiIdjVjEeaQC7UcEVEkkyjf4EF7l4FYq0,513
|
|
|
3
3
|
fastmcp/exceptions.py,sha256=-krEavxwddQau6T7MESCR4VjKNLfP9KHJrU1p3y72FU,744
|
|
4
4
|
fastmcp/mcp_config.py,sha256=YXZ0piljrxFgPYEwYSwPw6IiPwU3Cwp2VzlT9CWxutc,11397
|
|
5
5
|
fastmcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
fastmcp/settings.py,sha256=
|
|
6
|
+
fastmcp/settings.py,sha256=IL3r6kyGzy3WW4evL4BwDfBGNNArbCnwnGmKlOHTr8Y,13888
|
|
7
7
|
fastmcp/cli/__init__.py,sha256=Bo7WQWPBRQ6fqbYYPfbadefpXgl2h9gkdMaqTazGWyw,49
|
|
8
8
|
fastmcp/cli/__main__.py,sha256=cGU_smvfctQI9xEY13u7tTEwwUI4AUieikXXA7ykYhA,69
|
|
9
|
-
fastmcp/cli/cli.py,sha256=
|
|
9
|
+
fastmcp/cli/cli.py,sha256=Bedska9lyo7NuMUb4G0hvBK3OzG7BMTnA756s72rdq8,28664
|
|
10
10
|
fastmcp/cli/run.py,sha256=HeaiHYcVY17JpHg4UjnIHkP5ttU0PNd1bZIL3brif8A,7047
|
|
11
11
|
fastmcp/cli/tasks.py,sha256=B57vy76d3ybdi4wmlODRCCFrte1GmhLKqYixzRkGUuw,3791
|
|
12
12
|
fastmcp/cli/install/__init__.py,sha256=FUrwjMVaxONgz1qO7suzJNz1xosRfR3TOHlr3Z77JXA,797
|
|
@@ -25,10 +25,10 @@ fastmcp/client/oauth_callback.py,sha256=3xqL5_HD1QS9eGfw31HzoVF94QQelq_0TTqS7qWD
|
|
|
25
25
|
fastmcp/client/progress.py,sha256=WjLLDbUKMsx8DK-fqO7AGsXb83ak-6BMrLvzzznGmcI,1043
|
|
26
26
|
fastmcp/client/roots.py,sha256=Uap1RSr3uEeQRZTHkEttkhTI2fOA8IeDcRSggtZp9aY,2568
|
|
27
27
|
fastmcp/client/tasks.py,sha256=zjiTfvjU9NaA4e3XTBGHsqvSfBRR19UqZMIUhJ_nQTo,19480
|
|
28
|
-
fastmcp/client/transports.py,sha256=
|
|
28
|
+
fastmcp/client/transports.py,sha256=g-LDLwTw-t8lkV0u_nFL3XgC0L_sccD98R7OnU5d9F0,43583
|
|
29
29
|
fastmcp/client/auth/__init__.py,sha256=4DNsfp4iaQeBcpds0JDdMn6Mmfud44stWLsret0sVKY,91
|
|
30
30
|
fastmcp/client/auth/bearer.py,sha256=MFEFqcH6u_V86msYiOsEFKN5ks1V9BnBNiPsPLHUTqo,399
|
|
31
|
-
fastmcp/client/auth/oauth.py,sha256=
|
|
31
|
+
fastmcp/client/auth/oauth.py,sha256=PXtWFFSqR29QZ_ZYk74EIRHdj_qOGP2yerXb0HDw2ns,12745
|
|
32
32
|
fastmcp/client/sampling/__init__.py,sha256=jaquyp7c5lz4mczv0d5Skl153uWrnXVcS4qCmbjLKRY,2208
|
|
33
33
|
fastmcp/client/sampling/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
34
|
fastmcp/client/sampling/handlers/anthropic.py,sha256=LjxTYzIWOnbJwJDHJOkRybW0dXBcyt2c7B4sCaG3uLM,14318
|
|
@@ -61,19 +61,19 @@ fastmcp/resources/resource_manager.py,sha256=yG3EieKY9DqIcYTIFJkSJlRoXeffV6mTOnW
|
|
|
61
61
|
fastmcp/resources/template.py,sha256=MSAK46bYk74nqJTQ923xb4KETlof9clfg_QaqLrJX_Y,15495
|
|
62
62
|
fastmcp/resources/types.py,sha256=efFLGD1Xc5Xq3sxlPaZ_8gtJ2UOixueTBV4KQTi4cOU,4936
|
|
63
63
|
fastmcp/server/__init__.py,sha256=qxNmIJcqsrpxpUvCv0mhdEAaUn1UZd1xLd8XRoWUlfY,119
|
|
64
|
-
fastmcp/server/context.py,sha256=
|
|
64
|
+
fastmcp/server/context.py,sha256=vivwwI4u7RuaYMivQ0IlqqHDQxZo682ZMckt-5muC3A,43187
|
|
65
65
|
fastmcp/server/dependencies.py,sha256=gRc60PhEvna9rlqMW-ZlYNszPlUeEeOWT5winYGNH2A,20928
|
|
66
66
|
fastmcp/server/elicitation.py,sha256=CmHi_SERmhEcNjwnM90_HGihUKlCM3RPGHI0uns2t7M,17912
|
|
67
67
|
fastmcp/server/event_store.py,sha256=ZiBbrUQHw9--G8lzK1qLZmUAF2le2XchFen4pGbFKsE,6170
|
|
68
68
|
fastmcp/server/http.py,sha256=_HjMSYWH8mfKugDODU4iV0AhKDU2VRc40tS56L6i-_s,12737
|
|
69
69
|
fastmcp/server/low_level.py,sha256=o3jDf5SuZBQeurhLWRzaSVCnvrmaKMH_w-TbHk6BuZ4,7963
|
|
70
70
|
fastmcp/server/proxy.py,sha256=bsgVkcdlRtVK3bB4EeVKrq4PLjIoUvWN_hgzr1hq8yE,26837
|
|
71
|
-
fastmcp/server/server.py,sha256=
|
|
71
|
+
fastmcp/server/server.py,sha256=MjNPByytoVMyNvxjn9K26vDB1k7hKeXvOa52K3uivT4,121608
|
|
72
72
|
fastmcp/server/auth/__init__.py,sha256=MTZvDKEUMqjs9-raRN0h8Zjx8pWFXs_iSRbB1UqBUqU,527
|
|
73
73
|
fastmcp/server/auth/auth.py,sha256=Bvm98USOP0A0yTckKCN7yHJHS4JgCG804W5cQx6GgO4,20430
|
|
74
74
|
fastmcp/server/auth/jwt_issuer.py,sha256=lJYvrpC1ygI4jkoJlL_nTH6m7FKdTw2lbEycKo4eHLY,7197
|
|
75
75
|
fastmcp/server/auth/middleware.py,sha256=xwj3fUCLSlJK6n1Ehp-FN1qnjKqEz8b7LGAGMTqQ8Hk,3284
|
|
76
|
-
fastmcp/server/auth/oauth_proxy.py,sha256=
|
|
76
|
+
fastmcp/server/auth/oauth_proxy.py,sha256=pEvYQAhTRT6eiIkK4eG5VnlOIr7fnHSEDO803ULCP5Q,94785
|
|
77
77
|
fastmcp/server/auth/oidc_proxy.py,sha256=gU_RgBbVMj-9vn0TSRTmT1YaT19VFmJLpARcIXn208k,17969
|
|
78
78
|
fastmcp/server/auth/redirect_validation.py,sha256=Jlhela9xpTbw4aWnQ04A5Z-TW0HYOC3f9BMsq3NXx1Q,2000
|
|
79
79
|
fastmcp/server/auth/handlers/authorize.py,sha256=1zrmXqRUhjiWSHgUhfj0CcCkj3uSlGkTnxHzaic0xYs,11617
|
|
@@ -113,17 +113,17 @@ fastmcp/server/tasks/__init__.py,sha256=VizXvmXgA3SvrApQ6PSz4z1TPA9B6uROvmWeGSYO
|
|
|
113
113
|
fastmcp/server/tasks/capabilities.py,sha256=-8QMBjs6HZuQdUNmOrNEBvJs-opGptIyxOODU0TGGFE,574
|
|
114
114
|
fastmcp/server/tasks/config.py,sha256=msPkUuxnZKuqSj21Eh8m5Cwq0htwUzTCeoWsnbvKGkk,3006
|
|
115
115
|
fastmcp/server/tasks/converters.py,sha256=ON7c8gOMjBYiQoyk_vkymI8J01ccoYzizDwtgIIqIZQ,6701
|
|
116
|
-
fastmcp/server/tasks/handlers.py,sha256=
|
|
116
|
+
fastmcp/server/tasks/handlers.py,sha256=1KTyfPgpQ-6YRfiK3s10sqa2pkRB0tR6ZZzb55KLZDk,12884
|
|
117
117
|
fastmcp/server/tasks/keys.py,sha256=w9diycj0N6ViVqe6stxUS9vg2H94bl_614Bu5kNRM-k,3011
|
|
118
|
-
fastmcp/server/tasks/protocol.py,sha256=
|
|
119
|
-
fastmcp/server/tasks/subscriptions.py,sha256=
|
|
118
|
+
fastmcp/server/tasks/protocol.py,sha256=1wmpubpLb5URzz9JMrPSKmoRRtvrYJ_SW16DROAvXQo,12350
|
|
119
|
+
fastmcp/server/tasks/subscriptions.py,sha256=cNJptdgkofJ6Gg8ae92MAkr95aZewxl--l8BE1_ZJ1U,6615
|
|
120
120
|
fastmcp/tools/__init__.py,sha256=XGcaMkBgwr-AHzbNjyjdb3ATgp5TQ0wzSq0nsrBD__E,201
|
|
121
121
|
fastmcp/tools/tool.py,sha256=_l0HEnuTyYxm_xNWYxO2seRnzb6NunvjnEsWQIeKBDY,23394
|
|
122
122
|
fastmcp/tools/tool_manager.py,sha256=_SSHYgKygZaJ86B2pncmBm2Kbj0NLIDrpphsc9qgB3M,5788
|
|
123
123
|
fastmcp/tools/tool_transform.py,sha256=m1XDYuu_BDPxpH3yRNdT3jCca9KmVSO-Jd00BK4F5rw,38099
|
|
124
124
|
fastmcp/utilities/__init__.py,sha256=-imJ8S-rXmbXMWeDamldP-dHDqAPg_wwmPVz-LNX14E,31
|
|
125
125
|
fastmcp/utilities/auth.py,sha256=ZVHkNb4YBpLE1EmmFyhvFB2qfWDZdEYNH9TRI9jylOE,1140
|
|
126
|
-
fastmcp/utilities/cli.py,sha256=
|
|
126
|
+
fastmcp/utilities/cli.py,sha256=QPYbVJnH0oNmGbo-vg-3nhqr-zJYSxJfsF7r2n9uQXc,12455
|
|
127
127
|
fastmcp/utilities/components.py,sha256=fF4M9cdqbZTlDAZ0hltcTTg_8IU2jNSzOyH4oqH49ig,6087
|
|
128
128
|
fastmcp/utilities/exceptions.py,sha256=7Z9j5IzM5rT27BC1Mcn8tkS-bjqCYqMKwb2MMTaxJYU,1350
|
|
129
129
|
fastmcp/utilities/http.py,sha256=1ns1ymBS-WSxbZjGP6JYjSO52Wa_ls4j4WbnXiupoa4,245
|
|
@@ -135,6 +135,7 @@ fastmcp/utilities/mcp_config.py,sha256=lVllZtAXZ3Zy78D40aXN-S5fs-ms0lgryL1tY2Wzw
|
|
|
135
135
|
fastmcp/utilities/tests.py,sha256=VIsYPpk07tXvE02yK_neBUeZgu5YtbUlK6JJNzU-6lQ,9229
|
|
136
136
|
fastmcp/utilities/types.py,sha256=7c56m736JjbKY-YP7RLWPZcsW5Z7mikpByKaDQ5IJwg,17586
|
|
137
137
|
fastmcp/utilities/ui.py,sha256=gcnha7Vj4xEBxdrS83EZlKpN_43AQzcgiZFEvkTqzqg,14252
|
|
138
|
+
fastmcp/utilities/version_check.py,sha256=zjY2HaSW4f09Gjun3V6TLyaeJC_ZPPf16VvQAdDigO8,4419
|
|
138
139
|
fastmcp/utilities/mcp_server_config/__init__.py,sha256=hHBxEwRsrgN0Q-1bvj28X6UVGDpfG6dt3yfSBGsOY80,791
|
|
139
140
|
fastmcp/utilities/mcp_server_config/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
140
141
|
fastmcp/utilities/mcp_server_config/v1/mcp_server_config.py,sha256=1B3J7sRR0GcOW6FcSNNTTTOtEePNhUKc7Y0xEDk-wao,15497
|
|
@@ -153,8 +154,8 @@ fastmcp/utilities/openapi/json_schema_converter.py,sha256=PxaYpgHBsdDTT0XSP6s4RZ
|
|
|
153
154
|
fastmcp/utilities/openapi/models.py,sha256=-kfndwZSe92tVtKAgOuFn5rk1tN7oydCZKtLOEMEalA,2805
|
|
154
155
|
fastmcp/utilities/openapi/parser.py,sha256=qsa68Ro1c8ov77kdEP20IwZqD74E4IGKjtfeIkn3HdE,34338
|
|
155
156
|
fastmcp/utilities/openapi/schemas.py,sha256=UXHHjkJyDp1WwJ8kowYt79wnwdbDwAbUFfqwcIY6mIM,23359
|
|
156
|
-
fastmcp-2.14.
|
|
157
|
-
fastmcp-2.14.
|
|
158
|
-
fastmcp-2.14.
|
|
159
|
-
fastmcp-2.14.
|
|
160
|
-
fastmcp-2.14.
|
|
157
|
+
fastmcp-2.14.3.dist-info/METADATA,sha256=Oj-OVe1cTUGmHVkFAlA6Zu_g4a92JnghvjJ-YbKwoAc,20771
|
|
158
|
+
fastmcp-2.14.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
159
|
+
fastmcp-2.14.3.dist-info/entry_points.txt,sha256=ff8bMtKX1JvXyurMibAacMSKbJEPmac9ffAKU9mLnM8,44
|
|
160
|
+
fastmcp-2.14.3.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
161
|
+
fastmcp-2.14.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|