Skip to content

Commit 490be0a

Browse files
committed
Rename codebase consultant to chat
1 parent e30758b commit 490be0a

16 files changed

Lines changed: 177 additions & 77 deletions

CLAUDE.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ This is a Model Context Protocol (MCP) server that provides AI clients with acce
9595
### Core Components
9696

9797
- **`codealive_mcp_server.py`**: Main entry point — bootstraps logging, tracing, registers tools and middleware
98-
- **Five tools**: `get_data_sources`, `codebase_search`, `fetch_artifacts`, `codebase_consultant`, `get_artifact_relationships`
98+
- **Eight tools**: `get_data_sources`, `semantic_search`, `grep_search`, `fetch_artifacts`, `get_artifact_relationships`, `chat`, `codebase_search`, `codebase_consultant`
9999
- **`core/client.py`**: `CodeAliveContext` dataclass + `codealive_lifespan` (httpx.AsyncClient lifecycle, `_server_ready` flag)
100100
- **`core/logging.py`**: loguru structured JSON logging + PII masking + OTel context injection
101101
- **`core/observability.py`**: OpenTelemetry TracerProvider setup with OTLP export
@@ -105,7 +105,7 @@ This is a Model Context Protocol (MCP) server that provides AI clients with acce
105105

106106
1. **FastMCP Framework**: Uses FastMCP 3.x with lifespan context, middleware hooks, and built-in `Client` for testing
107107
2. **HTTP Client Management**: Single persistent `httpx.AsyncClient` with connection pooling, created in lifespan
108-
3. **Streaming Support**: `codebase_consultant` uses SSE streaming (`response.aiter_lines()`) for chat completions
108+
3. **Streaming Support**: `chat` and the deprecated `codebase_consultant` alias use SSE streaming (`response.aiter_lines()`) for chat completions
109109
4. **Environment Configuration**: Supports both .env files and command-line arguments with precedence
110110
5. **Error Handling**: Centralized in `utils/errors.py` — all tools use `handle_api_error()` with `method=` prefix
111111
6. **N8N Middleware**: Strips extra parameters (sessionId, action, chatInput, toolCallId) from n8n tool calls before validation
@@ -114,7 +114,7 @@ This is a Model Context Protocol (MCP) server that provides AI clients with acce
114114
### Data Flow
115115

116116
1. AI client connects to MCP server via stdio/HTTP transport
117-
2. Client calls tools (`get_data_sources``codebase_search` `fetch_artifacts` / `codebase_consultant`)
117+
2. Client calls tools (`get_data_sources``semantic_search` / `grep_search` `fetch_artifacts` / `get_artifact_relationships``chat` only if synthesis is still needed)
118118
3. Middleware chain runs: N8N cleanup → ObservabilityMiddleware (OTel span + log correlation)
119119
4. Tool translates MCP call to CodeAlive API request (with `X-CodeAlive-*` headers)
120120
5. Response parsed, formatted as XML or text, returned to AI client
@@ -144,7 +144,7 @@ The server is designed to integrate with:
144144
- Any MCP-compatible AI client
145145

146146
Key integration considerations:
147-
- AI clients should use `get_data_sources` first to discover available repositories/workspaces, then use those IDs for targeted search and chat operations
147+
- AI clients should use `get_data_sources` first to discover available repositories/workspaces, then default to `semantic_search` and `grep_search` for evidence gathering; use `chat` only as a slower synthesis fallback
148148
- **n8n Integration**: The server includes middleware to automatically strip n8n's extra parameters (sessionId, action, chatInput, toolCallId) from tool calls, so n8n works out of the box without any special configuration
149149

150150
## Logging Best Practices
@@ -157,7 +157,7 @@ This project uses **loguru** for structured JSON logging. All logs go to **stder
157157

