Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,79 @@ For Windows:
}
```

## Shared SSE Server (Reduce Memory Usage)

By default, each MCP host project launches its own `code-memory` process, which loads the embedding model (~1–2 GB) once per project. To avoid this, you can run a **single shared instance** over SSE (Server-Sent Events) and point all your MCP hosts at it.

### Start the shared server

```bash
# Using uvx (recommended)
uvx code-memory --transport sse

# Custom port and host
uvx code-memory --transport sse --port 8765 --host 127.0.0.1

# Using standalone binary
./code-memory-linux-x86_64 --transport sse
```

The server listens on `http://127.0.0.1:8765/sse` by default.

### Configure MCP hosts to use the shared server

Instead of launching a new process, point your MCP host at the running SSE endpoint.

#### Claude Desktop

```json
{
"mcpServers": {
"code-memory": {
"url": "http://127.0.0.1:8765/sse"
}
}
}
```

#### VS Code (Copilot / Continue)

```json
{
"servers": {
"code-memory": {
"url": "http://127.0.0.1:8765/sse"
}
}
}
```

#### Claude Code (CLI) — `.mcp.json`

```json
{
"mcpServers": {
"code-memory": {
"url": "http://127.0.0.1:8765/sse"
}
}
}
```

> **Tip:** Configure `uvx code-memory --transport sse` to start via a single-instance service manager (e.g. systemd user service, launchd agent, or another one-time login/startup mechanism) so the shared server starts automatically.

> **Security:** The SSE endpoint is unauthenticated. Keep the default `--host 127.0.0.1` so only local processes can connect; do not bind to `0.0.0.0` or a public interface unless you've put authentication in front of it.

## Configuration

### CLI Options

| Option | Description | Default |
|--------|-------------|---------|
| `--transport` | Transport protocol: `stdio` or `sse` | `stdio` |
| `--port` | Port for SSE transport (only when `--transport sse` is used) | `8765` |
| `--host` | Host/bind address for SSE transport (only when `--transport sse` is used) | `127.0.0.1` |

### Environment Variables

| Variable | Description | Default |
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "code-memory"
version = "1.0.28"
version = "1.0.29"
description = "A deterministic, high-precision code intelligence MCP server"
readme = "README.md"
license = "MIT"
Expand Down
35 changes: 34 additions & 1 deletion server.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from __future__ import annotations

import argparse
import asyncio
from typing import Literal, cast

