Skip to content

Commit f9d7b42

Browse files
committed
refactor(server.py): remove duplicate error logging in call_tool function to simplify error handling
refactor(shell_executor.py): remove debug print statements to clean up code and improve readability test(tests): add tests for redirection path validation and IO handle closing functionality to ensure robustness of shell executor
1 parent 2f2dbec commit f9d7b42

File tree

4 files changed

+70
-29
lines changed

4 files changed

+70
-29
lines changed

src/mcp_shell_server/server.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,6 @@ async def call_tool(name: str, arguments: Any) -> Sequence[TextContent]:
113113
return await tool_handler.run_tool(arguments)
114114

115115
except Exception as e:
116-
logger.error(traceback.format_exc())
117-
logger.error(f"Error during call_tool: {str(e)}")
118-
raise RuntimeError(str(e)) from e
119-
120-
logger.error(traceback.format_exc())
121-
logger.error(f"Error during call_tool: {str(e)}")
122-
raise RuntimeError(str(e)) from e
123-
124-
125116
logger.error(traceback.format_exc())
126117
logger.error(f"Error during call_tool: {str(e)}")
127118
raise RuntimeError(f"Error executing command: {str(e)}") from e

src/mcp_shell_server/shell_executor.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -419,10 +419,8 @@ async def execute(
419419
return await self._execute_pipeline(commands, directory, timeout)
420420

421421
# Then check for other shell operators
422-
print("cleaned_command", cleaned_command)
423422
for token in cleaned_command:
424423
if token in [";", "&&", "||"]:
425-
print("token", token)
426424
return {
427425
"error": f"Unexpected shell operator: {token}",
428426
"status": 1,
@@ -590,7 +588,6 @@ async def _execute_pipeline(
590588

591589
for i, cmd in enumerate(parsed_commands):
592590
shell_cmd = self._create_shell_command(cmd)
593-
print(f"Executing command {i}: {shell_cmd}") # デバッグ出力
594591

595592
process = await asyncio.create_subprocess_shell(
596593
shell_cmd,
@@ -606,26 +603,15 @@ async def _execute_pipeline(
606603
process.communicate(input=prev_stdout), timeout=timeout
607604
)
608605

609-
# デバッグ出力
610-
print(f"Command {i} stdout: {stdout!r}")
611-
print(f"Command {i} stderr: {stderr!r}")
612-
613-
# 次のコマンドへの入力として使用
614606
prev_stdout = stdout if stdout else b""
615607

616-
# 最後のコマンドの場合は最終出力として保存
617608
if i == len(parsed_commands) - 1:
618609
final_stdout = stdout if stdout else b""
619610

620-
# エラー出力を累積
621611
final_stderr += stderr if stderr else b""
622612
processes.append(process)
623613

624-
# エラーチェック
625614
if process.returncode != 0:
626-
print(
627-
f"Command {i} failed with code {process.returncode}"
628-
) # デバッグ出力
629615
raise ValueError(
630616
f"Command failed with exit code {process.returncode}"
631617
)
@@ -634,15 +620,11 @@ async def _execute_pipeline(
634620
process.kill()
635621
raise
636622

637-
# 出力の処理
638623
if last_stdout:
639-
# ファイルにリイレクトされている場合
640624
last_stdout.write(final_stdout.decode("utf-8", errors="replace"))
641625
final_output = ""
642626
else:
643-
# 標準出力に出力する場合
644627
final_output = final_stdout.decode("utf-8", errors="replace")
645-
print(f"Final output: {final_output!r}") # デバッグ出力
646628

647629
return {
648630
"error": None,
@@ -654,7 +636,6 @@ async def _execute_pipeline(
654636
}
655637

656638
except Exception as e:
657-
print(f"Pipeline error: {str(e)}") # デバッグ出力
658639
return {
659640
"error": str(e),
660641
"stdout": "",

tests/test_server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ async def test_invalid_command_parameter():
201201
@pytest.mark.asyncio
202202
async def test_disallowed_command(monkeypatch):
203203
"""Test error handling for disallowed command"""
204-
monkeypatch.setenv("ALLOW_COMMANDS", "ls") # Add allowed command
204+
monkeypatch.setenv("ALLOW_COMMANDS", "ls") # Add allowed command
205205
with pytest.raises(RuntimeError) as exc:
206206
await call_tool(
207207
"shell_execute",

tests/test_shell_executor.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import tempfile
3+
from typing import IO
34

45
import pytest
56

@@ -501,3 +502,71 @@ def test_validate_pipeline(executor, monkeypatch):
501502
executor._validate_pipeline(["echo", "hello", "|", "grep", "h", "&&", "ls"])
502503
assert "Unexpected shell operator in pipeline: &&" in str(exc.value)
503504
assert executor._preprocess_command([]) == []
505+
506+
507+
def test_redirection_path_validation(executor):
508+
"""Test validation of redirection paths"""
509+
# Test missing output redirection path
510+
with pytest.raises(ValueError, match="Missing path for output redirection"):
511+
executor._parse_command(["echo", "hello", ">"])
512+
513+
# Test missing input redirection path
514+
with pytest.raises(ValueError, match="Missing path for input redirection"):
515+
executor._parse_command(["cat", "<"])
516+
517+
# Test operator as redirection target
518+
with pytest.raises(ValueError, match="Invalid redirection target: operator found"):
519+
executor._parse_command(["echo", "hello", ">", ">"])
520+
521+
# Test multiple operators
522+
with pytest.raises(ValueError, match="Invalid redirection target: operator found"):
523+
executor._parse_command(["echo", "hello", ">", ">>", "file.txt"])
524+
525+
526+
@pytest.mark.asyncio
527+
async def test_io_handle_close(executor, temp_test_dir, monkeypatch, mocker):
528+
"""Test IO handle closing functionality"""
529+
monkeypatch.setenv("ALLOW_COMMANDS", "echo")
530+
test_file = os.path.join(temp_test_dir, "test.txt")
531+
532+
# Create file handler that will raise IOError on close
533+
mock_file = mocker.MagicMock(spec=IO)
534+
mock_file.close.side_effect = IOError("Failed to close file")
535+
536+
# Patch the open function to return our mock
537+
mocker.patch("builtins.open", return_value=mock_file)
538+
await executor.execute(["echo", "hello", ">", test_file], directory=temp_test_dir)
539+
540+
# Verify our mock's close method was called
541+
assert mock_file.close.called
542+
543+
544+
def test_preprocess_command_pipeline(executor):
545+
"""Test pipeline command preprocessing functionality"""
546+
# Test empty command
547+
assert executor._preprocess_command([]) == []
548+
549+
# Test single command without pipe
550+
assert executor._preprocess_command(["echo", "hello"]) == ["echo", "hello"]
551+
552+
# Test simple pipe
553+
assert executor._preprocess_command(["echo", "hello", "|", "grep", "h"]) == [
554+
"echo",
555+
"hello",
556+
"|",
557+
"grep",
558+
"h",
559+
]
560+
561+
# Test multiple pipes
562+
assert executor._preprocess_command(
563+
["cat", "file", "|", "grep", "pattern", "|", "wc", "-l"]
564+
) == ["cat", "file", "|", "grep", "pattern", "|", "wc", "-l"]
565+
566+
# Test command with attached pipe operator
567+
assert executor._preprocess_command(["echo|", "grep", "pattern"]) == [
568+
"echo",
569+
"|",
570+
"grep",
571+
"pattern",
572+
]

0 commit comments

Comments
 (0)