fastmcp 2.13.0rc2__py3-none-any.whl → 2.13.0rc3__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 +1 -0
- fastmcp/cli/install/claude_code.py +3 -3
- fastmcp/client/oauth_callback.py +6 -2
- fastmcp/client/transports.py +1 -0
- fastmcp/resources/types.py +30 -24
- fastmcp/server/auth/handlers/authorize.py +324 -0
- fastmcp/server/auth/jwt_issuer.py +39 -92
- fastmcp/server/auth/middleware.py +96 -0
- fastmcp/server/auth/oauth_proxy.py +236 -217
- fastmcp/server/auth/oidc_proxy.py +16 -1
- fastmcp/server/auth/providers/auth0.py +28 -15
- fastmcp/server/auth/providers/aws.py +16 -1
- fastmcp/server/auth/providers/azure.py +69 -31
- fastmcp/server/auth/providers/github.py +16 -1
- fastmcp/server/auth/providers/google.py +16 -1
- fastmcp/server/auth/providers/workos.py +16 -1
- fastmcp/server/context.py +33 -2
- fastmcp/server/http.py +1 -1
- fastmcp/server/middleware/tool_injection.py +116 -0
- fastmcp/server/server.py +47 -26
- fastmcp/settings.py +2 -1
- fastmcp/tools/tool_manager.py +8 -4
- fastmcp/utilities/cli.py +45 -15
- fastmcp/utilities/ui.py +126 -6
- {fastmcp-2.13.0rc2.dist-info → fastmcp-2.13.0rc3.dist-info}/METADATA +5 -5
- {fastmcp-2.13.0rc2.dist-info → fastmcp-2.13.0rc3.dist-info}/RECORD +29 -26
- {fastmcp-2.13.0rc2.dist-info → fastmcp-2.13.0rc3.dist-info}/WHEEL +0 -0
- {fastmcp-2.13.0rc2.dist-info → fastmcp-2.13.0rc3.dist-info}/entry_points.txt +0 -0
- {fastmcp-2.13.0rc2.dist-info → fastmcp-2.13.0rc3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""A middleware for injecting tools into the MCP server context."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from logging import Logger
|
|
5
|
+
from typing import Annotated, Any
|
|
6
|
+
|
|
7
|
+
import mcp.types
|
|
8
|
+
from mcp.server.lowlevel.helper_types import ReadResourceContents
|
|
9
|
+
from mcp.types import Prompt
|
|
10
|
+
from pydantic import AnyUrl
|
|
11
|
+
from typing_extensions import override
|
|
12
|
+
|
|
13
|
+
from fastmcp.server.context import Context
|
|
14
|
+
from fastmcp.server.middleware.middleware import CallNext, Middleware, MiddlewareContext
|
|
15
|
+
from fastmcp.tools.tool import Tool, ToolResult
|
|
16
|
+
from fastmcp.utilities.logging import get_logger
|
|
17
|
+
|
|
18
|
+
logger: Logger = get_logger(name=__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ToolInjectionMiddleware(Middleware):
|
|
22
|
+
"""A middleware for injecting tools into the context."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, tools: Sequence[Tool]):
|
|
25
|
+
"""Initialize the tool injection middleware."""
|
|
26
|
+
self._tools_to_inject: Sequence[Tool] = tools
|
|
27
|
+
self._tools_to_inject_by_name: dict[str, Tool] = {
|
|
28
|
+
tool.name: tool for tool in tools
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@override
|
|
32
|
+
async def on_list_tools(
|
|
33
|
+
self,
|
|
34
|
+
context: MiddlewareContext[mcp.types.ListToolsRequest],
|
|
35
|
+
call_next: CallNext[mcp.types.ListToolsRequest, Sequence[Tool]],
|
|
36
|
+
) -> Sequence[Tool]:
|
|
37
|
+
"""Inject tools into the response."""
|
|
38
|
+
return [*self._tools_to_inject, *await call_next(context)]
|
|
39
|
+
|
|
40
|
+
@override
|
|
41
|
+
async def on_call_tool(
|
|
42
|
+
self,
|
|
43
|
+
context: MiddlewareContext[mcp.types.CallToolRequestParams],
|
|
44
|
+
call_next: CallNext[mcp.types.CallToolRequestParams, ToolResult],
|
|
45
|
+
) -> ToolResult:
|
|
46
|
+
"""Intercept tool calls to injected tools."""
|
|
47
|
+
if context.message.name in self._tools_to_inject_by_name:
|
|
48
|
+
tool = self._tools_to_inject_by_name[context.message.name]
|
|
49
|
+
return await tool.run(arguments=context.message.arguments or {})
|
|
50
|
+
|
|
51
|
+
return await call_next(context)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def list_prompts(context: Context) -> list[Prompt]:
|
|
55
|
+
"""List prompts available on the server."""
|
|
56
|
+
return await context.list_prompts()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
list_prompts_tool = Tool.from_function(
|
|
60
|
+
fn=list_prompts,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def get_prompt(
|
|
65
|
+
context: Context,
|
|
66
|
+
name: Annotated[str, "The name of the prompt to render."],
|
|
67
|
+
arguments: Annotated[
|
|
68
|
+
dict[str, Any] | None, "The arguments to pass to the prompt."
|
|
69
|
+
] = None,
|
|
70
|
+
) -> mcp.types.GetPromptResult:
|
|
71
|
+
"""Render a prompt available on the server."""
|
|
72
|
+
return await context.get_prompt(name=name, arguments=arguments)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
get_prompt_tool = Tool.from_function(
|
|
76
|
+
fn=get_prompt,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class PromptToolMiddleware(ToolInjectionMiddleware):
|
|
81
|
+
"""A middleware for injecting prompts as tools into the context."""
|
|
82
|
+
|
|
83
|
+
def __init__(self) -> None:
|
|
84
|
+
tools: list[Tool] = [list_prompts_tool, get_prompt_tool]
|
|
85
|
+
super().__init__(tools=tools)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def list_resources(context: Context) -> list[mcp.types.Resource]:
|
|
89
|
+
"""List resources available on the server."""
|
|
90
|
+
return await context.list_resources()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
list_resources_tool = Tool.from_function(
|
|
94
|
+
fn=list_resources,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
async def read_resource(
|
|
99
|
+
context: Context,
|
|
100
|
+
uri: Annotated[AnyUrl | str, "The URI of the resource to read."],
|
|
101
|
+
) -> list[ReadResourceContents]:
|
|
102
|
+
"""Read a resource available on the server."""
|
|
103
|
+
return await context.read_resource(uri=uri)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
read_resource_tool = Tool.from_function(
|
|
107
|
+
fn=read_resource,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class ResourceToolMiddleware(ToolInjectionMiddleware):
|
|
112
|
+
"""A middleware for injecting resources as tools into the context."""
|
|
113
|
+
|
|
114
|
+
def __init__(self) -> None:
|
|
115
|
+
tools: list[Tool] = [list_resources_tool, read_resource_tool]
|
|
116
|
+
super().__init__(tools=tools)
|
fastmcp/server/server.py
CHANGED
|
@@ -7,7 +7,14 @@ import json
|
|
|
7
7
|
import re
|
|
8
8
|
import secrets
|
|
9
9
|
import warnings
|
|
10
|
-
from collections.abc import
|
|
10
|
+
from collections.abc import (
|
|
11
|
+
AsyncIterator,
|
|
12
|
+
Awaitable,
|
|
13
|
+
Callable,
|
|
14
|
+
Collection,
|
|
15
|
+
Mapping,
|
|
16
|
+
Sequence,
|
|
17
|
+
)
|
|
11
18
|
from contextlib import AbstractAsyncContextManager, AsyncExitStack, asynccontextmanager
|
|
12
19
|
from dataclasses import dataclass
|
|
13
20
|
from functools import partial
|
|
@@ -43,9 +50,11 @@ import fastmcp
|
|
|
43
50
|
import fastmcp.server
|
|
44
51
|
from fastmcp.exceptions import DisabledError, NotFoundError
|
|
45
52
|
from fastmcp.mcp_config import MCPConfig
|
|
46
|
-
from fastmcp.prompts import Prompt
|
|
53
|
+
from fastmcp.prompts import Prompt
|
|
47
54
|
from fastmcp.prompts.prompt import FunctionPrompt
|
|
48
|
-
from fastmcp.
|
|
55
|
+
from fastmcp.prompts.prompt_manager import PromptManager
|
|
56
|
+
from fastmcp.resources.resource import Resource
|
|
57
|
+
from fastmcp.resources.resource_manager import ResourceManager
|
|
49
58
|
from fastmcp.resources.template import ResourceTemplate
|
|
50
59
|
from fastmcp.server.auth import AuthProvider
|
|
51
60
|
from fastmcp.server.http import (
|
|
@@ -56,8 +65,8 @@ from fastmcp.server.http import (
|
|
|
56
65
|
from fastmcp.server.low_level import LowLevelServer
|
|
57
66
|
from fastmcp.server.middleware import Middleware, MiddlewareContext
|
|
58
67
|
from fastmcp.settings import Settings
|
|
59
|
-
from fastmcp.tools import ToolManager
|
|
60
68
|
from fastmcp.tools.tool import FunctionTool, Tool, ToolResult
|
|
69
|
+
from fastmcp.tools.tool_manager import ToolManager
|
|
61
70
|
from fastmcp.tools.tool_transform import ToolTransformConfig
|
|
62
71
|
from fastmcp.utilities.cli import log_server_banner
|
|
63
72
|
from fastmcp.utilities.components import FastMCPComponent
|
|
@@ -66,7 +75,6 @@ from fastmcp.utilities.types import NotSet, NotSetT
|
|
|
66
75
|
|
|
67
76
|
if TYPE_CHECKING:
|
|
68
77
|
from fastmcp.client import Client
|
|
69
|
-
from fastmcp.client.sampling import ServerSamplingHandler
|
|
70
78
|
from fastmcp.client.transports import ClientTransport, ClientTransportT
|
|
71
79
|
from fastmcp.experimental.server.openapi import FastMCPOpenAPI as FastMCPOpenAPINew
|
|
72
80
|
from fastmcp.experimental.server.openapi.routing import (
|
|
@@ -80,6 +88,8 @@ if TYPE_CHECKING:
|
|
|
80
88
|
from fastmcp.server.openapi import FastMCPOpenAPI, RouteMap
|
|
81
89
|
from fastmcp.server.openapi import RouteMapFn as OpenAPIRouteMapFn
|
|
82
90
|
from fastmcp.server.proxy import FastMCPProxy
|
|
91
|
+
from fastmcp.server.sampling.handler import ServerSamplingHandler
|
|
92
|
+
from fastmcp.tools.tool import ToolResultSerializerType
|
|
83
93
|
|
|
84
94
|
logger = get_logger(__name__)
|
|
85
95
|
|
|
@@ -141,16 +151,16 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
141
151
|
website_url: str | None = None,
|
|
142
152
|
icons: list[mcp.types.Icon] | None = None,
|
|
143
153
|
auth: AuthProvider | None | NotSetT = NotSet,
|
|
144
|
-
middleware:
|
|
154
|
+
middleware: Sequence[Middleware] | None = None,
|
|
145
155
|
lifespan: LifespanCallable | None = None,
|
|
146
156
|
dependencies: list[str] | None = None,
|
|
147
157
|
resource_prefix_format: Literal["protocol", "path"] | None = None,
|
|
148
158
|
mask_error_details: bool | None = None,
|
|
149
|
-
tools:
|
|
150
|
-
tool_transformations:
|
|
151
|
-
tool_serializer:
|
|
152
|
-
include_tags:
|
|
153
|
-
exclude_tags:
|
|
159
|
+
tools: Sequence[Tool | Callable[..., Any]] | None = None,
|
|
160
|
+
tool_transformations: Mapping[str, ToolTransformConfig] | None = None,
|
|
161
|
+
tool_serializer: ToolResultSerializerType | None = None,
|
|
162
|
+
include_tags: Collection[str] | None = None,
|
|
163
|
+
exclude_tags: Collection[str] | None = None,
|
|
154
164
|
include_fastmcp_meta: bool | None = None,
|
|
155
165
|
on_duplicate_tools: DuplicateBehavior | None = None,
|
|
156
166
|
on_duplicate_resources: DuplicateBehavior | None = None,
|
|
@@ -179,27 +189,29 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
179
189
|
|
|
180
190
|
self._additional_http_routes: list[BaseRoute] = []
|
|
181
191
|
self._mounted_servers: list[MountedServer] = []
|
|
182
|
-
self._tool_manager = ToolManager(
|
|
192
|
+
self._tool_manager: ToolManager = ToolManager(
|
|
183
193
|
duplicate_behavior=on_duplicate_tools,
|
|
184
194
|
mask_error_details=mask_error_details,
|
|
185
195
|
transformations=tool_transformations,
|
|
186
196
|
)
|
|
187
|
-
self._resource_manager = ResourceManager(
|
|
197
|
+
self._resource_manager: ResourceManager = ResourceManager(
|
|
188
198
|
duplicate_behavior=on_duplicate_resources,
|
|
189
199
|
mask_error_details=mask_error_details,
|
|
190
200
|
)
|
|
191
|
-
self._prompt_manager = PromptManager(
|
|
201
|
+
self._prompt_manager: PromptManager = PromptManager(
|
|
192
202
|
duplicate_behavior=on_duplicate_prompts,
|
|
193
203
|
mask_error_details=mask_error_details,
|
|
194
204
|
)
|
|
195
|
-
self._tool_serializer = tool_serializer
|
|
205
|
+
self._tool_serializer: Callable[[Any], str] | None = tool_serializer
|
|
196
206
|
|
|
197
207
|
self._lifespan: LifespanCallable[LifespanResultT] = lifespan or default_lifespan
|
|
198
208
|
self._lifespan_result: LifespanResultT | None = None
|
|
199
|
-
self._lifespan_result_set = False
|
|
209
|
+
self._lifespan_result_set: bool = False
|
|
200
210
|
|
|
201
211
|
# Generate random ID if no name provided
|
|
202
|
-
self._mcp_server = LowLevelServer[
|
|
212
|
+
self._mcp_server: LowLevelServer[LifespanResultT, Any] = LowLevelServer[
|
|
213
|
+
LifespanResultT
|
|
214
|
+
](
|
|
203
215
|
fastmcp=self,
|
|
204
216
|
name=name or self.generate_name(),
|
|
205
217
|
version=version or fastmcp.__version__,
|
|
@@ -216,7 +228,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
216
228
|
auth = fastmcp.settings.server_auth_class()
|
|
217
229
|
else:
|
|
218
230
|
auth = None
|
|
219
|
-
self.auth = cast(AuthProvider | None, auth)
|
|
231
|
+
self.auth: AuthProvider | None = cast(AuthProvider | None, auth)
|
|
220
232
|
|
|
221
233
|
if tools:
|
|
222
234
|
for tool in tools:
|
|
@@ -224,15 +236,20 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
224
236
|
tool = Tool.from_function(tool, serializer=self._tool_serializer)
|
|
225
237
|
self.add_tool(tool)
|
|
226
238
|
|
|
227
|
-
self.include_tags =
|
|
228
|
-
|
|
229
|
-
|
|
239
|
+
self.include_tags: set[str] | None = (
|
|
240
|
+
set(include_tags) if include_tags is not None else None
|
|
241
|
+
)
|
|
242
|
+
self.exclude_tags: set[str] | None = (
|
|
243
|
+
set(exclude_tags) if exclude_tags is not None else None
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
self.strict_input_validation: bool = (
|
|
230
247
|
strict_input_validation
|
|
231
248
|
if strict_input_validation is not None
|
|
232
249
|
else fastmcp.settings.strict_input_validation
|
|
233
250
|
)
|
|
234
251
|
|
|
235
|
-
self.middleware = middleware or []
|
|
252
|
+
self.middleware: list[Middleware] = list(middleware or [])
|
|
236
253
|
|
|
237
254
|
# Set up MCP protocol handlers
|
|
238
255
|
self._setup_handlers()
|
|
@@ -251,14 +268,18 @@ class FastMCP(Generic[LifespanResultT]):
|
|
|
251
268
|
DeprecationWarning,
|
|
252
269
|
stacklevel=2,
|
|
253
270
|
)
|
|
254
|
-
self.dependencies = (
|
|
271
|
+
self.dependencies: list[str] = (
|
|
255
272
|
dependencies or fastmcp.settings.server_dependencies
|
|
256
273
|
) # TODO: Remove (deprecated in v2.11.4)
|
|
257
274
|
|
|
258
|
-
self.sampling_handler =
|
|
259
|
-
|
|
275
|
+
self.sampling_handler: ServerSamplingHandler[LifespanResultT] | None = (
|
|
276
|
+
sampling_handler
|
|
277
|
+
)
|
|
278
|
+
self.sampling_handler_behavior: Literal["always", "fallback"] = (
|
|
279
|
+
sampling_handler_behavior or "fallback"
|
|
280
|
+
)
|
|
260
281
|
|
|
261
|
-
self.include_fastmcp_meta = (
|
|
282
|
+
self.include_fastmcp_meta: bool = (
|
|
262
283
|
include_fastmcp_meta
|
|
263
284
|
if include_fastmcp_meta is not None
|
|
264
285
|
else fastmcp.settings.include_fastmcp_meta
|
fastmcp/settings.py
CHANGED
|
@@ -6,6 +6,7 @@ import warnings
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import TYPE_CHECKING, Annotated, Any, Literal
|
|
8
8
|
|
|
9
|
+
from platformdirs import user_data_dir
|
|
9
10
|
from pydantic import Field, ImportString, field_validator
|
|
10
11
|
from pydantic.fields import FieldInfo
|
|
11
12
|
from pydantic_settings import (
|
|
@@ -150,7 +151,7 @@ class Settings(BaseSettings):
|
|
|
150
151
|
)
|
|
151
152
|
return self
|
|
152
153
|
|
|
153
|
-
home: Path = Path
|
|
154
|
+
home: Path = Path(user_data_dir("fastmcp", appauthor=False))
|
|
154
155
|
|
|
155
156
|
test_mode: bool = False
|
|
156
157
|
|
fastmcp/tools/tool_manager.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import warnings
|
|
4
|
-
from collections.abc import Callable
|
|
4
|
+
from collections.abc import Callable, Mapping
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
7
|
from mcp.types import ToolAnnotations
|
|
@@ -27,11 +27,15 @@ class ToolManager:
|
|
|
27
27
|
self,
|
|
28
28
|
duplicate_behavior: DuplicateBehavior | None = None,
|
|
29
29
|
mask_error_details: bool | None = None,
|
|
30
|
-
transformations:
|
|
30
|
+
transformations: Mapping[str, ToolTransformConfig] | None = None,
|
|
31
31
|
):
|
|
32
32
|
self._tools: dict[str, Tool] = {}
|
|
33
|
-
self.mask_error_details =
|
|
34
|
-
|
|
33
|
+
self.mask_error_details: bool = (
|
|
34
|
+
mask_error_details or settings.mask_error_details
|
|
35
|
+
)
|
|
36
|
+
self.transformations: dict[str, ToolTransformConfig] = dict(
|
|
37
|
+
transformations or {}
|
|
38
|
+
)
|
|
35
39
|
|
|
36
40
|
# Default to "warn" if None is provided
|
|
37
41
|
if duplicate_behavior is None:
|
fastmcp/utilities/cli.py
CHANGED
|
@@ -150,22 +150,52 @@ _ __ ___ /_/ \____/____/\__/_/ /_/\____/_/ /_____(*)____/
|
|
|
150
150
|
# █▀▀ ▄▀█ █▀▀ ▀█▀ █▀▄▀█ █▀▀ █▀█
|
|
151
151
|
# █▀ █▀█ ▄▄█ █ █ ▀ █ █▄▄ █▀▀
|
|
152
152
|
LOGO_ASCII_2 = (
|
|
153
|
-
"\
|
|
154
|
-
"\
|
|
155
|
-
"\
|
|
156
|
-
"\
|
|
157
|
-
"\
|
|
158
|
-
"\
|
|
159
|
-
"\
|
|
160
|
-
"\
|
|
161
|
-
"\
|
|
162
|
-
"\
|
|
163
|
-
"\
|
|
164
|
-
"\
|
|
165
|
-
"\
|
|
166
|
-
"\
|
|
153
|
+
"\x1b[38;2;0;198;255m \x1b[38;2;0;195;255m█\x1b[38;2;0;192;255m▀\x1b[38;2;0;189;255m▀\x1b[38;2;0;186;255m "
|
|
154
|
+
"\x1b[38;2;0;184;255m▄\x1b[38;2;0;181;255m▀\x1b[38;2;0;178;255m█\x1b[38;2;0;175;255m "
|
|
155
|
+
"\x1b[38;2;0;172;255m█\x1b[38;2;0;169;255m▀\x1b[38;2;0;166;255m▀\x1b[38;2;0;163;255m "
|
|
156
|
+
"\x1b[38;2;0;160;255m▀\x1b[38;2;0;157;255m█\x1b[38;2;0;155;255m▀\x1b[38;2;0;152;255m "
|
|
157
|
+
"\x1b[38;2;0;149;255m█\x1b[38;2;0;146;255m▀\x1b[38;2;0;143;255m▄\x1b[38;2;0;140;255m▀\x1b[38;2;0;137;255m█\x1b[38;2;0;134;255m "
|
|
158
|
+
"\x1b[38;2;0;131;255m█\x1b[38;2;0;128;255m▀\x1b[38;2;0;126;255m▀\x1b[38;2;0;123;255m "
|
|
159
|
+
"\x1b[38;2;0;120;255m█\x1b[38;2;0;117;255m▀\x1b[38;2;0;114;255m█\x1b[39m\n"
|
|
160
|
+
"\x1b[38;2;0;198;255m \x1b[38;2;0;195;255m█\x1b[38;2;0;192;255m▀\x1b[38;2;0;189;255m \x1b[38;2;0;186;255m "
|
|
161
|
+
"\x1b[38;2;0;184;255m█\x1b[38;2;0;181;255m▀\x1b[38;2;0;178;255m█\x1b[38;2;0;175;255m "
|
|
162
|
+
"\x1b[38;2;0;172;255m▄\x1b[38;2;0;169;255m▄\x1b[38;2;0;166;255m█\x1b[38;2;0;163;255m "
|
|
163
|
+
"\x1b[38;2;0;160;255m \x1b[38;2;0;157;255m█\x1b[38;2;0;155;255m \x1b[38;2;0;152;255m "
|
|
164
|
+
"\x1b[38;2;0;149;255m█\x1b[38;2;0;146;255m \x1b[38;2;0;143;255m▀\x1b[38;2;0;140;255m \x1b[38;2;0;137;255m█\x1b[38;2;0;134;255m "
|
|
165
|
+
"\x1b[38;2;0;131;255m█\x1b[38;2;0;128;255m▄\x1b[38;2;0;126;255m▄\x1b[38;2;0;123;255m "
|
|
166
|
+
"\x1b[38;2;0;120;255m█\x1b[38;2;0;117;255m▀\x1b[38;2;0;114;255m▀\x1b[39m"
|
|
167
167
|
).strip()
|
|
168
168
|
|
|
169
|
+
# Prints the below in a blue gradient - sylized F
|
|
170
|
+
# ▄▀▀▀
|
|
171
|
+
# █▀▀
|
|
172
|
+
# ▀
|
|
173
|
+
LOGO_ASCII_3 = (
|
|
174
|
+
" \x1b[38;2;0;170;255m▄\x1b[38;2;0;142;255m▀\x1b[38;2;0;114;255m▀\x1b[38;2;0;86;255m▀\x1b[39m\n"
|
|
175
|
+
" \x1b[38;2;0;170;255m█\x1b[38;2;0;142;255m▀\x1b[38;2;0;114;255m▀\x1b[39m\n"
|
|
176
|
+
"\x1b[38;2;0;170;255m▀\x1b[39m\n"
|
|
177
|
+
"\x1b[0m"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Prints the below in a blue gradient - block logo with slightly stylized F
|
|
181
|
+
# ▄▀▀ ▄▀█ █▀▀ ▀█▀ █▀▄▀█ █▀▀ █▀█
|
|
182
|
+
# █▀ █▀█ ▄▄█ █ █ ▀ █ █▄▄ █▀▀
|
|
183
|
+
|
|
184
|
+
LOGO_ASCII_4 = (
|
|
185
|
+
"\x1b[38;2;0;198;255m \x1b[38;2;0;195;255m▄\x1b[38;2;0;192;255m▀\x1b[38;2;0;189;255m▀\x1b[38;2;0;186;255m \x1b[38;2;0;184;255m▄\x1b[38;2;0;181;255m▀\x1b[38;2;0;178;255m█\x1b[38;2;0;175;255m "
|
|
186
|
+
"\x1b[38;2;0;172;255m█\x1b[38;2;0;169;255m▀\x1b[38;2;0;166;255m▀\x1b[38;2;0;163;255m "
|
|
187
|
+
"\x1b[38;2;0;160;255m▀\x1b[38;2;0;157;255m█\x1b[38;2;0;155;255m▀\x1b[38;2;0;152;255m "
|
|
188
|
+
"\x1b[38;2;0;149;255m█\x1b[38;2;0;146;255m▀\x1b[38;2;0;143;255m▄\x1b[38;2;0;140;255m▀\x1b[38;2;0;137;255m█\x1b[38;2;0;134;255m "
|
|
189
|
+
"\x1b[38;2;0;131;255m█\x1b[38;2;0;128;255m▀\x1b[38;2;0;126;255m▀\x1b[38;2;0;123;255m "
|
|
190
|
+
"\x1b[38;2;0;120;255m█\x1b[38;2;0;117;255m▀\x1b[38;2;0;114;255m█\x1b[39m\n"
|
|
191
|
+
"\x1b[38;2;0;198;255m \x1b[38;2;0;195;255m█\x1b[38;2;0;192;255m▀\x1b[38;2;0;189;255m \x1b[38;2;0;186;255m \x1b[38;2;0;184;255m█\x1b[38;2;0;181;255m▀\x1b[38;2;0;178;255m█\x1b[38;2;0;175;255m "
|
|
192
|
+
"\x1b[38;2;0;172;255m▄\x1b[38;2;0;169;255m▄\x1b[38;2;0;166;255m█\x1b[38;2;0;163;255m "
|
|
193
|
+
"\x1b[38;2;0;160;255m \x1b[38;2;0;157;255m█\x1b[38;2;0;155;255m \x1b[38;2;0;152;255m "
|
|
194
|
+
"\x1b[38;2;0;149;255m█\x1b[38;2;0;146;255m \x1b[38;2;0;143;255m▀\x1b[38;2;0;140;255m \x1b[38;2;0;137;255m█\x1b[38;2;0;134;255m "
|
|
195
|
+
"\x1b[38;2;0;131;255m█\x1b[38;2;0;128;255m▄\x1b[38;2;0;126;255m▄\x1b[38;2;0;123;255m "
|
|
196
|
+
"\x1b[38;2;0;120;255m█\x1b[38;2;0;117;255m▀\x1b[38;2;0;114;255m▀\x1b[39m\n"
|
|
197
|
+
)
|
|
198
|
+
|
|
169
199
|
|
|
170
200
|
def log_server_banner(
|
|
171
201
|
server: FastMCP[Any],
|
|
@@ -187,7 +217,7 @@ def log_server_banner(
|
|
|
187
217
|
|
|
188
218
|
# Create the logo text
|
|
189
219
|
# Use Text with no_wrap and markup disabled to preserve ANSI escape codes
|
|
190
|
-
logo_text = Text.from_ansi(
|
|
220
|
+
logo_text = Text.from_ansi(LOGO_ASCII_4, no_wrap=True)
|
|
191
221
|
|
|
192
222
|
# Create the main title
|
|
193
223
|
title_text = Text(f"FastMCP {fastmcp.__version__}", style="bold blue")
|
fastmcp/utilities/ui.py
CHANGED
|
@@ -111,6 +111,54 @@ BUTTON_STYLES = """
|
|
|
111
111
|
# Info box / message box styles
|
|
112
112
|
INFO_BOX_STYLES = """
|
|
113
113
|
.info-box {
|
|
114
|
+
background: #f0f9ff;
|
|
115
|
+
border: 1px solid #bae6fd;
|
|
116
|
+
border-radius: 0.5rem;
|
|
117
|
+
padding: 1rem;
|
|
118
|
+
margin-bottom: 1.5rem;
|
|
119
|
+
text-align: left;
|
|
120
|
+
font-size: 0.9375rem;
|
|
121
|
+
line-height: 1.5;
|
|
122
|
+
color: #374151;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.info-box p {
|
|
126
|
+
margin-bottom: 0.5rem;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.info-box p:last-child {
|
|
130
|
+
margin-bottom: 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.info-box.centered {
|
|
134
|
+
text-align: center;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.info-box.error {
|
|
138
|
+
background: #fef2f2;
|
|
139
|
+
border-color: #fecaca;
|
|
140
|
+
color: #991b1b;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.info-box strong {
|
|
144
|
+
color: #0ea5e9;
|
|
145
|
+
font-weight: 600;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.info-box .server-name-link {
|
|
149
|
+
color: #0ea5e9;
|
|
150
|
+
text-decoration: underline;
|
|
151
|
+
font-weight: 600;
|
|
152
|
+
cursor: pointer;
|
|
153
|
+
transition: opacity 0.15s;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.info-box .server-name-link:hover {
|
|
157
|
+
opacity: 0.8;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Monospace info box - gray styling with code font */
|
|
161
|
+
.info-box-mono {
|
|
114
162
|
background: #f9fafb;
|
|
115
163
|
border: 1px solid #e5e7eb;
|
|
116
164
|
border-radius: 0.5rem;
|
|
@@ -122,17 +170,17 @@ INFO_BOX_STYLES = """
|
|
|
122
170
|
text-align: left;
|
|
123
171
|
}
|
|
124
172
|
|
|
125
|
-
.info-box.centered {
|
|
173
|
+
.info-box-mono.centered {
|
|
126
174
|
text-align: center;
|
|
127
175
|
}
|
|
128
176
|
|
|
129
|
-
.info-box.error {
|
|
177
|
+
.info-box-mono.error {
|
|
130
178
|
background: #fef2f2;
|
|
131
179
|
border-color: #fecaca;
|
|
132
180
|
color: #991b1b;
|
|
133
181
|
}
|
|
134
182
|
|
|
135
|
-
.info-box strong {
|
|
183
|
+
.info-box-mono strong {
|
|
136
184
|
color: #111827;
|
|
137
185
|
font-weight: 600;
|
|
138
186
|
}
|
|
@@ -236,10 +284,11 @@ DETAIL_BOX_STYLES = """
|
|
|
236
284
|
|
|
237
285
|
.detail-label {
|
|
238
286
|
font-weight: 600;
|
|
239
|
-
min-width:
|
|
287
|
+
min-width: 160px;
|
|
240
288
|
color: #6b7280;
|
|
241
289
|
font-size: 0.875rem;
|
|
242
290
|
flex-shrink: 0;
|
|
291
|
+
padding-right: 1rem;
|
|
243
292
|
}
|
|
244
293
|
|
|
245
294
|
.detail-value {
|
|
@@ -252,6 +301,72 @@ DETAIL_BOX_STYLES = """
|
|
|
252
301
|
}
|
|
253
302
|
"""
|
|
254
303
|
|
|
304
|
+
# Redirect section styles (for OAuth redirect URI box)
|
|
305
|
+
REDIRECT_SECTION_STYLES = """
|
|
306
|
+
.redirect-section {
|
|
307
|
+
background: #fffbeb;
|
|
308
|
+
border: 1px solid #fcd34d;
|
|
309
|
+
border-radius: 0.5rem;
|
|
310
|
+
padding: 1rem;
|
|
311
|
+
margin-bottom: 1.5rem;
|
|
312
|
+
text-align: left;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.redirect-section .label {
|
|
316
|
+
font-size: 0.875rem;
|
|
317
|
+
color: #6b7280;
|
|
318
|
+
font-weight: 600;
|
|
319
|
+
margin-bottom: 0.5rem;
|
|
320
|
+
display: block;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.redirect-section .value {
|
|
324
|
+
font-family: 'SF Mono', 'Monaco', 'Consolas', 'Courier New', monospace;
|
|
325
|
+
font-size: 0.875rem;
|
|
326
|
+
color: #111827;
|
|
327
|
+
word-break: break-all;
|
|
328
|
+
margin-top: 0.25rem;
|
|
329
|
+
}
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
# Collapsible details styles
|
|
333
|
+
DETAILS_STYLES = """
|
|
334
|
+
details {
|
|
335
|
+
margin-bottom: 1.5rem;
|
|
336
|
+
text-align: left;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
summary {
|
|
340
|
+
cursor: pointer;
|
|
341
|
+
font-size: 0.875rem;
|
|
342
|
+
color: #6b7280;
|
|
343
|
+
font-weight: 600;
|
|
344
|
+
list-style: none;
|
|
345
|
+
padding: 0.5rem;
|
|
346
|
+
border-radius: 0.25rem;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
summary:hover {
|
|
350
|
+
background: #f9fafb;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
summary::marker {
|
|
354
|
+
display: none;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
summary::before {
|
|
358
|
+
content: "▶";
|
|
359
|
+
display: inline-block;
|
|
360
|
+
margin-right: 0.5rem;
|
|
361
|
+
transition: transform 0.2s;
|
|
362
|
+
font-size: 0.75rem;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
details[open] summary::before {
|
|
366
|
+
transform: rotate(90deg);
|
|
367
|
+
}
|
|
368
|
+
"""
|
|
369
|
+
|
|
255
370
|
# Helper text styles
|
|
256
371
|
HELPER_TEXT_STYLES = """
|
|
257
372
|
.close-instruction, .help-text {
|
|
@@ -413,7 +528,10 @@ def create_status_message(message: str, is_success: bool = True) -> str:
|
|
|
413
528
|
|
|
414
529
|
|
|
415
530
|
def create_info_box(
|
|
416
|
-
content: str,
|
|
531
|
+
content: str,
|
|
532
|
+
is_error: bool = False,
|
|
533
|
+
centered: bool = False,
|
|
534
|
+
monospace: bool = False,
|
|
417
535
|
) -> str:
|
|
418
536
|
"""
|
|
419
537
|
Create an info box.
|
|
@@ -422,12 +540,14 @@ def create_info_box(
|
|
|
422
540
|
content: HTML content for the info box
|
|
423
541
|
is_error: True for error styling, False for normal
|
|
424
542
|
centered: True to center the text, False for left-aligned
|
|
543
|
+
monospace: True to use gray monospace font styling instead of blue
|
|
425
544
|
|
|
426
545
|
Returns:
|
|
427
546
|
HTML for info box
|
|
428
547
|
"""
|
|
429
548
|
content = html.escape(content)
|
|
430
|
-
|
|
549
|
+
base_class = "info-box-mono" if monospace else "info-box"
|
|
550
|
+
classes = [base_class]
|
|
431
551
|
if is_error:
|
|
432
552
|
classes.append("error")
|
|
433
553
|
if centered:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastmcp
|
|
3
|
-
Version: 2.13.
|
|
3
|
+
Version: 2.13.0rc3
|
|
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
|
|
@@ -14,6 +14,7 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
18
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
19
|
Classifier: Typing :: Typed
|
|
19
20
|
Requires-Python: >=3.10
|
|
@@ -24,10 +25,10 @@ Requires-Dist: httpx>=0.28.1
|
|
|
24
25
|
Requires-Dist: mcp<2.0.0,>=1.17.0
|
|
25
26
|
Requires-Dist: openapi-core>=0.19.5
|
|
26
27
|
Requires-Dist: openapi-pydantic>=0.5.1
|
|
27
|
-
Requires-Dist:
|
|
28
|
+
Requires-Dist: platformdirs>=4.0.0
|
|
29
|
+
Requires-Dist: py-key-value-aio[disk,keyring,memory]<0.3.0,>=0.2.6
|
|
28
30
|
Requires-Dist: pydantic[email]>=2.11.7
|
|
29
31
|
Requires-Dist: pyperclip>=1.9.0
|
|
30
|
-
Requires-Dist: pytest-asyncio>=1.2.0
|
|
31
32
|
Requires-Dist: python-dotenv>=1.1.0
|
|
32
33
|
Requires-Dist: rich>=13.9.4
|
|
33
34
|
Requires-Dist: websockets>=15.0.1
|
|
@@ -248,7 +249,6 @@ Access MCP session capabilities within your tools, resources, or prompts by addi
|
|
|
248
249
|
|
|
249
250
|
- **Logging:** Log messages to MCP clients with `ctx.info()`, `ctx.error()`, etc.
|
|
250
251
|
- **LLM Sampling:** Use `ctx.sample()` to request completions from the client's LLM.
|
|
251
|
-
- **HTTP Request:** Use `ctx.http_request()` to make HTTP requests to other servers.
|
|
252
252
|
- **Resource Access:** Use `ctx.read_resource()` to access resources on the server
|
|
253
253
|
- **Progress Reporting:** Use `ctx.report_progress()` to report progress to the client.
|
|
254
254
|
- and more...
|
|
@@ -358,7 +358,7 @@ FastMCP provides comprehensive authentication support that sets it apart from ba
|
|
|
358
358
|
Protecting a server takes just two lines:
|
|
359
359
|
|
|
360
360
|
```python
|
|
361
|
-
from fastmcp.server.auth import GoogleProvider
|
|
361
|
+
from fastmcp.server.auth.providers.google import GoogleProvider
|
|
362
362
|
|
|
363
363
|
auth = GoogleProvider(client_id="...", client_secret="...", base_url="https://myserver.com")
|
|
364
364
|
mcp = FastMCP("Protected Server", auth=auth)
|