This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
RMCP (R Model Context Protocol) is a statistical analysis server that bridges AI assistants with R statistical computing capabilities through the Model Context Protocol.
For Python-only development (cross-platform):
uv sync --group dev # Install minimal Python dependencies
uv run rmcp start # Start server in stdio mode (no R tools)
uv run rmcp serve-http # Start Streamable HTTP server
# Use configuration options
uv run rmcp --debug start # Enable debug mode
uv run rmcp --config config.json start # Use custom config file
RMCP_LOG_LEVEL=DEBUG uv run rmcp start # Use environment variables
# Shell completion (click provides it; zsh/bash/fish all verified)
eval "$(_RMCP_COMPLETE=zsh_source rmcp)" # bash needs 4.4+, macOS ships 3.2Logs go to stderr as one JSON object per line — stdout carries the JSON-RPC
stream in stdio mode. Every record, whether from structlog or plain logging,
gets the same envelope, and anything logged while serving a request carries
request_id, so a tool call and its R execution can be tied together:
{"event": "tool completed", "tool": "linear_model", "duration_ms": 412,
"ok": true, "request_id": "7", "component": "registries.tools", ...}For R integration development (Docker-based):
docker build --target development -t rmcp-dev . # Build R + Python dev environment
docker run -v $(pwd):/workspace -it rmcp-dev bash
# Inside container:
cd /workspace && pip install -e .
rmcp start # Full R integration availableFor production deployment (optimized):
docker build --target production -t rmcp-production . # Multi-stage optimized build
docker run -p 8000:8000 rmcp-production rmcp serve-http # Production HTTP serverPython-only tests (cross-platform via uv):
uv run pytest tests/unit/ # Schema validation, JSON-RPC, transport
uv run ruff check . # Linting and import sorting
uv run ruff format --check . # Code formatting checkComplete integration tests (Docker-based):
# Docker includes R for complete test coverage (all 240+ tests)
docker run -v $(pwd):/workspace rmcp-dev bash -c "cd /workspace && pip install -e . && pytest tests/integration/"
docker run -v $(pwd):/workspace rmcp-dev bash -c "cd /workspace && pip install -e . && pytest tests/scenarios/"
# Full test suite: smoke + unit + integration + scenarios + protocol + config
docker run -v $(pwd):/workspace rmcp-dev bash -c "cd /workspace && pip install -e . && pytest tests/"# Python formatting (cross-platform)
uv run ruff check --fix . # Auto-fix linting issues and sort imports
uv run ruff format . # Format code
uv run pyright
# R formatting (Docker-based)
docker run -v $(pwd):/workspace rmcp-dev R -e "library(styler); style_file(list.files('rmcp/r_assets', pattern='[.]R$', recursive=TRUE, full.names=TRUE))"
# Documentation building (cross-platform)
uv run sphinx-build docs docs/_build # Build documentation
uv run sphinx-build -b html docs docs/_build/html # HTML output
uv run sphinx-autogen docs/**/*.rst # Generate autosummary stubs- Transport Layer (
rmcp/transport/): Official MCP SDK transports (stdio + Streamable HTTP) viarmcp/transport/sdk.py; protocol bridge inrmcp/core/sdk_adapter.py - Core Server (
rmcp/core/): MCPServer, Context management, JSON-RPC protocol - Registries (
rmcp/registries/): Dynamic registration for tools, resources, prompts - Tools (
rmcp/tools/): 54 tools across 11 categories - Package Allowlist: 429 R packages from CRAN task views
- R Integration (
rmcp/r_integration.py): Python-R bridge via subprocess + JSON
- Create tool function in appropriate file under
rmcp/tools/ - Use
@tooldecorator with input/output JSON schemas - Implement corresponding R script in
r_assets/scripts/ - Write schema validation tests in
tests/unit/tools/(Python-only) - Write functional tests in
tests/integration/tools/(real R execution) - Add scenario tests in
tests/scenarios/for user workflows
- Registry Pattern: All tools/resources/prompts use centralized registries
- Context Pattern: Request-scoped state management with lifecycle
- Transport Abstraction: Common interface for stdio/HTTP transports
- Virtual Filesystem: Security sandbox for file operations (
rmcp/security/vfs.py) - Universal Operation Approval: User consent system for file operations and package installation
Tier 1: Basic Functionality (Python-only, cross-platform)
- Smoke tests (
tests/smoke/): Basic server startup, CLI, imports (no R required, very fast) - Unit tests (
tests/unit/): Pure Python logic organized by component:tests/unit/core/: Server, context, schemastests/unit/tools/: Tool schema validationtests/unit/transport/: HTTP transport logic
Tier 2: Integration Testing (R required, Docker-based)
- Protocol tests (
tests/integration/protocol/): MCP protocol validation with mocked R responses - Tool integration (
tests/integration/tools/): Real R execution for statistical tool functionality - Transport integration (
tests/integration/transport/): Streamable HTTP transport and HTTPS/auth behavior - Core integration (
tests/integration/core/): Server registries, capabilities, error handling
Tier 3: Complete User Scenarios (End-to-end)
- Scenario tests (
tests/scenarios/): Full user workflows and deployment scenarios:test_realistic_scenarios.py: Statistical analysis pipelinestest_claude_desktop_scenarios.py: Claude Desktop integration flows (includes concurrent load testing)test_excel_plotting_scenarios.py: File workflow scenariostest_deployment_scenarios.py: Docker environment validation, production builds, multi-platform testing
Development Utilities
scripts/testing/run_comprehensive_tests.py: Comprehensive test runner for development (exercises every tool with real R)
Complete Test Coverage: Docker environment includes all 240+ tests with comprehensive coverage across all components:
- ✅ R Integration: statistical tools exercised against real R
- ✅ HTTP Transport: MCP SDK Streamable HTTP, uvicorn, bearer auth, session management
- ✅ Core MCP Protocol: JSON-RPC 2.0, tool calls, capabilities, error handling
- ✅ Configuration System: Environment variables, config files, hierarchical loading
- ✅ Production Deployment: Multi-stage Docker builds, security validation, size optimization
- ✅ Scalability: Concurrent request handling, load testing, performance validation
- ✅ Cross-platform: Multi-architecture support, numerical consistency, platform compatibility
- ⏭️ Dependency-gated skips: Docker and Claude Desktop scenarios skip when those aren't present
Strategy: Tests progress from basic functionality → protocol compliance → component integration → complete scenarios. Each tier builds on the previous, ensuring fast feedback for basic issues while providing comprehensive validation for complex workflows.
Critical: Always test in CI-equivalent environments before committing to prevent CI failures.
Before every commit that touches Docker, CI workflows, or dependencies:
# 1. Build and test in actual CI containers (prevents environment drift)
docker build --target development -t rmcp-test:latest .
docker run --rm -v $(pwd):/workspace rmcp-test:latest bash -c "cd /workspace && pip install -e . && pytest tests/smoke/ -v"
docker run --rm -v $(pwd):/workspace rmcp-test:latest bash -c "cd /workspace && pip install -e . && pytest tests/unit/ -v"
# 2. Test dependency compatibility (prevents pytest-asyncio type issues)
docker run --rm rmcp-test:latest python -c "import pytest, pytest_asyncio; print('✅ Dependencies compatible')"
docker run --rm rmcp-test:latest pytest --version
# 3. Verify multi-platform builds work (prevents platform-specific errors)
docker buildx build --platform linux/amd64,linux/arm64 -f Dockerfile.base --cache-to=type=inline .
# 4. Test both fresh and cached build scenarios (prevents workflow logic errors)
# Fresh build scenario:
docker buildx build --no-cache --target development -t rmcp-fresh:latest .
# Cached build scenario:
docker buildx build --target development -t rmcp-cached:latest .Test GitHub Actions workflows locally before push:
# Install act (GitHub Actions local runner)
# macOS: brew install act
# Linux: curl -s https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
# Test CI workflow locally (requires Docker)
act push -W .github/workflows/ci.yml
# Test specific CI job
act -j python-checks
act -j docker-build
# Test with different scenarios
act push --var GITHUB_REF=refs/heads/main # Test main branch behavior
act pull_request --var GITHUB_REF=refs/pull/1/merge # Test PR behaviorPrevent local vs CI environment drift:
# 1. Verify Python package versions match between uv and Docker
uv export --no-hashes > requirements-uv.txt
docker run --rm rmcp-test:latest pip freeze > requirements-docker.txt
diff requirements-uv.txt requirements-docker.txt
# 2. Test pytest configuration consistency
uv run pytest --collect-only tests/smoke/ > pytest-uv.log
docker run --rm -v $(pwd):/workspace rmcp-test:latest pytest --collect-only /workspace/tests/smoke/ > pytest-docker.log
diff pytest-uv.log pytest-docker.log
# 3. Validate R environment consistency
docker run --rm rmcp-test:latest R -e "cat('R packages:', length(.packages(all.available=TRUE)), '\n')"Ensure Docker optimizations don't break with changes:
# 1. Time build performance (should remain sub-second for cached builds)
time docker build --target development -t rmcp-perf:latest .
# 2. Verify cache effectiveness
docker buildx build --target development --cache-from=type=gha --cache-to=type=gha,mode=max -t rmcp-cache-test:latest .
# 3. Test production build size (should remain optimized)
docker images --filter "reference=rmcp*" --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}"Test GitHub Actions conditional logic before commit:
# 1. Test workflow with no Docker-relevant changes (should skip builds)
git checkout -b test-skip-build
echo "# Test comment" >> README.md
git add . && git commit -m "Test: non-docker change"
# Push and verify CI skips Docker builds
# 2. Test workflow with Docker changes (should trigger builds)
git checkout -b test-trigger-build
echo "# Test comment" >> Dockerfile
git add . && git commit -m "Test: docker change"
# Push and verify CI runs Docker builds
# 3. Test attestation logic with both scenarios
# Verify attestation steps only run when should_build=truePatterns that prevent typical CI failures:
-
Always test with exact CI dependency versions:
# Don't just test with latest packages docker run --rm rmcp-test:latest pip list | grep pytest
-
Validate pytest configuration changes:
# Test asyncio_mode setting works docker run --rm -v $(pwd):/workspace rmcp-test:latest pytest /workspace/tests/ --collect-only
-
Test GitHub Actions conditional steps:
# Use act to test both cached and fresh build paths act -j docker-build --var should_build=true act -j docker-build --var should_build=false -
Verify multi-platform compatibility:
# Test both platforms build successfully docker buildx build --platform linux/amd64 --load -t test-amd64 . docker buildx build --platform linux/arm64 --load -t test-arm64 .
When to use this protocol:
- Always: Before committing changes to
Dockerfile*,.github/workflows/,pyproject.toml - Docker optimizations: When modifying cache strategies, base images, or build stages
- Dependency updates: When upgrading pytest, Python packages, or R packages
- CI logic changes: When modifying conditional steps, build matrices, or attestation workflows
- Pre-release: Before tagging releases or major feature merges
Quick validation for minor changes:
# Minimal check for small Python changes
docker run --rm -v $(pwd):/workspace rmcp-test:latest bash -c "cd /workspace && pip install -e . && python -c 'import rmcp; print(\"✅ Import OK\")'"This protocol ensures CI failures are caught locally, maintaining the 99%+ build optimization improvements while preventing regression issues.
Why Hybrid?
- Docker: Ensures consistent R environment for integration testing (complex R package dependencies)
- uv: Enables fast cross-platform Python testing on Mac/Windows/Linux (important for CLI tools)
- Reproducible:
uv.lockis committed for deterministic dependency resolution - Flexible: Developers can choose lightweight uv setup or full Docker environment
When to use what:
- Local development: uv for Python development, schema changes, CLI testing
- R tool development: Docker for testing actual R integration and statistical computations
- CI/CD: Docker for R tests, uv for cross-platform Python validation
RMCP includes a comprehensive configuration management system that supports:
- Command-line arguments (highest priority)
- Environment variables (
RMCP_*prefix) - User config file (
~/.rmcp/config.json) - System config file (
/etc/rmcp/config.json) - Built-in defaults (lowest priority)
# Environment variables for development
export RMCP_LOG_LEVEL=DEBUG
export RMCP_HTTP_PORT=9000
export RMCP_R_TIMEOUT=300
# Configuration file for development
echo '{"debug": true, "logging": {"level": "DEBUG"}}' > ~/.rmcp/config.json
# CLI options override everything
uv run rmcp --debug --config custom.json start# Environment variables in Docker
docker run -e RMCP_HTTP_HOST=0.0.0.0 -e RMCP_HTTP_PORT=8000 rmcp:latest
# Mount configuration file
docker run -v $(pwd)/config.json:/etc/rmcp/config.json rmcp:latest- Unit tests: Configuration module has 40+ tests covering all scenarios
- Integration tests: Configuration integration with HTTP/R/VFS components
- Environment tests: Validation of all environment variable mappings
📖 Complete documentation: Auto-generated from code in docs/configuration/ (build with uv run sphinx-build docs docs/_build)
RMCP v0.5.1 introduces a comprehensive user consent system for security-sensitive operations that require explicit approval.
The system categorizes operations by security level and impact:
OPERATION_CATEGORIES = {
"file_operations": {
"patterns": [r"ggsave\s*\(", r"write\.csv\s*\(", r"write\.table\s*\(", r"writeLines\s*\("],
"description": "File writing and saving operations",
"security_level": "medium",
},
"package_installation": {
"patterns": [r"install\.packages"],
"description": "R package installation from repositories",
"security_level": "medium",
},
"system_operations": {
"patterns": [r"system\s*\(", r"shell\s*\(", r"Sys\.setenv"],
"description": "System-level operations and environment changes",
"security_level": "high",
},
}- Operation Detection: When R code contains approval-required patterns, execution pauses
- User Notification: Clear description of operation and security implications shown
- Approval Decision: User accepts/denies with session-wide persistence
- Execution: Approved operations proceed, denied operations are blocked
- Session Memory: Decisions are stored on
LifespanState, so they persist across requests for the life of the server process
- Pattern Matching: Regex-based detection with negative lookbehind (e.g., allows
ggsave()but blockssave()) - VFS Integration: File operations require both approval AND VFS write permissions
- Session Tracking: Approval state maintained per operation category per session
- Graceful Fallback: Operations fail safely with clear error messages when denied
File Operations (Visualization):
# Requires approval for file saving
library(ggplot2)
p <- ggplot(data, aes(x=var1, y=var2)) + geom_point()
ggsave("plot.png", plot=p) # ← Triggers approval requestPackage Installation:
# Requires approval for package installation
install.packages("tidyverse") # ← Triggers approval request
library(tidyverse) # ← Proceeds without approvalSystem Operations:
# Requires approval for system access
system("ls -la") # ← Triggers approval request (high security)
Sys.setenv(PATH="/new/path") # ← Triggers approval request (high security)Adding New Operation Categories:
# In rmcp/tools/flexible_r.py
OPERATION_CATEGORIES["database_operations"] = {
"patterns": [r"dbConnect\s*\(", r"dbWriteTable\s*\("],
"description": "Database connection and write operations",
"security_level": "high",
}Testing Approval Workflows:
# Unit tests in tests/unit/tools/test_operation_approval.py
def test_approval_required():
context = Context.create("test", "test", LifespanState())
# validate_r_code is synchronous and returns (is_safe, error)
is_safe, error = validate_r_code('ggsave("test.png")', context)
assert not is_safe
assert error == "OPERATION_APPROVAL_NEEDED:file_operations:ggsave"Approvals are recorded on LifespanState (rmcp/core/context.py), not on the
per-request Context — a fresh Context is built for every MCP request, so
anything stored there would be forgotten before the next call:
context.lifespan.approved_operations # {category: {operation: metadata}}
context.lifespan.approved_packages # set[str]They persist for the life of the server process. There is no environment
variable to disable the approval system; the tools approve_operation and
approve_r_package are the only way to grant consent.
This system balances security with usability, ensuring sensitive operations require explicit user consent while maintaining smooth workflows for approved operations.
RMCP v0.5.1 introduces a systematic, evidence-based R package whitelist with 429 packages from CRAN task views, replacing the previous narrow 117-package list.
Evidence-Based Approach: Packages selected from official CRAN task views:
- Machine Learning & Statistical Learning (61 packages): caret, mlr3, randomForest, xgboost, etc.
- Econometrics (55 packages): AER, plm, vars, forecast, quantreg, causal inference tools
- Time Series Analysis (57 packages): zoo, xts, forecast, fable, prophet, ARIMA models
- Bayesian Inference (40 packages): rstan, brms, MCMCpack, coda, BayesFactor
- Survival Analysis (36 packages): survival, flexsurv, randomForestSRC, competing risks
- Tidyverse Ecosystem (32 packages): dplyr, ggplot2, tidyr, complete data science workflow
Additional Categories: Spatial analysis, optimization, meta-analysis, clinical trials, robust statistics, missing data, NLP, experimental design, network analysis.
Package access is a single flat allowlist: a package is either in ALLOWED_R_PACKAGES
or it is refused. A refused package can be permitted for the session with
approve_r_package.
Enforcement is regex over the R source text, so it catches honest mistakes but not
a determined caller — do.call("library", list(pkg)) and eval(parse(text=...))
both evade it. Treat the allowlist as a guardrail, not a sandbox boundary; the real
isolation is the process and filesystem the server runs under.
List Available Packages:
# Get comprehensive package summary
list_allowed_r_packages(category = "summary")
# Explore specific categories
list_allowed_r_packages(category = "machine_learning")
list_allowed_r_packages(category = "econometrics")
list_allowed_r_packages(category = "time_series")Package Categories Available:
base_r,core_infrastructure,tidyversemachine_learning,econometrics,time_series,bayesiansurvival,spatial,optimization,meta_analysisclinical_trials,robust_stats,missing_datanlp_text,data_io,experimental_design,network_analysis
- 6x Expansion: From 117 to 429 packages (comprehensive coverage)
- Evidence-Based: CRAN task views + download statistics + security assessment
- Systematic: Organized by statistical domain, not arbitrary selection
- Maintainable: Updates track CRAN task view evolution
- Secure: Risk-based tiering with approval workflows
- User-Friendly: Reduces approval friction for legitimate statistical work
Legacy categories maintained: stats, ml, visualization, data map to new comprehensive categories.
RMCP uses Sphinx with autodoc for automatic documentation generation from code docstrings, eliminating manual documentation maintenance.
Documentation is automatically generated from:
- Configuration models: Complete API documentation with examples
- Module docstrings: Comprehensive module-level documentation
- Function/class docstrings: Detailed parameter and return documentation
- Type hints: Automatic type documentation from annotations
# Build HTML documentation
uv run sphinx-build -b html docs docs/_build/html
# Build with clean rebuild
uv run sphinx-build -E -a -b html docs docs/_build/html
# Generate autosummary stubs
uv run sphinx-autogen docs/**/*.rst
# Serve documentation locally
cd docs/_build/html && python -m http.server 8080- User Guide: Installation, quick start, configuration
- API Reference: Auto-generated from docstrings
- Configuration: Comprehensive configuration documentation
- Examples: Real-world usage scenarios
- Single Source of Truth: Documentation lives in code
- Always Current: Auto-updates with code changes
- Type Safety: Shows actual type hints and defaults
- Cross-References: Automatic linking between components
- Search Integration: Full-text search across documentation
📖 Documentation URL: docs/_build/html/index.html (after building)
The git tag is the version. uv-dynamic-versioning derives it from the
latest v* tag, so there is nothing to bump — pyproject.toml carries no
version number and editing one in is a mistake.
# after the change is merged to main and CI is green
git tag -a vX.Y.Z -m "..." && git push origin vX.Y.ZThe tag push runs .github/workflows/python-publish.yml: build →
twine check → tag/version match guard → PyPI (trusted publishing, PEP 740
attestations) → GitHub Release with generated notes.
There is no manual trigger, deliberately. A workflow_dispatch publishes
whatever is on the runner with nothing gating it, which is how every release
before 0.10.0 shipped. Tags come from main, and main is CI-gated — that is
the gate.
The pypi environment restricts deployments to v* tags and nothing else,
so widening the workflow's trigger later would not quietly re-open manual
publishing. There is no required reviewer and no wait timer: a release is still
a single git push origin vX.Y.Z.
Know what that does not cover. The environment checks the ref, not whether CI
passed, so a v* tag on a commit that never went through CI would still
publish — tag from main. And it cannot protect against a dependency breaking
underneath a good release; that is what happened to 0.10.0.
The filename python-publish.yml is load-bearing — the PyPI trusted publisher
is keyed to it. Renaming it breaks publishing until the publisher config is
updated on pypi.org. See py-canon STANDARD.md "Legacy publishers".
- Python 3.11+ required
- R environment provided via Docker (no local R installation needed for development)
- All R communication uses JSON via subprocess
- HTTP transport is MCP Streamable HTTP (SDK-managed sessions); bearer auth via RMCP_API_KEY
- VFS security restricts file access to configured paths only
- Configuration system supports all deployment scenarios (local, Docker, production)
uv.lockis committed; runuv syncto reproduce the environment