smriti/cli/tests/conftest.py
Himanshu Dongre 2258231c30 Add MCP server wrapping all CLI commands as 12 tools
Round 4 validated that the Smriti CLI surface is complete. This is the
next transport: an MCP stdio server that exposes the same operations as
tools inside MCP-aware hosts (Claude Code, Cursor, Windsurf) so agents
can read and write reasoning state natively in their session instead of
shelling out to the `smriti` binary.

The server lives inside the existing CLI package as a sibling to
client.py and main.py. One `pip install -e ./cli` installs both the
`smriti` and `smriti-mcp` console scripts. Architecture is a thin shim:
each tool builds a SmritiClient, calls 1-2 client methods, pipes the
result through an existing formatter, and returns a string. FastMCP
auto-wraps the string into TextContent. Errors raise SmritiToolError
(wrapping SmritiError with HTTP status + structured detail); FastMCP
converts raised exceptions into MCP error responses.

Zero reimplementation of API logic, zero duplicated formatting, zero
changes to client.py, formatters.py, main.py, or the backend.

Twelve tools, 1:1 with the CLI verbs:

  smriti_list_spaces        smriti_state
  smriti_create_space       smriti_list_checkpoints
  smriti_delete_space       smriti_show_checkpoint
  smriti_create_checkpoint  smriti_review_checkpoint
  smriti_delete_checkpoint  smriti_restore
  smriti_fork               smriti_compare

Two deliberate differences from the CLI:

  - No `-y` confirmation flag on destructive tools. The MCP host's
    tool-approval UI is the gate.
  - smriti_create_checkpoint uses the extract path only (no
    --from-json mode) and does NOT auto-capture cwd as project_root.
    MCP servers run in the host's arbitrary working directory, so
    cwd would plant garbage paths. Callers pass project_root
    explicitly when they want it populated.

Testing: 33 unit tests across all 12 tools using a
MagicMock(spec=SmritiClient) fixture in tests/conftest.py. Each tool
has at least one happy path and one error path; the complex ones
(smriti_state, smriti_create_checkpoint, smriti_delete_checkpoint)
have extra tests for their branches (no-checkpoints short-circuit,
dry-run, existing-session, 409-with-dependents formatting,
409-with-non-dict-fallback, empty-content pre-check).

End-to-end stdio protocol smoke verified independently: the
`smriti-mcp` binary responds to `initialize` with protocol version
2025-03-26 and returns all 12 tools on `tools/list`. Ready for
`mcp dev smriti_cli.mcp_server:mcp` Inspector UI exploration or
direct Claude Code connection.

cli/README.md gets a new MCP server section with installation,
example Claude Code config, tool list, and notes on the project_root
and confirmation-gate differences from the CLI.
2026-04-12 00:54:40 +05:30

28 lines
933 B
Python

"""Shared pytest fixtures for the smriti-cli test suite.
The mock_client fixture lives here (rather than inside test_mcp_server.py)
so future test files (test_client.py, test_formatters.py, etc.) can reuse
it without cross-importing.
"""
from __future__ import annotations
from unittest.mock import MagicMock
import pytest
from smriti_cli import mcp_server
from smriti_cli.client import SmritiClient
@pytest.fixture
def mock_client(monkeypatch) -> MagicMock:
"""Patch mcp_server._client() to return a MagicMock(spec=SmritiClient).
Returns the mock directly so tests can configure return values and
assert on method_calls. The spec=SmritiClient argument gives us
attribute-access validation for free: typing `list_spacse` in a test
crashes loudly instead of silently passing.
"""
client = MagicMock(spec=SmritiClient)
monkeypatch.setattr(mcp_server, "_client", lambda: client)
return client