agent-mcp 0.1.3__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 (44) hide show
  1. agent_mcp/__init__.py +2 -2
  2. agent_mcp/camel_mcp_adapter.py +521 -0
  3. agent_mcp/cli.py +47 -0
  4. agent_mcp/heterogeneous_group_chat.py +412 -38
  5. agent_mcp/langchain_mcp_adapter.py +176 -43
  6. agent_mcp/mcp_agent.py +26 -0
  7. agent_mcp/mcp_transport.py +11 -5
  8. {agent_mcp-0.1.3.dist-info → agent_mcp-0.1.4.dist-info}/METADATA +6 -4
  9. agent_mcp-0.1.4.dist-info/RECORD +49 -0
  10. {agent_mcp-0.1.3.dist-info → agent_mcp-0.1.4.dist-info}/WHEEL +1 -1
  11. agent_mcp-0.1.4.dist-info/entry_points.txt +2 -0
  12. agent_mcp-0.1.4.dist-info/top_level.txt +3 -0
  13. demos/__init__.py +1 -0
  14. demos/basic/__init__.py +1 -0
  15. demos/basic/framework_examples.py +108 -0
  16. demos/basic/langchain_camel_demo.py +272 -0
  17. demos/basic/simple_chat.py +355 -0
  18. demos/basic/simple_integration_example.py +51 -0
  19. demos/collaboration/collaborative_task_example.py +437 -0
  20. demos/collaboration/group_chat_example.py +130 -0
  21. demos/collaboration/simplified_crewai_example.py +39 -0
  22. demos/langgraph/autonomous_langgraph_network.py +808 -0
  23. demos/langgraph/langgraph_agent_network.py +415 -0
  24. demos/langgraph/langgraph_collaborative_task.py +619 -0
  25. demos/langgraph/langgraph_example.py +227 -0
  26. demos/langgraph/run_langgraph_examples.py +213 -0
  27. demos/network/agent_network_example.py +381 -0
  28. demos/network/email_agent.py +130 -0
  29. demos/network/email_agent_demo.py +46 -0
  30. demos/network/heterogeneous_network_example.py +216 -0
  31. demos/network/multi_framework_example.py +199 -0
  32. demos/utils/check_imports.py +49 -0
  33. demos/workflows/autonomous_agent_workflow.py +248 -0
  34. demos/workflows/mcp_features_demo.py +353 -0
  35. demos/workflows/run_agent_collaboration_demo.py +63 -0
  36. demos/workflows/run_agent_collaboration_with_logs.py +396 -0
  37. demos/workflows/show_agent_interactions.py +107 -0
  38. demos/workflows/simplified_autonomous_demo.py +74 -0
  39. functions/main.py +144 -0
  40. functions/mcp_network_server.py +513 -0
  41. functions/utils.py +47 -0
  42. agent_mcp-0.1.3.dist-info/RECORD +0 -18
  43. agent_mcp-0.1.3.dist-info/entry_points.txt +0 -2
  44. agent_mcp-0.1.3.dist-info/top_level.txt +0 -1
@@ -10,6 +10,7 @@ from langchain.agents import AgentExecutor
10
10
  from langchain.agents.openai_functions_agent.base import OpenAIFunctionsAgent
11
11
  import traceback
12
12
  import json
13
+ import uuid
13
14
 
14
15
  # --- Setup Logger ---
15
16
  import logging
@@ -66,12 +67,13 @@ class LangchainMCPAdapter(MCPAgent):
66
67
  """Handle incoming messages from other agents"""
67
68
  # First check if type is directly in the message
68
69
  msg_type = message.get("type")
69
-
70
+ logger.info(f"[{self.name}] Raw message: {message}")
71
+
70
72
  # If not, check if it's inside the content field
71
73
  if not msg_type and "content" in message and isinstance(message["content"], dict):
72
74
  msg_type = message["content"].get("type")
73
75
 
74
- sender = message.get("sender", "Unknown")
76
+ sender = self._extract_sender(message)
75
77
  task_id = message.get("task_id") or message.get("content", {}).get("task_id") if isinstance(message.get("content"), dict) else message.get("task_id")
76
78
  logger.info(f"[{self.name}] Received message (ID: {message_id}) of type '{msg_type}' from {sender} (Task ID: {task_id})")
77
79
 
@@ -87,24 +89,118 @@ class LangchainMCPAdapter(MCPAgent):
87
89
  if msg_type == "task":
88
90
  logger.info(f"[{self.name}] Queueing task {task_id} (message_id: {message_id}) from {sender}")
89
91
  content = message.get("content", {})
