Skip to content

Commit f7f9c5e

Browse files
fix: update tests to use mcp_module, add CLI support to describe_server.py
Co-Authored-By: AJ Steers <aj@airbyte.io>
1 parent 7deddee commit f7f9c5e

2 files changed

Lines changed: 57 additions & 26 deletions

File tree

src/fastmcp_extensions/utils/describe_server.py

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,15 @@
55
which is useful for tracking context truncation issues when AI agents call list_tools.
66
77
Usage:
8-
Create a module in your MCP server project that can be called with -m syntax:
8+
python -m fastmcp_extensions.utils.describe_server --app <module:app>
99
10-
```python
11-
# my_mcp_server/describe.py
12-
from my_mcp_server.server import app
13-
from fastmcp_extensions.utils.describe_server import run_measurement
10+
Example:
11+
python -m fastmcp_extensions.utils.describe_server --app my_mcp_server.server:app
1412
15-
if __name__ == "__main__":
16-
run_measurement(app, server_name="my-mcp-server")
17-
```
18-
19-
Then add a poe task:
20-
```toml
21-
[tool.poe.tasks.mcp-describe-server]
22-
cmd = "python -m my_mcp_server.describe"
23-
help = "Describe MCP server tool list"
24-
```
25-
26-
Run with: `poe mcp-describe-server`
13+
Poe task configuration:
14+
[tool.poe.tasks.mcp-describe-server]
15+
cmd = "python -m fastmcp_extensions.utils.describe_server --app my_mcp_server.server:app"
16+
help = "Describe MCP server tool list"
2717
2818
Output includes:
2919
- Tool count
@@ -33,7 +23,9 @@
3323

3424
from __future__ import annotations
3525

26+
import argparse
3627
import asyncio
28+
import importlib
3729
from dataclasses import dataclass
3830
from typing import TYPE_CHECKING, Any
3931

@@ -159,3 +151,42 @@ async def get_tool_details(app: FastMCP) -> list[dict[str, Any]]:
159151
)
160152

161153
return details
154+
155+
156+
def _import_app(app_path: str) -> FastMCP:
157+
"""Import an app from a module:attribute path."""
158+
if ":" not in app_path:
159+
msg = f"Invalid app path '{app_path}'. Expected format: 'module.path:attribute'"
160+
raise ValueError(msg)
161+
162+
module_path, attr_name = app_path.rsplit(":", 1)
163+
module = importlib.import_module(module_path)
164+
return getattr(module, attr_name)
165+
166+
167+
def main() -> None:
168+
"""Main entry point for the MCP server description CLI."""
169+
parser = argparse.ArgumentParser(
170+
description="Describe MCP server tool list",
171+
formatter_class=argparse.RawDescriptionHelpFormatter,
172+
epilog=__doc__,
173+
)
174+
175+
parser.add_argument(
176+
"--app",
177+
required=True,
178+
help="App module path in format 'module.path:attribute'",
179+
)
180+
parser.add_argument(
181+
"--name",
182+
help="Optional server name for reporting",
183+
)
184+
185+
cli_args = parser.parse_args()
186+
187+
app = _import_app(cli_args.app)
188+
run_measurement(app, server_name=cli_args.name)
189+
190+
191+
if __name__ == "__main__":
192+
main()

tests/test_fastmcp_extensions.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def test_all_exports() -> None:
5858

5959
@pytest.mark.unit
6060
def test_mcp_tool_decorator() -> None:
61-
"""Test that mcp_tool decorator registers tools with auto-inferred domain."""
61+
"""Test that mcp_tool decorator registers tools with auto-inferred mcp_module."""
6262
clear_registrations()
6363

6464
@mcp_tool(read_only=True)
@@ -70,16 +70,16 @@ def my_test_tool() -> str:
7070
assert len(tools) == 1
7171
func, annotations = tools[0]
7272
assert func.__name__ == "my_test_tool"
73-
# Domain is auto-inferred from module name (test_fastmcp_extensions)
74-
assert annotations["domain"] == "test_fastmcp_extensions"
73+
# mcp_module is auto-inferred from module name (test_fastmcp_extensions)
74+
assert annotations["mcp_module"] == "test_fastmcp_extensions"
7575
assert annotations[READ_ONLY_HINT] is True
7676

7777
clear_registrations()
7878

7979

8080
@pytest.mark.unit
8181
def test_mcp_prompt_decorator() -> None:
82-
"""Test that mcp_prompt decorator registers prompts with auto-inferred domain."""
82+
"""Test that mcp_prompt decorator registers prompts with auto-inferred mcp_module."""
8383
clear_registrations()
8484

8585
@mcp_prompt("test_prompt", "A test prompt")
@@ -93,15 +93,15 @@ def my_test_prompt() -> list[dict[str, str]]:
9393
assert func.__name__ == "my_test_prompt"
9494
assert annotations["name"] == "test_prompt"
9595
assert annotations["description"] == "A test prompt"
96-
# Domain is auto-inferred from module name (test_fastmcp_extensions)
97-
assert annotations["domain"] == "test_fastmcp_extensions"
96+
# mcp_module is auto-inferred from module name (test_fastmcp_extensions)
97+
assert annotations["mcp_module"] == "test_fastmcp_extensions"
9898

9999
clear_registrations()
100100

101101

102102
@pytest.mark.unit
103103
def test_mcp_resource_decorator() -> None:
104-
"""Test that mcp_resource decorator registers resources with auto-inferred domain."""
104+
"""Test that mcp_resource decorator registers resources with auto-inferred mcp_module."""
105105
clear_registrations()
106106

107107
@mcp_resource(
@@ -120,7 +120,7 @@ def my_test_resource() -> dict[str, str]:
120120
assert annotations["uri"] == "test://resource"
121121
assert annotations["description"] == "A test resource"
122122
assert annotations["mime_type"] == "application/json"
123-
# Domain is auto-inferred from module name (test_fastmcp_extensions)
124-
assert annotations["domain"] == "test_fastmcp_extensions"
123+
# mcp_module is auto-inferred from module name (test_fastmcp_extensions)
124+
assert annotations["mcp_module"] == "test_fastmcp_extensions"
125125

126126
clear_registrations()

0 commit comments

Comments
 (0)