Development¶
This page is for contributors working on local-shell-mcp itself. It focuses on how to run, debug, test, and regenerate docs without relying on stale code walkthroughs.
Local environment¶
git clone https://github.com/rijuyuezhu/local-shell-mcp.git
cd local-shell-mcp
uv sync --group dev
uv run pre-commit install
Run the server during development¶
Run MCP-over-HTTP locally without OAuth:
Run the REST debug API locally without OAuth:
Use an explicit workspace when needed:
LOCAL_SHELL_MCP_WORKSPACE_ROOT=/path/to/project \
LOCAL_SHELL_MCP_AUTH_MODE=none \
uv run local-shell-mcp server --mode http --port 13444
Use full-control mode only for disposable test workspaces:
LOCAL_SHELL_MCP_AUTH_MODE=none \
uv run local-shell-mcp server --mode http --port 13444 --allow-full-control true
Smoke-test with curl¶
Health check:
Inspect environment through the REST debug API:
curl -s -X POST http://127.0.0.1:13444/tools/session_start \
-H 'content-type: application/json' \
-d '{"workdir":"."}' | jq
Read a file through the REST debug API:
curl -s -X POST http://127.0.0.1:13444/tools/read \
-H 'content-type: application/json' \
-d '{"path":"README.md:1-40"}' | jq
List files:
curl -s -X POST http://127.0.0.1:13444/tools/list_files \
-H 'content-type: application/json' \
-d '{"path":".","max_entries":20}' | jq
Export the MCP tool surface:
uv run python scripts/generation/export-tools-json.py --wrapped > /tmp/local-shell-mcp-tools.json
jq '.count, [.tools[].name]' /tmp/local-shell-mcp-tools.json
Watch logs and audit output¶
For a foreground dev process, read the terminal output first.
For a user systemd service:
Audit log:
Audit state can contain prompts, tool inputs, tool outputs, file contents, bounded JSONL previews, and recoverable sanitized payload objects. Credential-like values are redacted on a best-effort basis before storage, but both JSONL and the payload directory must still be treated as sensitive.
Documentation ownership¶
Keep documentation at the level owned by each section:
- Home, Getting started, and Guides are for users. Explain the task, prerequisites, commands, expected result, limitations, and recovery steps.
- Reference is generated or contract-oriented. Put exact CLI options, settings, tool arguments, defaults, and server instructions there instead of copying them into several guides.
- Architecture, Development, Security, and Maintenance are for contributors and operators who need design rationale, module ownership, release constraints, or migration history.
Do not turn a user guide into a code walkthrough. Protocol frames, lock ordering, byte budgets, state-machine transitions, generated-artifact details, and private storage formats belong in source docstrings, tests, architecture notes, or focused maintenance records. Link to those sources when the distinction matters to a contributor.
When behavior changes:
- update the public contract or source docstring closest to the implementation;
- update or add tests that demonstrate the behavior;
- regenerate Reference pages when their source changes;
- change a user guide only when the user's workflow, requirement, limitation, or recovery action changed.
Useful implementation entry points are:
- Human UI HTTP/WebSocket adapters:
src/local_shell_mcp/ui/http/ - Browser assets:
src/local_shell_mcp/ui/static/ - OpenTUI client:
ui-opentui/ - Terminal asset build:
ui-terminal/ - Remote controller and worker domains:
src/local_shell_mcp/remote/andsrc/local_shell_mcp/remote_worker/ - Agent Bridge:
src/local_shell_mcp/agent_bridge/ - Audit storage and queries:
src/local_shell_mcp/audit/(principallycore.pyandpayloads.py) and the corresponding tool/UI adapters
Use nearby tests as the canonical executable description. For native UI packaging and release artifacts, also see Native artifact provenance.
Architecture boundaries¶
The package is layered around transport-neutral tool behavior. Keep dependency direction explicit when adding features or moving code:
config,schemas, and smallutilsmodules provide dependency-leaf settings, contracts, serialization, and filesystem primitives.agent_bridge,remote,remote_worker, andtool_sessionown their domain state and protocols. Source-only worker modules must remain usable without the controller's full dependency stack.opsimplements transport-neutral use cases. It may use domain services and schemas, but it must not depend on HTTP, MCP, or Human UI adapters.toolsowns public tool contracts, discovery, metadata, and registration.httpowns executor-neutral Starlette/ASGI infrastructure shared by REST and MCP-over-HTTP. It must not import an executor or Human UI implementation.executors/mcpowns MCP composition, MCP-specific middleware, and stdio or MCP-over-HTTP runtime selection.executors/httpowns REST tool routes, REST error and timeout policy, and the runnable FastAPI application.ui/httpowns Human UI HTTP and WebSocket adapters. The REST executor may import only its explicit route-composition contract; lower layers must not import executors or UI delivery adapters. The obsoleteserverpackage has been removed and must not be restored.mainis the process-composition entry point and is allowed to select executors.
The canonical OpenTUI executable names live in ui/contracts.py. After changing
that contract, run uv run python scripts/generation/generate-tui-executable-contract.py to
refresh the Bun-side mirror; pre-commit rejects stale generated output.
Human UI route handlers should validate transport input and delegate reusable
behavior to ops or domain services. MCP-facing annotations and OAuth security
metadata are tool presentation contracts, not transport implementation details.
Avoid generic service locators: extract small dependency-leaf contracts and
explicit facades instead. See Architecture and module ownership
for the file-by-file ownership map and rejected placement alternatives.
tests/test_architecture.py enforces current dependency cycles, explicit
executor/UI composition imports, and dependency-leaf package boundaries. Its
allowlists are visible technical debt, not extension points: when a cycle or reversed import is
removed, shrink the allowlist in the same change. New entries require an explicit
architecture review.
Run checks before committing¶
For focused work, run the relevant subset first:
uv run pytest tests/test_tool_surface.py -q
uv run pytest tests/test_config_surface.py -q
uv run pytest tests/test_agent_bridge_tools.py -q
Check branch coverage¶
The Ubuntu CI job runs the complete Python suite under branch coverage. The
risk-weighted policy is serialized in scripts/validation/coverage-baseline.json and
validated by the checker:
- aggregate package branch coverage may not fall below the committed baseline;
- credential, private-file, audit, OAuth, remote-worker lifecycle, terminal, destructive file/process, and transfer modules retain a near-zero per-file drift allowance of 0.05 percentage points;
- ordinary existing modules may move by at most one percentage point, avoiding low-value tests for harmless branch-denominator changes;
- new core modules must start at 80%, while new executor, HTTP, registry, schema, and other thin adapter modules must start at 60%; and
- zero-statement package markers do not create a coverage obligation.
When moving a file, move its baseline key in the same commit so an existing module is not misclassified as new. The committed baseline records the lowest value observed for each module across local Linux and GitHub Ubuntu reports, so environment-only branches cannot make either supported run flaky. Run the same gate locally with:
find . -maxdepth 1 -name '.coverage*' -delete
uv run python -m coverage run -m pytest -q
uv run python -m coverage combine
uv run python -m coverage json
uv run python -m coverage xml
uv run python -m coverage report
uv run python scripts/validation/check-coverage.py
Only update the baseline after reviewing why coverage changed. Download the
python-coverage artifact from the Ubuntu job for the same source revision,
then merge it with the local report. The checker rejects different source sets
or statement/branch counts, and stores the per-module and aggregate minima:
gh run download <run-id> -n python-coverage -D /tmp/python-coverage
uv run python scripts/validation/check-coverage.py \
--write-baseline \
--report coverage.json \
--report /tmp/python-coverage/coverage.json
Coverage work should target behavior, trust boundaries, failure modes, or a meaningful migration regression. Do not add tests solely to recover a small percentage caused by relocation or denominator movement inside the documented allowance. Regressions beyond the allowance should gain useful tests or be explicitly justified by changing the policy and baseline together in review.
Regenerate generated reference data¶
Configuration examples and reference JSON are generated from the settings registry:
uv run python scripts/generation/generate-config-examples.py
uv run python scripts/generation/generate-config-examples.py --check
Tool and instruction reference JSON are generated from the live MCP app:
uv run python scripts/generation/export-tools-json.py \
--wrapped \
--output docs/reference/generated/tools.json \
--instructions-output docs/reference/generated/server-instructions.json
uv run python scripts/generation/export-tools-json.py \
--wrapped \
--output docs/reference/generated/tools.json \
--instructions-output docs/reference/generated/server-instructions.json \
--check
The pre-commit hooks run these generators when related source files change.
Documentation development¶
The documentation site uses Material for MkDocs.
Release checks¶
Before cutting a release:
uv run pre-commit run --all-files
uv run pyright
uv run pytest -q
uv run mkdocs build --strict
uv run python scripts/validation/check-release-matrix.py
uv build --out-dir dist
The ordinary uv build output must contain exactly one payload-free universal
py3-none-any wheel and one source-only sdist. Native OpenTUI wheels are built
only on matching runners with the pinned Bun release, for example:
uv run python scripts/release/build-platform-wheel.py \
--platform-tag linux_x86_64 \
--output-dir dist
The builder accepts only the repository's explicit target tags, validates the
native host and executable magic, creates deterministic gzip metadata, rewrites
and verifies WHEEL plus RECORD, and removes staging files on success or
failure. Release CI installs every platform wheel in an isolated environment
with Bun and sidecars absent, then runs scripts/release/smoke-platform-wheel.py before
all Python package artifacts are published together. Native source, license,
platform, rebuild, and checksum ownership is recorded in
Native artifact provenance. Run
uv run python scripts/validation/check-native-provenance.py whenever those inputs change.
Also test the Docker image or binary artifact and at least one real MCP connection path before publishing.