devpilot-agentic-cli 1.0.0__tar.gz → 1.0.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.
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/PKG-INFO +1 -1
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/cli.py +7 -6
- devpilot_agentic_cli-1.0.1/agent/mcp_client.py +130 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/devpilot_agentic_cli.egg-info/PKG-INFO +1 -1
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/pyproject.toml +1 -1
- devpilot_agentic_cli-1.0.0/agent/mcp_client.py +0 -104
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/README.md +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/__init__.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/a2a_client.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/a2a_server.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/config.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/context.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/history.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/loop.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/providers/__init__.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/providers/anthropic_provider.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/providers/base.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/providers/factory.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/providers/openai_provider.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/providers/system_prompt.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/setup_wizard.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/__init__.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/a2a.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/base.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/diagram.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/doc_gen.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/fs.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/git_ops.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/registry.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/search_code.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/shell.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/web_search.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tui/__init__.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tui/app.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/ui.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/devpilot_agentic_cli.egg-info/SOURCES.txt +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/devpilot_agentic_cli.egg-info/dependency_links.txt +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/devpilot_agentic_cli.egg-info/entry_points.txt +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/devpilot_agentic_cli.egg-info/requires.txt +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/devpilot_agentic_cli.egg-info/top_level.txt +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/setup.cfg +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/tests/test_config.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/tests/test_e2e.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/tests/test_history.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/tests/test_loop.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/tests/test_providers.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/tests/test_setup_wizard.py +0 -0
- {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/tests/test_tools.py +0 -0
|
@@ -217,12 +217,13 @@ async def main_async() -> None:
|
|
|
217
217
|
config=config,
|
|
218
218
|
repo_context=repo_context,
|
|
219
219
|
)
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
a2a_server
|
|
224
|
-
|
|
225
|
-
|
|
220
|
+
try:
|
|
221
|
+
await app.run_async()
|
|
222
|
+
finally:
|
|
223
|
+
if a2a_server and a2a_task:
|
|
224
|
+
a2a_server.should_exit = True
|
|
225
|
+
await a2a_task
|
|
226
|
+
await mcp_manager.close()
|
|
226
227
|
|
|
227
228
|
|
|
228
229
|
def main() -> None:
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""
|
|
2
|
+
agent/mcp_client.py
|
|
3
|
+
───────────────────
|
|
4
|
+
MCP Client Integration (Sprint 3).
|
|
5
|
+
Connects to servers defined in mcp_servers.json, discovers tools,
|
|
6
|
+
and registers them into the ToolRegistry.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import asyncio
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from mcp.client.session import ClientSession
|
|
14
|
+
from mcp.client.stdio import StdioServerParameters, stdio_client
|
|
15
|
+
from mcp.types import TextContent
|
|
16
|
+
|
|
17
|
+
from agent.tools import ToolRegistry, ToolResult
|
|
18
|
+
from agent.ui import UI
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MCPManager:
|
|
22
|
+
"""Manages connections to multiple MCP servers."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, config_path: Path):
|
|
25
|
+
self.config_path = config_path
|
|
26
|
+
self.sessions: dict[str, ClientSession] = {}
|
|
27
|
+
self._tasks: list[asyncio.Task] = []
|
|
28
|
+
|
|
29
|
+
async def _run_server(
|
|
30
|
+
self,
|
|
31
|
+
name: str,
|
|
32
|
+
command: str,
|
|
33
|
+
args: list[str],
|
|
34
|
+
env: dict | None,
|
|
35
|
+
registry: ToolRegistry,
|
|
36
|
+
ready_event: asyncio.Event
|
|
37
|
+
) -> None:
|
|
38
|
+
try:
|
|
39
|
+
server_params = StdioServerParameters(command=command, args=args, env=env)
|
|
40
|
+
async with stdio_client(server_params) as (read, write):
|
|
41
|
+
async with ClientSession(read, write) as session:
|
|
42
|
+
await session.initialize()
|
|
43
|
+
self.sessions[name] = session
|
|
44
|
+
|
|
45
|
+
# Fetch and register tools
|
|
46
|
+
tools_response = await session.list_tools()
|
|
47
|
+
for mcp_tool in tools_response.tools:
|
|
48
|
+
canonical_schema = {
|
|
49
|
+
"name": mcp_tool.name,
|
|
50
|
+
"description": mcp_tool.description or "",
|
|
51
|
+
"input_schema": mcp_tool.inputSchema,
|
|
52
|
+
"_mcp_server_id": name,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
def make_executor(session_ref: ClientSession, tool_name: str):
|
|
56
|
+
async def _executor(tool_input: dict) -> ToolResult:
|
|
57
|
+
try:
|
|
58
|
+
result = await session_ref.call_tool(tool_name, tool_input)
|
|
59
|
+
text_contents = [c.text for c in result.content if isinstance(c, TextContent)]
|
|
60
|
+
output = "\n".join(text_contents)
|
|
61
|
+
return ToolResult(output, is_error=result.isError)
|
|
62
|
+
except Exception as e:
|
|
63
|
+
return ToolResult(f"MCP execution error: {e}", is_error=True)
|
|
64
|
+
return _executor
|
|
65
|
+
|
|
66
|
+
registry.register_mcp_tool(canonical_schema, make_executor(session, mcp_tool.name))
|
|
67
|
+
|
|
68
|
+
UI.print_info(f"Connected to MCP server: {name} ({len(tools_response.tools)} tools)")
|
|
69
|
+
ready_event.set()
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
await asyncio.Event().wait()
|
|
73
|
+
except asyncio.CancelledError:
|
|
74
|
+
pass
|
|
75
|
+
except Exception as e:
|
|
76
|
+
UI.print_error(f"Failed to connect to MCP server '{name}': {e}")
|
|
77
|
+
registry.deregister_mcp_tools(name)
|
|
78
|
+
finally:
|
|
79
|
+
ready_event.set()
|
|
80
|
+
|
|
81
|
+
async def connect_all(self, registry: ToolRegistry) -> None:
|
|
82
|
+
"""Connect to all servers in mcp_servers.json and register tools."""
|
|
83
|
+
if not self.config_path.exists():
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
with open(self.config_path, "r", encoding="utf-8") as f:
|
|
88
|
+
data = json.load(f)
|
|
89
|
+
servers = data.get("mcpServers", data.get("servers", {}))
|
|
90
|
+
except (json.JSONDecodeError, OSError) as e:
|
|
91
|
+
UI.print_error(f"Failed to read mcp_servers.json: {e}")
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
if isinstance(servers, dict):
|
|
95
|
+
server_items = servers.items()
|
|
96
|
+
else:
|
|
97
|
+
server_items = [(s.get("name", f"server_{i}"), s) for i, s in enumerate(servers)]
|
|
98
|
+
|
|
99
|
+
events = []
|
|
100
|
+
for name, server_config in server_items:
|
|
101
|
+
if server_config.get("enabled", True) is False:
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
command = server_config.get("command")
|
|
105
|
+
args = server_config.get("args", [])
|
|
106
|
+
|
|
107
|
+
if not command:
|
|
108
|
+
UI.print_error(f"MCP server '{name}' missing 'command'. Skipping.")
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
ready_event = asyncio.Event()
|
|
112
|
+
events.append(ready_event)
|
|
113
|
+
task = asyncio.create_task(
|
|
114
|
+
self._run_server(name, command, args, server_config.get("env"), registry, ready_event)
|
|
115
|
+
)
|
|
116
|
+
self._tasks.append(task)
|
|
117
|
+
|
|
118
|
+
if events:
|
|
119
|
+
await asyncio.gather(*(e.wait() for e in events))
|
|
120
|
+
|
|
121
|
+
async def close(self) -> None:
|
|
122
|
+
"""Close all connections."""
|
|
123
|
+
for task in self._tasks:
|
|
124
|
+
task.cancel()
|
|
125
|
+
|
|
126
|
+
if self._tasks:
|
|
127
|
+
await asyncio.gather(*self._tasks, return_exceptions=True)
|
|
128
|
+
|
|
129
|
+
self.sessions.clear()
|
|
130
|
+
self._tasks.clear()
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "devpilot-agentic-cli"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.1"
|
|
8
8
|
description = "Autonomous AI coding agent for your terminal — Claude, GPT-4o, Groq, Ollama, and more"
|
|
9
9
|
authors = [{ name = "Thijesh Praveen V" }]
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
agent/mcp_client.py
|
|
3
|
-
───────────────────
|
|
4
|
-
MCP Client Integration (Sprint 3).
|
|
5
|
-
Connects to servers defined in mcp_servers.json, discovers tools,
|
|
6
|
-
and registers them into the ToolRegistry.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import json
|
|
10
|
-
from contextlib import AsyncExitStack
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
|
|
13
|
-
from mcp.client.session import ClientSession
|
|
14
|
-
from mcp.client.stdio import StdioServerParameters, stdio_client
|
|
15
|
-
from mcp.types import TextContent
|
|
16
|
-
|
|
17
|
-
from agent.tools import ToolRegistry, ToolResult
|
|
18
|
-
from agent.ui import UI
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class MCPManager:
|
|
22
|
-
"""Manages connections to multiple MCP servers."""
|
|
23
|
-
|
|
24
|
-
def __init__(self, config_path: Path):
|
|
25
|
-
self.config_path = config_path
|
|
26
|
-
self.exit_stack = AsyncExitStack()
|
|
27
|
-
self.sessions: dict[str, ClientSession] = {}
|
|
28
|
-
|
|
29
|
-
async def connect_all(self, registry: ToolRegistry) -> None:
|
|
30
|
-
"""Connect to all servers in mcp_servers.json and register tools."""
|
|
31
|
-
if not self.config_path.exists():
|
|
32
|
-
return
|
|
33
|
-
|
|
34
|
-
try:
|
|
35
|
-
with open(self.config_path, "r", encoding="utf-8") as f:
|
|
36
|
-
data = json.load(f)
|
|
37
|
-
servers = data.get("mcpServers", data.get("servers", {}))
|
|
38
|
-
except (json.JSONDecodeError, OSError) as e:
|
|
39
|
-
UI.print_error(f"Failed to read mcp_servers.json: {e}")
|
|
40
|
-
return
|
|
41
|
-
|
|
42
|
-
# Handle both list of dicts and dict of dicts formats for mcp_servers.json
|
|
43
|
-
if isinstance(servers, dict):
|
|
44
|
-
# In official MCP config format, it's a dict mapping name to config
|
|
45
|
-
server_items = servers.items()
|
|
46
|
-
else:
|
|
47
|
-
# Fallback if it's a list
|
|
48
|
-
server_items = [(s.get("name", f"server_{i}"), s) for i, s in enumerate(servers)]
|
|
49
|
-
|
|
50
|
-
for name, server_config in server_items:
|
|
51
|
-
if server_config.get("enabled", True) is False:
|
|
52
|
-
continue
|
|
53
|
-
|
|
54
|
-
command = server_config.get("command")
|
|
55
|
-
args = server_config.get("args", [])
|
|
56
|
-
|
|
57
|
-
if not command:
|
|
58
|
-
UI.print_error(f"MCP server '{name}' missing 'command'. Skipping.")
|
|
59
|
-
continue
|
|
60
|
-
|
|
61
|
-
try:
|
|
62
|
-
server_params = StdioServerParameters(command=command, args=args, env=server_config.get("env"))
|
|
63
|
-
stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
|
|
64
|
-
read, write = stdio_transport
|
|
65
|
-
session = await self.exit_stack.enter_async_context(ClientSession(read, write))
|
|
66
|
-
await session.initialize()
|
|
67
|
-
|
|
68
|
-
self.sessions[name] = session
|
|
69
|
-
|
|
70
|
-
# Fetch and register tools
|
|
71
|
-
tools_response = await session.list_tools()
|
|
72
|
-
for mcp_tool in tools_response.tools:
|
|
73
|
-
# Convert to canonical schema format
|
|
74
|
-
canonical_schema = {
|
|
75
|
-
"name": mcp_tool.name,
|
|
76
|
-
"description": mcp_tool.description or "",
|
|
77
|
-
"input_schema": mcp_tool.inputSchema,
|
|
78
|
-
"_mcp_server_id": name,
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
# Create closure for execution
|
|
82
|
-
def make_executor(session_ref: ClientSession, tool_name: str):
|
|
83
|
-
async def _executor(tool_input: dict) -> ToolResult:
|
|
84
|
-
try:
|
|
85
|
-
result = await session_ref.call_tool(tool_name, tool_input)
|
|
86
|
-
# Flatten result text
|
|
87
|
-
text_contents = [c.text for c in result.content if isinstance(c, TextContent)]
|
|
88
|
-
output = "\n".join(text_contents)
|
|
89
|
-
return ToolResult(output, is_error=result.isError)
|
|
90
|
-
except Exception as e:
|
|
91
|
-
return ToolResult(f"MCP execution error: {e}", is_error=True)
|
|
92
|
-
return _executor
|
|
93
|
-
|
|
94
|
-
registry.register_mcp_tool(canonical_schema, make_executor(session, mcp_tool.name))
|
|
95
|
-
|
|
96
|
-
UI.print_info(f"Connected to MCP server: {name} ({len(tools_response.tools)} tools)")
|
|
97
|
-
except Exception as e:
|
|
98
|
-
UI.print_error(f"Failed to connect to MCP server '{name}': {e}")
|
|
99
|
-
registry.deregister_mcp_tools(name)
|
|
100
|
-
|
|
101
|
-
async def close(self) -> None:
|
|
102
|
-
"""Close all connections."""
|
|
103
|
-
await self.exit_stack.aclose()
|
|
104
|
-
self.sessions.clear()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/providers/anthropic_provider.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/providers/openai_provider.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/devpilot_agentic_cli.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/devpilot_agentic_cli.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|