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
9 changes: 6 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[test]"
pip install jsonschema
python -m pip install "uv~=0.11.0"
uv sync --locked --extra test

- name: Audit locked dependencies
run: uv audit

- name: Run tests
run: |
python -m pytest src/tests/ -v --cov=src --cov-report=term-missing --cov-report=xml --junitxml=junit/test-results.xml
uv run python -m pytest src/tests/ -v --cov=src --cov-report=term-missing --cov-report=xml --junitxml=junit/test-results.xml

- name: Upload test results
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
Expand Down
16 changes: 9 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ This is a Model Context Protocol (MCP) server that provides AI clients with acce
### Core Components

- **`codealive_mcp_server.py`**: Main entry point — bootstraps logging, tracing, registers tools and middleware
- **Eight tools**: `get_data_sources`, `semantic_search`, `grep_search`, `fetch_artifacts`, `get_artifact_relationships`, `chat`, `codebase_search`, `codebase_consultant`
- **Eleven tools**: `get_data_sources`, `semantic_search`, `grep_search`, `get_repository_ontology`, `get_file_tree`, `read_file`, `fetch_artifacts`, `get_artifact_relationships`, `get_artifact_query_schema`, `query_artifact_metadata`, `chat`
- **`core/client.py`**: `CodeAliveContext` dataclass + `codealive_lifespan` (httpx.AsyncClient lifecycle, `_server_ready` flag)
- **`core/logging.py`**: loguru structured JSON logging + PII masking + OTel context injection
- **`core/observability.py`**: OpenTelemetry TracerProvider setup with OTLP export
Expand All @@ -106,7 +106,7 @@ This is a Model Context Protocol (MCP) server that provides AI clients with acce
1. **FastMCP Framework**: Uses FastMCP 3.x with lifespan context, middleware hooks, and built-in `Client` for testing
2. **HTTP Auth via `get_http_headers`**: FastMCP 3.x strips the `authorization` header by default (to prevent accidental credential forwarding to downstream services). Our `get_api_key_from_context()` in `core/client.py` must use `get_http_headers(include={"authorization"})` to read Bearer tokens from HTTP/streamable-http clients. **Do not remove the `include=` parameter** — without it, all HTTP-transport clients (LibreChat, n8n, etc.) will fail with a misleading STDIO-mode error.
3. **HTTP Client Management**: Single persistent `httpx.AsyncClient` with connection pooling, created in lifespan
3. **Streaming Support**: `chat` and the deprecated `codebase_consultant` alias use SSE streaming (`response.aiter_lines()`) for chat completions
3. **Tool API v3 Backend Contract**: every MCP tool delegates to `POST /api/tools/{name}` and requests `output_format=agentic`
4. **Environment Configuration**: Supports both .env files and command-line arguments with precedence
5. **Error Handling**: Centralized in `utils/errors.py` — all tools use `handle_api_error()` with `method=` prefix
6. **N8N Middleware**: Strips extra parameters (sessionId, action, chatInput, toolCallId) from n8n tool calls before validation
Expand Down Expand Up @@ -158,7 +158,7 @@ This project uses **loguru** for structured JSON logging. All logs go to **stder

2. **All logs go to stderr.** The stdio MCP transport uses stdout for protocol messages. Any stray `print()` or stdout write will corrupt the MCP protocol and break the client. If you add a new log sink, it must target `sys.stderr`.