158158
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`.
159159

160-
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 `codebase_consultant` tool streams 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:
160+
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:
161161
```python
162162
if not _is_debug_enabled():
163163
return # Do NOT touch response body at INFO level
@@ -269,7 +269,7 @@ Key points:
269269
- Custom lifespan yields a real `CodeAliveContext` with a mock-backed httpx client
270270
- `monkeypatch.setenv("CODEALIVE_API_KEY", ...)` for `get_api_key_from_context` fallback
271271
- Use `raise_on_error=False` when testing error paths, then assert on `result.content[0].text`
272-
- For SSE streaming (codebase_consultant), return `httpx.Response(200, text=sse_body)``aiter_lines()` works on buffered responses
272+
- For SSE streaming (`chat` / `codebase_consultant`), return `httpx.Response(200, text=sse_body)``aiter_lines()` works on buffered responses
273273

274274
### Unit Test Patterns
275275

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ Once connected, you'll have access to these powerful tools:
3030
3. **`grep_search`** - Exact text or regex search with line-level matches
3131
4. **`fetch_artifacts`** - Load the full source for relevant search hits
3232
5. **`get_artifact_relationships`** - Expand call graph, inheritance, and reference relationships for one artifact
33-
6. **`codebase_consultant`** - AI consultant with full project expertise
33+
6. **`chat`** - Slower synthesized codebase Q&A, typically only after search
3434
7. **`codebase_search`** - Deprecated legacy semantic search alias kept for backward compatibility
35+
8. **`codebase_consultant`** - Deprecated alias for `chat`
3536

3637
## 🎯 Usage Examples
3738

@@ -40,7 +41,9 @@ After setup, try these commands with your AI assistant:
4041
- *"Show me all available repositories"* → Uses `get_data_sources`
4142
- *"Find authentication code in the user service"* → Uses `semantic_search`
4243
- *"Find the exact regex that matches JWT tokens"* → Uses `grep_search`
43-
- *"Explain how the payment flow works in this codebase"* → Uses `codebase_consultant`
44+
- *"Explain how the payment flow works in this codebase"* → Usually starts with `semantic_search`/`grep_search`, then optionally uses `chat`
45+
46+
`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.
4447

4548
## 📚 Agent Skill
4649

@@ -808,8 +811,9 @@ See [JetBrains MCP Documentation](https://www.jetbrains.com/help/ai-assistant/mc
808811
- `semantic_search` - Search code semantically
809812
- `grep_search` - Search by exact text or regex
810813
- `get_artifact_relationships` - Expand relationships for one artifact
814+
- `chat` - Slower synthesized codebase Q&A, usually after search
811815
- `codebase_search` - Legacy semantic search alias
812-
- `codebase_consultant` - Ask questions about code
816+
- `codebase_consultant` - Deprecated alias for `chat`
813817

814818
**Example Workflow:**
815819
```

