agent-mcp 0.1.3__py3-none-any.whl → 0.1.5__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.
- agent_mcp/__init__.py +66 -12
- agent_mcp/a2a_protocol.py +316 -0
- agent_mcp/agent_lightning_library.py +214 -0
- agent_mcp/camel_mcp_adapter.py +521 -0
- agent_mcp/claude_mcp_adapter.py +195 -0
- agent_mcp/cli.py +47 -0
- agent_mcp/google_ai_mcp_adapter.py +183 -0
- agent_mcp/heterogeneous_group_chat.py +412 -38
- agent_mcp/langchain_mcp_adapter.py +176 -43
- agent_mcp/llamaindex_mcp_adapter.py +410 -0
- agent_mcp/mcp_agent.py +26 -0
- agent_mcp/mcp_transport.py +11 -5
- agent_mcp/microsoft_agent_framework.py +591 -0
- agent_mcp/missing_frameworks.py +435 -0
- agent_mcp/openapi_protocol.py +616 -0
- agent_mcp/payments.py +804 -0
- agent_mcp/pydantic_ai_mcp_adapter.py +628 -0
- agent_mcp/registry.py +768 -0
- agent_mcp/security.py +864 -0
- {agent_mcp-0.1.3.dist-info → agent_mcp-0.1.5.dist-info}/METADATA +173 -49
- agent_mcp-0.1.5.dist-info/RECORD +62 -0
- {agent_mcp-0.1.3.dist-info → agent_mcp-0.1.5.dist-info}/WHEEL +1 -1
- agent_mcp-0.1.5.dist-info/entry_points.txt +4 -0
- agent_mcp-0.1.5.dist-info/top_level.txt +3 -0
- demos/__init__.py +1 -0
- demos/basic/__init__.py +1 -0
- demos/basic/framework_examples.py +108 -0
- demos/basic/langchain_camel_demo.py +272 -0
- demos/basic/simple_chat.py +355 -0
- demos/basic/simple_integration_example.py +51 -0
- demos/collaboration/collaborative_task_example.py +437 -0
- demos/collaboration/group_chat_example.py +130 -0
- demos/collaboration/simplified_crewai_example.py +39 -0
- demos/comprehensive_framework_demo.py +202 -0
- demos/langgraph/autonomous_langgraph_network.py +808 -0
- demos/langgraph/langgraph_agent_network.py +415 -0
- demos/langgraph/langgraph_collaborative_task.py +619 -0
- demos/langgraph/langgraph_example.py +227 -0
- demos/langgraph/run_langgraph_examples.py +213 -0
- demos/network/agent_network_example.py +381 -0
- demos/network/email_agent.py +130 -0
- demos/network/email_agent_demo.py +46 -0
- demos/network/heterogeneous_network_example.py +216 -0
- demos/network/multi_framework_example.py +199 -0
- demos/utils/check_imports.py +49 -0
- demos/workflows/autonomous_agent_workflow.py +248 -0
- demos/workflows/mcp_features_demo.py +353 -0
- demos/workflows/run_agent_collaboration_demo.py +63 -0
- demos/workflows/run_agent_collaboration_with_logs.py +396 -0
- demos/workflows/show_agent_interactions.py +107 -0
- demos/workflows/simplified_autonomous_demo.py +74 -0
- functions/main.py +144 -0
- functions/mcp_network_server.py +513 -0
- functions/utils.py +47 -0
- agent_mcp-0.1.3.dist-info/RECORD +0 -18
- agent_mcp-0.1.3.dist-info/entry_points.txt +0 -2
- 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 =
|
|
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
|
-
|
|
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
|
|
95
|
-
|
|
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
|
-
#
|
|
102
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
211
|
+
logger.info(f"[{self.name}] Message processor loop started.")
|
|
116
212
|
while True:
|
|
117
213
|
try:
|
|
118
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
#
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
|
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.
|
|
180
|
-
# Execute the task using the Langchain agent executor
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
|
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":
|
|
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}")
|