Skip to content

Commit bec4516

Browse files
authored
Merge pull request #5 from BrunoV21/mcp-server
Mcp server
2 parents a1fdf69 + c25260d commit bec4516

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+5023
-111
lines changed

.github/workflows/python_package.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@ jobs:
4444
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
4545
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
4646
- name: Test with pytest
47-
run: pytest tests/
47+
run: pytest tests --ignore=tests/agents
48+
- name: Install package with agents requirements
49+
run: |
50+
python -m pip install .[agents]
51+
- name: Test agents with pytest
52+
run: pytest tests/agents
4853
security:
4954
name: Security Check
5055
runs-on: ubuntu-latest

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,5 @@ cython_debug/
175175

176176
storage/
177177
logs/
178+
observability_data/
178179

README.md

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,48 @@ CodeTide is available as a native [**Visual Studio Code extension**](https://mar
3636

3737
---
3838

39+
## 🖧 CodeTide as an MCP Server
40+
41+
CodeTide now supports acting as an **MCP Server**, enabling seamless integration with AI agents and tools. This feature allows agents to dynamically interact with your codebase and retrieve context efficiently.
42+
43+
#### Why This Helps Agents
44+
Agents working with codebases often need:
45+
- **Contextual Understanding**: Retrieve declarations, imports, and references for any part of the code.
46+
- **Tool Integration**: Use built-in tools to navigate and analyze code.
47+
48+
#### Available Tools
49+
CodeTide provides the following tools for agents:
50+
1. **`getContext`**: Retrieve code context for identifiers (e.g., functions, classes).
51+
2. **`getRepoTree`**: Explore the repository structure.
52+
53+
#### Example: Initializing an LLM with CodeTide
54+
Here’s a snippet from `agent_tide.py` demonstrating how to initialize an LLM with CodeTide as an MCP server:
55+
56+
```python
57+
from aicore.llm import Llm, LlmConfig
58+
from codetide.mcp import codeTideMCPServer
59+
import os
60+
61+
def init_llm() -> Llm:
62+
llm = Llm.from_config(
63+
LlmConfig(
64+
model="deepseek-chat",
65+
provider="deepseek",
66+
temperature=0,
67+
api_key=os.getenv("DEEPSEEK-API-KEY")
68+
)
69+
)
70+
llm.provider.mcp.add_server(name=codeTideMCPServer.name, parameters=codeTideMCPServer)
71+
return llm
72+
```
73+
74+
This setup allows the LLM to leverage CodeTide’s tools for codebase interactions.
75+
76+
CodeTide can now be used as an MCP (Multi-Code Processor) Server! This allows seamless integration with AI tools and workflows. Below are the tools available:
77+
The available tools are:
78+
- **getContext**: Retrieve code context for identifiers.
79+
- **getRepoTree**: Generate a visual tree representation of the repository.
80+
3981
## ⚙️ Installation
4082

4183
### 📦 From PyPI
@@ -399,9 +441,57 @@ Here’s what’s next for CodeTide:
399441
400442
~~- 🧭 **Handle relative imports** in Python projects
401443
→ Improve resolution for intra-package navigation.~~
402-
- 🤖 **Long-term vision**: Release a native **CodeTide Agent**
403-
→ Seamless, intelligent context resolution directly integrated into the CodeTide core.
404-
→ Unlock **clinical issue detection**, **guided refactors**, and **agent-level navigation**.
444+
445+
---
446+
447+
## 🤖 Agents Module: AgentTide
448+
449+
CodeTide now includes an `agents` module, featuring **AgentTide**—a precision-driven software engineering agent that connects directly to your codebase and executes your requests with full code context.
450+
451+
**AgentTide** leverages CodeTide’s symbolic code understanding to:
452+
- Retrieve and reason about relevant code context for any request
453+
- Generate atomic, high-precision patches using strict protocols
454+
- Apply changes directly to your codebase, with robust validation
455+
456+
### Where to Find It
457+
- Source: [`codetide/agents/tide/agent.py`](codetide/agents/tide/agent.py)
458+
459+
### What It Does
460+
AgentTide acts as an autonomous agent that:
461+
- Connects to your codebase using CodeTide’s parsing and context tools
462+
- Interacts with users via a conversational interface
463+
- Identifies relevant files, classes, and functions for any request
464+
- Generates and applies diff-style patches, ensuring code quality and requirements fidelity
465+
466+
### Example Usage
467+
To use AgentTide, ensure you have the `aicore` package installed (`pip install codetide[agents]`), then instantiate and run the agent:
468+
469+
```python
470+
from codetide import CodeTide
471+
from codetide.agents.tide.agent import AgentTide
472+
from aicore.llm import Llm, LlmConfig
473+
import os, asyncio
474+
475+
async def main():
476+
tide = await CodeTide.from_path("/path/to/your/repo")
477+
llm = Llm.from_config(
478+
LlmConfig(
479+
model="deepseek-chat",
480+
provider="deepseek",
481+
temperature=0,
482+
api_key=os.getenv("DEEPSEEK-API-KEY")
483+
)
484+
)
485+
agent = AgentTide(llm=llm, tide=tide)
486+
await agent.run()
487+
488+
if __name__ == "__main__":
489+
asyncio.run(main())
490+
```
491+
492+
AgentTide will prompt you for requests, retrieve the relevant code context, and generate precise patches to fulfill your requirements.
493+
494+
For more details, see the [agents module source code](codetide/agents/tide/agent.py).
405495
406496
---
407497

codetide/__init__.py

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from codetide.core.defaults import (
2-
DEFAULT_SERIALIZATION_PATH, DEFAULT_MAX_CONCURRENT_TASKS,
2+
CODETIDE_ASCII_ART, DEFAULT_SERIALIZATION_PATH, DEFAULT_MAX_CONCURRENT_TASKS,
33
DEFAULT_BATCH_SIZE, DEFAULT_CACHED_ELEMENTS_FILE, DEFAULT_CACHED_IDS_FILE,
44
LANGUAGE_EXTENSIONS
55
)
@@ -17,7 +17,7 @@
1717
import asyncio
1818
import pygit2
1919
import time
20-
import json
20+
import orjson
2121
import os
2222

2323
class CodeTide(BaseModel):
@@ -80,7 +80,7 @@ async def from_path(
8080

8181
codeTide._add_results_to_codebase(results)
8282
codeTide._resolve_files_dependencies()
83-
logger.info(f"CodeTide initialized with {len(results)} files processed in {time.time() - st:.2f}s")
83+
logger.info(f"\n{CODETIDE_ASCII_ART}\nInitialized with {len(results)} files processed in {time.time() - st:.2f}s")
8484

8585
return codeTide
8686

@@ -90,6 +90,10 @@ def relative_filepaths(self)->List[str]:
9090
str(filepath.relative_to(self.rootpath)).replace("\\", "/") for filepath in self.files
9191
]
9292

93+
@property
94+
def cached_ids(self)->List[str]:
95+
return self.codebase.unique_ids+self.relative_filepaths
96+
9397
async def _reset(self):
9498
self = await self.from_path(self.rootpath)
9599

@@ -138,7 +142,7 @@ def serialize(self,
138142

139143
if include_cached_ids:
140144
cached_ids_path = dir_path / DEFAULT_CACHED_IDS_FILE
141-
writeFile(json.dumps(self.codebase.unique_ids+self.relative_filepaths, indent=4), cached_ids_path)
145+
writeFile(str(orjson.dumps(self.cached_ids, option=orjson.OPT_INDENT_2)), cached_ids_path)
142146

143147
@classmethod
144148
def deserialize(cls, filepath :Optional[Union[str, Path]]=DEFAULT_SERIALIZATION_PATH, rootpath :Optional[Union[str, Path]] = None)->"CodeTide":
@@ -158,7 +162,7 @@ def deserialize(cls, filepath :Optional[Union[str, Path]]=DEFAULT_SERIALIZATION_
158162
if not os.path.exists(filepath):
159163
raise FileNotFoundError(f"{filepath} is not a valid path")
160164

161-
kwargs = json.loads(readFile(filepath))
165+
kwargs = orjson.loads(readFile(filepath))
162166
tideInstance = cls(**kwargs)
163167

164168
# dir_path = Path(os.path.split(filepath))[0]
@@ -458,7 +462,7 @@ async def check_for_updates(self,
458462
if self.rootpath / newFile.file_path in filepaths
459463
]
460464
parser.resolve_inter_files_dependencies(self.codebase, filteredNewFiles)
461-
parser.resolve_intra_file_dependencies(filteredNewFiles)
465+
parser.resolve_intra_file_dependencies(self.codebase, filteredNewFiles)
462466

463467
for codeFile in filteredNewFiles:
464468
i = changedPaths.get(codeFile.file_path)
@@ -485,36 +489,47 @@ def _precheck_id_is_file(self, unique_ids : List[str])->Dict[Path, str]:
485489
if self.rootpath / unique_id in self.files
486490
}
487491

488-
def get(self, unique_id :Union[str, List[str]], degree :int=1, slim :bool=False, as_string :bool=True, as_list_str :bool=False)->Union[CodeContextStructure, str, List[str]]:
492+
def get(
493+
self,
494+
code_identifiers: Union[str, List[str]],
495+
context_depth: int = 1,
496+
concise_mode: bool = False,
497+
as_string: bool = True,
498+
as_string_list: bool = False
499+
) -> Union[CodeContextStructure, str, List[str]]:
489500
"""
490-
Retrieve context around code by unique ID(s).
501+
Retrieves code context for given identifiers with flexible return formats.
502+
Returns None if no matching identifiers are found.
491503
492504
Args:
493-
unique_id: Single or list of unique IDs for code entities.
494-
degree: Depth of context to fetch.
495-
as_string: Whether to return as a single string.
496-
as_list_str: Whether to return as list of strings.
505+
code_identifiers: One or more code element IDs or file paths to analyze.
506+
Examples: 'package.ClassName', 'dir/module.py:function', ['file.py', 'module.var']
507+
context_depth: Number of reference levels to include (1=direct references only)
508+
concise_mode: If True, returns minimal docstrings instead of full code (slim=True)
509+
as_string: Return as single formatted string (default)
510+
as_string_list: Return as list of strings (overrides as_string if True)
497511
498512
Returns:
499-
Code context in the requested format.
513+
- CodeContextStructure if both format flags are False
514+
- Single concatenated string if as_string=True
515+
- List of context strings if as_string_list=True
516+
- None if no matching identifiers exist
500517
"""
501-
if isinstance(unique_id, str):
502-
unique_id = [unique_id]
518+
if isinstance(code_identifiers, str):
519+
code_identifiers = [code_identifiers]
503520

504-
# Log the incoming request
505521
logger.info(
506-
f"Getting code context - IDs: {unique_id}, "
507-
f"degree: {degree}, "
508-
f"as_string: {as_string}, "
509-
f"as_list_str: {as_list_str}"
522+
f"Context request - IDs: {code_identifiers}, "
523+
f"Depth: {context_depth}, "
524+
f"Formats: string={as_string}, list={as_string_list}"
510525
)
511526

512-
requestedFiles = self._precheck_id_is_file(unique_id)
527+
requested_files = self._precheck_id_is_file(code_identifiers)
513528
return self.codebase.get(
514-
unique_id=unique_id,
515-
degree=degree,
516-
slim=slim,
529+
unique_id=code_identifiers,
530+
degree=context_depth,
531+
slim=concise_mode,
517532
as_string=as_string,
518-
as_list_str=as_list_str,
519-
preloaded_files=requestedFiles
533+
as_list_str=as_string_list,
534+
preloaded_files=requested_files
520535
)

codetide/agents/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .tide import AgentTide
2+
3+
__all__ = [
4+
"AgentTide"
5+
]

codetide/agents/tide/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .agent import AgentTide
2+
3+
__all__ = [
4+
"AgentTide"
5+
]

0 commit comments

Comments
 (0)