agent-mcp 0.1.2__py3-none-any.whl → 0.1.4__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 (53) hide show
  1. agent_mcp/__init__.py +16 -0
  2. agent_mcp/camel_mcp_adapter.py +521 -0
  3. agent_mcp/cli.py +47 -0
  4. agent_mcp/crewai_mcp_adapter.py +281 -0
  5. agent_mcp/enhanced_mcp_agent.py +601 -0
  6. agent_mcp/heterogeneous_group_chat.py +798 -0
  7. agent_mcp/langchain_mcp_adapter.py +458 -0
  8. agent_mcp/langgraph_mcp_adapter.py +325 -0
  9. agent_mcp/mcp_agent.py +658 -0
  10. agent_mcp/mcp_decorator.py +257 -0
  11. agent_mcp/mcp_langgraph.py +733 -0
  12. agent_mcp/mcp_transaction.py +97 -0
  13. agent_mcp/mcp_transport.py +706 -0
  14. agent_mcp/mcp_transport_enhanced.py +46 -0
  15. agent_mcp/proxy_agent.py +24 -0
  16. agent_mcp-0.1.4.dist-info/METADATA +333 -0
  17. agent_mcp-0.1.4.dist-info/RECORD +49 -0
  18. {agent_mcp-0.1.2.dist-info → agent_mcp-0.1.4.dist-info}/WHEEL +1 -1
  19. agent_mcp-0.1.4.dist-info/entry_points.txt +2 -0
  20. agent_mcp-0.1.4.dist-info/top_level.txt +3 -0
  21. demos/__init__.py +1 -0
  22. demos/basic/__init__.py +1 -0
  23. demos/basic/framework_examples.py +108 -0
  24. demos/basic/langchain_camel_demo.py +272 -0
  25. demos/basic/simple_chat.py +355 -0
  26. demos/basic/simple_integration_example.py +51 -0
  27. demos/collaboration/collaborative_task_example.py +437 -0
  28. demos/collaboration/group_chat_example.py +130 -0
  29. demos/collaboration/simplified_crewai_example.py +39 -0
  30. demos/langgraph/autonomous_langgraph_network.py +808 -0
  31. demos/langgraph/langgraph_agent_network.py +415 -0
  32. demos/langgraph/langgraph_collaborative_task.py +619 -0
  33. demos/langgraph/langgraph_example.py +227 -0
  34. demos/langgraph/run_langgraph_examples.py +213 -0
  35. demos/network/agent_network_example.py +381 -0
  36. demos/network/email_agent.py +130 -0
  37. demos/network/email_agent_demo.py +46 -0
  38. demos/network/heterogeneous_network_example.py +216 -0
  39. demos/network/multi_framework_example.py +199 -0
  40. demos/utils/check_imports.py +49 -0
  41. demos/workflows/autonomous_agent_workflow.py +248 -0
  42. demos/workflows/mcp_features_demo.py +353 -0
  43. demos/workflows/run_agent_collaboration_demo.py +63 -0
  44. demos/workflows/run_agent_collaboration_with_logs.py +396 -0
  45. demos/workflows/show_agent_interactions.py +107 -0
  46. demos/workflows/simplified_autonomous_demo.py +74 -0
  47. functions/main.py +144 -0
  48. functions/mcp_network_server.py +513 -0
  49. functions/utils.py +47 -0
  50. agent_mcp-0.1.2.dist-info/METADATA +0 -475
  51. agent_mcp-0.1.2.dist-info/RECORD +0 -5
  52. agent_mcp-0.1.2.dist-info/entry_points.txt +0 -2
  53. agent_mcp-0.1.2.dist-info/top_level.txt +0 -1
