Skip to content

Commit 4c40b81

Browse files
Yoshihiro TakaharaYoshihiro Takahara
authored andcommitted
feat: Use interactive shell to execute commands
- Remove login shell option (-l) - Add interactive shell option (-i) - Use zsh for shell execution - Add test for .zshrc loading
1 parent 38793e0 commit 4c40b81

File tree

2 files changed

+38
-3
lines changed

2 files changed

+38
-3
lines changed

src/mcp_shell_server/shell_executor.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,10 @@ def _preprocess_command(self, command: List[str]) -> List[str]:
378378
preprocessed_command.append(token)
379379
return preprocessed_command
380380

381+
def _get_default_shell(self) -> str:
382+
"""Get the default shell from environment variables"""
383+
return os.environ.get("SHELL", "/bin/sh")
384+
381385
async def execute(
382386
self,
383387
command: List[str],
@@ -517,14 +521,17 @@ async def execute(
517521
except IOError as e:
518522
raise ValueError(f"Failed to open output file: {e}") from e
519523

520-
# Execute the command
524+
# Execute the command with interactive shell
525+
shell = self._get_default_shell()
521526
shell_cmd = self._create_shell_command(cmd)
527+
shell_cmd = f"{shell} -i -c {shlex.quote(shell_cmd)}"
528+
522529
process = await asyncio.create_subprocess_shell(
523530
shell_cmd,
524531
stdin=asyncio.subprocess.PIPE if stdin else None,
525532
stdout=stdout_handle,
526533
stderr=asyncio.subprocess.PIPE,
527-
env={"PATH": os.environ.get("PATH", "")},
534+
env=os.environ, # Use all environment variables
528535
cwd=directory,
529536
)
530537

@@ -642,12 +649,17 @@ async def _execute_pipeline(
642649
for i, cmd in enumerate(parsed_commands):
643650
shell_cmd = self._create_shell_command(cmd)
644651

652+
# Get default shell for the first command and set interactive mode
653+
if i == 0:
654+
shell = self._get_default_shell()
655+
shell_cmd = f"{shell} -i -c {shlex.quote(shell_cmd)}"
656+
645657
process = await asyncio.create_subprocess_shell(
646658
shell_cmd,
647659
stdin=asyncio.subprocess.PIPE if prev_stdout is not None else None,
648660
stdout=asyncio.subprocess.PIPE,
649661
stderr=asyncio.subprocess.PIPE,
650-
env={"PATH": os.environ.get("PATH", "")},
662+
env=os.environ, # Use all environment variables
651663
cwd=directory,
652664
)
653665

tests/test_server.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,3 +322,26 @@ def stdio_server_impl():
322322
await main()
323323

324324
assert str(exc.value) == "Test error"
325+
326+
327+
@pytest.mark.asyncio
328+
async def test_shell_startup(monkeypatch, temp_test_dir):
329+
"""Test shell startup and environment"""
330+
monkeypatch.setenv("ALLOW_COMMANDS", "ps")
331+
result = await call_tool(
332+
"shell_execute",
333+
{"command": ["ps", "-p", "$$", "-o", "command="], "directory": temp_test_dir},
334+
)
335+
print("Shell process info:", result) # Debug output
336+
337+
338+
@pytest.mark.asyncio
339+
async def test_environment_variables(monkeypatch, temp_test_dir):
340+
"""Test to check environment variables during test execution"""
341+
monkeypatch.setenv("ALLOW_COMMANDS", "env")
342+
result = await call_tool(
343+
"shell_execute",
344+
{"command": ["env"], "directory": temp_test_dir},
345+
)
346+
assert len(result) == 1
347+
print(result[0].text) # 環境変数の内容を確認

0 commit comments

Comments
 (0)