Skip to content

Commit 4f2de87

Browse files
committed
fix compatibility
1 parent 0ab120e commit 4f2de87

File tree

8 files changed

+101
-186
lines changed

8 files changed

+101
-186
lines changed

.github/workflows/mcp-compatibility.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,15 @@ jobs:
2323
# Get all available versions from PyPI
2424
versions=$(pip index versions mcp 2>/dev/null | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+' | sort -V)
2525
26-
# Filter to versions >= 1.0.0 and get only latest patch version for each minor
26+
# Filter to versions >= 1.2.0 and get only latest patch version for each minor
2727
declare -A latest_minor
2828
for version in $versions; do
2929
major=$(echo $version | cut -d. -f1)
3030
minor=$(echo $version | cut -d. -f2)
31+
patch=$(echo $version | cut -d. -f3)
3132
32-
# Include if major >= 1
33-
if [ "$major" -ge 1 ]; then
33+
# Include if version >= 1.2.0
34+
if [ "$major" -gt 1 ] || ([ "$major" -eq 1 ] && [ "$minor" -ge 2 ]); then
3435
minor_key="$major.$minor"
3536
latest_minor[$minor_key]="$version"
3637
fi
Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
from typing import Any
1+
from typing import Any, TYPE_CHECKING
22

33
from mcp import ServerResult, Tool
4-
from mcp.server import FastMCP
54
from mcp.types import CallToolRequest, CallToolResult, ListToolsRequest, TextContent
65

76
from mcpcat.modules.overrides.mcp_server import override_lowlevel_mcp_server
@@ -11,11 +10,22 @@
1110
from ...types import MCPCatData
1211
from ..logging import log_info, log_warning
1312
from ..session import capture_session_info
13+
from ..version_detection import has_fastmcp_support
1414

1515
"""Tool management and interception for MCPCat."""
1616

17+
if TYPE_CHECKING or has_fastmcp_support():
18+
try:
19+
from mcp.server import FastMCP
20+
except ImportError:
21+
FastMCP = None
22+
else:
23+
FastMCP = None
1724

18-
def override_fastmcp(server: FastMCP, data: MCPCatData) -> None:
25+
26+
def override_fastmcp(server: Any, data: MCPCatData) -> None:
1927
"""Set up tool list and call handlers for FastMCP."""
28+
if FastMCP is None:
29+
raise ImportError("FastMCP is not available in this MCP version")
2030
from mcp.types import CallToolResult, ListToolsResult
2131
override_lowlevel_mcp_server(server._mcp_server, data)

src/mcpcat/modules/overrides/mcp_server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from typing import Any
22

33
from mcp import ServerResult, Tool
4-
from mcp.server.lowlevel import Server
4+
from mcp.server import Server
55
from mcp.types import CallToolRequest, CallToolResult, ListToolsRequest, TextContent
66

77
from mcpcat.modules.tools import handle_report_missing

src/mcpcat/modules/tools.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
"""Tool management and interception for MCPCat."""
22

3-
from typing import Any
3+
from typing import Any, TYPE_CHECKING
44

55
from mcp import ServerResult, Tool
6-
from mcp.server import FastMCP
76
from mcp.types import CallToolRequest, CallToolResult, ListToolsRequest, TextContent
87

98
from mcpcat.modules.tracing import record_trace
9+
from mcpcat.modules.version_detection import has_fastmcp_support
1010

1111
from ..types import MCPCatData
1212
from .logging import log_info
1313
from .session import capture_session_info
1414

15+
if TYPE_CHECKING or has_fastmcp_support():
16+
try:
17+
from mcp.server import FastMCP
18+
except ImportError:
19+
FastMCP = None
20+
1521
async def handle_report_missing(arguments: dict[str, Any], data: MCPCatData) -> CallToolResult:
1622
"""Handle the report_missing tool."""
1723
missing_tool = arguments.get("missing_tool", "")
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""MCP version detection utilities."""
2+
3+
import importlib.metadata
4+
from typing import Tuple, Optional
5+
6+
7+
def get_mcp_version() -> Optional[str]:
8+
"""Get the installed MCP version."""
9+
try:
10+
return importlib.metadata.version("mcp")
11+
except importlib.metadata.PackageNotFoundError:
12+
return None
13+
14+
15+
def parse_version(version_str: str) -> Tuple[int, int, int]:
16+
"""Parse version string to tuple of integers."""
17+
parts = version_str.split(".")
18+
major = int(parts[0]) if len(parts) > 0 else 0
19+
minor = int(parts[1]) if len(parts) > 1 else 0
20+
patch = int(parts[2]) if len(parts) > 2 else 0
21+
return (major, minor, patch)
22+
23+
24+
def has_fastmcp_support() -> bool:
25+
"""Check if the current MCP version supports FastMCP."""
26+
version = get_mcp_version()
27+
if not version:
28+
return False
29+
30+
major, minor, _ = parse_version(version)
31+
32+
# FastMCP was introduced after version 1.1
33+
if major < 1:
34+
return False
35+
if major == 1 and minor <= 1:
36+
return False
37+
38+
return True
39+
40+
41+
def can_import_fastmcp() -> bool:
42+
"""Check if FastMCP can be imported."""
43+
try:
44+
from mcp.server import FastMCP
45+
return True
46+
except ImportError:
47+
return False

tests/test_mcp_version_compatibility_comprehensive.py

Lines changed: 0 additions & 74 deletions
This file was deleted.

tests/test_utils/client.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,18 @@
33
from contextlib import asynccontextmanager
44
from typing import Any
55

6-
from mcp.server import FastMCP
76
from mcp.shared.memory import create_connected_server_and_client_session
87

8+
try:
9+
from mcp.server import FastMCP
10+
HAS_FASTMCP = True
11+
except ImportError:
12+
FastMCP = None
13+
HAS_FASTMCP = False
14+
915

1016
@asynccontextmanager
11-
async def create_test_client(server: FastMCP):
17+
async def create_test_client(server: Any):
1218
"""Create a test client for the given server.
1319
1420
This creates a properly connected MCP client/server pair with full
@@ -21,11 +27,18 @@ async def create_test_client(server: FastMCP):
2127
async with create_test_client(server) as client:
2228
result = await client.call_tool("add_todo", {"text": "Test"})
2329
"""
24-
async with create_connected_server_and_client_session(server._mcp_server) as client:
25-
yield client
30+
# Handle both FastMCP and low-level Server
31+
if hasattr(server, '_mcp_server'):
32+
# FastMCP server
33+
async with create_connected_server_and_client_session(server._mcp_server) as client:
34+
yield client
35+
else:
36+
# Low-level Server
37+
async with create_connected_server_and_client_session(server) as client:
38+
yield client
2639

2740

28-
async def call_tool_via_client(server: FastMCP, tool_name: str, arguments: dict[str, Any]) -> Any:
41+
async def call_tool_via_client(server: Any, tool_name: str, arguments: dict[str, Any]) -> Any:
2942
"""Helper to call a tool through a proper client session."""
3043
async with create_test_client(server) as client:
3144
result = await client.call_tool(tool_name, arguments)

tests/test_utils/todo_server.py

Lines changed: 10 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
"""Todo server implementation for testing."""
22

3+
from mcp.server import Server
34

4-
from mcp.server import FastMCP, Server
5+
try:
6+
from mcp.server import FastMCP
7+
HAS_FASTMCP = True
8+
except ImportError:
9+
FastMCP = None
10+
HAS_FASTMCP = False
511

612

713
class Todo:
@@ -13,8 +19,10 @@ def __init__(self, id: str, text: str, completed: bool = False):
1319
self.completed = completed
1420

1521

16-
def create_todo_server() -> FastMCP:
22+
def create_todo_server():
1723
"""Create a todo server for testing."""
24+
if FastMCP is None:
25+
raise ImportError("FastMCP is not available in this MCP version. Use create_low_level_todo_server() instead.")
1826
# Fix deprecation warning by not passing version as kwarg
1927
server = FastMCP("todo-server")
2028

@@ -61,99 +69,3 @@ def complete_todo(id: str) -> str:
6169
}
6270

6371
return server
64-
65-
66-
def create_low_level_todo_server() -> Server:
67-
"""Create a low-level MCP server for testing."""
68-
from mcp.types import Tool, TextContent
69-
70-
server = Server("todo-server-lowlevel")
71-
72-
todos: list[Todo] = []
73-
next_id = 1
74-
75-
# Define tools
76-
add_todo_tool = Tool(
77-
name="add_todo",
78-
description="Add a new todo item",
79-
inputSchema={
80-
"type": "object",
81-
"properties": {
82-
"text": {
83-
"type": "string",
84-
"description": "The todo item text"
85-
}
86-
},
87-
"required": ["text"]
88-
}
89-
)
90-
91-
list_todos_tool = Tool(
92-
name="list_todos",
93-
description="List all todo items",
94-
inputSchema={
95-
"type": "object",
96-
"properties": {}
97-
}
98-
)
99-
100-
complete_todo_tool = Tool(
101-
name="complete_todo",
102-
description="Mark a todo item as completed",
103-
inputSchema={
104-
"type": "object",
105-
"properties": {
106-
"id": {
107-
"type": "string",
108-
"description": "The todo item ID"
109-
}
110-
},
111-
"required": ["id"]
112-
}
113-
)
114-
115-
@server.list_tools()
116-
async def list_tools():
117-
return [add_todo_tool, list_todos_tool, complete_todo_tool]
118-
119-
@server.call_tool()
120-
async def call_tool(name: str, arguments: dict):
121-
nonlocal next_id
122-
123-
if name == "add_todo":
124-
text = arguments["text"]
125-
todo = Todo(str(next_id), text)
126-
todos.append(todo)
127-
next_id += 1
128-
return [TextContent(
129-
type="text",
130-
text=f'Added todo: "{text}" with ID {todo.id}'
131-
)]
132-
133-
elif name == "list_todos":
134-
if not todos:
135-
return [TextContent(type="text", text="No todos found")]
136-
137-
todo_list = []
138-
for todo in todos:
139-
status = "✓" if todo.completed else "○"
140-
todo_list.append(f"{todo.id}: {todo.text} {status}")
141-
142-
return [TextContent(type="text", text="\n".join(todo_list))]
143-
144-
elif name == "complete_todo":
145-
todo_id = arguments["id"]
146-
for todo in todos:
147-
if todo.id == todo_id:
148-
todo.completed = True
149-
return [TextContent(
150-
type="text",
151-
text=f'Completed todo: "{todo.text}"'
152-
)]
153-
154-
raise ValueError(f"Todo with ID {todo_id} not found")
155-
156-
else:
157-
raise ValueError(f"Unknown tool: {name}")
158-
159-
return server

0 commit comments

Comments
 (0)