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.
Files changed (66) hide show
  1. a2a_server/__init__.py +29 -0
  2. a2a_server/a2a_agent_card.py +365 -0
  3. a2a_server/a2a_errors.py +1133 -0
  4. a2a_server/a2a_executor.py +926 -0
  5. a2a_server/a2a_router.py +1033 -0
  6. a2a_server/a2a_types.py +344 -0
  7. a2a_server/agent_card.py +408 -0
  8. a2a_server/agents_server.py +271 -0
  9. a2a_server/auth_api.py +349 -0
  10. a2a_server/billing_api.py +638 -0
  11. a2a_server/billing_service.py +712 -0
  12. a2a_server/billing_webhooks.py +501 -0
  13. a2a_server/config.py +96 -0
  14. a2a_server/database.py +2165 -0
  15. a2a_server/email_inbound.py +398 -0
  16. a2a_server/email_notifications.py +486 -0
  17. a2a_server/enhanced_agents.py +919 -0
  18. a2a_server/enhanced_server.py +160 -0
  19. a2a_server/hosted_worker.py +1049 -0
  20. a2a_server/integrated_agents_server.py +347 -0
  21. a2a_server/keycloak_auth.py +750 -0
  22. a2a_server/livekit_bridge.py +439 -0
  23. a2a_server/marketing_tools.py +1364 -0
  24. a2a_server/mcp_client.py +196 -0
  25. a2a_server/mcp_http_server.py +2256 -0
  26. a2a_server/mcp_server.py +191 -0
  27. a2a_server/message_broker.py +725 -0
  28. a2a_server/mock_mcp.py +273 -0
  29. a2a_server/models.py +494 -0
  30. a2a_server/monitor_api.py +5904 -0
  31. a2a_server/opencode_bridge.py +1594 -0
  32. a2a_server/redis_task_manager.py +518 -0
  33. a2a_server/server.py +726 -0
  34. a2a_server/task_manager.py +668 -0
  35. a2a_server/task_queue.py +742 -0
  36. a2a_server/tenant_api.py +333 -0
  37. a2a_server/tenant_middleware.py +219 -0
  38. a2a_server/tenant_service.py +760 -0
  39. a2a_server/user_auth.py +721 -0
  40. a2a_server/vault_client.py +576 -0
  41. a2a_server/worker_sse.py +873 -0
  42. agent_worker/__init__.py +8 -0
  43. agent_worker/worker.py +4877 -0
  44. codetether/__init__.py +10 -0
  45. codetether/__main__.py +4 -0
  46. codetether/cli.py +112 -0
  47. codetether/worker_cli.py +57 -0
  48. codetether-1.2.2.dist-info/METADATA +570 -0
  49. codetether-1.2.2.dist-info/RECORD +66 -0
  50. codetether-1.2.2.dist-info/WHEEL +5 -0
  51. codetether-1.2.2.dist-info/entry_points.txt +4 -0
  52. codetether-1.2.2.dist-info/licenses/LICENSE +202 -0
  53. codetether-1.2.2.dist-info/top_level.txt +5 -0
  54. codetether_voice_agent/__init__.py +6 -0
  55. codetether_voice_agent/agent.py +445 -0
  56. codetether_voice_agent/codetether_mcp.py +345 -0
  57. codetether_voice_agent/config.py +16 -0
  58. codetether_voice_agent/functiongemma_caller.py +380 -0
  59. codetether_voice_agent/session_playback.py +247 -0
  60. codetether_voice_agent/tools/__init__.py +21 -0
  61. codetether_voice_agent/tools/definitions.py +135 -0
  62. codetether_voice_agent/tools/handlers.py +380 -0
  63. run_server.py +314 -0
  64. ui/monitor-tailwind.html +1790 -0
  65. ui/monitor.html +1775 -0
  66. ui/monitor.js +2662 -0
@@ -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