diff --git a/docs/servers/server.mdx b/docs/servers/server.mdx index 6b192a3b0..b85ed034e 100644 --- a/docs/servers/server.mdx +++ b/docs/servers/server.mdx @@ -123,6 +123,12 @@ These parameters control how your server presents itself to clients. List of icon representations for your server. See [Icons](/servers/icons) for details + + + + + Arbitrary experimental capabilities to advertise in the MCP `initialize` response. Use this to declare cross-server interop conventions or draft extensions that follow the MCP spec's `experimental` field. Keys are capability names; values are free-form dicts. FastMCP's built-in derived capabilities (`tools`, `resources`, etc.) are unaffected — this only populates `capabilities.experimental` + ### Composition diff --git a/src/fastmcp/server/low_level.py b/src/fastmcp/server/low_level.py index 36255f4c7..e9dda4e35 100644 --- a/src/fastmcp/server/low_level.py +++ b/src/fastmcp/server/low_level.py @@ -182,9 +182,13 @@ class LowLevelServer(_Server[LifespanResultT, RequestT]): # ensure we use the FastMCP notification options if notification_options is None: notification_options = self.notification_options + merged = { + **self.fastmcp.experimental_capabilities, + **(experimental_capabilities or {}), + } return super().create_initialization_options( notification_options=notification_options, - experimental_capabilities=experimental_capabilities, + experimental_capabilities=merged or None, **kwargs, ) diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index cedeedf85..b03992168 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -307,6 +307,7 @@ class FastMCP( sampling_handler: SamplingHandler | None = None, sampling_handler_behavior: Literal["always", "fallback"] | None = None, client_log_level: mcp.types.LoggingLevel | None = None, + experimental_capabilities: dict[str, dict[str, Any]] | None = None, **kwargs: Any, ): _check_removed_kwargs(kwargs) @@ -405,6 +406,10 @@ class FastMCP( else fastmcp.settings.client_log_level ) + self.experimental_capabilities: dict[str, dict[str, Any]] = ( + experimental_capabilities or {} + ) + self.middleware: list[Middleware] = list(middleware or []) if dereference_schemas: diff --git a/tests/test_apps.py b/tests/test_apps.py index ba2765805..414514bd1 100644 --- a/tests/test_apps.py +++ b/tests/test_apps.py @@ -398,6 +398,23 @@ class TestExtensionAdvertisement: extensions = extras.get("extensions", {}) assert UI_EXTENSION_ID in extensions + async def test_experimental_capabilities_in_initialize_result(self): + server = FastMCP( + "test", + experimental_capabilities={"file_exchange": {"version": "0.3"}}, + ) + + async with Client(server) as client: + experimental = client.initialize_result.capabilities.experimental or {} + assert experimental.get("file_exchange") == {"version": "0.3"} + + async def test_experimental_capabilities_default_empty(self): + server = FastMCP("test") + + async with Client(server) as client: + experimental = client.initialize_result.capabilities.experimental + assert not experimental + # --------------------------------------------------------------------------- # Context.client_supports_extension