Contributing to ChatSpatial

May 1, 2026 · View on GitHub

Contributions are welcome — bug reports, new analysis methods, documentation improvements, and feature requests.

Getting Started

# Fork and clone
git clone https://github.com/YOUR_USERNAME/ChatSpatial.git
cd ChatSpatial

# Create environment and install
python3 -m venv venv && source venv/bin/activate
pip install -e ".[dev]"

# Verify
pytest tests/unit/ -x

Prerequisites: Python 3.11-3.13, Git. For R-based methods (RCTD, CellChat, SPARK-X, etc.): R 4.4+ and rpy2.

Project Structure

chatspatial/
├── server.py                 # MCP tool definitions (entry point)
├── spatial_mcp_adapter.py    # ToolContext and data manager
├── config.py                 # Runtime configuration
├── tools/                    # Analysis implementations
│   ├── spatial_genes.py      # SpatialDE, SPARK-X, FlashS
│   ├── spatial_domains.py    # SpaGCN, STAGATE, GraphST, BANKSY, Leiden
│   ├── cell_communication.py # FastCCC, LIANA, CellPhoneDB, CellChat (`cellchat_r`)
│   ├── deconvolution/        # FlashDeconv, Cell2location, RCTD, etc.
│   ├── visualization/        # 11 plot types
│   └── ...
├── models/
│   ├── data.py               # Pydantic parameter models
│   └── analysis.py           # Pydantic result models
└── utils/
    ├── mcp_utils.py           # @mcp_tool_error_handler decorator
    ├── exceptions.py          # Custom exception classes
    ├── adata_utils.py         # AnnData validation helpers
    └── dependency_manager.py  # Optional dependency checking

Adding a New Analysis Method

This is the most common contribution. Follow the existing pattern:

1. Parameter model (models/data.py)

class YourMethodParameters(BaseModel):
    method: Literal["method_a", "method_b"] = Field(
        default="method_a",
        description="Which algorithm to use.",
    )
    n_top_genes: Optional[int] = Field(
        default=None, description="Number of top genes to return."
    )

2. Result model (models/analysis.py)

class YourMethodResult(BaseModel):
    data_id: str
    method: str
    n_genes_analyzed: int
    results_key: Optional[str] = None

3. Tool implementation (tools/your_tool.py)

from ..utils.exceptions import DataError, ProcessingError
from ..utils.dependency_manager import require

async def your_method(
    data_id: str,
    ctx: "ToolContext",
    params: YourMethodParameters,
) -> YourMethodResult:
    """Implement your analysis."""
    require("optional_package")  # Checks at runtime, clear error if missing

    adata = await ctx.get_adata(data_id)
    # ... analysis logic ...
    return YourMethodResult(...)

4. Register in server.py

@mcp.tool()
@mcp_tool_error_handler()
async def your_tool(
    data_id: str,
    params: Optional[YourMethodParameters] = None,
    context: Optional[Context] = None,
) -> YourMethodResult:
    """Brief description for LLM tool selection."""
    ctx = ToolContext(_data_manager=data_manager, _mcp_context=context)
    p = _resolve_params(params, YourMethodParameters)
    return await your_method(data_id, ctx, p)

5. Add tests

# tests/unit/test_your_tool.py
@pytest.mark.asyncio
async def test_your_method_basic(minimal_spatial_adata, monkeypatch):
    # Mock external dependencies, test logic
    ...

Checklist

  • Parameter model with Pydantic validation
  • Result model following existing patterns
  • Implementation using ToolContext (not raw data_store dict)
  • Optional dependencies handled via require()
  • MCP tool registered with @mcp_tool_error_handler()
  • Unit tests with mocked dependencies
  • Docstrings on public functions

Code Style

# Format and lint
black chatspatial/
isort chatspatial/
ruff check chatspatial/ --fix

# Type check
mypy chatspatial/
  • Max line length: 88 (Black default)
  • Type hints on all public functions
  • Imports: stdlib, third-party, local (isort handles this)

Testing

pytest tests/unit/           # Fast, no external deps
pytest tests/integration/    # Multi-component workflows
pytest tests/e2e/            # Full MCP tool calls

# Pre-PR quality gate
make test-gates
  • Unit tests: mock external packages, test logic in isolation
  • Integration tests: test tool dispatch and result storage
  • Keep test data small (<1000 spots, <500 genes)
  • Set random seeds for reproducibility

Submitting Changes

  1. Create a branch: git checkout -b feature/your-feature
  2. Make changes, run tests and linting
  3. Commit with clear messages: feat: add X method for Y analysis
  4. Open a PR against main

Commit style

feat: add new spatial analysis method
fix: handle edge case in deconvolution
docs: update methods reference
test: add integration test for trajectory

Reporting Issues

  • Bugs: include a minimal reproducible example, error traceback, and pip show chatspatial output
  • Feature requests: describe the use case and suggest which tool category it fits

Questions?

Open a GitHub Discussion or check the docs.