manifest.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,17 @@
6565
"name": "grep_search",
6666
"description": "Search indexed artifacts by exact text or regex and return line-level matches."
6767
},
68+
{
69+
"name": "chat",
70+
"description": "Synthesized codebase Q&A. Slower and usually not the default choice; prefer semantic_search and grep_search first. Can take up to 30 seconds."
71+
},
6872
{
6973
"name": "fetch_artifacts",
7074
"description": "Fetch full source for specific search results when you need the underlying code."
7175
},
7276
{
7377
"name": "codebase_consultant",
74-
"description": "Ask architecture and implementation questions with full codebase context."
78+
"description": "Deprecated alias for chat kept for backward compatibility."
7579
},
7680
{
7781
"name": "get_artifact_relationships",

server.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,13 @@
6666
"name": "grep_search",
6767
"description": "Search indexed artifacts using exact text or regex patterns and return line-level matches."
6868
},
69+
{
70+
"name": "chat",
71+
"description": "Synthesized codebase Q&A. Use only after semantic_search and grep_search when you need a slower, up-to-30-second answer."
72+
},
6973
{
7074
"name": "codebase_consultant",
71-
"description": "Get comprehensive AI-powered analysis, explanations, and insights about your codebase. Ask complex questions about architecture, patterns, dependencies, and implementation details."
75+
"description": "Deprecated alias for chat retained for backward compatibility."
7276
},
7377
{
7478
"name": "fetch_artifacts",

src/codealive_mcp_server.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import core.client as _client_module # for /ready flag access
3030
from middleware import N8NRemoveParametersMiddleware, ObservabilityMiddleware
3131
from tools import (
32+
chat,
3233
codebase_consultant,
3334
codebase_search,
3435
fetch_artifacts,
@@ -58,13 +59,20 @@
5859
3. To get full content:
5960
- For repos in your working directory: use `Read()` on the local files
6061
- For external repos: use `fetch_artifacts` with identifiers from search results
61-
4. Use `codebase_consultant` for in-depth analysis and synthesized answers
62+
4. Use `get_artifact_relationships` or `fetch_artifacts` to drill into the most relevant hits
63+
5. If your environment supports subagents and you need the highest reliability or depth,
64+
prefer an agentic workflow where a subagent combines `semantic_search`, `grep_search`,
65+
artifact fetches, relationship inspection, and local file reads
66+
6. Use `chat` only when you specifically need a synthesized answer after search;
67+
it is usually not the default choice and can take up to 30 seconds
6268
6369
For effective code exploration:
6470
- Start with broad natural-language queries in `semantic_search` to understand the overall structure
6571
- Use `grep_search(regex=false)` for exact strings and `grep_search(regex=true)` for regex patterns
6672
- Use specific function/class names or file path scopes when looking for particular implementations
73+
- Treat `semantic_search` and `grep_search` as the default discovery tools
6774
- Prefer `semantic_search` over the deprecated `codebase_search` legacy alias
75+
- Reserve `chat` for synthesis after search, not for first-pass evidence gathering
6876
- Remember that context from previous messages is maintained in the same conversation
6977
7078
Flexible data source usage:
@@ -122,10 +130,6 @@ async def readiness_check(request: Request) -> JSONResponse:
122130
# Register tools with metadata suitable for Claude Desktop and MCP directories.
123131
_READ_ONLY_TOOL = {"readOnlyHint": True}
124132

125-
mcp.tool(
126-
title="Consult Codebase",
127-
annotations=_READ_ONLY_TOOL,
128-
)(codebase_consultant)
129133
mcp.tool(
130134
title="List Data Sources",
131135
annotations=_READ_ONLY_TOOL,
@@ -142,6 +146,10 @@ async def readiness_check(request: Request) -> JSONResponse:
142146
title="Grep Search",
143147
annotations=_READ_ONLY_TOOL,
144148
)(grep_search)
149+
mcp.tool(
150+
title="Chat About Codebase",
151+
annotations=_READ_ONLY_TOOL,
152+
)(chat)
145153
mcp.tool(
146154
title="Fetch Artifacts",
147155
annotations=_READ_ONLY_TOOL,
@@ -150,6 +158,10 @@ async def readiness_check(request: Request) -> JSONResponse:
150158
title="Inspect Artifact Relationships",
151159
annotations=_READ_ONLY_TOOL,
152160
)(get_artifact_relationships)
161+
mcp.tool(
162+
title="Consult Codebase (Deprecated)",
163+
annotations=_READ_ONLY_TOOL,
164+
)(codebase_consultant)
153165

154166

155167
def main():

src/tests/test_chat_tool.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
"""Test suite for codebase consultant tool."""
1+
"""Test suite for chat tool and legacy consultant alias."""
22

33
import pytest
44
from unittest.mock import AsyncMock, MagicMock, patch
55
import json
66
from fastmcp import Context
7-
from tools.chat import codebase_consultant
7+
from tools.chat import chat, codebase_consultant
88

99

1010
@pytest.mark.asyncio
1111
@patch('tools.chat.get_api_key_from_context')
12-
async def test_consultant_with_simple_names(mock_get_api_key):
13-
"""Test codebase consultant with simple string names."""
12+
async def test_chat_with_simple_names(mock_get_api_key):
13+
"""Test chat with simple string names."""
1414
mock_get_api_key.return_value = "test_key"
1515

1616
ctx = MagicMock(spec=Context)
@@ -40,7 +40,7 @@ async def mock_aiter_lines():
4040
ctx.request_context.lifespan_context = mock_codealive_context
4141

4242
# Test with simple string names
43-
result = await codebase_consultant(
43+
result = await chat(
4444
ctx=ctx,
4545
question="Test question",
4646
data_sources=["repo123", "repo456"]
@@ -57,12 +57,13 @@ async def mock_aiter_lines():
5757
]
5858

5959
assert result == "Hello world"
60+
assert call_args.kwargs["headers"]["X-CodeAlive-Tool"] == "chat"
6061

6162

6263
@pytest.mark.asyncio
6364
@patch('tools.chat.get_api_key_from_context')
64-
async def test_consultant_preserves_string_names(mock_get_api_key):
65-
"""Test codebase consultant preserves string names."""
65+
async def test_consultant_alias_preserves_string_names(mock_get_api_key):
66+
"""Test deprecated consultant alias preserves behavior."""
6667
mock_get_api_key.return_value = "test_key"
6768

6869
ctx = MagicMock(spec=Context)
@@ -109,8 +110,8 @@ async def mock_aiter_lines():
109110

110111
@pytest.mark.asyncio
111112
@patch('tools.chat.get_api_key_from_context')
112-
async def test_consultant_with_conversation_id(mock_get_api_key):
113-
"""Test codebase consultant with existing conversation ID."""
113+
async def test_chat_with_conversation_id(mock_get_api_key):
114+
"""Test chat with existing conversation ID."""
114115
mock_get_api_key.return_value = "test_key"
115116

116117
ctx = MagicMock(spec=Context)
@@ -134,7 +135,7 @@ async def mock_aiter_lines():
134135

135136
ctx.request_context.lifespan_context = mock_codealive_context
136137

137-
result = await codebase_consultant(
138+
result = await chat(
138139
ctx=ctx,
139140
question="Follow up",
140141
conversation_id="conv_123"
@@ -153,19 +154,19 @@ async def mock_aiter_lines():
153154

154155
@pytest.mark.asyncio
155156
@patch('tools.chat.get_api_key_from_context')
156-
async def test_consultant_empty_question_validation(mock_get_api_key):
157+
async def test_chat_empty_question_validation(mock_get_api_key):
157158
"""Test validation of empty question."""
158159
mock_get_api_key.return_value = "test_key"
159160

160161
ctx = MagicMock(spec=Context)
161162
ctx.request_context.lifespan_context = MagicMock()
162163

163164
# Test with empty question
164-
result = await codebase_consultant(ctx=ctx, question="")
165+
result = await chat(ctx=ctx, question="")
165166
assert "Error: No question provided" in result
166167

167168
# Test with whitespace only
168-
result = await codebase_consultant(ctx=ctx, question=" ")
169+
result = await chat(ctx=ctx, question=" ")
169170
assert "Error: No question provided" in result
170171

171172

@@ -174,8 +175,8 @@ async def test_consultant_empty_question_validation(mock_get_api_key):
174175
@pytest.mark.asyncio
175176
@patch('tools.chat.get_api_key_from_context')
176177
@patch('tools.chat.handle_api_error')
177-
async def test_consultant_error_handling(mock_handle_error, mock_get_api_key):
178-
"""Test error handling in codebase consultant."""
178+
async def test_chat_error_handling(mock_handle_error, mock_get_api_key):
179+
"""Test error handling in chat."""
179180
mock_get_api_key.return_value = "test_key"
180181
mock_handle_error.return_value = "Error: Authentication failed"
181182

@@ -191,11 +192,11 @@ async def test_consultant_error_handling(mock_handle_error, mock_get_api_key):
191192

192193
ctx.request_context.lifespan_context = mock_codealive_context
193194

194-
result = await codebase_consultant(
195+
result = await chat(
195196
ctx=ctx,
196197
question="Test",
197198
data_sources=["repo123"]
198199
)
199200

200201
assert result == "Error: Authentication failed"
201-
mock_handle_error.assert_called_once()
202+
mock_handle_error.assert_called_once()

src/tests/test_e2e_tools.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from core import CodeAliveContext
2222
from tools import (
23+
chat,
2324
codebase_consultant,
2425
codebase_search,
2526
fetch_artifacts,
@@ -69,6 +70,7 @@ async def lifespan(server: FastMCP) -> AsyncIterator[CodeAliveContext]:
6970
mcp.tool()(semantic_search)
7071
mcp.tool()(grep_search)
7172
mcp.tool()(fetch_artifacts)
73+
mcp.tool()(chat)
7274
mcp.tool()(codebase_consultant)
7375
mcp.tool()(get_artifact_relationships)
7476
return mcp
@@ -464,10 +466,10 @@ async def test_artifact_with_relationships(self):
464466

465467

466468
# ---------------------------------------------------------------------------
467-
# codebase_consultant (streaming SSE)
469+
# chat / codebase_consultant (streaming SSE)
468470
# ---------------------------------------------------------------------------
469471

470-
class TestCodebaseConsultantE2E:
472+
class TestChatE2E:
471473
@staticmethod
472474
def _sse_body(chunks: list[str], conv_id: str = "conv-42", msg_id: str = "msg-1") -> str:
473475
"""Build an SSE response body with metadata + content chunks + DONE."""
@@ -497,7 +499,7 @@ def handler(req):
497499
mcp = _server({"/api/chat/completions": handler})
498500
async with Client(mcp) as client:
499501
result = await client.call_tool(
500-
"codebase_consultant",
502+
"chat",
501503
{"question": "How does auth work?", "data_sources": ["backend"]},
502504
)
503505

@@ -518,7 +520,7 @@ def handler(req):
518520
mcp = _server({"/api/chat/completions": handler})
519521
async with Client(mcp) as client:
520522
result = await client.call_tool(
521-
"codebase_consultant",
523+
"chat",
522524
{"question": "And the error handling?", "conversation_id": "conv-existing"},
523525
)
524526

@@ -530,7 +532,7 @@ async def test_empty_question_returns_error(self):
530532
mcp = _server({})
531533
async with Client(mcp) as client:
532534
result = await client.call_tool(
533-
"codebase_consultant", {"question": ""},
535+
"chat", {"question": ""},
534536
raise_on_error=False,
535537
)
536538

@@ -544,14 +546,31 @@ async def test_backend_error_handled(self):
544546
})
545547
async with Client(mcp) as client:
546548
result = await client.call_tool(
547-
"codebase_consultant",
549+
"chat",
548550
{"question": "hello"},
549551
raise_on_error=False,
550552
)
551553

552554
text = _text(result)
553555
assert "401" in text or "auth" in text.lower()
554556

557+
@pytest.mark.asyncio
558+
async def test_legacy_alias_still_works(self):
559+
body = self._sse_body(["Legacy alias"])
560+
561+
def handler(req):
562+
assert req.headers["X-CodeAlive-Tool"] == "codebase_consultant"
563+
return httpx.Response(200, text=body, headers={"content-type": "text/event-stream"})
564+
565+
mcp = _server({"/api/chat/completions": handler})
566+
async with Client(mcp) as client:
567+
result = await client.call_tool(
568+
"codebase_consultant",
569+
{"question": "How does auth work?", "data_sources": ["backend"]},
570+
)
571+
572+
assert "Legacy alias" in _text(result)
573+
555574

556575
# ---------------------------------------------------------------------------
557576
# get_artifact_relationships

0 commit comments

Comments
 (0)