90
- task_id = content.get("task_id") or message.get("task_id")
92
+ current_task_id = content.get("task_id") or message.get("task_id") # Handle potential nesting
91
93
  description = content.get("description") or message.get("description")
92
- reply_to = content.get("reply_to")
93
-
94
- if not task_id or not description:
95
- print(f"[ERROR] {self.name}: Task message missing required fields: {message}")
94
+ reply_to = content.get("reply_to") or message.get("reply_to")
95
+
96
+ if not current_task_id or not description:
97
+ logger.error(f"[{self.name}] Task message missing required fields: {message}")
98
+ # Acknowledge if possible to prevent reprocessing bad message
99
+ if message_id and self.transport:
100
+ asyncio.create_task(self.transport.acknowledge_message(self.name, message_id))
96
101
  return
97
-
98
- # Add message_id to task
102
+
103
+ # Add message_id to task context for processing
99
104
  message['message_id'] = message_id
100
-
101
- # Queue task for async processing
102
- print(f"[DEBUG] {self.name}: Queueing task {task_id} with message_id {message_id} for processing")
105
+
106
+ #task_context = {
107
+ # "type": "task", # Ensure type is explicitly set for process_tasks
108
+ # "task_id": current_task_id,
109
+ # "description": description,
110
+ # "reply_to": reply_to,
111
+ # "sender": sender,
112
+ # "message_id": message_id
113
+ #}
114
+ #logger.debug(f"[{self.name}] Queueing task context: {task_context}")
115
+ logger.debug(f"[DEBUG] {self.name}: Queueing task {task_id} with message_id {message_id} for processing")
116
+
103
117
  await self.task_queue.put(message)
104
- print(f"[DEBUG] {self.name}: Successfully queued task {task_id}")
105
- else:
106
- print(f"[WARN] {self.name}: Received unknown message type: {msg_type}")
118
+ logger.debug(f"[{self.name}] Successfully queued task {current_task_id}")
119
+
120
+ elif msg_type == "task_result":
121
+ # Received a result, treat it as the next step in the conversation
122
+ result_content = message.get("result")
123
+
124
+ # --- Robust extraction for various formats ---
125
+ content = message.get("content")
126
+ if result_content is None and content is not None:
127
+ # 1. Try content["result"]
128
+ if isinstance(content, dict) and "result" in content:
129
+ result_content = content["result"]
130
+ # 2. Try content["text"] as JSON
131
+ elif isinstance(content, dict) and "text" in content:
132
+ text_val = content["text"]
133
+ if isinstance(text_val, str):
134
+ try:
135
+ parsed = json.loads(text_val)
136
+ if isinstance(parsed, dict) and "result" in parsed:
137
+ result_content = parsed["result"]
138
+ except Exception:
139
+ pass
140
+ # 3. Try content itself as JSON string
141
+ elif isinstance(content, str):
142
+ try:
143
+ parsed = json.loads(content)
144
+ if isinstance(parsed, dict) and "result" in parsed:
145
+ result_content = parsed["result"]
146
+ except Exception:
147
+ pass
148
+ # 4. Fallback: use content["text"] as plain string
149
+ if result_content is None and isinstance(content, dict) and "text" in content:
150
+ result_content = content["text"]
151
+
152
+ # Handle JSON string content
153
+ if isinstance(result_content, str):
154
+ try:
155
+ result_content = json.loads(result_content)
156
+ except json.JSONDecodeError:
157
+ pass
158
+
159
+ # Direct parsing of content["text"] structure
160
+ if isinstance(result_content, str):
161
+ try:
162
+ text_content = json.loads(result_content)
163
+ if isinstance(text_content, dict):
164
+ result_content = text_content
165
+ except json.JSONDecodeError:
166
+ pass
167
+
168
+ # --- End Robust extraction ---
169
+ original_task_id = (
170
+ (result_content.get("task_id") if isinstance(result_content, dict) else None)
171
+ or message.get("task_id")
172
+ )
173
+ logger.info(f"[{self.name}] Received task_result from {sender} for task {original_task_id}. Content: '{str(result_content)[:100]}...'")
174
+
175
+ if not result_content:
176
+ logger.warning(f"[{self.name}] Received task_result from {sender} with empty content.")
107
177
 
