fastmcp-agents-library-agent-simple-code 0.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,183 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .env.*
133
+ .venv
134
+ env/
135
+ venv/
136
+ ENV/
137
+ env.bak/
138
+ venv.bak/
139
+
140
+ # Spyder project settings
141
+ .spyderproject
142
+ .spyproject
143
+
144
+ # Rope project settings
145
+ .ropeproject
146
+
147
+ # mkdocs documentation
148
+ /site
149
+
150
+ # mypy
151
+ .mypy_cache/
152
+ .dmypy.json
153
+ dmypy.json
154
+
155
+ # Pyre type checker
156
+ .pyre/
157
+
158
+ # pytype static type analyzer
159
+ .pytype/
160
+
161
+ # Cython debug symbols
162
+ cython_debug/
163
+
164
+ # PyCharm
165
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
166
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
167
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
168
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
169
+ #.idea/
170
+
171
+ # Ruff stuff:
172
+ .ruff_cache/
173
+
174
+ # PyPI configuration file
175
+ .pypirc
176
+ secrets
177
+
178
+ prompt_suggestions
179
+ agent-working-dir
180
+
181
+ **/*.bak
182
+
183
+ storage
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastmcp-agents-library-agent-simple-code
3
+ Version: 0.5.1
4
+ Summary: Agents for Simple Code
5
+ Requires-Python: >=3.13
6
+ Requires-Dist: fastmcp-ai-agent-bridge-pydantic-ai>=0.1.2
7
+ Requires-Dist: gitpython>=3.1.44
@@ -0,0 +1,3 @@
1
+ from .investigate import code_investigation_agent
2
+
3
+ __all__ = ["code_investigation_agent"]
@@ -0,0 +1,49 @@
1
+ """
2
+ This agent is used to perform simple code tasks.
3
+ """
4
+
5
+ from collections.abc import AsyncIterator
6
+ from contextlib import ExitStack, asynccontextmanager
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from fastmcp import FastMCP
11
+ from fastmcp_ai_agent_bridge.pydantic_ai import FastMCPToolset
12
+ from pydantic import BaseModel, Field
13
+ from pydantic_ai import Agent
14
+ from pydantic_ai.agent import AgentRunResult
15
+
16
+ from fastmcp_agents.core.agents.base import Field
17
+ from fastmcp_agents.core.agents.task import TaskAgent
18
+ from fastmcp_agents.library.agent.simple_code.helpers.git import quick_clone_git_repo
19
+ from fastmcp_agents.library.mcp.strawgate import (
20
+ read_only_filesystem_mcp,
21
+ read_write_filesystem_mcp,
22
+ )
23
+
24
+ class BaseCodeAgent(TaskAgent):
25
+ """An agent that can perform simple code tasks."""
26
+
27
+ clean_workspace: bool = Field(
28
+ default=True,
29
+ description="Whether to clean the workspace after the task is complete. Only applicable if a Git URL is provided.",
30
+ )
31
+
32
+ @asynccontextmanager
33
+ async def prepare_workspace(self, *, git_url: str | None = None) -> AsyncIterator[tuple[Path, dict[str, Any] | None]]:
34
+ directory = Path.cwd()
35
+
36
+ with ExitStack() as stack:
37
+ if git_url is not None:
38
+ self.logger.info(f"Cloning git repository from {git_url}")
39
+ context_manager = quick_clone_git_repo(git_url, set_cwd=True, delete_on_exit=self.clean_workspace)
40
+
41
+ git_repo = stack.enter_context(context_manager)
42
+
43
+ self.logger.info(f"Cloned git repository to {git_repo}")
44
+
45
+ tools = await self.get_tools()
46
+
47
+ structure = await tools["get_structure"].run(arguments={})
48
+
49
+ yield directory, structure.structured_content
@@ -0,0 +1,18 @@
1
+
2
+ from collections.abc import AsyncIterator
3
+ from contextlib import ExitStack, asynccontextmanager
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from fastmcp_agents.library.agent.simple_code.helpers.git import quick_clone_git_repo
8
+
9
+
10
+ @asynccontextmanager
11
+ async def prepare_workspace(*, git_url: str | None = None, clean_workspace: bool = True) -> AsyncIterator[tuple[Path, dict[str, Any] | None]]:
12
+ directory = Path.cwd()
13
+
14
+ with ExitStack() as stack:
15
+ if git_url is not None:
16
+ context_manager = quick_clone_git_repo(git_url=git_url, set_cwd=True, delete_on_exit=clean_workspace)
17
+
18
+ _ = stack.enter_context(cm=context_manager)
@@ -0,0 +1,120 @@
1
+ from collections.abc import Generator
2
+ from pathlib import Path
3
+
4
+ from fastmcp.mcp_config import TransformingStdioMCPServer
5
+ from fastmcp.tools.tool_transform import ArgTransformConfig, ToolTransformConfig
6
+ from pydantic import BaseModel, Field, computed_field
7
+
8
+
9
+ def read_write_filesystem_mcp(root_dir: Path | None = None) -> TransformingStdioMCPServer:
10
+ additional_args: list[str] = []
11
+ if root_dir is not None:
12
+ additional_args.append(f"--root-dir={root_dir.resolve()}")
13
+
14
+ return TransformingStdioMCPServer(
15
+ command="uvx",
16
+ args=["filesystem-operations-mcp", *additional_args],
17
+ tools={
18
+ "search_files": ToolTransformConfig(
19
+ arguments={
20
+ "included_types": ArgTransformConfig(hide=True),
21
+ "excluded_types": ArgTransformConfig(hide=True),
22
+ },
23
+ ),
24
+ "find_files": ToolTransformConfig(
25
+ arguments={
26
+ "included_types": ArgTransformConfig(hide=True),
27
+ "excluded_types": ArgTransformConfig(hide=True),
28
+ },
29
+ ),
30
+ },
31
+ )
32
+
33
+
34
+ def read_only_filesystem_mcp(root_dir: Path | None = None) -> TransformingStdioMCPServer:
35
+ mcp = read_write_filesystem_mcp(root_dir=root_dir)
36
+
37
+ mcp.tools = {
38
+ "find_files": ToolTransformConfig(
39
+ tags={"allowed_tools"},
40
+ arguments={
41
+ "included_types": ArgTransformConfig(hide=True),
42
+ "excluded_types": ArgTransformConfig(hide=True),
43
+ },
44
+ ),
45
+ "search_files": ToolTransformConfig(
46
+ tags={"allowed_tools"},
47
+ arguments={
48
+ "included_types": ArgTransformConfig(hide=True),
49
+ "excluded_types": ArgTransformConfig(hide=True),
50
+ },
51
+ ),
52
+ "get_structure": ToolTransformConfig(
53
+ tags={"allowed_tools"},
54
+ ),
55
+ "get_file": ToolTransformConfig(
56
+ tags={"allowed_tools"},
57
+ ),
58
+ "read_file_lines": ToolTransformConfig(
59
+ tags={"allowed_tools"},
60
+ ),
61
+ }
62
+
63
+ mcp.include_tags = {"allowed_tools"}
64
+
65
+ return mcp
66
+
67
+
68
+ class FileStructure(BaseModel):
69
+ """A file structure."""
70
+
71
+ results: list[str]
72
+ max_results: int = Field(description="The maximum number of results to return.", exclude=True)
73
+
74
+ @computed_field
75
+ @property
76
+ def limit_reached(self) -> bool:
77
+ """Check if the limit has been reached."""
78
+
79
+ return len(self.results) >= self.max_results
80
+
81
+
82
+ def get_structure(root_dir: Path, max_results: int = 150) -> FileStructure:
83
+ """Get the structure of a directory."""
84
+
85
+ results: list[str] = []
86
+
87
+ for item in limited_depth_iterdir(path=root_dir, max_depth=3):
88
+ if len(results) >= max_results:
89
+ break
90
+ if item.is_file():
91
+ results.append(item.name)
92
+ elif item.is_dir():
93
+ results.append(item.name + "/")
94
+
95
+ return FileStructure(results=results, max_results=max_results)
96
+
97
+
98
+ def limited_depth_iterdir(
99
+ path: Path,
100
+ max_depth: int = 3,
101
+ current_depth: int = 0,
102
+ ) -> Generator[Path]:
103
+ """
104
+ Iterates through directory contents up to a specified maximum depth.
105
+
106
+ Args:
107
+ path (Path): The starting directory path.
108
+ max_depth (int): The maximum depth to traverse (0 for current directory only).
109
+ current_depth (int): The current depth during recursion (internal use).
110
+
111
+ Yields:
112
+ Path: A path object for each file or directory within the depth limit.
113
+ """
114
+ if current_depth > max_depth:
115
+ return
116
+
117
+ for item in path.iterdir():
118
+ yield item
119
+ if item.is_dir():
120
+ yield from limited_depth_iterdir(path=item, max_depth=max_depth, current_depth=current_depth + 1)
@@ -0,0 +1,10 @@
1
+ from pathlib import Path
2
+
3
+ import git
4
+ from git import Repo
5
+
6
+
7
+ def quick_clone_git_repo(target_dir: Path, git_url: str) -> Repo:
8
+ """Quickly clone a git repository."""
9
+
10
+ return git.Repo.clone_from(url=git_url, to_path=target_dir, single_branch=True, depth=1)
@@ -0,0 +1,141 @@
1
+ import os
2
+ import tempfile
3
+ from pathlib import Path
4
+ from textwrap import dedent
5
+ from typing import Annotated, Any, Literal
6
+
7
+ from fastmcp import FastMCP
8
+ from fastmcp.tools.tool import Tool
9
+ from fastmcp_ai_agent_bridge.pydantic_ai import FastMCPToolset
10
+ from git import Repo
11
+ from pydantic import AnyHttpUrl, BaseModel, Field, RootModel
12
+ from pydantic_ai.agent import Agent
13
+
14
+ from fastmcp_agents.library.agent.simple_code.helpers.filesystem import (
15
+ get_structure,
16
+ read_write_filesystem_mcp,
17
+ )
18
+
19
+ code_implementation_instructions = """
20
+ You are an expert software engineer. You are able to handle a wide variety of tasks related to software development.
21
+
22
+ Your goal is to study the assigned task, gather the necessary information to properly understand the task,
23
+ produce a viable plan to complete the task and then execute the plan.
24
+
25
+ You have access to the local filesystem, the tools you have available can search, summarize, create, read, update, and delete
26
+ files and directories as needed.
27
+
28
+ For any task, it will be extremely important for you to gather the necessary information from the codebase:
29
+ 1. Bug Fixes: If you are asked to fix a bug you will first understand the bug. You will review the different ways the relevant code
30
+ can be invoked and you will understand when and why the bug occurs and when and why it does not occur. You will first think
31
+ of a test that will fail if the bug is present and pass if the bug is not present. If the codebase includes tests, you will
32
+ write the test.
33
+
34
+ 2. Propose a solution to a problem: If you are asked to propose a solution to a problem you will first understand the problem. You will
35
+ try to understand the cause of the problem.
36
+
37
+ 3. Refactor code: If you are asked to refactor code you will first understand the code. You will review the different areas of the code
38
+ that are relevant to the refactoring and you will understand how the different parts of the code interact. You will then propose a
39
+ refactoring plan.
40
+
41
+ 4. Implement a new feature: If you are asked to implement a new feature you will first understand the feature. You will review the different
42
+ areas of the code that are relevant to the feature and you will understand how the different parts of the code interact. You will then
43
+ propose an implementation plan.
44
+
45
+ If the plan is more than a couple steps, you will then write the plan to a Markdown file called `plan.md`. You will read this plan after
46
+ writing it to ensure that it accurately reflects the plan. Once you have a plan, you will execute the plan performing the necessary steps
47
+ to complete the task.
48
+
49
+ Once you believe you have completed the task you will step through the code line by line ensuring that the task is completed. You will
50
+ re-read the written plan to make sure you have not missed any steps. When calling report_task_success you will enumerate the action you
51
+ took to resolve each part of the task. If you have not completed a part of the task, you will continue working on that part.
52
+ """
53
+
54
+ mcp_servers = {
55
+ "filesystem": read_write_filesystem_mcp(),
56
+ }
57
+
58
+
59
+ class FileLines(RootModel[list[str]]):
60
+ """A file line with line number and content."""
61
+
62
+
63
+ class PotentialFlaw(BaseModel):
64
+ """A potential flaw in the code."""
65
+
66
+ description: str
67
+ file_path: str | None = None
68
+ lines: FileLines = Field(default=..., description="The relevant lines of code in the file with their line numbers.")
69
+
70
+
71
+ class ImplementationResponse(BaseModel):
72
+ """A response from the implementation agent."""
73
+
74
+ summary: str
75
+ confidence: Literal["low", "medium", "high"]
76
+ potential_flaws: list[PotentialFlaw] = Field(
77
+ default=..., description="A list of potential flaws in the code that a reviewer should review before merging."
78
+ )
79
+
80
+
81
+ code_implementation_agent: Agent[None, ImplementationResponse] = Agent[None, ImplementationResponse](
82
+ model=os.environ.get("MODEL"),
83
+ toolsets=[
84
+ # We will provide a directory-locked toolset at runtime
85
+ ],
86
+ system_prompt=code_implementation_instructions,
87
+ output_type=ImplementationResponse,
88
+ retries=2,
89
+ output_retries=2,
90
+ )
91
+
92
+ server = FastMCP[Any](name="investigate-code-agent")
93
+
94
+ CODE_REPOSITORY_TYPE = Annotated[
95
+ AnyHttpUrl | Path | None,
96
+ Field(
97
+ description=dedent(
98
+ text="""
99
+ The code repository to investigate.
100
+
101
+ If a Git URL is provided, it will be cloned.
102
+ A local path can also be provided.
103
+ If neither is provided, the agent will look in the current working directory."""
104
+ ).strip(),
105
+ ),
106
+ ]
107
+
108
+
109
+ async def implement_code(task: str, code_repository: CODE_REPOSITORY_TYPE) -> ImplementationResponse:
110
+ """Perform a code investigation."""
111
+
112
+ with tempfile.TemporaryDirectory(delete=False) as temp_dir:
113
+ # We only actually use the tempdir if we are cloning a git repository
114
+ if isinstance(code_repository, AnyHttpUrl):
115
+ Repo.clone_from(url=str(code_repository), to_path=temp_dir, single_branch=True, depth=1)
116
+ code_repository = Path(temp_dir)
117
+ if code_repository is None:
118
+ code_repository = Path.cwd()
119
+
120
+ directory_locked_toolset = FastMCPToolset.from_mcp_config(
121
+ mcp_config={"filesystem": read_write_filesystem_mcp(root_dir=code_repository)}
122
+ )
123
+
124
+ structure = get_structure(root_dir=code_repository, max_results=10)
125
+
126
+ repo_info = f"""
127
+ We've called get_structure on your behalf, here were the initial results to get you started:
128
+ {structure}
129
+ """
130
+
131
+ run_result = await code_implementation_agent.run(user_prompt=[task, repo_info], toolsets=[directory_locked_toolset])
132
+
133
+ return run_result.output
134
+
135
+
136
+ implement_code_tool = Tool.from_function(fn=implement_code)
137
+
138
+ server.add_tool(tool=implement_code_tool)
139
+
140
+ if __name__ == "__main__":
141
+ server.run(transport="sse")
@@ -0,0 +1,158 @@
1
+ """
2
+ This agent is used to perform simple code tasks.
3
+ """
4
+
5
+ import os
6
+ import tempfile
7
+ from pathlib import Path
8
+ from textwrap import dedent
9
+ from typing import Annotated, Any, Literal
10
+
11
+ from fastmcp import FastMCP
12
+ from fastmcp.tools.tool import Tool
13
+ from fastmcp_ai_agent_bridge.pydantic_ai import FastMCPToolset
14
+ from git import Repo
15
+ from pydantic import AnyHttpUrl, BaseModel, Field, RootModel
16
+ from pydantic_ai import Agent
17
+
18
+ from fastmcp_agents.library.agent.simple_code.helpers.filesystem import (
19
+ get_structure,
20
+ read_only_filesystem_mcp,
21
+ )
22
+
23
+ mcp_servers = {
24
+ "filesystem": read_only_filesystem_mcp(),
25
+ }
26
+
27
+ toolset = FastMCPToolset.from_mcp_config(mcp_config=mcp_servers)
28
+
29
+ code_investigation_instructions = """
30
+ You are an expert software engineer. You are able to handle a wide variety of tasks related to software development.
31
+
32
+ Your goal is to study the assigned task, gather the necessary information to properly understand the task, and then
33
+ produce a viable plan to complete the task. You are to be thorough and do this right, you are not to concerned with
34
+ how much time it takes to complete the task.
35
+
36
+ You have access to the local filesystem, the tools you have available can search, summarize, and read files as needed.
37
+
38
+ For any task, it will be extremely important for you to gather the necessary information from the codebase:
39
+ 1. Bug Fixes: If you are asked to fix a bug you will first understand the bug. You will review the different ways the relevant code
40
+ can be invoked and you will understand when and why the bug occurs and when and why it does not occur. You will first think
41
+ of a test that will fail if the bug is present and pass if the bug is not present. If the codebase includes tests, you will
42
+ write the test.
43
+
44
+ 2. Propose a solution to a problem: If you are asked to propose a solution to a problem you will first understand the problem. You will
45
+ try to understand the cause of the problem.
46
+
47
+ 3. Refactor code: If you are asked to plan a refactoring you will first understand the code. You will review the different areas of the code
48
+ that are relevant to the refactoring and you will understand how the different parts of the code interact. You will then propose a
49
+ refactoring plan.
50
+
51
+ 4. Implement a new feature: If you are asked to implement a new feature you will first understand the feature. You will review the different
52
+ areas of the code that are relevant to the feature and you will understand how the different parts of the code interact. You will then
53
+ propose an implementation plan.
54
+
55
+ Once you believe you have completed the task you will step through the code line by line ensuring that the task is completed. If you have
56
+ not completed a part of the task, you will continue working on that part.
57
+
58
+ Once you have believe you have completed the task you will perform additional review of other files in the codebase, looking for any
59
+ references to the relevant code or tests that might need to be updated, or removed.
60
+
61
+ Remember, you cannot make any changes to the codebase. You can only read files.
62
+ """
63
+
64
+
65
+ class FileLines(RootModel[list[str]]):
66
+ """A file line with line number and content."""
67
+
68
+
69
+ class InvestigationFinding(BaseModel):
70
+ """An investigation finding."""
71
+
72
+ description: str
73
+ file_path: str | None = None
74
+ lines: FileLines = Field(default=..., description="The relevant lines of code in the file with their line numbers.")
75
+
76
+
77
+ class InvestigationRecommendation(BaseModel):
78
+ """An investigation recommendation."""
79
+
80
+ description: str
81
+ action: Literal["fix", "refactor", "propose", "implement"]
82
+ file_path: str | None = None
83
+ current_lines: FileLines = Field(default=..., description="The relevant lines of code in the file with their line numbers.")
84
+ proposed_lines: FileLines = Field(default=..., description="The proposed lines of code in the file with their line numbers.")
85
+
86
+
87
+ class InvestigationResponse(BaseModel):
88
+ """An investigation response."""
89
+
90
+ summary: str = Field(default=..., description="A summary of the findings. Under 1 page.")
91
+ confidence: Literal["high", "medium", "low"] = Field(default=..., description="The confidence of the findings.")
92
+ findings: list[InvestigationFinding]
93
+ recommendations: list[InvestigationRecommendation] = Field(
94
+ default=..., description="Recommendations for next steps based on the findings."
95
+ )
96
+
97
+
98
+ code_investigation_agent = Agent[None, InvestigationResponse](
99
+ model=os.environ.get("MODEL"),
100
+ toolsets=[
101
+ # We will provide a directory-locked toolset at runtime
102
+ ],
103
+ system_prompt=code_investigation_instructions,
104
+ output_type=InvestigationResponse,
105
+ retries=2,
106
+ output_retries=2,
107
+ )
108
+
109
+ server = FastMCP[Any](name="investigate-code-agent")
110
+
111
+ CODE_REPOSITORY_TYPE = Annotated[
112
+ AnyHttpUrl | Path | None,
113
+ Field(
114
+ description=dedent(
115
+ text="""
116
+ The code repository to investigate.
117
+
118
+ If a Git URL is provided, it will be cloned.
119
+ A local path can also be provided.
120
+ If neither is provided, the agent will look in the current working directory."""
121
+ ).strip(),
122
+ ),
123
+ ]
124
+
125
+
126
+ async def investigate_code(task: str, code_repository: CODE_REPOSITORY_TYPE) -> InvestigationResponse:
127
+ """Perform a code investigation."""
128
+
129
+ with tempfile.TemporaryDirectory(delete=False) as temp_dir:
130
+ # We only actually use the tempdir if we are cloning a git repository
131
+ if isinstance(code_repository, AnyHttpUrl):
132
+ Repo.clone_from(url=str(code_repository), to_path=temp_dir, single_branch=True, depth=1)
133
+ code_repository = Path(temp_dir)
134
+ if code_repository is None:
135
+ code_repository = Path.cwd()
136
+
137
+ directory_locked_toolset = FastMCPToolset.from_mcp_config(
138
+ mcp_config={"filesystem": read_only_filesystem_mcp(root_dir=code_repository)}
139
+ )
140
+
141
+ structure = get_structure(root_dir=code_repository, max_results=10)
142
+
143
+ repo_info = f"""
144
+ We've called get_structure on your behalf, here were the initial results to get you started:
145
+ {structure}
146
+ """
147
+
148
+ run_result = await code_investigation_agent.run(user_prompt=[task, repo_info], toolsets=[directory_locked_toolset])
149
+
150
+ return run_result.output
151
+
152
+
153
+ investigate_code_tool = Tool.from_function(fn=investigate_code)
154
+
155
+ server.add_tool(tool=investigate_code_tool)
156
+
157
+ if __name__ == "__main__":
158
+ server.run(transport="sse")
@@ -0,0 +1,51 @@
1
+ [project]
2
+ name = "fastmcp-agents-library-agent-simple-code"
3
+ version = "0.5.1"
4
+ description = "Agents for Simple Code"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+
8
+ dependencies = [
9
+ "fastmcp-ai-agent-bridge-pydantic-ai>=0.1.2",
10
+ "gitpython>=3.1.44",
11
+ ]
12
+
13
+ [project.scripts]
14
+ simple-code = "fastmcp_agents.library.agent.simple_code:main"
15
+
16
+ [tool.uv.sources]
17
+ fastmcp-agents-library-mcp = { workspace = true }
18
+
19
+ [dependency-groups]
20
+ dev = [
21
+ "pytest>=8.4.1",
22
+ "pytest-asyncio>=1.1.0",
23
+ "pytest-dotenv>=0.5.2",
24
+ "pytest-env>=1.1.5",
25
+ "ruff>=0.12.4",
26
+ ]
27
+
28
+ [build-system]
29
+ requires = ["hatchling"]
30
+ build-backend = "hatchling.build"
31
+
32
+
33
+ [tool.hatch.build.targets.wheel]
34
+ packages = ["src/fastmcp_agents"]
35
+
36
+ [tool.hatch.build.targets.sdist]
37
+ packages = ["src/fastmcp_agents"]
38
+
39
+ [tool.pytest.ini_options]
40
+ env = ["COLUMNS=120"]
41
+
42
+ asyncio_mode = "auto"
43
+ asyncio_default_fixture_loop_scope = "function"
44
+ addopts = "-s -vvv" # Disable Captures
45
+
46
+
47
+ [tool.ruff]
48
+ extend="../../../pyproject.toml"
49
+
50
+ [tool.pyright]
51
+ extends = "../../../pyproject.toml"