Expand Down Expand Up @@ -772,10 +773,42 @@ def search_history(


# ── Entrypoint ────────────────────────────────────────────────────────────
def build_arg_parser() -> argparse.ArgumentParser:
"""Build and return the CLI argument parser for code-memory."""
parser = argparse.ArgumentParser(
prog="code-memory",
description="code-memory MCP server",
)
parser.add_argument(
"--transport",
choices=["stdio", "sse"],
default="stdio",
help="Transport protocol to use (default: stdio). Use 'sse' to run a shared HTTP server.",
)
parser.add_argument(
"--port",
type=int,
default=8765,
help="Port for SSE transport (default: 8765). Only used when --transport=sse.",
)
parser.add_argument(
"--host",
default="127.0.0.1",
help="Host for SSE transport (default: 127.0.0.1). Only used when --transport=sse.",
)
return parser


def main():
"""Entry point for the MCP server when installed as a package."""
args = build_arg_parser().parse_args()

if args.transport == "sse":
mcp.settings.host = args.host
mcp.settings.port = args.port

# Warmup is now done lazily on first index_codebase call
mcp.run()
mcp.run(transport=args.transport)


if __name__ == "__main__":
Expand Down
90 changes: 90 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Tests for the main() entrypoint CLI argument parsing."""

from __future__ import annotations

import argparse
from unittest.mock import MagicMock, patch

import pytest

import server as server_mod
from server import build_arg_parser


class TestMainArgParsing:
"""Tests for CLI argument parsing in main()."""

def _parse_args(self, argv: list[str]) -> argparse.Namespace:
"""Parse args using the real CLI parser from server.py."""
Comment on lines +11 to +18
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from server import build_arg_parser imports server.py at test module import time. Since server.py imports db.py, which imports sqlite_vec at module import time, these arg-parsing tests will error (not skip) in environments where sqlite_vec isn't installed. To make the tests robust, either import server lazily inside the tests after pytest.importorskip(...), or move build_arg_parser() into a lightweight module that doesn’t import the full server stack.

Suggested change
from server import build_arg_parser
class TestMainArgParsing:
"""Tests for CLI argument parsing in main()."""
def _parse_args(self, argv: list[str]) -> argparse.Namespace:
"""Parse args using the real CLI parser from server.py."""
class TestMainArgParsing:
"""Tests for CLI argument parsing in main()."""
def _parse_args(self, argv: list[str]) -> argparse.Namespace:
"""Parse args using the real CLI parser from server.py."""
pytest.importorskip("sqlite_vec", reason="sqlite_vec not installed")
from server import build_arg_parser

Copilot uses AI. Check for mistakes.
return build_arg_parser().parse_args(argv)

def test_default_transport_is_stdio(self):
"""Default transport should be stdio."""
args = self._parse_args([])
assert args.transport == "stdio"

def test_default_port_is_8765(self):
"""Default SSE port should be 8765."""
args = self._parse_args([])
assert args.port == 8765

def test_default_host_is_localhost(self):
"""Default SSE host should be 127.0.0.1."""
args = self._parse_args([])
assert args.host == "127.0.0.1"

def test_sse_transport_flag(self):
"""--transport sse should set transport to sse."""
args = self._parse_args(["--transport", "sse"])
assert args.transport == "sse"

def test_custom_port(self):
"""--port should accept a custom port number."""
args = self._parse_args(["--transport", "sse", "--port", "9000"])
assert args.port == 9000

def test_custom_host(self):
"""--host should accept a custom host."""
args = self._parse_args(["--transport", "sse", "--host", "0.0.0.0"])
assert args.host == "0.0.0.0"

def test_invalid_transport_raises_error(self):
"""Invalid transport should raise SystemExit."""
with pytest.raises(SystemExit):
self._parse_args(["--transport", "invalid"])


class TestMainRunsBehavior:
"""Tests that main() calls mcp.run() with the correct arguments."""

def test_stdio_calls_run_with_stdio(self):
"""main() with no args should call mcp.run(transport='stdio')."""
with patch("sys.argv", ["code-memory"]):
with patch.object(server_mod, "mcp") as mock_mcp:
mock_mcp.settings = MagicMock()
server_mod.main()
mock_mcp.run.assert_called_once_with(transport="stdio")

def test_sse_calls_run_with_sse(self):
"""main() with --transport sse should call mcp.run(transport='sse')."""
with patch("sys.argv", ["code-memory", "--transport", "sse"]):
with patch.object(server_mod, "mcp") as mock_mcp:
mock_mcp.settings = MagicMock()
server_mod.main()
mock_mcp.run.assert_called_once_with(transport="sse")

def test_sse_sets_port_on_settings(self):
"""main() with --transport sse --port 9000 should set mcp.settings.port."""
with patch("sys.argv", ["code-memory", "--transport", "sse", "--port", "9000"]):
with patch.object(server_mod, "mcp") as mock_mcp:
mock_mcp.settings = MagicMock()
server_mod.main()
assert mock_mcp.settings.port == 9000

def test_sse_sets_host_on_settings(self):
"""main() with --transport sse --host 0.0.0.0 should set mcp.settings.host."""
with patch("sys.argv", ["code-memory", "--transport", "sse", "--host", "0.0.0.0"]):
with patch.object(server_mod, "mcp") as mock_mcp:
mock_mcp.settings = MagicMock()
server_mod.main()
assert mock_mcp.settings.host == "0.0.0.0"
6 changes: 5 additions & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading