Developer Guide

April 28, 2026 ยท View on GitHub

Setup

Prerequisites

  • Python 3.10 or later
  • Git

Environment

git clone https://github.com/your-org/opensips-mcp-server.git
cd opensips-mcp-server

python -m venv .venv
source .venv/bin/activate

pip install -e ".[dev]"

This installs the package in editable mode along with development dependencies: pytest, ruff, mypy, respx, and factory-boy.

Project Structure

opensips-mcp-server/
  src/opensips_mcp/
    __init__.py
    __main__.py              # CLI entry point (Click)
    server.py                # FastMCP instance, lifespan, and module registration
    settings.py              # Pydantic settings (env vars)
    mi/
      __init__.py
      client.py              # Async MI JSON-RPC client
      commands.py            # MI command helpers
      exceptions.py          # MI error hierarchy
    db/
      __init__.py
      engine.py              # SQLAlchemy base and engine factory
      models/                # ORM models (one file per table)
      crud/                  # Async CRUD functions (one file per domain)
    cfg/
      __init__.py
      builder.py             # ConfigBuilder (Jinja2 rendering)
      parser.py              # Config parser (extracts structure)
      validator.py           # Config validator (opensips -C -f)
      templates/
        scenarios/           # 8 complete scenario templates
        partials/            # 13 reusable template fragments
    tools/                   # MCP tool modules (one file per domain)
    resources/               # MCP resource modules
    prompts/                 # MCP prompt modules
    security/
      __init__.py
      rbac.py                # Role-based access control
      validators.py          # Input validation functions
      audit.py               # Audit logging
    version/
      __init__.py
      base.py                # Base version strategy
      v3_4.py                # OpenSIPS 3.4 specifics
      v3_6.py                # OpenSIPS 3.6 specifics
      v4_0.py                # OpenSIPS 4.0 specifics
      registry.py            # Version strategy registry
  tests/
    conftest.py              # Shared fixtures
    test_server.py           # Server-level tests
    unit/                    # Unit tests (no external deps)
    integration/             # Integration tests (needs running OpenSIPS)
    e2e/                     # End-to-end tests
  docker/
    Dockerfile               # Multi-stage production build
    docker-compose.yml        # Full stack (OpenSIPS + MySQL + MCP)
    docker-compose.dev.yml    # Development stack
    scenarios/               # 7 deployment scenario Docker setups
    ecosystem/               # Monitoring, testing stacks
  scripts/
    seed_test_data.py        # Database seeder
    validate_all_templates.py # Template validator
  docs/                      # Documentation

Adding New Tools

1. Create or edit a tool module

Tool modules live in src/opensips_mcp/tools/. Each file groups related tools by domain.

# src/opensips_mcp/tools/my_tools.py
"""My new tools."""
from __future__ import annotations
from typing import Any
from mcp.server.fastmcp import Context
from opensips_mcp.security.rbac import require_permission
from opensips_mcp.server import mcp


@mcp.tool()
@require_permission("mi.read")
async def my_new_tool(ctx: Context, param1: str, param2: int = 10) -> dict[str, Any]:
    """One-line description of what this tool does.

    Parameters
    ----------
    param1:
        Description of param1.
    param2:
        Description of param2.
    """
    app = ctx.request_context.lifespan_context
    result = await app.mi_client.execute("some_command", {"key": param1})
    return result

2. Register the module

Add an import in src/opensips_mcp/server.py:

from opensips_mcp.tools import my_tools as _my_tools  # noqa: E402, F401

3. Apply security

  • Use @require_permission("scope") with the appropriate scope from the RBAC model.
  • For write operations, consider adding @audited("operation_name").
  • Validate all user-provided inputs using functions from security/validators.py.

4. Write tests

Create tests/unit/test_my_tools.py:

import pytest
from unittest.mock import AsyncMock, MagicMock

@pytest.mark.asyncio
async def test_my_new_tool(mock_ctx):
    from opensips_mcp.tools.my_tools import my_new_tool
    # mock_ctx is a fixture from conftest.py
    result = await my_new_tool(mock_ctx, param1="test")
    assert "result" in result

Adding New Templates

Scenario Template

  1. Create src/opensips_mcp/cfg/templates/scenarios/my_scenario.cfg.j2.
  2. Register it in src/opensips_mcp/cfg/builder.py:
SCENARIOS = {
    # ... existing scenarios ...
    "my_scenario": {
        "description": "My new scenario",
        "required_params": ["db_url"],
        "optional_params": ["listen_ip", "listen_port"],
    },
}
  1. Add default params in scripts/validate_all_templates.py under DEFAULT_SCENARIO_PARAMS.
  2. Validate:
python scripts/validate_all_templates.py

Partial Template

  1. Create src/opensips_mcp/cfg/templates/partials/my_feature.cfg.j2.
  2. Include it in scenario templates with:
{% include "partials/my_feature.cfg.j2" %}
  1. Add default params in the validation script and run it.

Testing Guide

Run all tests

make test

Run unit tests only

make test-unit

Unit tests must not require any external services (no OpenSIPS, no database server).

Run integration tests

make test-int

Integration tests are marked with @pytest.mark.integration and require a running OpenSIPS instance and database.

Test fixtures

The tests/conftest.py file provides shared fixtures:

  • mock_ctx -- A mock MCP context with a fake AppContext.
  • db_session -- An in-memory SQLite session for database tests.
  • mi_client -- A mock MI client using respx.

Coverage

python -m pytest tests/ -v --cov=opensips_mcp --cov-report=term-missing

Code Style

Tools

  • Ruff: Linting and formatting. Run make lint and make format.
  • mypy: Type checking in strict mode. Run make type-check.

Conventions

  • All function signatures must include type hints.
  • All public functions, classes, and modules must have docstrings.
  • Use from __future__ import annotations at the top of every module.
  • Line length: 100 characters.
  • Import order: standard library, third-party, local (enforced by ruff).
  • Use async/await for all I/O operations.
  • Use dict[str, Any] (lowercase) instead of Dict[str, Any] (Python 3.10+ syntax).

Pre-commit Checklist

Before submitting a PR:

make lint      # Fix any linting issues
make format    # Auto-format code
make type-check  # Fix type errors
make test      # All tests pass