Skip to content
licco edited this page Aug 2, 2026 · 1 revision

DEVLOG

Local-only development log. Intentionally not tracked by git (configured via .git/info/exclude, never via .gitignore).

Conventions:

  • One ## [YYYY-MM-DD] Title block per logical change.
  • Fields: What, Why, How, Learned.
  • Append-only 鈥?never rewrite history.

[2026-07-20] Integrate execution logs into existing logging system

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: changed logger = logging.getLogger(__name__) to logger = get_logger(__name__) so ctx.logger uses the same configured logger as session_manager.py and other modules.
  • ssh_mcp/session_manager.py: downgraded [cmd-heartbeat] from INFO to DEBUG.
  • ssh_mcp/handlers/execute.py: downgraded [wait-task] heartbeat from INFO to DEBUG.
  • Start/done/timeout/error logs remain at INFO/ERROR.

Files:

  • ssh_mcp/server.py
  • ssh_mcp/session_manager.py
  • ssh_mcp/handlers/execute.py

Result: python -m pytest tests/ -q reports 424 passed with zero warnings.


[2026-07-20] Add execution timeout and heartbeat logging

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.py
  • ssh_mcp/handlers/execute.py

Result: python -m pytest tests/ -q reports 424 passed with zero warnings.


[2026-07-19] Bump version to v2.5.3

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.


[2026-07-19] Fix watchdog test RuntimeWarning

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.


[2026-07-19] Fix pytest-asyncio deprecation warning

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.


[2026-07-19] Bump version to v2.5.2

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.


[2026-07-19] rmdir backup prompt wording

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.py
  • tests/test_security.py

Result: 424 tests passed; local package reinstalled at v2.5.1.


[2026-07-19] Fix backup-before-delete test failures and background false-positive

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:

  1. test_generate_backup_command_quotes_targets expected every target to be single-quoted, but shlex.quote() only adds quotes when necessary. The generated command was actually correct and safe.
  2. test_deletion_backup_prepended_when_enabled failed because the prepended backup command (mkdir -p /tmp/ssh_mcp_backup_...) contains -p , which matched should_run_background()'s listen pattern and forced the whole command into background mode. The test mock was waiting for a normal session.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.py and tests/test_server.py assertions to match shlex.quote() output (./data stays unquoted, ./my logs is quoted).
  • Added test_deletion_backup_prepended_when_user_inputs_true_string in tests/test_server.py to verify that a string "true" response correctly prepends the backup command before rm.

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.


[2026-06-28] Fix ssh_connect command bypassing security validation

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.


Planned

  • (in-progress or upcoming work goes here)

Clone this wiki locally