178
+ # Acknowledge the result message even if content is empty
179
+ if message_id and self.transport:
180
+ asyncio.create_task(self.transport.acknowledge_message(self.name, message_id))
181
+ return
182
+
183
+ # Create a *new* task for this agent based on the received result
184
+ #new_task_id = f"conv_{uuid.uuid4()}" # Generate a new ID for this conversational turn
185
+ #new_task_context = {
186
+ # "type": "task", # Still a task for this agent to process
187
+ # "task_id": new_task_id,
188
+ # "description": str(result_content), # The result becomes the new input/description
189
+ # "reply_to": message.get("reply_to") or result_content.get("reply_to"),
190
+ # "sender": sender, # This agent is the conceptual sender of this internal task
191
+ # "message_id": message_id # Carry over original message ID for acknowledgement
192
+ #}
193
+
194
+ #logger.info(f"[{self.name}] Queueing new conversational task {new_task_id} based on result from {sender}")
195
+ #await self.task_queue.put(new_task_context)
196
+ #logger.debug(f"[{self.name}] Successfully queued new task {new_task_id}")
197
+
198
+ else:
199
+ logger.warning(f"[{self.name}] Received unknown message type: {msg_type}. Message: {message}")
200
+ # Acknowledge other message types immediately if they have an ID
201
+ #if message_id and self.transport:
202
+ # asyncio.create_task(self.transport.acknowledge_message(self.name, message_id))
203
+
108
204
  async def _handle_task(self, message: Dict[str, Any]):
109
205
  """Handle incoming task"""
110
206
  print(f"{self.name}: Received task: {message}")
@@ -112,14 +208,14 @@ class LangchainMCPAdapter(MCPAgent):
112
208
  return {"status": "ok"}
113
209
 
114
210
  async def process_messages(self):
115
- print(f"[{self.name}] Message processor loop started.")
211
+ logger.info(f"[{self.name}] Message processor loop started.")
116
212
  while True:
117
213
  try:
118
- print(f"[{self.name}] Waiting for message from queue...")
214
+ logger.debug(f"[{self.name}] Waiting for message from transport...")
215
+ # Pass agent name to receive_message
119
216
  message, message_id = await self.transport.receive_message()
120
- print(f"{self.name}: Processing message {message_id}: {message}")
121
-
122
- # Skip None messages
217
+ logger.debug(f"[{self.name}] Received raw message from transport: {message} (ID: {message_id})")
218
+
123
219
  if message is None:
124
220
  print(f"[{self.name}] Received None message, skipping...")
125
221
  continue
@@ -143,48 +239,77 @@ class LangchainMCPAdapter(MCPAgent):
143
239
  try:
144
240
  print(f"[{self.name}] Waiting for task from queue...")
145
241
  task = await self.task_queue.get()
146
- print(f"\n[{self.name}] Got task from queue: {task}")
242
+ print(f"\n[{self.name}] Got item from queue: {task}")
147
243
 
148
244
  if not isinstance(task, dict):
149
- print(f"[ERROR] {self.name}: Task is not a dictionary: {task}")
245
+ print(f"[ERROR] {self.name}: Task item is not a dictionary: {task}")
150
246
  self.task_queue.task_done()
151
247
  continue
152
248
 
153
- # Get task details from content field if present
154
- content = task.get("content", {})
155
- task_desc = content.get("description") or task.get("description")
156
- task_id = content.get("task_id") or task.get("task_id")
157
- task_type = content.get("type") or task.get("type")
158
- reply_to = content.get("reply_to") or task.get("reply_to")
159
-
160
- print(f"[DEBUG] {self.name}: Task details:")
161
- print(f" - Type: {task_type}")
249
+ # Extract task details (handle both original message format and task_context format)
250
+ task_desc = task.get("description")
251
+ task_id = task.get("task_id")
252
+ task_type = task.get("type") # Should always be 'task' if queued correctly
253
+ reply_to = task.get("reply_to")
254
+ message_id = task.get("message_id") # For acknowledgement
255
+ sender = self._extract_sender(task)
256
+ # Fallback for nested content (less likely now but safe)
257
+ if not task_desc and isinstance(task.get("content"), dict):
258
+ content = task.get("content", {})
259
+ task_desc = content.get("description")
260
+ if not task_id: task_id = content.get("task_id")
261
+ if not task_type: task_type = content.get("type")
262
+ if not reply_to: reply_to = content.get("reply_to")
263
+ if not sender: sender = content.get("sender", "from")
264
+
265
+ print(f"[DEBUG] {self.name}: Processing task details:")
162
266
  print(f" - Task ID: {task_id}")
267
+ print(f" - Type: {task_type}")
268
+ print(f" - Sender: {sender}")
163
269
  print(f" - Reply To: {reply_to}")
