Skip to content

Commit ce27d7c

Browse files
committed
Merge branch 'fix/stderr-timeout' into develop
2 parents 14eec84 + 768cbe1 commit ce27d7c

File tree

4 files changed

+36
-14
lines changed

4 files changed

+36
-14
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ asyncio_default_fixture_loop_scope = "function"
4444
filterwarnings = [
4545
"ignore::RuntimeWarning:selectors:",
4646
"ignore::pytest.PytestUnhandledCoroutineWarning:",
47+
"ignore::pytest.PytestUnraisableExceptionWarning:",
4748
]
4849

4950
[tool.ruff]

src/mcp_shell_server/server.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,21 +78,26 @@ async def run_tool(self, arguments: dict) -> Sequence[TextContent]:
7878
if not directory:
7979
raise ValueError("Directory is required")
8080

81+
content: list[TextContent] = []
8182
try:
82-
result = await self.executor.execute(command, directory, stdin, timeout)
83-
except asyncio.TimeoutError as e:
84-
raise ValueError(f"Command timed out after {timeout} seconds") from e
83+
# Handle execution with timeout
84+
result = await asyncio.wait_for(
85+
self.executor.execute(
86+
command, directory, stdin, None
87+
), # Pass None for timeout
88+
timeout=timeout,
89+
)
8590

86-
# Convert executor result to TextContent sequence
87-
content: list[TextContent] = []
91+
if result.get("error"):
92+
raise ValueError(result["error"])
8893

89-
if result.get("error"):
90-
raise ValueError(result["error"])
94+
if result.get("stdout"):
95+
content.append(TextContent(type="text", text=result["stdout"]))
96+
if result.get("stderr"):
97+
content.append(TextContent(type="text", text=result["stderr"]))
9198

92-
if result.get("stdout"):
93-
content.append(TextContent(type="text", text=result["stdout"]))
94-
if result.get("stderr"):
95-
content.append(TextContent(type="text", text=result["stderr"]))
99+
except asyncio.TimeoutError as e:
100+
raise ValueError(f"Command timed out after {timeout} seconds") from e
96101

97102
return content
98103

src/mcp_shell_server/shell_executor.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -501,9 +501,24 @@ async def execute(
501501
try:
502502
# Send input if provided
503503
stdin_bytes = stdin.encode() if stdin else None
504-
stdout, stderr = await asyncio.wait_for(
505-
process.communicate(input=stdin_bytes), timeout=timeout
506-
)
504+
505+
async def communicate_with_timeout():
506+
try:
507+
return await process.communicate(input=stdin_bytes)
508+
except Exception as e:
509+
try:
510+
process.kill()
511+
await process.wait()
512+
except Exception:
513+
pass
514+
raise e
515+
516+
if timeout:
517+
stdout, stderr = await asyncio.wait_for(
518+
communicate_with_timeout(), timeout=timeout
519+
)
520+
else:
521+
stdout, stderr = await communicate_with_timeout()
507522

508523
# Close file handle if using file redirection
509524
if isinstance(stdout_handle, IO):

tests/test_shell_executor.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ async def test_execute_with_file_as_directory(executor, temp_test_dir, monkeypat
142142
assert result["status"] == 1
143143

144144

145+
@pytest.mark.asyncio
145146
async def test_execute_with_nested_directory(executor, temp_test_dir, monkeypatch):
146147
"""Test command execution in a nested directory"""
147148
monkeypatch.setenv("ALLOW_COMMANDS", "pwd,mkdir,ls")

0 commit comments

Comments
 (0)