3. **Never call `response.text` without a debug guard.** `log_api_response()` is protected by `_is_debug_enabled()` because reading `response.text` consumes the response body. The `chat` tool and deprecated `codebase_consultant` alias stream SSE via `response.aiter_lines()` — calling `.text` first would silently consume the stream and produce empty results. If you add new response logging, always check `_is_debug_enabled()` first:
3. **Never call `response.text` without a debug guard.** `log_api_response()` is protected by `_is_debug_enabled()` because reading `response.text` consumes the response body. If you add new response logging, always check `_is_debug_enabled()` first:
```python
if not _is_debug_enabled():
return # Do NOT touch response body at INFO level
Expand Down Expand Up @@ -264,8 +264,10 @@ Tools that return **structured metadata** (identifiers, match counts, line
numbers, relationship groups, data source listings) return a `dict` (or list of
dicts). FastMCP serializes it automatically via `pydantic_core.to_json`, which
preserves Unicode — no manual `json.dumps()` needed. Examples:
`semantic_search`, `grep_search`, `codebase_search`, `get_data_sources`,
`get_artifact_relationships`.
`semantic_search`, `grep_search`, `get_data_sources`,
`get_repository_ontology`, `get_file_tree`, `read_file`,
`get_artifact_relationships`, `get_artifact_query_schema`, and
`query_artifact_metadata`.

**Never call `json.dumps(...)` from a tool's return path.** Python's `json.dumps`
defaults to `ensure_ascii=True` and escapes Cyrillic/CJK/etc. to `\uXXXX`.
Expand All @@ -289,7 +291,7 @@ description alone — descriptions are not always re-read mid-conversation, but
the response is always in front of the model when it decides what to do next.

Examples in this repo:
- `codebase_search` returns a `hint` field telling the agent that `description`
- `semantic_search` and `grep_search` return a `hint` field telling the agent that `description`
is a triage pointer only and that real understanding must come from
`fetch_artifacts(identifier)` or a local `Read(path)`. Implementation:
`_SEARCH_HINT` in `src/utils/response_transformer.py`.
Expand Down Expand Up @@ -352,7 +354,7 @@ Key points:
- Custom lifespan yields a real `CodeAliveContext` with a mock-backed httpx client
- `monkeypatch.setenv("CODEALIVE_API_KEY", ...)` for `get_api_key_from_context` fallback
- Use `raise_on_error=False` when testing error paths, then assert on `result.content[0].text`
- For SSE streaming (`chat` / `codebase_consultant`), return `httpx.Response(200, text=sse_body)` — `aiter_lines()` works on buffered responses
- For chat-style buffered responses, return `httpx.Response(200, json=payload)` and assert against the Tool API v3 envelope content

### Unit Test Patterns

Expand Down
19 changes: 11 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
.PHONY: help install test smoke-test unit-test clean run dev
.PHONY: help install audit test smoke-test unit-test integration-test clean run dev

help: ## Show this help message
@echo "Available commands:"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'

install: ## Install dependencies
uv pip install -e ".[test]"
uv sync --locked --extra test

audit: ## Check the locked environment for known vulnerabilities
uv audit

smoke-test: ## Run smoke tests (quick sanity check)
@echo "Running smoke tests..."
python smoke_test.py
uv run python smoke_test.py

unit-test: ## Run unit tests with pytest
@echo "Running unit tests..."
pytest src/tests/ -v
uv run pytest src/tests/ -v

integration-test: ## Run integration tests against live backend (requires CODEALIVE_API_KEY)
integration-test: ## Run live smoke tests (requires CODEALIVE_API_KEY and CODEALIVE_TEST_DATA_SOURCE)
@echo "Running integration tests..."
python integration_test.py
uv run python integration_test.py

test: unit-test smoke-test ## Run all tests (unit + smoke)

Expand All @@ -32,7 +35,7 @@ clean: ## Clean up cache and temp files
rm -rf htmlcov

run: ## Run the MCP server with stdio transport
python src/codealive_mcp_server.py
uv run python src/codealive_mcp_server.py

dev: ## Run the MCP server in debug mode
python src/codealive_mcp_server.py --debug
uv run python src/codealive_mcp_server.py --debug
40 changes: 31 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,14 @@ Once connected, you'll have access to these powerful tools:
1. **`get_data_sources`** - List your indexed repositories and workspaces
2. **`semantic_search`** - Canonical semantic search across indexed artifacts
3. **`grep_search`** - Exact literal or regex text search inside file content, plus literal file-name/path matching (returns files like `Form.xml` even when their content never mentions the name), with line-level previews for content matches
4. **`fetch_artifacts`** - Load the full source for relevant search hits (missing or inaccessible identifiers are reported back in a `<not_found>` block, not silently dropped)
5. **`get_artifact_relationships`** - Expand call graph, inheritance, and reference relationships for one artifact
6. **`chat`** - Slower synthesized codebase Q&A, typically only after search
7. **`codebase_search`** - Deprecated legacy semantic search alias kept for backward compatibility
8. **`codebase_consultant`** - Deprecated alias for `chat`
4. **`get_repository_ontology`** - Get repository-level orientation for one selected repository
5. **`get_file_tree`** - Inspect a bounded file tree for one repository
6. **`read_file`** - Read a repository-relative file path, optionally with a line range
7. **`fetch_artifacts`** - Load the full source for relevant search hits (missing or inaccessible identifiers are reported back, not silently dropped)
8. **`get_artifact_relationships`** - Expand call graph, inheritance, and reference relationships for one artifact
9. **`get_artifact_query_schema`** - Inspect supported ArtifactQuery entities, fields, and examples
10. **`query_artifact_metadata`** - Run read-only metadata analytics across selected repositories
11. **`chat`** - Stateless, slower synthesized codebase Q&A; call only when explicitly requested

## 🎯 Usage Examples

Expand All @@ -43,7 +46,7 @@ After setup, try these commands with your AI assistant:
- *"Find the exact regex that matches JWT tokens"* → Uses `grep_search`
- *"Explain how the payment flow works in this codebase"* → Usually starts with `semantic_search`/`grep_search`, then optionally uses `chat`

`semantic_search` and `grep_search` should be the default tools for most agents. `chat` is a slower synthesis fallback, can take up to 30 seconds, and is usually unnecessary when an agent can run a multi-step workflow with search, fetch, relationships, and local file reads. If your agent supports subagents, the highest-confidence path is to delegate a focused subagent that orchestrates `semantic_search` and `grep_search` first.
`semantic_search` and `grep_search` should be the default tools for most agents. `chat` is a slower stateless synthesis fallback that can take substantially longer than retrieval, and is usually unnecessary when an agent can run a multi-step workflow with ontology, search, fetch/read, relationships, ArtifactQuery, and local file reads. If your agent supports subagents, the highest-confidence path is to delegate a focused subagent that orchestrates `semantic_search` and `grep_search` first.

## 📚 Agent Skill

Expand Down Expand Up @@ -840,10 +843,14 @@ See [JetBrains MCP Documentation](https://www.jetbrains.com/help/ai-assistant/mc
- `get_data_sources` - List available repositories
- `semantic_search` - Search code semantically
- `grep_search` - Search by exact text or regex
- `get_repository_ontology` - Orient around one repository
- `get_file_tree` - Inspect repository files
- `read_file` - Read one repository-relative file
- `fetch_artifacts` - Fetch source for search result identifiers
- `get_artifact_relationships` - Expand relationships for one artifact
- `chat` - Slower synthesized codebase Q&A, usually after search
- `codebase_search` - Legacy semantic search alias
- `codebase_consultant` - Deprecated alias for `chat`
- `get_artifact_query_schema` - Inspect metadata query schema
- `query_artifact_metadata` - Run metadata analytics
- `chat` - Stateless synthesized codebase Q&A, only when explicitly requested

**Example Workflow:**
```
Expand Down Expand Up @@ -915,6 +922,20 @@ python src/codealive_mcp_server.py --transport http --host localhost --port 8000
curl http://localhost:8000/health
```

HTTP transport validates `Host` and browser `Origin` headers. Loopback hosts
(`localhost`, `127.0.0.1`, `::1`) work without extra configuration. For a
shared hostname, configure an exact allowlist:

```bash
export CODEALIVE_MCP_ALLOWED_HOSTS="mcp.codealive.yourcompany.com"
# Only for browser callers; ordinary MCP clients do not send Origin.
export CODEALIVE_MCP_ALLOWED_ORIGINS="https://mcp.codealive.yourcompany.com"
python src/codealive_mcp_server.py --transport http --host 0.0.0.0 --port 8000
```

The equivalent repeatable CLI options are `--allowed-host` and
`--allowed-origin`. Do not use `*` for an Internet-facing server.

### Testing Your Local Installation

After making changes, quickly verify everything works:
Expand Down Expand Up @@ -1016,6 +1037,7 @@ curl http://localhost:8000/health

2. **For Self-Hosted CodeAlive:**
- Set `CODEALIVE_BASE_URL` to your CodeAlive instance URL (e.g., `https://codealive.yourcompany.com`)
- Set `CODEALIVE_MCP_ALLOWED_HOSTS` to the exact hostname clients use for this MCP server
- Clients must provide their API key via `Authorization: Bearer YOUR_KEY` header

See `docker-compose.example.yml` for the complete configuration template.
Expand Down
8 changes: 8 additions & 0 deletions docker-compose.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ services:
# ==========================================
# OPTIONAL SETTINGS
# ==========================================
# Required when clients use a hostname other than localhost/127.0.0.1.
# Use a comma-separated exact allowlist; do not use "*" on public servers.
# CODEALIVE_MCP_ALLOWED_HOSTS: "mcp.codealive.yourcompany.com"

# Usually unnecessary for non-browser MCP clients. Set exact origins only
# when browser JavaScript calls the MCP endpoint.
# CODEALIVE_MCP_ALLOWED_ORIGINS: "https://mcp.codealive.yourcompany.com"

# Enable debug mode (verbose logging)
# DEBUG_MODE: "false"

Expand Down
Loading
Loading