164
- print(f" - Description: {task_desc}")
270
+ print(f" - Description: {str(task_desc)[:100]}...")
271
+ print(f" - Original Message ID: {message_id}")
165
272
 
166
273
  if not task_desc or not task_id:
167
- print(f"[ERROR] {self.name}: Task is missing description or task_id")
274
+ print(f"[ERROR] {self.name}: Task is missing description or task_id: {task}")
168
275
  self.task_queue.task_done()
276
+ # Acknowledge if possible
277
+ #if message_id and self.transport:
278
+ # asyncio.create_task(self.transport.acknowledge_message(self.name, message_id))
169
279
  continue
170
280
 
281
+ # We only queue tasks now, so this check might be redundant but safe
171
282
  if task_type != "task":
172
- print(f"[ERROR] {self.name}: Invalid task type: {task_type}")
283
+ print(f"[ERROR] {self.name}: Invalid item type received in task queue: {task_type}. Item: {task}")
173
284
  self.task_queue.task_done()
285
+ #if message_id and self.transport:
286
+ # asyncio.create_task(self.transport.acknowledge_message(self.name, message_id))
174
287
  continue
175
288
 
176
289
  print(f"[DEBUG] {self.name}: Starting execution of task {task_id}")
177
290
  # Execute task using Langchain agent
178
291
  try:
179
- print(f"[DEBUG] {self.name}: Calling agent_executor.arun with task description")
180
- # Execute the task using the Langchain agent executor
181
- result = await self.agent_executor.arun(task_desc)
182
- print(f"[DEBUG] {self.name}: Agent execution completed. Result type: {type(result)}")
292
+ print(f"[DEBUG] {self.name}: Calling agent_executor.ainvoke with task description")
293
+ # Execute the task using the Langchain agent executor's ainvoke method
294
+ # Pass input AND agent_name in a dictionary matching the prompt's input variables
295
+ input_data = {
296
+ "input": task_desc,
297
+ "agent_name": self.name # Add agent_name here to indicate this agent as the executor (who's currently executing the task)
298
+ }
299
+ result_dict = await self.agent_executor.ainvoke(input_data)
300
+ print(f"[DEBUG] {self.name}: Agent execution completed. Full result: {result_dict}")
301
+ # Extract the final output string, typically under the 'output' key
302
+ if isinstance(result_dict, dict) and 'output' in result_dict:
303
+ result = result_dict['output']
304
+ print(f"[DEBUG] {self.name}: Extracted output: {result}")
305
+ else:
306
+ logger.warning(f"[{self.name}] Could not find 'output' key in agent result: {result_dict}. Using full dict as string.")
307
+ result = str(result_dict)
183
308
  except Exception as e:
184
309
  print(f"[ERROR] {self.name}: Agent execution failed: {e}")
185
310
  print(f"[ERROR] {self.name}: Error type: {type(e)}")
186
311
  traceback.print_exc() # Print the full traceback for detailed debugging
187
- # Provide a user-friendly error message as the result
312
+ # Assign error message to result variable for graceful failure
188
313
  result = f"Agent execution failed due to an error: {str(e)}"
189
314
 
190
315
  # Ensure result is always a string before sending
@@ -202,11 +327,20 @@ class LangchainMCPAdapter(MCPAgent):
202
327
  try:
203
328
  # --- FIX: Extract agent name from reply_to URL ---
204
329
  try:
205
- target_agent_name = reply_to.split('/')[-1]
330
+ # Handle both URL paths and direct agent names
331
+ if '/' in reply_to:
332
+ target_agent_name = reply_to.split('/')[-1]
333
+ else:
334
+ target_agent_name = reply_to
206
335
  except IndexError:
207
- print(f"[ERROR] {self.name}: Could not extract agent name from reply_to URL: {reply_to}")
336
+ print(f"[ERROR] {self.name}: Could not extract agent name from reply_to: {reply_to}")
208
337
  target_agent_name = reply_to # Fallback, though likely wrong
209
338
 
339
+ print(f"[DEBUG] Conversation Routing - Original sender: {reply_to}, Current agent: {self.name}, Final reply_to: {reply_to}")
340
+ print(f"[DEBUG] Derived target agent: {target_agent_name} from reply_to: {reply_to}")
341
+ print(f"[DEBUG] TASK_MESSAGE: {task}")
342
+ print(f"[DEBUG] Message Chain - From: {sender} -> To: {self.name} -> ReplyTo: {reply_to}")
343
+
210
344
  print(f"[DEBUG] {self.name}: Sending result to target agent: {target_agent_name} (extracted from {reply_to})")
211
345
  # --- END FIX ---
212
346
 