@@ -0,0 +1,281 @@
1
+ """
2
+ CrewAI MCP Adapter - Adapt CrewAI agents to work with MCP.
3
+
4
+ This module provides an adapter that allows CrewAI agents to work within
5
+ the Model Context Protocol (MCP) framework, enabling them to collaborate
6
+ with agents from other frameworks like Autogen and Langchain.
7
+ """
8
+
9
+ import asyncio
10
+ from typing import Dict, Any, Optional, Callable
11
+ from crewai import Agent as CrewAgent
12
+ from fastapi import FastAPI, Request
13
+ from .mcp_agent import MCPAgent
14
+ from .mcp_transport import HTTPTransport
15
+ import uvicorn
16
+ from threading import Thread
17
+ import time
18
+
19
+ class CrewAIMCPAdapter(MCPAgent):
20
+ """
21
+ Adapter for CrewAI agents to work with MCP.
22
+
23
+ This adapter wraps a CrewAI agent and makes it compatible with the MCP framework,
24
+ allowing it to communicate with other agents through the transport layer.
25
+ """
26
+
27
+ def __init__(
28
+ self,
29
+ name: str,
30
+ crewai_agent: CrewAgent,
31
+ process_message: Optional[Callable] = None,
32
+ transport: Optional[HTTPTransport] = None,
33
+ client_mode: bool = True,
34
+ **kwargs
35
+ ):
36
+ """
37
+ Initialize the CrewAI MCP adapter.
38
+
39
+ Args:
40
+ name: Name of the agent
41
+ crewai_agent: CrewAI agent to adapt
42
+ process_message: Optional custom message processing function
43
+ transport: Optional transport layer
44
+ client_mode: Whether to run in client mode
45
+ **kwargs: Additional arguments to pass to MCPAgent
46
+ """
47
+ super().__init__(name=name, **kwargs)
48
+
49
+ self.crewai_agent = crewai_agent
50
+ self.custom_process_message = process_message
51
+ self.transport = transport
52
+ self.client_mode = client_mode
53
+ self.task_queue = asyncio.Queue()
54
+ self.server_ready = asyncio.Event()
55
+
56
+ # Create FastAPI app for server mode
57
+ self.app = FastAPI()
58
+
59
+ @self.app.post("/message")
60
+ async def handle_message(request: Request):
61
+ return await self._handle_message(request)
62
+
63
+ @self.app.on_event("startup")
64
+ async def startup_event():
65
+ self.server_ready.set()
66
+
67
+ self.server_thread = None
68
+
69
+ async def _handle_message(self, request: Request):
70
+ """Handle incoming HTTP messages"""
71
+ try:
72
+ message = await request.json()
73
+ await self.task_queue.put(message)
74
+ return {"status": "ok"}
75
+ except Exception as e:
76
+ return {"status": "error", "message": str(e)}
77
+
78
+ async def process_messages(self):
79
+ """Process incoming messages from the transport layer"""
80
+ while True:
81
+ try:
82
+ message, message_id = await self.transport.receive_message()
83
+ print(f"{self.name}: Received message {message_id}: {message}")
84
+
85
+ if message and isinstance(message, dict):
86
+ # Add message_id to message for tracking
87
+ message['message_id'] = message_id
88
+
89
+ # Standardize message structure
90
+ if 'content' not in message and message.get('type') == 'task':
91
+ message = {
92
+ 'type': 'task',
93
+ 'content': {
94
+ 'task_id': message.get('task_id'),
95
+ 'description': message.get('description'),
96
+ 'type': 'task'
97
+ },
98
+ 'message_id': message_id,
99
+ 'from': message.get('from', 'unknown')
100
+ }
101
+
102
+ # --- Idempotency Check ---
103
+ if not super()._should_process_message(message):
104
+ # If skipped, acknowledge and continue
105
+ if message_id and self.transport:
106
+ asyncio.create_task(self.transport.acknowledge_message(self.name, message_id))
107
+ print(f"[{self.name}] Acknowledged duplicate task {message.get('task_id')} (msg_id: {message_id})")
108
+ continue
109
+
110
+ if message.get("type") == "task":
111
+ print(f"{self.name}: Queueing task with message_id {message_id}")
112
+ await self.task_queue.put(message)
113
+ elif self.custom_process_message:
114
+ await self.custom_process_message(self, message)
115
+ else:
116
+ print(f"{self.name}: Unknown message type: {message.get('type')}")
117
+ # Acknowledge unknown messages
118
+ if message_id and self.transport:
119
+ await self.transport.acknowledge_message(self.name, message_id)
120
+ print(f"{self.name}: Acknowledged unknown message {message_id}")
121
+ except asyncio.CancelledError:
122
+ print(f"{self.name}: Message processor cancelled")
123
+ break
124
+ except Exception as e:
125
+ print(f"{self.name}: Error processing message: {e}")
126
+ traceback.print_exc()
127
+ await asyncio.sleep(1)
128
+
129
+ async def process_tasks(self):
130
+ """Process tasks from the queue using the CrewAI agent"""
131
+ while True:
132
+ try:
133
+ task = await self.task_queue.get()
134
+ task_id = task.get('task', {}).get('task_id')
135
+ message_id = task.get('message_id')
136
+
137
+ print(f"\n{self.name}: Processing task {task_id} with message_id {message_id}")
138
+
139
+ try:
140
+ # Extract task details from content or root level
141
+ # Standardized task extraction
142
+ # Unified content extraction with backward compatibility
143
+ task_content = task.get('content', task.get('task', {}))
144
+ task_id = task_content.get('task_id')
145
+ task_description = task_content.get('description')
146
+
147
+ # Validate required fields
148
+ if not all([task_id, task_description]):
149
+ raise ValueError(f"Missing required task fields in message {message_id}")
150
+ message_id = task.get('message_id')
151
+ reply_to = task.get('reply_to')
152
+
153
+ if not task_id or not task_description:
154
+ print(f"[{self.name}] ERROR: Invalid task structure received: {task}")
155
+ # Acknowledge bad tasks
156
+ if message_id and self.transport:
157
+ await self.transport.acknowledge_message(self.name, message_id)
158
+ print(f"[ERROR] {self.name}: Task missing required fields: {task}")
159
+ self.task_queue.task_done()
160
+ continue
161
+
162
+ print(f"\n{self.name}: Processing task {task_id} (from msg {message_id}) Desc: {task_description}")
163
+
164
+ result = await self.execute_task(task_description)
165
+
166
+ # --- Mark task completed (Uses Base Class Method) ---
167
+ super()._mark_task_completed(task_id)
168
+ # --- End mark task completed ---
169
+
170
+ # Send result back if reply_to is specified
171
+ if reply_to:
172
+ await self.transport.send_message(
173
+ reply_to,
174
+ {
175
+ "type": "task_result",
176
+ "task_id": task_id,
177
+ "result": result,
178
+ "sender": self.name,
179
+ "original_message_id": message_id # Include original message ID
180
+ }
181
+ )
182
+ print(f"{self.name}: Result sent successfully")
183
+
184
+ # Acknowledge task completion
185
+ if message_id:
186
+ await self.transport.acknowledge_message(self.name, message_id)
187
+ print(f"{self.name}: Task {task_id} acknowledged with message_id {message_id}")
188
+ else:
189
+ print(f"{self.name}: No message_id for task {task_id}, cannot acknowledge")
190
+ except Exception as e:
191
+ print(f"{self.name}: Error executing task: {e}")
192
+ traceback.print_exc()
193
+
194
+ # Send error result back if reply_to is specified
195
+ if reply_to:
196
+ await self.transport.send_message(
197
+ task['reply_to'],
198
+ {
199
+ "type": "task_result",
200
+ "task_id": task_id,
201
+ "result": f"Error: {str(e)}",
202
+ "sender": self.name,
203
+ "original_message_id": message_id,
204
+ "error": True
205
+ }
206
+ )
207
+
208
+ self.task_queue.task_done()
209
+
210
+ except Exception as e:
211
+ print(f"{self.name}: Error processing task: {e}")
212
+ traceback.print_exc()
213
+ await asyncio.sleep(1)
214
+
215
+ async def execute_task(self, task_description: str) -> str:
216
+ """
217
+ Execute a task using the CrewAI agent.
218
+
219
+ Args:
220
+ task_description: Description of the task to execute
221
+
222
+ Returns:
223
+ The result of the task execution
224
+ """
225
+ try:
226
+ # Execute task using CrewAI agent
227
+ result = await asyncio.to_thread(
228
+ self.crewai_agent.execute,
229
+ task_description
230
+ )
231
+ return str(result)
232
+ except Exception as e:
233
+ return f"Error executing task: {e}"
234
+
235
+ def run(self):
236
+ """Start the message and task processors"""
237
+ if not self.transport:
238
+ raise ValueError(f"{self.name}: No transport configured")
239
+
240
+ # Start the transport server if not in client mode
241
+ if not self.client_mode:
242
+ def run_server():
243
+ config = uvicorn.Config(
244
+ app=self.app,
245
+ host=self.transport.host,
246
+ port=self.transport.port,
247
+ log_level="info"
248
+ )
249
+ server = uvicorn.Server(config)
250
+ server.run()
251
+
252
+ self.server_thread = Thread(target=run_server, daemon=True)
253
+ self.server_thread.start()
254
+ else:
255
+ # In client mode, we're ready immediately
256
+ self.server_ready.set()
257
+
258
+ print(f"{self.name}: Starting message processor...")
259
+ asyncio.create_task(self.process_messages())
260
+
261
+ print(f"{self.name}: Starting task processor...")
262
+ asyncio.create_task(self.process_tasks())
263
+
264
+ async def connect_to_server(self, server_url: str):
265
+ """Connect to a coordinator server"""
266
+ if not self.client_mode:
267
+ raise ValueError("Agent not configured for client mode")
268
+
269
+ # Wait for server to be ready before connecting
270
+ if not self.server_ready.is_set():
271
+ await asyncio.wait_for(self.server_ready.wait(), timeout=10)
272
+
273
+ # Register with the coordinator
274
+ await self.transport.send_message(
275
+ server_url,
276
+ {
277
+ "type": "register",
278
+ "agent_name": self.name,
279
+ "agent_url": self.transport.get_url()
280
+ }
281
+ )