Skip to content

Commit e3c560e

Browse files
authored
Merge pull request #90 from shira-ayal/bugfix/issue-66
Bugfix/issue 66
2 parents dc4a9d4 + 1dbd275 commit e3c560e

File tree

2 files changed

+193
-1
lines changed

2 files changed

+193
-1
lines changed

fastapi_mcp/server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,7 @@ async def _execute_api_tool(
422422
# TODO: Better typing for the AsyncClientProtocol. It should return a ResponseProtocol that has a json() method that returns a dict/list/etc.
423423
try:
424424
result = response.json()
425-
result_text = json.dumps(result, indent=2)
425+
result_text = json.dumps(result, indent=2, ensure_ascii=False)
426426
except json.JSONDecodeError:
427427
if hasattr(response, "text"):
428428
result_text = response.text

tests/test_mcp_execute_api_tool.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import pytest
2+
from unittest.mock import AsyncMock, patch, MagicMock
3+
from fastapi import FastAPI
4+
5+
from fastapi_mcp import FastApiMCP
6+
from mcp.types import TextContent
7+
8+
9+
@pytest.mark.asyncio
10+
async def test_execute_api_tool_success(simple_fastapi_app: FastAPI):
11+
"""Test successful execution of an API tool."""
12+
mcp = FastApiMCP(simple_fastapi_app)
13+
14+
# Mock the HTTP client response
15+
mock_response = MagicMock()
16+
mock_response.json.return_value = {"id": 1, "name": "Test Item"}
17+
mock_response.status_code = 200
18+
mock_response.text = '{"id": 1, "name": "Test Item"}'
19+
20+
# Mock the HTTP client
21+
mock_client = AsyncMock()
22+
mock_client.get.return_value = mock_response
23+
24+
# Test parameters
25+
tool_name = "get_item"
26+
arguments = {"item_id": 1}
27+
28+
# Execute the tool
29+
with patch.object(mcp, '_http_client', mock_client):
30+
result = await mcp._execute_api_tool(
31+
client=mock_client,
32+
tool_name=tool_name,
33+
arguments=arguments,
34+
operation_map=mcp.operation_map
35+
)
36+
37+
# Verify the result
38+
assert len(result) == 1
39+
assert isinstance(result[0], TextContent)
40+
assert result[0].text == '{\n "id": 1,\n "name": "Test Item"\n}'
41+
42+
# Verify the HTTP client was called correctly
43+
mock_client.get.assert_called_once_with(
44+
"/items/1",
45+
params={},
46+
headers={}
47+
)
48+
49+
50+
@pytest.mark.asyncio
51+
async def test_execute_api_tool_with_query_params(simple_fastapi_app: FastAPI):
52+
"""Test execution of an API tool with query parameters."""
53+
mcp = FastApiMCP(simple_fastapi_app)
54+
55+
# Mock the HTTP client response
56+
mock_response = MagicMock()
57+
mock_response.json.return_value = [{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]
58+
mock_response.status_code = 200
59+
mock_response.text = '[{"id": 1, "name": "Item 1"}, {"id": 2, "name": "Item 2"}]'
60+
61+
# Mock the HTTP client
62+
mock_client = AsyncMock()
63+
mock_client.get.return_value = mock_response
64+
65+
# Test parameters
66+
tool_name = "list_items"
67+
arguments = {"skip": 0, "limit": 2}
68+
69+
# Execute the tool
70+
with patch.object(mcp, '_http_client', mock_client):
71+
result = await mcp._execute_api_tool(
72+
client=mock_client,
73+
tool_name=tool_name,
74+
arguments=arguments,
75+
operation_map=mcp.operation_map
76+
)
77+
78+
# Verify the result
79+
assert len(result) == 1
80+
assert isinstance(result[0], TextContent)
81+
82+
# Verify the HTTP client was called with query parameters
83+
mock_client.get.assert_called_once_with(
84+
"/items/",
85+
params={"skip": 0, "limit": 2},
86+
headers={}
87+
)
88+
89+
90+
@pytest.mark.asyncio
91+
async def test_execute_api_tool_with_body(simple_fastapi_app: FastAPI):
92+
"""Test execution of an API tool with request body."""
93+
mcp = FastApiMCP(simple_fastapi_app)
94+
95+
# Mock the HTTP client response
96+
mock_response = MagicMock()
97+
mock_response.json.return_value = {"id": 1, "name": "New Item"}
98+
mock_response.status_code = 200
99+
mock_response.text = '{"id": 1, "name": "New Item"}'
100+
101+
# Mock the HTTP client
102+
mock_client = AsyncMock()
103+
mock_client.post.return_value = mock_response
104+
105+
# Test parameters
106+
tool_name = "create_item"
107+
arguments = {
108+
"item": {
109+
"id": 1,
110+
"name": "New Item",
111+
"price": 10.0,
112+
"tags": ["tag1"],
113+
"description": "New item description"
114+
}
115+
}
116+
117+
# Execute the tool
118+
with patch.object(mcp, '_http_client', mock_client):
119+
result = await mcp._execute_api_tool(
120+
client=mock_client,
121+
tool_name=tool_name,
122+
arguments=arguments,
123+
operation_map=mcp.operation_map
124+
)
125+
126+
# Verify the result
127+
assert len(result) == 1
128+
assert isinstance(result[0], TextContent)
129+
130+
# Verify the HTTP client was called with the request body
131+
mock_client.post.assert_called_once_with(
132+
"/items/",
133+
params={},
134+
headers={},
135+
json=arguments
136+
)
137+
138+
139+
@pytest.mark.asyncio
140+
async def test_execute_api_tool_with_non_ascii_chars(simple_fastapi_app: FastAPI):
141+
"""Test execution of an API tool with non-ASCII characters."""
142+
mcp = FastApiMCP(simple_fastapi_app)
143+
144+
# Test data with both ASCII and non-ASCII characters
145+
test_data = {
146+
"id": 1,
147+
"name": "你好 World", # Chinese characters + ASCII
148+
"price": 10.0,
149+
"tags": ["tag1", "标签2"], # Chinese characters in tags
150+
"description": "这是一个测试描述" # All Chinese characters
151+
}
152+
153+
# Mock the HTTP client response
154+
mock_response = MagicMock()
155+
mock_response.json.return_value = test_data
156+
mock_response.status_code = 200
157+
mock_response.text = '{"id": 1, "name": "你好 World", "price": 10.0, "tags": ["tag1", "标签2"], "description": "这是一个测试描述"}'
158+
159+
# Mock the HTTP client
160+
mock_client = AsyncMock()
161+
mock_client.get.return_value = mock_response
162+
163+
# Test parameters
164+
tool_name = "get_item"
165+
arguments = {"item_id": 1}
166+
167+
# Execute the tool
168+
with patch.object(mcp, '_http_client', mock_client):
169+
result = await mcp._execute_api_tool(
170+
client=mock_client,
171+
tool_name=tool_name,
172+
arguments=arguments,
173+
operation_map=mcp.operation_map
174+
)
175+
176+
# Verify the result
177+
assert len(result) == 1
178+
assert isinstance(result[0], TextContent)
179+
180+
# Verify that the response contains both ASCII and non-ASCII characters
181+
response_text = result[0].text
182+
assert "你好" in response_text # Chinese characters preserved
183+
assert "World" in response_text # ASCII characters preserved
184+
assert "标签2" in response_text # Chinese characters in tags preserved
185+
assert "这是一个测试描述" in response_text # All Chinese description preserved
186+
187+
# Verify the HTTP client was called correctly
188+
mock_client.get.assert_called_once_with(
189+
"/items/1",
190+
params={},
191+
headers={}
192+
)

0 commit comments

Comments
 (0)