@@ -217,13 +351,12 @@ class LangchainMCPAdapter(MCPAgent):
217
351
  "task_id": task_id,
218
352
  "result": result_str,
219
353
  "sender": self.name,
220
- "original_message_id": task.get('message_id') # Include original message ID
354
+ "original_message_id": message_id # Include original message ID
221
355
  }
222
356
  )
223
357
  print(f"[DEBUG] {self.name}: Result sent successfully")
224
358
 
225
359
  # Acknowledge task completion using message_id
226
- message_id = task.get('message_id')
227
360
  if message_id:
228
361
  await self.transport.acknowledge_message(self.name, message_id)
229
362
  print(f"[DEBUG] {self.name}: Task {task_id} acknowledged with message_id {message_id}")
agent_mcp/mcp_agent.py CHANGED
@@ -630,3 +630,29 @@ class MCPAgent(ConversableAgent):
630
630
  self.completed_task_ids.add(task_id)
631
631
  else:
632
632
  logger.warning(f"[{self.name}] Attempted to mark task completed, but task_id was None.")
633
+
634
+ def _extract_sender(self, message: Dict) -> str:
635
+ """Centralized sender extraction with nested JSON support
636
+ TODO: Migrate to MessageSchema validation (GitHub Issue #1?)
637
+ """
638
+ # First check root level
639
+ if sender := message.get('sender') or message.get('from'):
640
+ return sender
641
+
642
+ content = message.get('content', {})
643
+
644
+ # Check nested JSON in content.text
645
+ if isinstance(content, dict):
646
+ try:
647
+ if text_content := content.get('text'):
648
+ parsed = json.loads(text_content)
649
+ if sender := parsed.get('sender') or parsed.get('from'):
650
+ return sender
651
+ except json.JSONDecodeError:
652
+ pass
653
+
654
+ # Fallback to content.sender
655
+ if sender := content.get('sender') or content.get('from'):
656
+ return sender
657
+
658
+ return "Unknown"
@@ -630,7 +630,7 @@ class HTTPTransport(MCPTransport):
630
630
  self.server_thread = Thread(target=run_server, daemon=True)
631
631
  self.server_thread.start()
632
632
 
633
- def stop(self):
633
+ async def stop(self):
634
634
  """Stops the local HTTP server (if running).
635
635
 
636
636
  This method gracefully shuts down the local HTTP server when operating in
@@ -639,12 +639,18 @@ class HTTPTransport(MCPTransport):
639
639
  The method ensures proper cleanup of server resources and thread termination.
640
640
  """
641
641
  if self.is_remote:
642
- logger.info(f"[{self.agent_name or 'Unknown'}] In remote mode. Call disconnect() to stop polling.")
643
- pass
642
+ logger.info(f"[{self.agent_name or 'Unknown'}] In remote mode. Call disconnect() to stop polling.")
643
+ return
644
+ # Close client session if exists
645
+ if hasattr(self, '_client_session') and self._client_session:
646
+ await self._client_session.close()
647
+ self._client_session = None
648
+
644
649
  elif self.server_thread:
645
650
  logger.info(f"Stopping local server thread (implementation pending)...")
646
- pass
647
-
651
+ self.server_thread = None # Important for GC
652
+
653
+
648
654
  def set_message_handler(self, handler: Callable):
