codetether 1.2.2__py3-none-any.whl
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.
- a2a_server/__init__.py +29 -0
- a2a_server/a2a_agent_card.py +365 -0
- a2a_server/a2a_errors.py +1133 -0
- a2a_server/a2a_executor.py +926 -0
- a2a_server/a2a_router.py +1033 -0
- a2a_server/a2a_types.py +344 -0
- a2a_server/agent_card.py +408 -0
- a2a_server/agents_server.py +271 -0
- a2a_server/auth_api.py +349 -0
- a2a_server/billing_api.py +638 -0
- a2a_server/billing_service.py +712 -0
- a2a_server/billing_webhooks.py +501 -0
- a2a_server/config.py +96 -0
- a2a_server/database.py +2165 -0
- a2a_server/email_inbound.py +398 -0
- a2a_server/email_notifications.py +486 -0
- a2a_server/enhanced_agents.py +919 -0
- a2a_server/enhanced_server.py +160 -0
- a2a_server/hosted_worker.py +1049 -0
- a2a_server/integrated_agents_server.py +347 -0
- a2a_server/keycloak_auth.py +750 -0
- a2a_server/livekit_bridge.py +439 -0
- a2a_server/marketing_tools.py +1364 -0
- a2a_server/mcp_client.py +196 -0
- a2a_server/mcp_http_server.py +2256 -0
- a2a_server/mcp_server.py +191 -0
- a2a_server/message_broker.py +725 -0
- a2a_server/mock_mcp.py +273 -0
- a2a_server/models.py +494 -0
- a2a_server/monitor_api.py +5904 -0
- a2a_server/opencode_bridge.py +1594 -0
- a2a_server/redis_task_manager.py +518 -0
- a2a_server/server.py +726 -0
- a2a_server/task_manager.py +668 -0
- a2a_server/task_queue.py +742 -0
- a2a_server/tenant_api.py +333 -0
- a2a_server/tenant_middleware.py +219 -0
- a2a_server/tenant_service.py +760 -0
- a2a_server/user_auth.py +721 -0
- a2a_server/vault_client.py +576 -0
- a2a_server/worker_sse.py +873 -0
- agent_worker/__init__.py +8 -0
- agent_worker/worker.py +4877 -0
- codetether/__init__.py +10 -0
- codetether/__main__.py +4 -0
- codetether/cli.py +112 -0
- codetether/worker_cli.py +57 -0
- codetether-1.2.2.dist-info/METADATA +570 -0
- codetether-1.2.2.dist-info/RECORD +66 -0
- codetether-1.2.2.dist-info/WHEEL +5 -0
- codetether-1.2.2.dist-info/entry_points.txt +4 -0
- codetether-1.2.2.dist-info/licenses/LICENSE +202 -0
- codetether-1.2.2.dist-info/top_level.txt +5 -0
- codetether_voice_agent/__init__.py +6 -0
- codetether_voice_agent/agent.py +445 -0
- codetether_voice_agent/codetether_mcp.py +345 -0
- codetether_voice_agent/config.py +16 -0
- codetether_voice_agent/functiongemma_caller.py +380 -0
- codetether_voice_agent/session_playback.py +247 -0
- codetether_voice_agent/tools/__init__.py +21 -0
- codetether_voice_agent/tools/definitions.py +135 -0
- codetether_voice_agent/tools/handlers.py +380 -0
- run_server.py +314 -0
- ui/monitor-tailwind.html +1790 -0
- ui/monitor.html +1775 -0
- ui/monitor.js +2662 -0
a2a_server/mcp_client.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Client for A2A agents to interact with tools.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any, Dict, List, Optional, Union
|
|
9
|
+
import subprocess
|
|
10
|
+
import os
|
|
11
|
+
import signal
|
|
12
|
+
|
|
13
|
+
from mcp.client import ClientSession, StdioServerParameters
|
|
14
|
+
from mcp.types import CallToolRequest, ListToolsRequest
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MCPClient:
|
|
20
|
+
"""MCP client for A2A agents to use tools."""
|
|
21
|
+
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self.session: Optional[ClientSession] = None
|
|
24
|
+
self.tools: List[Dict[str, Any]] = []
|
|
25
|
+
self._server_process: Optional[subprocess.Popen] = None
|
|
26
|
+
|
|
27
|
+
async def connect(self) -> bool:
|
|
28
|
+
"""Connect to the MCP server."""
|
|
29
|
+
try:
|
|
30
|
+
# Start the MCP server as a subprocess
|
|
31
|
+
self._server_process = subprocess.Popen(
|
|
32
|
+
["python", "-m", "a2a_server.mcp_server"],
|
|
33
|
+
stdin=subprocess.PIPE,
|
|
34
|
+
stdout=subprocess.PIPE,
|
|
35
|
+
stderr=subprocess.PIPE,
|
|
36
|
+
text=True,
|
|
37
|
+
cwd="/home/runner/work/A2A-Server-MCP/A2A-Server-MCP"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Create client session
|
|
41
|
+
server_params = StdioServerParameters(
|
|
42
|
+
command="python",
|
|
43
|
+
args=["-m", "a2a_server.mcp_server"],
|
|
44
|
+
cwd="/home/runner/work/A2A-Server-MCP/A2A-Server-MCP"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
self.session = ClientSession(server_params)
|
|
48
|
+
await self.session.initialize()
|
|
49
|
+
|
|
50
|
+
# Get available tools
|
|
51
|
+
await self._load_tools()
|
|
52
|
+
|
|
53
|
+
logger.info(f"Connected to MCP server with {len(self.tools)} tools")
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
except Exception as e:
|
|
57
|
+
logger.error(f"Failed to connect to MCP server: {e}")
|
|
58
|
+
await self.disconnect()
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
async def disconnect(self):
|
|
62
|
+
"""Disconnect from the MCP server."""
|
|
63
|
+
if self.session:
|
|
64
|
+
await self.session.close()
|
|
65
|
+
self.session = None
|
|
66
|
+
|
|
67
|
+
if self._server_process:
|
|
68
|
+
try:
|
|
69
|
+
self._server_process.terminate()
|
|
70
|
+
self._server_process.wait(timeout=5)
|
|
71
|
+
except subprocess.TimeoutExpired:
|
|
72
|
+
self._server_process.kill()
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger.error(f"Error stopping MCP server process: {e}")
|
|
75
|
+
finally:
|
|
76
|
+
self._server_process = None
|
|
77
|
+
|
|
78
|
+
async def _load_tools(self):
|
|
79
|
+
"""Load available tools from the MCP server."""
|
|
80
|
+
if not self.session:
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
tools_response = await self.session.list_tools()
|
|
85
|
+
self.tools = [tool.model_dump() for tool in tools_response.tools]
|
|
86
|
+
logger.info(f"Loaded {len(self.tools)} tools: {[tool['name'] for tool in self.tools]}")
|
|
87
|
+
except Exception as e:
|
|
88
|
+
logger.error(f"Failed to load tools: {e}")
|
|
89
|
+
self.tools = []
|
|
90
|
+
|
|
91
|
+
async def get_available_tools(self) -> List[Dict[str, Any]]:
|
|
92
|
+
"""Get list of available tools."""
|
|
93
|
+
return self.tools.copy()
|
|
94
|
+
|
|
95
|
+
async def call_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
96
|
+
"""Call a tool with the given arguments."""
|
|
97
|
+
if not self.session:
|
|
98
|
+
return {"error": "Not connected to MCP server"}
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
# Find the tool
|
|
102
|
+
tool = next((t for t in self.tools if t['name'] == tool_name), None)
|
|
103
|
+
if not tool:
|
|
104
|
+
return {"error": f"Tool '{tool_name}' not found"}
|
|
105
|
+
|
|
106
|
+
# Call the tool
|
|
107
|
+
request = CallToolRequest(name=tool_name, arguments=arguments)
|
|
108
|
+
response = await self.session.call_tool(request)
|
|
109
|
+
|
|
110
|
+
# Process response
|
|
111
|
+
if response.content:
|
|
112
|
+
# Extract text content
|
|
113
|
+
result_text = ""
|
|
114
|
+
for content in response.content:
|
|
115
|
+
if hasattr(content, 'text'):
|
|
116
|
+
result_text += content.text
|
|
117
|
+
elif hasattr(content, 'content'):
|
|
118
|
+
result_text += str(content.content)
|
|
119
|
+
else:
|
|
120
|
+
result_text += str(content)
|
|
121
|
+
|
|
122
|
+
# Try to parse as JSON
|
|
123
|
+
try:
|
|
124
|
+
result = json.loads(result_text)
|
|
125
|
+
except json.JSONDecodeError:
|
|
126
|
+
result = {"result": result_text}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
"success": True,
|
|
130
|
+
"tool": tool_name,
|
|
131
|
+
"arguments": arguments,
|
|
132
|
+
"result": result
|
|
133
|
+
}
|
|
134
|
+
else:
|
|
135
|
+
return {
|
|
136
|
+
"success": True,
|
|
137
|
+
"tool": tool_name,
|
|
138
|
+
"arguments": arguments,
|
|
139
|
+
"result": "No content returned"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
logger.error(f"Error calling tool {tool_name}: {e}")
|
|
144
|
+
return {
|
|
145
|
+
"error": f"Tool call failed: {str(e)}",
|
|
146
|
+
"tool": tool_name,
|
|
147
|
+
"arguments": arguments
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async def calculator(self, operation: str, a: float, b: Optional[float] = None) -> Dict[str, Any]:
|
|
151
|
+
"""Convenience method for calculator tool."""
|
|
152
|
+
args = {"operation": operation, "a": a}
|
|
153
|
+
if b is not None:
|
|
154
|
+
args["b"] = b
|
|
155
|
+
return await self.call_tool("calculator", args)
|
|
156
|
+
|
|
157
|
+
async def get_weather(self, location: str) -> Dict[str, Any]:
|
|
158
|
+
"""Convenience method for weather tool."""
|
|
159
|
+
return await self.call_tool("weather_info", {"location": location})
|
|
160
|
+
|
|
161
|
+
async def analyze_text(self, text: str) -> Dict[str, Any]:
|
|
162
|
+
"""Convenience method for text analyzer tool."""
|
|
163
|
+
return await self.call_tool("text_analyzer", {"text": text})
|
|
164
|
+
|
|
165
|
+
async def memory_operation(self, action: str, key: Optional[str] = None, value: Optional[str] = None) -> Dict[str, Any]:
|
|
166
|
+
"""Convenience method for memory store tool."""
|
|
167
|
+
args = {"action": action}
|
|
168
|
+
if key is not None:
|
|
169
|
+
args["key"] = key
|
|
170
|
+
if value is not None:
|
|
171
|
+
args["value"] = value
|
|
172
|
+
return await self.call_tool("memory_store", args)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# Global MCP client instance
|
|
176
|
+
_mcp_client: Optional[MCPClient] = None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
async def get_mcp_client() -> MCPClient:
|
|
180
|
+
"""Get or create the global MCP client instance."""
|
|
181
|
+
global _mcp_client
|
|
182
|
+
|
|
183
|
+
if _mcp_client is None:
|
|
184
|
+
_mcp_client = MCPClient()
|
|
185
|
+
await _mcp_client.connect()
|
|
186
|
+
|
|
187
|
+
return _mcp_client
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def cleanup_mcp_client():
|
|
191
|
+
"""Clean up the global MCP client instance."""
|
|
192
|
+
global _mcp_client
|
|
193
|
+
|
|
194
|
+
if _mcp_client:
|
|
195
|
+
await _mcp_client.disconnect()
|
|
196
|
+
_mcp_client = None
|