-
Notifications
You must be signed in to change notification settings - Fork 0
Devlog
Local-only development log. Intentionally not tracked by git (configured via
.git/info/exclude, never via.gitignore).
Conventions:
- One
## [YYYY-MM-DD] Titleblock per logical change. - Fields: What, Why, How, Learned.
- Append-only 鈥?never rewrite history.
What: Wired the newly added execution/timeout logs into the project's
existing logging_config setup and normalized log levels.
Why: handlers/execute.py emitted logs via ctx.logger, which was bound to
logging.getLogger(__name__) in server.py and did not share the custom
formatter/level used by the rest of the codebase. This caused inconsistent log
output. Heartbeat logs were also too verbose at INFO.
How:
-
ssh_mcp/server.py: changedlogger = logging.getLogger(__name__)tologger = get_logger(__name__)soctx.loggeruses the same configured logger assession_manager.pyand other modules. -
ssh_mcp/session_manager.py: downgraded[cmd-heartbeat]fromINFOtoDEBUG. -
ssh_mcp/handlers/execute.py: downgraded[wait-task]heartbeat fromINFOtoDEBUG. - Start/done/timeout/error logs remain at
INFO/ERROR.
Files:
ssh_mcp/server.pyssh_mcp/session_manager.pyssh_mcp/handlers/execute.py
Result: python -m pytest tests/ -q reports 424 passed with zero
warnings.
What: Added structured timeout/heartbeat logs on the server side to help
diagnose long-running or hanging ssh_execute calls.
Why: Trae IDE's MCP client produced an opaque REQUEST_TIMEOUT JSON-RPC
parse error when a tool call timed out. Server-side logs make it possible to
distinguish between: (a) the SSH command itself hanging, (b) the background
wrapper failing to return, and (c) the MCP client timing out before the server
could respond.
How:
-
ssh_mcp/session_manager.py(SSHSession.execute_command): emits[cmd-start],[cmd-done],[cmd-timeout],[cmd-error]logs and spawns a 10-second[cmd-heartbeat]task while the blocking SSH call runs in the thread pool. -
ssh_mcp/handlers/execute.py: emits[execute]logs for normal/background mode selection,[execute-background]logs around nohup wrapper launch and status parsing, and[wait-task]heartbeat logs when waiting for a background task to complete.
Files:
ssh_mcp/session_manager.pyssh_mcp/handlers/execute.py
Result: python -m pytest tests/ -q reports 424 passed with zero
warnings.
What: Bumped patch version from 2.5.2 to 2.5.3 after fixing all test warnings.
Synced version files and documentation. Pushed commit chore(v2.5.3): bump version
and tag v2.5.3 to GitHub.
Result: Local package reinstalled at ssh-licco==2.5.3; 424 tests passed with
zero warnings.
What: Patched Watchdog._monitor_loop to a plain MagicMock in the
start/stop tests so the async coroutine is not created and left unawaited.
This removes the remaining RuntimeWarning from tests/test_watchdog.py.
Files:
tests/test_watchdog.py
Result: python -m pytest -q now reports 424 passed with zero warnings.
What: Added asyncio_default_fixture_loop_scope = "function" to
[tool.pytest.ini_options] in pyproject.toml. This removes the pytest-asyncio
warning about unset default fixture loop scope.
Files:
pyproject.toml
Result: python -m pytest -q now reports 424 passed without the
pytest-asyncio warning. A separate RuntimeWarning from test_watchdog.py
remains unrelated.
What: Bumped patch version from 2.5.1 to 2.5.2 after the rmdir prompt wording fix.
Synced version files (__init__.py, pyproject.toml, VERSION) and documentation.
Pushed commit chore(v2.5.2): bump version and tag v2.5.2 to GitHub.
Result: Local package reinstalled at ssh-licco==2.5.2; 424 tests passed.
What: Updated format_backup_prompt in ssh_mcp/security.py to show
"检测到目录删除操作" for rmdir commands while keeping
"检测到递归删除操作" for recursive rm commands. Added a unit test to guard
this distinction.
Files:
ssh_mcp/security.pytests/test_security.py
Result: 424 tests passed; local package reinstalled at v2.5.1.
What: Fixed two failing tests introduced by the recursive-deletion backup
confirmation feature, and adjusted handle_execute to detect background mode from
the original command before prepending the backup prefix.
Why: Running the test suite revealed:
-
test_generate_backup_command_quotes_targetsexpected every target to be single-quoted, butshlex.quote()only adds quotes when necessary. The generated command was actually correct and safe. -
test_deletion_backup_prepended_when_enabledfailed because the prepended backup command (mkdir -p /tmp/ssh_mcp_backup_...) contains-p, which matchedshould_run_background()'s listen pattern and forced the whole command into background mode. The test mock was waiting for a normalsession.execute_command()call that never came.
How:
- Moved background auto-detection before the deletion-backup wrapping in
ssh_mcp/handlers/execute.py, so the decision is based on the user's original command rather than the backup prefix. - Updated
tests/test_security.pyandtests/test_server.pyassertions to matchshlex.quote()output (./datastays unquoted,./my logsis quoted). - Added
test_deletion_backup_prepended_when_user_inputs_true_stringintests/test_server.pyto verify that a string"true"response correctly prepends the backup command beforerm.
Learned: Wrapping a user command changes what heuristic classifiers see. Always run behaviour-preserving checks (background detection, timeout, sudo wrapping order) on the original user intent, or the wrapper itself can trigger unintended side-effects.
What: Added command_validator.validate_command() and audit logging to the
ssh_connect tool's optional command parameter execution path.
Why: ssh_connect supports an optional command parameter that executes a
command immediately after connecting, but it completely bypassed the security
framework — no validate_command, no confirm_dangerous, no security level
check, and no audit logging. This was a security gap since the MCP tool
definition exposed this parameter to clients.
How: Added from .security import SecurityError, command_validator inside
the _handle_connect method, wrapping the command execution in a try/except.
On SecurityError, the connection is still established (success returned) but
the command is blocked with a clear message, and the attempt is logged to the
audit trail with return_code=-1.
Learned: Every execution path in an MCP tool must go through security
validation — even "quick convenience" features. ssh_connect's command
parameter was a historical leftover that predated the security framework.
- (in-progress or upcoming work goes here)