649
655
  """Set a handler for incoming messages.
650
656
 
@@ -1,17 +1,19 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent-mcp
3
- Version: 0.1.3
4
- Summary: A bridge agent to enable agents with Model Context Protocol capabilities to be added to a Multi-agent Collaboration Network (MCN) to run on a Multi-agent Collaboration Platform (MCP)
3
+ Version: 0.1.4
4
+ Summary: A bridge agent to enable agents with Model Context Protocol capabilities to be added to a Multi-agent Collaboration Network (MCN) to run on a Multi-agent Collaboration Platform (MCP)
5
5
  Home-page: https://github.com/grupa-ai/agent-mcp
6
6
  Author: GrupaAI
7
- License: MIT
7
+ License-Expression: MIT
8
8
  Project-URL: Homepage, https://github.com/grupa-ai/agent-mcp
9
+ Project-URL: Documentation, https://github.com/grupa-ai/agent-mcp#readme
10
+ Project-URL: Repository, https://github.com/grupa-ai/agent-mcp
9
11
  Classifier: Development Status :: 3 - Alpha
10
12
  Classifier: Intended Audience :: Developers
11
- Classifier: License :: OSI Approved :: MIT License
12
13
  Classifier: Operating System :: OS Independent
13
14
  Classifier: Programming Language :: Python :: 3
14
15
  Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3 :: Only
15
17
  Requires-Python: >=3.11
16
18
  Description-Content-Type: text/markdown
17
19
  Requires-Dist: autogen
@@ -0,0 +1,49 @@
1
+ agent_mcp/__init__.py,sha256=CvLYkpC0DvH4Mo9v3KkT5RsKoQrZkA-IGIk1o-Uk5ks,495
2
+ agent_mcp/camel_mcp_adapter.py,sha256=dMUHlkPpPRx2AAqr-SW2sMGmBNqHJU1rP7jSX5tq4bo,27339
3
+ agent_mcp/cli.py,sha256=s2bILqyRhF-Ebyfr0jj1xOKSuU_pFiljCBVXhZQTU7k,1217
4
+ agent_mcp/crewai_mcp_adapter.py,sha256=WbJNr4d6lQuesQ-ONKIt0KE1XsLN1Yl5Hh3Fo3dVwx8,12073
5
+ agent_mcp/enhanced_mcp_agent.py,sha256=DJyyL0Cf6Qp0mIAgu4uV4y1wueinl6W0UN3idn2B0Ds,28813
6
+ agent_mcp/heterogeneous_group_chat.py,sha256=ecvTBI6DgMoZFKTpFKcx1bhHMVX63DCpfqOc18I9wLU,38231
7
+ agent_mcp/langchain_mcp_adapter.py,sha256=uJsmqv_tFeG-vn-FnTS0bUYztSJzQ_dnVH-Vstjd__g,23825
8
+ agent_mcp/langgraph_mcp_adapter.py,sha256=eIiW3P2MBrEzLHHKpPBd4Rci5EFLAaOQrnSILH4RWBM,14028
9
+ agent_mcp/mcp_agent.py,sha256=qO6zYuAUowZE1B5-T-noDZiyGmjsLpgRZPlzudvvIvE,25577
10
+ agent_mcp/mcp_decorator.py,sha256=CDhq9jDliY0cnpc5JiJj2u8uC9nAHMEQaW7G2e1MapA,12126
11
+ agent_mcp/mcp_langgraph.py,sha256=TdhHVwXzrM-Oe2dy-biR1lIQ1f37LxQlkeE147v5hG8,26975
12
+ agent_mcp/mcp_transaction.py,sha256=iSr_DSFSMAU30TEQMTHbHDNooy1w80CkF9tIGKHbqZQ,3069
13
+ agent_mcp/mcp_transport.py,sha256=Mby0FMB4TKncMFOzhOxTLoabd1d1jwBne8RiJYJVR78,35373
14
+ agent_mcp/mcp_transport_enhanced.py,sha256=RSkHQ_fUXaFI7_6wZk5oAF3vSIM_mgvpTcJrRX_tuDE,1711
15
+ agent_mcp/proxy_agent.py,sha256=YAlOoEpKSO5hGF638kS_XJfKa-pIgY2pS6Z3J9XF1-4,1038
16
+ demos/__init__.py,sha256=6awmpBFb6EK0EB7dmezclX2AJRzIwt1F_LL6pW1oFAY,58
17
+ demos/basic/__init__.py,sha256=jWuTMaIE1wrb3s5lvpAmL_GnIyG9AviMgBmT9AvJFUU,64
18
+ demos/basic/framework_examples.py,sha256=i5YnykChWwwBjz8WGV2TLe-Elf0i5n-XLlLvNhJeVtU,3723
19
+ demos/basic/langchain_camel_demo.py,sha256=EeK6GgfCaA6Ew8xZxgkbNV4SUPy2RxPJv84XlCXyX_g,10611
20
+ demos/basic/simple_chat.py,sha256=JlyhZ8Nq3y6AHSvbejrbC6mKP6CCXg3tRXHOwPZiPyw,13783
21
+ demos/basic/simple_integration_example.py,sha256=_1JiYhKwPzh26-B1yoZe8cEbHIhJH6sDEDbK0FoHMlI,1570
22
+ demos/collaboration/collaborative_task_example.py,sha256=MVtxwaF_Aq-sL8wd7LAQTBCzI5TakNho-4bq6EtTik4,16830
23
+ demos/collaboration/group_chat_example.py,sha256=dfBp1zF0WAPkZiwMDKH01e8_K2oTvkgJJ7DdDMrX4rE,4111
24
+ demos/collaboration/simplified_crewai_example.py,sha256=U-o5VLrZMaqpOQ7IgYytNozIXljl-c_R2BPo9n7t-7M,1301
25
+ demos/langgraph/autonomous_langgraph_network.py,sha256=OkeC2YvuUkSTFj6dxi8WDf_KM48AG0wLrrRh30HUkXc,35801
26
+ demos/langgraph/langgraph_agent_network.py,sha256=Eg8hWlqIj-IWOcQhK_IWHvQl5AkQTBKFZyCPhgFJ9xA,15727
27
+ demos/langgraph/langgraph_collaborative_task.py,sha256=nZ_VW3vlB94BEqhK00WA5jtICCEATbhbsDbqxZA_4A8,24114
28
+ demos/langgraph/langgraph_example.py,sha256=51KY3-SPdwxKO1p7Wr0Q3NPISXSdaVCLmuO8TPvjv0s,8024
29
+ demos/langgraph/run_langgraph_examples.py,sha256=YFhDrqpQV0ugakQobhvxAkAL0A1cv2L5-c-njDOPJIM,7253
30
+ demos/network/agent_network_example.py,sha256=YLMDCjZaddmkbUHCRWgXXoP5tjxRr_QQqpLNGhHbRbI,14931
31
+ demos/network/email_agent.py,sha256=22ef1ZRfOBH4BwYCXsNyziUBp-rr0ve9qSCwnhRd3hU,5031
32
+ demos/network/email_agent_demo.py,sha256=le1SRhfVjOTLvQ-kRPb3ZxVazKN7hbGR50-vOeYoxJo,1310
33
+ demos/network/heterogeneous_network_example.py,sha256=1BmaJ1-CTNNcCeDSe0KbFw9Hq-VLFLzyNpPsmUaetQo,7315
34
+ demos/network/multi_framework_example.py,sha256=nRpJ0PmlSBV8KlpS7_tDUp3e2lHnpZxlnuf9qqpvi5I,6495
35
+ demos/utils/check_imports.py,sha256=cHNzsxwjnGaPKHAE-Vu4-JhBT7fPSAC_ppx2a0u3ecI,1475
36
+ demos/workflows/autonomous_agent_workflow.py,sha256=mr5Vymr5fUimW1F9PJFqOp46SkH6lIFv1yqYtQk9XFM,10269
37
+ demos/workflows/mcp_features_demo.py,sha256=Ot-tS8Rb25RdyhmzGAr-8VkZInVq624I3xsIFioe92o,13635
38
+ demos/workflows/run_agent_collaboration_demo.py,sha256=SOGJUlvKFZT-39TOPkxWTFJn2RpDtV8U59FKnrRrfIA,2184
39
+ demos/workflows/run_agent_collaboration_with_logs.py,sha256=AEEJwR0UE97DgMV_TXfn5G18QKRYRtoeB0cEce1ogFc,14056
40
+ demos/workflows/show_agent_interactions.py,sha256=QCzNByQUcuSKDQ5hVXMMFmAATw_Q763OSNZmUDZymXQ,4981
41
+ demos/workflows/simplified_autonomous_demo.py,sha256=VN1X9_z3sIyndkH1dICT_0Pu7AUCDyvGsOG3ZqGKPvc,2325
42
+ functions/main.py,sha256=KvbjSq2DQsFAnRpSzt_6Lr8sFbQ_T9AZ7XAaVFOYkoI,5844
43
+ functions/mcp_network_server.py,sha256=1lAhd4TZs882Bhkmm0pa1l2X84mXaZs4fEIu4icwVxs,21738
44
+ functions/utils.py,sha256=vR0R34ma84rp7zovlsawmffav6ZHsW6K6nrp9GlM9bk,2059
45
+ agent_mcp-0.1.4.dist-info/METADATA,sha256=qLqpBhF3Bm5BcG6G11CHkHQKzAbioIZwKZJNQC-IVek,11474
46
+ agent_mcp-0.1.4.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
47
+ agent_mcp-0.1.4.dist-info/entry_points.txt,sha256=_vOH0WhJfAMNRa61MQAv4cQxs55LZtAoAsfFuRzvXvk,49
48
+ agent_mcp-0.1.4.dist-info/top_level.txt,sha256=sxddPYaKyfJdF_J7-yFNSeMjMat0WQUcYB91f9g6RYM,26
49
+ agent_mcp-0.1.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mcp-agent = agent_mcp.cli:main
@@ -0,0 +1,3 @@
1
+ agent_mcp
2
+ demos
3
+ functions
demos/__init__.py ADDED
@@ -0,0 +1 @@
1
+ # This file makes the 'demos' directory a Python package.
@@ -0,0 +1 @@
1
+ # This file makes the 'demos/basic' directory a Python package.
@@ -0,0 +1,108 @@
1
+ """
2
+ Example of using the @mcp_agent decorator with different frameworks.
3
+ """
4
+
5
+ import os
6
+ from dotenv import load_dotenv
7
+ from agent_mcp.mcp_decorator import mcp_agent
8
+
9
+ # Load environment variables
10
+ load_dotenv()
11
+
12
+ # Set up OpenAI API key
13
+ os.environ['OPENAI_API_KEY'] = os.getenv('OPENAI_API_KEY')
14
+
15
+ # Example 1: LangChain Agent
16
+ from langchain_openai import ChatOpenAI
17
+ from langchain.agents import AgentExecutor, create_openai_functions_agent
18
+ from langchain_community.tools.ddg_search import DuckDuckGoSearchRun
19
+ from langchain.schema.messages import SystemMessage
20
+ from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
21
+
22
+ @mcp_agent(name="LangChainResearcher")
23
+ class LangChainResearchAgent:
24
+ def __init__(self):
25
+ # Set up LangChain components
26
+ self.llm = ChatOpenAI(model="gpt-3.5-turbo")
27
+ self.tools = [DuckDuckGoSearchRun()]
28
+
29
+ # Create prompt template
30
+ prompt = ChatPromptTemplate.from_messages([
31
+ ("system", "You are a research agent that uses search tools."),
32
+ ("user", "{input}"),
33
+ MessagesPlaceholder(variable_name="agent_scratchpad")
34
+ ])
35
+
36
+ # Create the agent
37
+ self.agent = create_openai_functions_agent(
38
+ llm=self.llm,
39
+ tools=self.tools,
40
+ prompt=prompt
41
+ )
42
+ self.agent_executor = AgentExecutor(agent=self.agent, tools=self.tools)
43
+
44
+ def research(self, query: str) -> str:
45
+ """Perform research on a given query"""
46
+ return self.agent_executor.invoke({"input": query})["output"]
47
+
48
+ # Example 2: LangGraph Agent
49
+ from langgraph.graph import Graph, StateGraph
50
+ from typing import Dict, TypedDict, Annotated
51
+
52
+ # Define the state type
53
+ class GraphState(TypedDict):
54
+ input: str
55
+ analysis: str
56
+ output: str
57
+
58
+ @mcp_agent(name="LangGraphAnalyzer")
59
+ class LangGraphAnalysisAgent:
60
+ def __init__(self):
61
+ # Set up LangGraph components
62
+ self.llm = ChatOpenAI(model="gpt-3.5-turbo")
63
+
64
+ # Define the workflow graph
65
+ self.workflow = StateGraph(GraphState)
66
+
67
+ # Add nodes to the graph
68
+ self.workflow.add_node("analyze", self.analyze_step)
69
+ self.workflow.add_node("summarize", self.summarize_step)
70
+
71
+ # Add edges
72
+ self.workflow.add_edge("analyze", "summarize")
73
+ self.workflow.set_entry_point("analyze")
74
+ self.workflow.set_finish_point("summarize")
75
+
76
+ # Compile the graph
77
+ self.graph = self.workflow.compile()
78
+
79
+ def analyze_step(self, state):
80
+ """Analyze the input data"""
81
+ analysis = self.llm.invoke(f"Analyze this topic: {state['input']}")
82
+ state['analysis'] = analysis
83
+ return state
84
+
85
+ def summarize_step(self, state):
86
+ """Summarize the analysis"""
87
+ summary = self.llm.invoke(f"Summarize this analysis: {state['analysis']}")
88
+ state['output'] = summary
89
+ return state
90
+
91
+ def process(self, topic: str) -> str:
92
+ """Process a topic through the LangGraph workflow"""
93
+ result = self.graph.invoke({"input": topic})
94
+ return result["output"]
95
+
96
+ # Example usage
97
+ if __name__ == "__main__":
98
+ print("Testing LangChain Agent:")
99
+ langchain_agent = LangChainResearchAgent()
100
+ result = langchain_agent.research("Latest developments in quantum computing 2025")
101
+ print(f"Research result: {result}")
102
+ print(f"Available MCP tools: {langchain_agent.mcp_tools.keys()}\n")
103
+
104
+ print("Testing LangGraph Agent:")
105
+ langgraph_agent = LangGraphAnalysisAgent()
106
+ result = langgraph_agent.process("Impact of AI on healthcare in 2025")
107
+ print(f"Analysis result: {result}")
108
+ print(f"Available MCP tools: {langgraph_agent.mcp_tools.keys()}")