Skip to content

Commit ba25a94

Browse files
committed
refactor(__init__.py): update module imports to only include main and add module docstring for clarity
refactor(server.py): simplify error handling in command execution by raising an error directly instead of appending to content test: enhance test coverage for tool execution with additional test cases for valid and invalid commands
1 parent 7193838 commit ba25a94

File tree

3 files changed

+71
-31
lines changed

3 files changed

+71
-31
lines changed

mcp_shell_server/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from .server import ShellServer, main
2-
from .shell_executor import ShellExecutor
1+
"""MCP Shell Server Package."""
2+
from .server import main
33

44
__version__ = "0.1.0"
5-
__all__ = ["ShellServer", "ShellExecutor", "main"]
5+
__all__ = ["main"]

mcp_shell_server/server.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,13 @@ async def run_tool(self, arguments: dict) -> Sequence[TextContent]:
5252

5353
result = await self.executor.execute(command, stdin)
5454

55+
# Raise error if command execution failed
56+
if result.get("error"):
57+
raise RuntimeError(result["error"])
58+
5559
# Convert executor result to TextContent sequence
5660
content: list[TextContent] = []
5761

58-
if result.get("error"):
59-
content.append(TextContent(
60-
type="text",
61-
text=result["error"]
62-
))
6362
if result.get("stdout"):
6463
content.append(TextContent(
6564
type="text",

tests/test_server.py

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,82 @@
11
import pytest
2-
from mcp_shell_server.server import ShellServer
2+
from mcp.types import TextContent, Tool
3+
from mcp_shell_server.server import call_tool, list_tools, tool_handler
34

4-
@pytest.fixture
5-
def server():
6-
return ShellServer()
75

86
@pytest.mark.asyncio
9-
async def test_server_handle_empty_request(server):
10-
response = await server.handle({})
11-
assert response["error"] == "No command provided"
12-
assert response["status"] == 1
7+
async def test_list_tools():
8+
"""Test listing of available tools"""
9+
tools = await list_tools()
10+
assert len(tools) == 1
11+
tool = tools[0]
12+
assert isinstance(tool, Tool)
13+
assert tool.name == "execute"
14+
assert tool.description
15+
assert tool.inputSchema["type"] == "object"
16+
assert "command" in tool.inputSchema["properties"]
17+
assert "stdin" in tool.inputSchema["properties"]
18+
assert tool.inputSchema["required"] == ["command"]
19+
1320

1421
@pytest.mark.asyncio
15-
async def test_server_handle_valid_command(server, monkeypatch):
22+
async def test_call_tool_valid_command(monkeypatch):
23+
"""Test execution of a valid command"""
1624
monkeypatch.setenv("ALLOW_COMMANDS", "echo")
17-
response = await server.handle({
25+
result = await call_tool("execute", {
1826
"command": ["echo", "hello world"]
1927
})
20-
assert response["stdout"].strip() == "hello world"
21-
assert response["status"] == 0
22-
assert "execution_time" in response
28+
assert len(result) == 1
29+
assert isinstance(result[0], TextContent)
30+
assert result[0].type == "text"
31+
assert result[0].text.strip() == "hello world"
32+
2333

2434
@pytest.mark.asyncio
25-
async def test_server_handle_stdin(server, monkeypatch):
35+
async def test_call_tool_with_stdin(monkeypatch):
36+
"""Test command execution with stdin"""
2637
monkeypatch.setenv("ALLOW_COMMANDS", "cat")
27-
response = await server.handle({
38+
result = await call_tool("execute", {
2839
"command": ["cat"],
2940
"stdin": "test input"
3041
})
31-
assert response["stdout"].strip() == "test input"
32-
assert response["status"] == 0
42+
assert len(result) == 1
43+
assert isinstance(result[0], TextContent)
44+
assert result[0].type == "text"
45+
assert result[0].text.strip() == "test input"
46+
3347

3448
@pytest.mark.asyncio
35-
async def test_server_handle_invalid_command(server, monkeypatch):
49+
async def test_call_tool_invalid_command(monkeypatch):
50+
"""Test execution of an invalid command"""
3651
monkeypatch.setenv("ALLOW_COMMANDS", "echo")
37-
response = await server.handle({
38-
"command": ["invalid_command"]
39-
})
40-
assert response["error"] == "Command not allowed: invalid_command"
41-
assert response["status"] == 1
52+
with pytest.raises(RuntimeError) as excinfo:
53+
await call_tool("execute", {
54+
"command": ["invalid_command"]
55+
})
56+
assert "Command not allowed: invalid_command" in str(excinfo.value)
57+
58+
59+
@pytest.mark.asyncio
60+
async def test_call_tool_unknown_tool():
61+
"""Test calling an unknown tool"""
62+
with pytest.raises(RuntimeError) as excinfo:
63+
await call_tool("unknown_tool", {})
64+
assert "Unknown tool: unknown_tool" in str(excinfo.value)
65+
66+
67+
@pytest.mark.asyncio
68+
async def test_call_tool_invalid_arguments():
69+
"""Test calling a tool with invalid arguments"""
70+
with pytest.raises(RuntimeError) as excinfo:
71+
await call_tool("execute", "not a dict")
72+
assert "Arguments must be a dictionary" in str(excinfo.value)
73+
74+
75+
@pytest.mark.asyncio
76+
async def test_call_tool_empty_command():
77+
"""Test execution with empty command"""
78+
with pytest.raises(RuntimeError) as excinfo:
79+
await call_tool("execute", {
80+
"command": []
81+
})
82+
assert "No command provided" in str(excinfo.value)

0 commit comments

Comments
 (0)