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.
Files changed (48) hide show
  1. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/PKG-INFO +1 -1
  2. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/cli.py +7 -6
  3. devpilot_agentic_cli-1.0.1/agent/mcp_client.py +130 -0
  4. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/devpilot_agentic_cli.egg-info/PKG-INFO +1 -1
  5. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/pyproject.toml +1 -1
  6. devpilot_agentic_cli-1.0.0/agent/mcp_client.py +0 -104
  7. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/README.md +0 -0
  8. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/__init__.py +0 -0
  9. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/a2a_client.py +0 -0
  10. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/a2a_server.py +0 -0
  11. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/config.py +0 -0
  12. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/context.py +0 -0
  13. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/history.py +0 -0
  14. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/loop.py +0 -0
  15. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/providers/__init__.py +0 -0
  16. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/providers/anthropic_provider.py +0 -0
  17. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/providers/base.py +0 -0
  18. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/providers/factory.py +0 -0
  19. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/providers/openai_provider.py +0 -0
  20. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/providers/system_prompt.py +0 -0
  21. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/setup_wizard.py +0 -0
  22. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/__init__.py +0 -0
  23. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/a2a.py +0 -0
  24. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/base.py +0 -0
  25. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/diagram.py +0 -0
  26. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/doc_gen.py +0 -0
  27. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/fs.py +0 -0
  28. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/git_ops.py +0 -0
  29. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/registry.py +0 -0
  30. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/search_code.py +0 -0
  31. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/shell.py +0 -0
  32. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tools/web_search.py +0 -0
  33. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tui/__init__.py +0 -0
  34. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/tui/app.py +0 -0
  35. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/agent/ui.py +0 -0
  36. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/devpilot_agentic_cli.egg-info/SOURCES.txt +0 -0
  37. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/devpilot_agentic_cli.egg-info/dependency_links.txt +0 -0
  38. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/devpilot_agentic_cli.egg-info/entry_points.txt +0 -0
  39. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/devpilot_agentic_cli.egg-info/requires.txt +0 -0
  40. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/devpilot_agentic_cli.egg-info/top_level.txt +0 -0
  41. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/setup.cfg +0 -0
  42. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/tests/test_config.py +0 -0
  43. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/tests/test_e2e.py +0 -0
  44. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/tests/test_history.py +0 -0
  45. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/tests/test_loop.py +0 -0
  46. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/tests/test_providers.py +0 -0
  47. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/tests/test_setup_wizard.py +0 -0
  48. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.1}/tests/test_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devpilot-agentic-cli
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: Autonomous AI coding agent for your terminal — Claude, GPT-4o, Groq, Ollama, and more
5
5
  Author: Thijesh Praveen V
6
6
  License: MIT
@@ -217,12 +217,13 @@ async def main_async() -> None:
217
217
  config=config,
218
218
  repo_context=repo_context,
219
219
  )
220
- await app.run_async()
221
-
222
- if a2a_server and a2a_task:
223
- a2a_server.should_exit = True
224
- await a2a_task
225
- await mcp_manager.close()
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()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devpilot-agentic-cli
3
- Version: 1.0.0
3
+ Version: 1.0.1
4
4
  Summary: Autonomous AI coding agent for your terminal — Claude, GPT-4o, Groq, Ollama, and more
5
5
  Author: Thijesh Praveen V
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devpilot-agentic-cli"
7
- version = "1.0.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()