From 5af39035f8708107042a561afc4d476112ddbcdf Mon Sep 17 00:00:00 2001 From: Compyle Bot Date: Sun, 19 Oct 2025 21:11:47 +0000 Subject: [PATCH 1/9] Auto-commit: Agent tool execution --- src/tools/overview.py | 114 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 src/tools/overview.py diff --git a/src/tools/overview.py b/src/tools/overview.py new file mode 100644 index 0000000..ea60735 --- /dev/null +++ b/src/tools/overview.py @@ -0,0 +1,114 @@ +"""Repository overview tool for CodeAlive MCP server.""" + +from typing import Optional +from xml.etree import ElementTree as ET + +from mcp.server.fastmcp import Context + +from core.config import get_api_key_from_context +from core.logging import log_api_request, log_api_response, logger +from utils.errors import ( + handle_api_error, + normalize_data_source_names, + format_data_source_names, +) +import httpx + + +async def get_repo_overview( + ctx: Context, + data_sources: Optional[list[str]] = None +) -> str: + """Get high-level overview of repositories including purpose, responsibilities, ubiquitous language, and domain descriptions. + + This tool retrieves domain-focused information about repositories to help users understand + the business context and vocabulary of codebases. It returns structured information including: + - Purpose: What the repository is for + - Responsibilities: What it does + - Ubiquitous Language: Domain-specific terminology and concepts + - Domain(s): Business domains covered with their vocabulary + + Args: + ctx: FastMCP context containing API client and configuration + data_sources: Optional list of repository/workspace names. If not provided, returns + overviews for all available data sources + + Returns: + XML formatted string containing repository overviews in markdown format + + Example: + # Get overview for specific repositories + result = await get_repo_overview(ctx, ["my-backend-api", "frontend-app"]) + + # Get overviews for all repositories + result = await get_repo_overview(ctx) + + # Example output structure: + # + # + # + # # Purpose + # Backend API for e-commerce platform + # + # ## Responsibilities + # - Handle user authentication + # - Process orders and payments + # + # ## Ubiquitous Language + # - Order: A customer purchase request + # - Cart: Collection of items before checkout + # + # ## Domains + # ### E-commerce + # - Product catalog management + # - Order processing + # + # + # + """ + try: + # Get context and API key + context = ctx.request_context.lifespan_context + api_key = get_api_key_from_context(ctx) + + # Normalize and format data_sources if provided + data_sources = normalize_data_source_names(data_sources) + + # Build request URL and params + url = f"{context.base_url}/api/overview" + params = {} + + if data_sources: + formatted_names = format_data_source_names(data_sources) + params = formatted_names + + # Log and execute GET request + log_api_request(logger, "GET", url, params) + + headers = {"Authorization": f"Bearer {api_key}"} + response = await context.client.get(url, headers=headers, params=params) + response.raise_for_status() + + # Parse JSON response + overview_data = response.json() + log_api_response(logger, response.status_code, overview_data) + + # Transform to XML format + root = ET.Element("repository_overviews") + + for repo in overview_data: + repo_element = ET.SubElement(root, "repository") + repo_element.set("name", repo.get("name", "unknown")) + + overview_element = ET.SubElement(repo_element, "overview") + overview_element.text = repo.get("overview", "") + + # Convert to string with proper formatting + xml_string = ET.tostring(root, encoding="unicode", method="xml") + + return xml_string + + except httpx.HTTPError as e: + return handle_api_error(ctx, e, "get repository overview") + except Exception as e: + return handle_api_error(ctx, e, "get repository overview") From 9c5c970f4d5ff05953a1824cfb919bcac7332e38 Mon Sep 17 00:00:00 2001 From: Compyle Bot Date: Sun, 19 Oct 2025 21:12:08 +0000 Subject: [PATCH 2/9] Auto-commit: Agent tool execution --- src/tools/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/__init__.py b/src/tools/__init__.py index 0f84086..347f02d 100644 --- a/src/tools/__init__.py +++ b/src/tools/__init__.py @@ -3,5 +3,6 @@ from .chat import codebase_consultant from .datasources import get_data_sources from .search import codebase_search +from .overview import get_repo_overview -__all__ = ['codebase_consultant', 'get_data_sources', 'codebase_search'] \ No newline at end of file +__all__ = ['codebase_consultant', 'get_data_sources', 'codebase_search', 'get_repo_overview'] \ No newline at end of file From 5f2d1f9cc94e5df82dfcd51409492cb47af6adea Mon Sep 17 00:00:00 2001 From: Compyle Bot Date: Sun, 19 Oct 2025 21:12:26 +0000 Subject: [PATCH 3/9] Auto-commit: Agent tool execution --- src/codealive_mcp_server.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/codealive_mcp_server.py b/src/codealive_mcp_server.py index 5bfe7b0..8e15d3e 100644 --- a/src/codealive_mcp_server.py +++ b/src/codealive_mcp_server.py @@ -25,7 +25,7 @@ # Import core components from core import codealive_lifespan, setup_debug_logging -from tools import codebase_consultant, get_data_sources, codebase_search +from tools import codebase_consultant, get_data_sources, codebase_search, get_repo_overview # Initialize FastMCP server with lifespan and enhanced system instructions mcp = FastMCP( @@ -106,6 +106,7 @@ async def health_check(request: Request) -> JSONResponse: mcp.tool()(codebase_consultant) mcp.tool()(get_data_sources) mcp.tool()(codebase_search) +mcp.tool()(get_repo_overview) def main(): From 0509a318ce75098cf7aff0214573b581873777d6 Mon Sep 17 00:00:00 2001 From: Compyle Bot Date: Sun, 19 Oct 2025 21:13:15 +0000 Subject: [PATCH 4/9] Auto-commit: Agent tool execution --- src/tests/test_overview.py | 308 +++++++++++++++++++++++++++++++++++++ 1 file changed, 308 insertions(+) create mode 100644 src/tests/test_overview.py diff --git a/src/tests/test_overview.py b/src/tests/test_overview.py new file mode 100644 index 0000000..742e811 --- /dev/null +++ b/src/tests/test_overview.py @@ -0,0 +1,308 @@ +"""Tests for the get_repo_overview tool.""" + +import pytest +from unittest.mock import AsyncMock, patch, MagicMock + +from mcp.server.fastmcp import Context + +from tools.overview import get_repo_overview +from core.client import CodeAliveContext +import httpx + + +@pytest.mark.asyncio +@patch("tools.overview.get_api_key_from_context") +async def test_get_repo_overview_success(mock_get_api_key): + """Test successful retrieval of repository overview.""" + # Mock API key + mock_get_api_key.return_value = "test-api-key" + + # Mock response + mock_response = MagicMock() + mock_response.json.return_value = [ + { + "name": "test-repo", + "overview": "# Purpose\nTest repository\n## Responsibilities\n- Task 1" + } + ] + mock_response.status_code = 200 + + # Mock client + mock_client = AsyncMock() + mock_client.get = AsyncMock(return_value=mock_response) + + # Mock context + mock_context = MagicMock(spec=CodeAliveContext) + mock_context.client = mock_client + mock_context.base_url = "https://app.codealive.ai" + + mock_ctx = MagicMock(spec=Context) + mock_ctx.request_context.lifespan_context = mock_context + + # Call tool + result = await get_repo_overview(mock_ctx, ["test-repo"]) + + # Assertions + assert '' in result + assert '' in result + assert '# Purpose' in result + assert 'Test repository' in result + assert '## Responsibilities' in result + assert '- Task 1' in result + + # Verify API call + mock_client.get.assert_called_once() + call_args = mock_client.get.call_args + assert call_args.kwargs['headers'] == {"Authorization": "Bearer test-api-key"} + assert "https://app.codealive.ai/api/overview" in call_args.args[0] + + +@pytest.mark.asyncio +@patch("tools.overview.get_api_key_from_context") +async def test_get_repo_overview_multiple_repos(mock_get_api_key): + """Test retrieval of multiple repository overviews.""" + mock_get_api_key.return_value = "test-api-key" + + # Mock response with 3 repositories + mock_response = MagicMock() + mock_response.json.return_value = [ + {"name": "repo-1", "overview": "Overview 1"}, + {"name": "repo-2", "overview": "Overview 2"}, + {"name": "repo-3", "overview": "Overview 3"} + ] + mock_response.status_code = 200 + + mock_client = AsyncMock() + mock_client.get = AsyncMock(return_value=mock_response) + + mock_context = MagicMock(spec=CodeAliveContext) + mock_context.client = mock_client + mock_context.base_url = "https://app.codealive.ai" + + mock_ctx = MagicMock(spec=Context) + mock_ctx.request_context.lifespan_context = mock_context + + result = await get_repo_overview(mock_ctx, ["repo-1", "repo-2", "repo-3"]) + + # Verify 3 repository blocks + assert result.count('' in result + assert '' in result + assert '' in result + assert 'Overview 1' in result + assert 'Overview 2' in result + assert 'Overview 3' in result + + +@pytest.mark.asyncio +@patch("tools.overview.get_api_key_from_context") +async def test_get_repo_overview_no_data_sources(mock_get_api_key): + """Test retrieval without specifying data sources (all repos).""" + mock_get_api_key.return_value = "test-api-key" + + mock_response = MagicMock() + mock_response.json.return_value = [ + {"name": "all-repo-1", "overview": "Overview 1"}, + {"name": "all-repo-2", "overview": "Overview 2"} + ] + mock_response.status_code = 200 + + mock_client = AsyncMock() + mock_client.get = AsyncMock(return_value=mock_response) + + mock_context = MagicMock(spec=CodeAliveContext) + mock_context.client = mock_client + mock_context.base_url = "https://app.codealive.ai" + + mock_ctx = MagicMock(spec=Context) + mock_ctx.request_context.lifespan_context = mock_context + + result = await get_repo_overview(mock_ctx, None) + + # Verify API called without Names[] params + mock_client.get.assert_called_once() + call_args = mock_client.get.call_args + # When data_sources is None, params should be empty dict + assert call_args.kwargs['params'] == {} + + # Verify returns overviews for all repos + assert '' in result + assert '' in result + + +@pytest.mark.asyncio +@patch("tools.overview.get_api_key_from_context") +async def test_get_repo_overview_empty_result(mock_get_api_key): + """Test handling of empty API response.""" + mock_get_api_key.return_value = "test-api-key" + + # Empty array response + mock_response = MagicMock() + mock_response.json.return_value = [] + mock_response.status_code = 200 + + mock_client = AsyncMock() + mock_client.get = AsyncMock(return_value=mock_response) + + mock_context = MagicMock(spec=CodeAliveContext) + mock_context.client = mock_client + mock_context.base_url = "https://app.codealive.ai" + + mock_ctx = MagicMock(spec=Context) + mock_ctx.request_context.lifespan_context = mock_context + + result = await get_repo_overview(mock_ctx, ["nonexistent"]) + + # Should return empty root element + assert '' in result + # Should not contain any repository elements + assert ' Date: Sun, 19 Oct 2025 21:14:37 +0000 Subject: [PATCH 5/9] Auto-commit: Agent tool execution --- src/tools/overview.py | 50 +++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/tools/overview.py b/src/tools/overview.py index ea60735..a9437dc 100644 --- a/src/tools/overview.py +++ b/src/tools/overview.py @@ -1,18 +1,18 @@ """Repository overview tool for CodeAlive MCP server.""" from typing import Optional +from urllib.parse import urljoin from xml.etree import ElementTree as ET -from mcp.server.fastmcp import Context +import httpx +from fastmcp import Context -from core.config import get_api_key_from_context -from core.logging import log_api_request, log_api_response, logger -from utils.errors import ( +from core import CodeAliveContext, get_api_key_from_context, log_api_request, log_api_response +from utils import ( handle_api_error, normalize_data_source_names, format_data_source_names, ) -import httpx async def get_repo_overview( @@ -66,32 +66,38 @@ async def get_repo_overview( # # """ + context: CodeAliveContext = ctx.request_context.lifespan_context + try: - # Get context and API key - context = ctx.request_context.lifespan_context api_key = get_api_key_from_context(ctx) - # Normalize and format data_sources if provided - data_sources = normalize_data_source_names(data_sources) + # Normalize data_sources (handles Claude Desktop serialization issues) + data_source_names = normalize_data_source_names(data_sources) - # Build request URL and params - url = f"{context.base_url}/api/overview" + # Build request params params = {} - - if data_sources: - formatted_names = format_data_source_names(data_sources) + if data_source_names and len(data_source_names) > 0: + formatted_names = format_data_source_names(data_source_names) params = formatted_names - # Log and execute GET request - log_api_request(logger, "GET", url, params) - + # Prepare headers headers = {"Authorization": f"Bearer {api_key}"} - response = await context.client.get(url, headers=headers, params=params) + + # Log the request + endpoint = "/api/overview" + full_url = urljoin(context.base_url, endpoint) + request_id = log_api_request("GET", full_url, headers, params=params) + + # Make API request + response = await context.client.get(endpoint, headers=headers, params=params) + + # Log the response + log_api_response(response, request_id) + response.raise_for_status() # Parse JSON response overview_data = response.json() - log_api_response(logger, response.status_code, overview_data) # Transform to XML format root = ET.Element("repository_overviews") @@ -108,7 +114,5 @@ async def get_repo_overview( return xml_string - except httpx.HTTPError as e: - return handle_api_error(ctx, e, "get repository overview") - except Exception as e: - return handle_api_error(ctx, e, "get repository overview") + except (httpx.HTTPStatusError, Exception) as e: + return await handle_api_error(ctx, e, "get repository overview") From 481cc030663b50019da8a54b6d869883e5aa3148 Mon Sep 17 00:00:00 2001 From: Compyle Bot Date: Sun, 19 Oct 2025 22:02:57 +0000 Subject: [PATCH 6/9] Auto-commit: Agent tool execution --- src/tools/overview.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/tools/overview.py b/src/tools/overview.py index a9437dc..0b687c1 100644 --- a/src/tools/overview.py +++ b/src/tools/overview.py @@ -15,15 +15,16 @@ ) -async def get_repo_overview( +async def get_overview( ctx: Context, data_sources: Optional[list[str]] = None ) -> str: - """Get high-level overview of repositories including purpose, responsibilities, ubiquitous language, and domain descriptions. + """Get high-level overview of data sources including purpose, responsibilities, ubiquitous language, and domain descriptions. - This tool retrieves domain-focused information about repositories to help users understand - the business context and vocabulary of codebases. It returns structured information including: - - Purpose: What the repository is for + This tool retrieves domain-focused information about data sources (repositories or workspaces) + to help users understand the business context and vocabulary of codebases. It returns structured + information including: + - Purpose: What the data source is for - Responsibilities: What it does - Ubiquitous Language: Domain-specific terminology and concepts - Domain(s): Business domains covered with their vocabulary @@ -34,14 +35,14 @@ async def get_repo_overview( overviews for all available data sources Returns: - XML formatted string containing repository overviews in markdown format + XML formatted string containing data source overviews in markdown format Example: - # Get overview for specific repositories - result = await get_repo_overview(ctx, ["my-backend-api", "frontend-app"]) + # Get overview for specific data sources + result = await get_overview(ctx, ["my-backend-api", "frontend-app"]) - # Get overviews for all repositories - result = await get_repo_overview(ctx) + # Get overviews for all data sources + result = await get_overview(ctx) # Example output structure: # From 720c0284760c7847102d310c788f30d3b9f70b43 Mon Sep 17 00:00:00 2001 From: Compyle Bot Date: Sun, 19 Oct 2025 22:03:10 +0000 Subject: [PATCH 7/9] Auto-commit: Agent tool execution --- src/tools/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tools/__init__.py b/src/tools/__init__.py index 347f02d..4578240 100644 --- a/src/tools/__init__.py +++ b/src/tools/__init__.py @@ -3,6 +3,6 @@ from .chat import codebase_consultant from .datasources import get_data_sources from .search import codebase_search -from .overview import get_repo_overview +from .overview import get_overview -__all__ = ['codebase_consultant', 'get_data_sources', 'codebase_search', 'get_repo_overview'] \ No newline at end of file +__all__ = ['codebase_consultant', 'get_data_sources', 'codebase_search', 'get_overview'] \ No newline at end of file From 57751ccda2ff80cd2d975906c55439316a2e47a4 Mon Sep 17 00:00:00 2001 From: Compyle Bot Date: Sun, 19 Oct 2025 22:03:23 +0000 Subject: [PATCH 8/9] Auto-commit: Agent tool execution --- src/codealive_mcp_server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/codealive_mcp_server.py b/src/codealive_mcp_server.py index 8e15d3e..5122950 100644 --- a/src/codealive_mcp_server.py +++ b/src/codealive_mcp_server.py @@ -25,7 +25,7 @@ # Import core components from core import codealive_lifespan, setup_debug_logging -from tools import codebase_consultant, get_data_sources, codebase_search, get_repo_overview +from tools import codebase_consultant, get_data_sources, codebase_search, get_overview # Initialize FastMCP server with lifespan and enhanced system instructions mcp = FastMCP( @@ -106,7 +106,7 @@ async def health_check(request: Request) -> JSONResponse: mcp.tool()(codebase_consultant) mcp.tool()(get_data_sources) mcp.tool()(codebase_search) -mcp.tool()(get_repo_overview) +mcp.tool()(get_overview) def main(): From 9ebf5cc221cabedef4757222dc6b83f63b3daf57 Mon Sep 17 00:00:00 2001 From: Compyle Bot Date: Sun, 19 Oct 2025 22:04:12 +0000 Subject: [PATCH 9/9] Auto-commit: Agent tool execution --- src/tests/test_overview.py | 42 +++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/tests/test_overview.py b/src/tests/test_overview.py index 742e811..3239f31 100644 --- a/src/tests/test_overview.py +++ b/src/tests/test_overview.py @@ -1,19 +1,19 @@ -"""Tests for the get_repo_overview tool.""" +"""Tests for the get_overview tool.""" import pytest from unittest.mock import AsyncMock, patch, MagicMock from mcp.server.fastmcp import Context -from tools.overview import get_repo_overview +from tools.overview import get_overview from core.client import CodeAliveContext import httpx @pytest.mark.asyncio @patch("tools.overview.get_api_key_from_context") -async def test_get_repo_overview_success(mock_get_api_key): - """Test successful retrieval of repository overview.""" +async def test_get_overview_success(mock_get_api_key): + """Test successful retrieval of data source overview.""" # Mock API key mock_get_api_key.return_value = "test-api-key" @@ -40,7 +40,7 @@ async def test_get_repo_overview_success(mock_get_api_key): mock_ctx.request_context.lifespan_context = mock_context # Call tool - result = await get_repo_overview(mock_ctx, ["test-repo"]) + result = await get_overview(mock_ctx, ["test-repo"]) # Assertions assert '' in result @@ -59,8 +59,8 @@ async def test_get_repo_overview_success(mock_get_api_key): @pytest.mark.asyncio @patch("tools.overview.get_api_key_from_context") -async def test_get_repo_overview_multiple_repos(mock_get_api_key): - """Test retrieval of multiple repository overviews.""" +async def test_get_overview_multiple_repos(mock_get_api_key): + """Test retrieval of multiple data source overviews.""" mock_get_api_key.return_value = "test-api-key" # Mock response with 3 repositories @@ -82,7 +82,7 @@ async def test_get_repo_overview_multiple_repos(mock_get_api_key): mock_ctx = MagicMock(spec=Context) mock_ctx.request_context.lifespan_context = mock_context - result = await get_repo_overview(mock_ctx, ["repo-1", "repo-2", "repo-3"]) + result = await get_overview(mock_ctx, ["repo-1", "repo-2", "repo-3"]) # Verify 3 repository blocks assert result.count('