Spaces:
Running
Running
Jeremiah Lowin commited on
Fix server_version field in inspect manifest (#1383)
Browse files- src/fastmcp/server/server.py +4 -0
- src/fastmcp/utilities/inspect.py +12 -17
- tests/utilities/test_inspect.py +23 -19
src/fastmcp/server/server.py
CHANGED
|
@@ -303,6 +303,10 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 303 |
def instructions(self) -> str | None:
|
| 304 |
return self._mcp_server.instructions
|
| 305 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
async def run_async(
|
| 307 |
self,
|
| 308 |
transport: Transport | None = None,
|
|
|
|
| 303 |
def instructions(self) -> str | None:
|
| 304 |
return self._mcp_server.instructions
|
| 305 |
|
| 306 |
+
@property
|
| 307 |
+
def version(self) -> str | None:
|
| 308 |
+
return self._mcp_server.version
|
| 309 |
+
|
| 310 |
async def run_async(
|
| 311 |
self,
|
| 312 |
transport: Transport | None = None,
|
src/fastmcp/utilities/inspect.py
CHANGED
|
@@ -9,6 +9,7 @@ from typing import Any
|
|
| 9 |
from mcp.server.fastmcp import FastMCP as FastMCP1x
|
| 10 |
|
| 11 |
import fastmcp
|
|
|
|
| 12 |
from fastmcp.server.server import FastMCP
|
| 13 |
|
| 14 |
|
|
@@ -71,7 +72,7 @@ class FastMCPInfo:
|
|
| 71 |
instructions: str | None
|
| 72 |
fastmcp_version: str
|
| 73 |
mcp_version: str
|
| 74 |
-
server_version: str
|
| 75 |
tools: list[ToolInfo]
|
| 76 |
prompts: list[PromptInfo]
|
| 77 |
resources: list[ResourceInfo]
|
|
@@ -170,7 +171,9 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
|
|
| 170 |
instructions=mcp.instructions,
|
| 171 |
fastmcp_version=fastmcp.__version__,
|
| 172 |
mcp_version=importlib.metadata.version("mcp"),
|
| 173 |
-
server_version=
|
|
|
|
|
|
|
| 174 |
tools=tool_infos,
|
| 175 |
prompts=prompt_infos,
|
| 176 |
resources=resource_infos,
|
|
@@ -179,7 +182,7 @@ async def inspect_fastmcp_v2(mcp: FastMCP[Any]) -> FastMCPInfo:
|
|
| 179 |
)
|
| 180 |
|
| 181 |
|
| 182 |
-
async def inspect_fastmcp_v1(mcp:
|
| 183 |
"""Extract information from a FastMCP v1.x instance using a Client.
|
| 184 |
|
| 185 |
Args:
|
|
@@ -188,7 +191,6 @@ async def inspect_fastmcp_v1(mcp: Any) -> FastMCPInfo:
|
|
| 188 |
Returns:
|
| 189 |
FastMCPInfo dataclass containing the extracted information
|
| 190 |
"""
|
| 191 |
-
from fastmcp import Client
|
| 192 |
|
| 193 |
# Use a client to interact with the FastMCP1x server
|
| 194 |
async with Client(mcp) as client:
|
|
@@ -288,11 +290,11 @@ async def inspect_fastmcp_v1(mcp: Any) -> FastMCPInfo:
|
|
| 288 |
}
|
| 289 |
|
| 290 |
return FastMCPInfo(
|
| 291 |
-
name=mcp.name,
|
| 292 |
-
instructions=
|
| 293 |
-
fastmcp_version=
|
| 294 |
mcp_version=importlib.metadata.version("mcp"),
|
| 295 |
-
server_version=
|
| 296 |
tools=tool_infos,
|
| 297 |
prompts=prompt_infos,
|
| 298 |
resources=resource_infos,
|
|
@@ -301,14 +303,7 @@ async def inspect_fastmcp_v1(mcp: Any) -> FastMCPInfo:
|
|
| 301 |
)
|
| 302 |
|
| 303 |
|
| 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:
|
| 312 |
"""Extract information from a FastMCP instance into a dataclass.
|
| 313 |
|
| 314 |
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 |
Returns:
|
| 321 |
FastMCPInfo dataclass containing the extracted information
|
| 322 |
"""
|
| 323 |
-
if
|
| 324 |
return await inspect_fastmcp_v1(mcp)
|
| 325 |
else:
|
| 326 |
return await inspect_fastmcp_v2(mcp)
|
|
|
|
| 9 |
from mcp.server.fastmcp import FastMCP as FastMCP1x
|
| 10 |
|
| 11 |
import fastmcp
|
| 12 |
+
from fastmcp import Client
|
| 13 |
from fastmcp.server.server import FastMCP
|
| 14 |
|
| 15 |
|
|
|
|
| 72 |
instructions: str | None
|
| 73 |
fastmcp_version: str
|
| 74 |
mcp_version: str
|
| 75 |
+
server_version: str | None
|
| 76 |
tools: list[ToolInfo]
|
| 77 |
prompts: list[PromptInfo]
|
| 78 |
resources: list[ResourceInfo]
|
|
|
|
| 171 |
instructions=mcp.instructions,
|
| 172 |
fastmcp_version=fastmcp.__version__,
|
| 173 |
mcp_version=importlib.metadata.version("mcp"),
|
| 174 |
+
server_version=(
|
| 175 |
+
mcp.version if hasattr(mcp, "version") else mcp._mcp_server.version
|
| 176 |
+
),
|
| 177 |
tools=tool_infos,
|
| 178 |
prompts=prompt_infos,
|
| 179 |
resources=resource_infos,
|
|
|
|
| 182 |
)
|
| 183 |
|
| 184 |
|
| 185 |
+
async def inspect_fastmcp_v1(mcp: FastMCP1x) -> FastMCPInfo:
|
| 186 |
"""Extract information from a FastMCP v1.x instance using a Client.
|
| 187 |
|
| 188 |
Args:
|
|
|
|
| 191 |
Returns:
|
| 192 |
FastMCPInfo dataclass containing the extracted information
|
| 193 |
"""
|
|
|
|
| 194 |
|
| 195 |
# Use a client to interact with the FastMCP1x server
|
| 196 |
async with Client(mcp) as client:
|
|
|
|
| 290 |
}
|
| 291 |
|
| 292 |
return FastMCPInfo(
|
| 293 |
+
name=mcp._mcp_server.name,
|
| 294 |
+
instructions=mcp._mcp_server.instructions,
|
| 295 |
+
fastmcp_version=importlib.metadata.version("mcp"),
|
| 296 |
mcp_version=importlib.metadata.version("mcp"),
|
| 297 |
+
server_version=mcp._mcp_server.version,
|
| 298 |
tools=tool_infos,
|
| 299 |
prompts=prompt_infos,
|
| 300 |
resources=resource_infos,
|
|
|
|
| 303 |
)
|
| 304 |
|
| 305 |
|
| 306 |
+
async def inspect_fastmcp(mcp: FastMCP[Any] | FastMCP1x) -> FastMCPInfo:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
"""Extract information from a FastMCP instance into a dataclass.
|
| 308 |
|
| 309 |
This function automatically detects whether the instance is FastMCP v1.x or v2.x
|
|
|
|
| 315 |
Returns:
|
| 316 |
FastMCPInfo dataclass containing the extracted information
|
| 317 |
"""
|
| 318 |
+
if isinstance(mcp, FastMCP1x):
|
| 319 |
return await inspect_fastmcp_v1(mcp)
|
| 320 |
else:
|
| 321 |
return await inspect_fastmcp_v2(mcp)
|
tests/utilities/test_inspect.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
"""Tests for the inspect.py module."""
|
| 2 |
|
| 3 |
-
|
|
|
|
| 4 |
from mcp.server.fastmcp import FastMCP as FastMCP1x
|
| 5 |
|
| 6 |
import fastmcp
|
|
@@ -8,7 +9,6 @@ from fastmcp import Client, FastMCP
|
|
| 8 |
from fastmcp.utilities.inspect import (
|
| 9 |
FastMCPInfo,
|
| 10 |
ToolInfo,
|
| 11 |
-
_is_fastmcp_v1,
|
| 12 |
inspect_fastmcp,
|
| 13 |
inspect_fastmcp_v1,
|
| 14 |
)
|
|
@@ -67,15 +67,15 @@ class TestGetFastMCPInfo:
|
|
| 67 |
|
| 68 |
async def test_empty_server(self):
|
| 69 |
"""Test get_fastmcp_info with an empty server."""
|
| 70 |
-
mcp = FastMCP("EmptyServer"
|
| 71 |
|
| 72 |
info = await inspect_fastmcp(mcp)
|
| 73 |
|
| 74 |
assert info.name == "EmptyServer"
|
| 75 |
-
assert info.instructions
|
| 76 |
assert info.fastmcp_version == fastmcp.__version__
|
| 77 |
-
assert info.mcp_version
|
| 78 |
-
assert info.server_version
|
| 79 |
assert info.tools == []
|
| 80 |
assert info.prompts == []
|
| 81 |
assert info.resources == []
|
|
@@ -85,6 +85,18 @@ class TestGetFastMCPInfo:
|
|
| 85 |
assert "prompts" in info.capabilities
|
| 86 |
assert "logging" in info.capabilities
|
| 87 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
async def test_server_with_tools(self):
|
| 89 |
"""Test get_fastmcp_info with a server that has tools."""
|
| 90 |
mcp = FastMCP("ToolServer")
|
|
@@ -246,14 +258,6 @@ class TestGetFastMCPInfo:
|
|
| 246 |
class TestFastMCP1xCompatibility:
|
| 247 |
"""Tests for FastMCP 1.x compatibility."""
|
| 248 |
|
| 249 |
-
async def test_fastmcp1x_detection(self):
|
| 250 |
-
"""Test that FastMCP1x instances are correctly detected."""
|
| 251 |
-
mcp1x = FastMCP1x("Test1x")
|
| 252 |
-
mcp2x = FastMCP("Test2x")
|
| 253 |
-
|
| 254 |
-
assert _is_fastmcp_v1(mcp1x) is True
|
| 255 |
-
assert _is_fastmcp_v1(mcp2x) is False
|
| 256 |
-
|
| 257 |
async def test_fastmcp1x_empty_server(self):
|
| 258 |
"""Test get_fastmcp_info_v1 with an empty FastMCP1x server."""
|
| 259 |
mcp = FastMCP1x("Test1x")
|
|
@@ -262,9 +266,9 @@ class TestFastMCP1xCompatibility:
|
|
| 262 |
|
| 263 |
assert info.name == "Test1x"
|
| 264 |
assert info.instructions is None
|
| 265 |
-
assert info.fastmcp_version ==
|
| 266 |
-
assert info.mcp_version
|
| 267 |
-
assert info.server_version
|
| 268 |
assert info.tools == []
|
| 269 |
assert info.prompts == []
|
| 270 |
assert info.resources == []
|
|
@@ -380,8 +384,8 @@ class TestFastMCP1xCompatibility:
|
|
| 380 |
assert "tool2x" in tool2x_names
|
| 381 |
|
| 382 |
# Check server versions
|
| 383 |
-
assert info1x.server_version
|
| 384 |
-
assert info2x.server_version
|
| 385 |
|
| 386 |
# No templates added in these tests
|
| 387 |
assert len(info1x.templates) == 0
|
|
|
|
| 1 |
"""Tests for the inspect.py module."""
|
| 2 |
|
| 3 |
+
import importlib.metadata
|
| 4 |
+
|
| 5 |
from mcp.server.fastmcp import FastMCP as FastMCP1x
|
| 6 |
|
| 7 |
import fastmcp
|
|
|
|
| 9 |
from fastmcp.utilities.inspect import (
|
| 10 |
FastMCPInfo,
|
| 11 |
ToolInfo,
|
|
|
|
| 12 |
inspect_fastmcp,
|
| 13 |
inspect_fastmcp_v1,
|
| 14 |
)
|
|
|
|
| 67 |
|
| 68 |
async def test_empty_server(self):
|
| 69 |
"""Test get_fastmcp_info with an empty server."""
|
| 70 |
+
mcp = FastMCP("EmptyServer")
|
| 71 |
|
| 72 |
info = await inspect_fastmcp(mcp)
|
| 73 |
|
| 74 |
assert info.name == "EmptyServer"
|
| 75 |
+
assert info.instructions is None
|
| 76 |
assert info.fastmcp_version == fastmcp.__version__
|
| 77 |
+
assert info.mcp_version == importlib.metadata.version("mcp")
|
| 78 |
+
assert info.server_version is None
|
| 79 |
assert info.tools == []
|
| 80 |
assert info.prompts == []
|
| 81 |
assert info.resources == []
|
|
|
|
| 85 |
assert "prompts" in info.capabilities
|
| 86 |
assert "logging" in info.capabilities
|
| 87 |
|
| 88 |
+
async def test_server_with_instructions(self):
|
| 89 |
+
"""Test get_fastmcp_info with a server that has instructions."""
|
| 90 |
+
mcp = FastMCP("InstructionsServer", instructions="Test instructions")
|
| 91 |
+
info = await inspect_fastmcp(mcp)
|
| 92 |
+
assert info.instructions == "Test instructions"
|
| 93 |
+
|
| 94 |
+
async def test_server_with_version(self):
|
| 95 |
+
"""Test get_fastmcp_info with a server that has a version."""
|
| 96 |
+
mcp = FastMCP("VersionServer", version="1.2.3")
|
| 97 |
+
info = await inspect_fastmcp(mcp)
|
| 98 |
+
assert info.server_version == "1.2.3"
|
| 99 |
+
|
| 100 |
async def test_server_with_tools(self):
|
| 101 |
"""Test get_fastmcp_info with a server that has tools."""
|
| 102 |
mcp = FastMCP("ToolServer")
|
|
|
|
| 258 |
class TestFastMCP1xCompatibility:
|
| 259 |
"""Tests for FastMCP 1.x compatibility."""
|
| 260 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
async def test_fastmcp1x_empty_server(self):
|
| 262 |
"""Test get_fastmcp_info_v1 with an empty FastMCP1x server."""
|
| 263 |
mcp = FastMCP1x("Test1x")
|
|
|
|
| 266 |
|
| 267 |
assert info.name == "Test1x"
|
| 268 |
assert info.instructions is None
|
| 269 |
+
assert info.fastmcp_version == importlib.metadata.version("mcp")
|
| 270 |
+
assert info.mcp_version == importlib.metadata.version("mcp")
|
| 271 |
+
assert info.server_version is None
|
| 272 |
assert info.tools == []
|
| 273 |
assert info.prompts == []
|
| 274 |
assert info.resources == []
|
|
|
|
| 384 |
assert "tool2x" in tool2x_names
|
| 385 |
|
| 386 |
# Check server versions
|
| 387 |
+
assert info1x.server_version is None
|
| 388 |
+
assert info2x.server_version is None
|
| 389 |
|
| 390 |
# No templates added in these tests
|
| 391 |
assert len(info1x.templates) == 0
|