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,733 @@
1
+ """
2
+ MCPLangGraph - A LangGraph node with Model Context Protocol capabilities.
3
+
4
+ This module provides a transparent implementation of the Model Context Protocol
5
+ for LangGraph, allowing nodes to standardize context provision to LLMs and
6
+ interact with other MCP-capable systems with minimal configuration.
7
+ """
8
+
9
+ import json
10
+ import uuid
11
+ import inspect
12
+ from typing import Any, Dict, List, Optional, Union, Callable, TypeVar, Type, cast
13
+
14
+ # Import LangGraph components
15
+ import langgraph.graph
16
+ from langgraph.graph import END, StateGraph
17
+ from langgraph.prebuilt import create_react_agent, ToolNode
18
+ from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
19
+ from langchain_core.runnables import RunnableConfig
20
+ from langchain_core.tools import tool
21
+
22
+
23
+ T = TypeVar('T')
24
+
25
+
26
+ class SharedContext:
27
+ """
28
+ A shared context store that can be used by multiple MCPNodes.
29
+
30
+ This class provides a centralized context store that allows multiple
31
+ MCPNodes to share context with each other, enabling seamless
32
+ context sharing across a LangGraph agent network.
33
+
34
+ Attributes:
35
+ context_store (Dict): The shared context store
36
+ """
37
+
38
+ def __init__(self):
39
+ """Initialize a new shared context store."""
40
+ self.context_store = {}
41
+
42
+ def set(self, key: str, value: Any) -> None:
43
+ """
44
+ Set a value in the shared context.
45
+
46
+ Args:
47
+ key: The key to store the value under
48
+ value: The value to store
49
+ """
50
+ self.context_store[key] = value
51
+
52
+ def get(self, key: str, default: Any = None) -> Any:
53
+ """
54
+ Get a value from the shared context.
55
+
56
+ Args:
57
+ key: The key to retrieve
58
+ default: Default value to return if key not found
59
+
60
+ Returns:
61
+ The value associated with the key, or the default if not found
62
+ """
63
+ return self.context_store.get(key, default)
64
+
65
+ def has(self, key: str) -> bool:
66
+ """
67
+ Check if a key exists in the shared context.
68
+
69
+ Args:
70
+ key: The key to check for
71
+
72
+ Returns:
73
+ True if the key exists, False otherwise
74
+ """
75
+ return key in self.context_store
76
+
77
+ def remove(self, key: str) -> bool:
78
+ """
79
+ Remove a key from the shared context.
80
+
81
+ Args:
82
+ key: The key to remove
83
+
84
+ Returns:
85
+ True if the key was removed, False if it didn't exist
86
+ """
87
+ if key in self.context_store:
88
+ del self.context_store[key]
89
+ return True
90
+ return False
91
+
92
+ def list_keys(self) -> List[str]:
93
+ """
94
+ List all keys in the shared context.
95
+
96
+ Returns:
97
+ List of all keys in the context
98
+ """
99
+ return list(self.context_store.keys())
100
+
101
+ def clear(self) -> None:
102
+ """Clear all keys from the shared context."""
103
+ self.context_store.clear()
104
+
105
+ def update(self, other_context: Dict[str, Any]) -> None:
106
+ """
107
+ Update the shared context with another dictionary.
108
+
109
+ Args:
110
+ other_context: Dictionary to update the context with
111
+ """
112
+ self.context_store.update(other_context)
113
+
114
+ class MCPNode:
115
+ """A LangGraph node with Model Context Protocol capabilities.
116
+
117
+ This class provides a standardized implementation of the Model Context Protocol
118
+ for LangGraph nodes, enabling seamless context sharing between different parts
119
+ of agent graphs. It supports both local and shared context management, allowing
120
+ nodes to either maintain their own context or participate in a shared context
121
+ environment.
122
+
123
+ Features:
124
+ - Context Management: Both local and shared context support
125
+ - Tool Integration: Register and manage MCP-compatible tools
126
+ - LLM Integration: Seamless integration with language models
127
+ - Context Sharing: Share context between nodes in a graph
128
+
129
+ Example:
130
+ >>> shared_context = SharedContext()
131
+ >>> node = MCPNode("my_node", context=shared_context)
132
+ >>> node.update_context("key", "value")
133
+ >>> value = node.get_context("key")
134
+
135
+ Attributes:
136
+ name (str): Name of the node
137
+ llm (Any): Language model instance for this node
138
+ mcp_tools (Dict): Registry of MCP tools available to this node
139
+ mcp_id (str): Unique identifier for this MCP node
140
+ mcp_version (str): The MCP version implemented by this node
141
+ _shared_context (SharedContext): Optional shared context instance
142
+ _use_shared_context (bool): Whether using shared or local context
143
+ """
144
+
145
+ def __init__(
146
+ self,
147
+ name: str,
148
+ system_message: Optional[str] = None,
149
+ context: Optional[SharedContext] = None,
150
+ llm: Any = None,
151
+ **kwargs
152
+ ):
153
+ """
154
+ Initialize an MCPNode.
155
+
156
+ Args:
157
+ name: The name of the node
158
+ system_message: Optional system message to include in the node's context
159
+ context: Optional shared context object to use instead of local context
160
+ llm: The language model to use with this node
161
+ **kwargs: Additional keyword arguments
162
+ """
163
+ self.name = name
164
+ self.llm = llm
165
+
166
+ # MCP specific attributes
167
+ self.mcp_tools = {}
168
+ self.mcp_id = str(uuid.uuid4())
169
+ self.mcp_version = "0.1.0" # MCP version implemented
170
+
171
+ # Set up context - either use provided shared context or create local context
172
+ if context is not None and isinstance(context, SharedContext):
173
+ # Use provided shared context
174
+ self._shared_context = context
175
+ self._use_shared_context = True
176
+ # Set a node-specific key in the shared context to store node-specific data
177
+ node_key = f"node_{self.mcp_id}"
178
+ if not self._shared_context.has(node_key):
179
+ self._shared_context.set(node_key, {})
180
+ else:
181
+ # Use local context
182
+ self.context_store = {}
183
+ self._use_shared_context = False
184
+
185
+ # Add system message to context if provided
186
+ if system_message:
187
+ self.update_context("system_message", system_message)
188
+
189
+ # Register default MCP tools
190
+ self._register_default_mcp_tools()
191
+
192
+ def _register_default_mcp_tools(self):
193
+ """Register default MCP tools that are available to all MCP nodes."""
194
+
195
+ # Define tool functions as simple Python functions
196
+ def context_get(key: str) -> Dict:
197
+ """Get a context item by key."""
198
+ return self._mcp_context_get(key)
199
+
200
+ def context_set(key: str, value: str) -> Dict:
201
+ """Set a context item with the given key and value."""
202
+ return self._mcp_context_set(key, value)
203
+
204
+ def context_list() -> Dict:
205
+ """List all available context keys."""
206
+ return self._mcp_context_list()
207
+
208
+ def context_remove(key: str) -> Dict:
209
+ """Remove a context item by key."""
210
+ return self._mcp_context_remove(key)
211
+
212
+ def mcp_info() -> Dict:
213
+ """Get information about this MCP node's capabilities."""
214
+ return self._mcp_info()
215
+
216
+ # Register the tools using our custom method for better compatibility
217
+ self.register_custom_tool("context_get", "Get a context item by key", context_get)
218
+ self.register_custom_tool("context_set", "Set a context item with the given key and value", context_set)
219
+ self.register_custom_tool("context_list", "List all available context keys", context_list)
220
+ self.register_custom_tool("context_remove", "Remove a context item by key", context_remove)
221
+ self.register_custom_tool("mcp_info", "Get information about this MCP node's capabilities", mcp_info)
222
+
223
+ def register_mcp_tool(self, tool_func: Callable) -> None:
224
+ """
225
+ Register an MCP tool with this node.
226
+
227
+ Args:
228
+ tool_func: A LangChain tool function to register
229
+ """
230
+ # Extract information from the tool decorator
231
+ if hasattr(tool_func, "name"):
232
+ tool_name = tool_func.name
233
+ elif hasattr(tool_func, "__name__"):
234
+ tool_name = tool_func.__name__
235
+ else:
236
+ # Generate a unique name if no name attribute exists
237
+ tool_name = f"tool_{str(uuid.uuid4())[:8]}"
238
+
239
+ # Get tool description
240
+ if hasattr(tool_func, "description"):
241
+ tool_description = tool_func.description
242
+ else:
243
+ tool_description = tool_func.__doc__ if hasattr(tool_func, "__doc__") and tool_func.__doc__ else "No description provided"
244
+
245
+ # Inspect function signature to build parameter info
246
+ try:
247
+ sig = inspect.signature(tool_func)
248
+ params = []
249
+
250
+ for param_name, param in sig.parameters.items():
251
+ if param_name == 'self':
252
+ continue
253
+
254
+ param_info = {
255
+ "name": param_name,
256
+ "description": f"Parameter {param_name}",
257
+ "required": param.default == inspect.Parameter.empty
258
+ }
259
+
260
+ # Add type information if available
261
+ if param.annotation != inspect.Parameter.empty:
262
+ try:
263
+ if hasattr(param.annotation, "__name__"):
264
+ type_name = param.annotation.__name__
265
+ if type_name in ["str", "string"]:
266
+ param_info["type"] = "string"
267
+ elif type_name in ["int", "integer", "float", "number"]:
268
+ param_info["type"] = "number"
269
+ elif type_name in ["bool", "boolean"]:
270
+ param_info["type"] = "boolean"
271
+ else:
272
+ param_info["type"] = "string" # Default to string for other types
273
+ else:
274
+ param_info["type"] = "string" # Default to string for complex types
275
+ except Exception:
276
+ # If we can't get the type, use string as default for Gemini
277
+ param_info["type"] = "string"
278
+ else:
279
+ # If no annotation, add default type for Gemini compatibility
280
+ param_info["type"] = "string"
281
+
282
+ params.append(param_info)
283
+ except (ValueError, TypeError):
284
+ # If we can't inspect the signature, use an empty parameter list
285
+ params = []
286
+
287
+ # Register the tool
288
+ self.mcp_tools[tool_name] = {
289
+ "name": tool_name,
290
+ "description": tool_description,
291
+ "parameters": params,
292
+ "function": tool_func,
293
+ }
294
+
295
+ def register_custom_tool(
296
+ self,
297
+ name: str,
298
+ description: str,
299
+ func: Callable,
300
+ **kwargs
301
+ ) -> None:
302
+ """
303
+ Register a custom function as an MCP tool.
304
+
305
+ Args:
306
+ name: The name of the tool
307
+ description: Description of the tool
308
+ func: The function to be called
309
+ **kwargs: Additional parameters
310
+ """
311
+ # Instead of using the tool decorator which may vary between versions,
312
+ # directly register the function with our metadata
313
+
314
+ # Inspect function signature to build parameter info
315
+ params = []
316
+ try:
317
+ sig = inspect.signature(func)
318
+
319
+ for param_name, param in sig.parameters.items():
320
+ if param_name == 'self':
321
+ continue
322
+
323
+ param_info = {
324
+ "name": param_name,
325
+ "description": f"Parameter {param_name}",
326
+ "required": param.default == inspect.Parameter.empty,
327
+ "type": "string" # Set a default type for Gemini compatibility
328
+ }
329
+
330
+ # Add more specific type information if available
331
+ if param.annotation != inspect.Parameter.empty:
332
+ try:
333
+ if hasattr(param.annotation, "__name__"):
334
+ type_name = param.annotation.__name__
335
+ if type_name in ["str", "string"]:
336
+ param_info["type"] = "string"
337
+ elif type_name in ["int", "integer", "float", "number"]:
338
+ param_info["type"] = "number"
339
+ elif type_name in ["bool", "boolean"]:
340
+ param_info["type"] = "boolean"
341
+ else:
342
+ param_info["type"] = "string" # Default to string for other types
343
+ else:
344
+ param_info["type"] = "string" # Default to string for complex types
345
+ except Exception:
346
+ # If we can't get the type, use string as default for Gemini
347
+ param_info["type"] = "string"
348
+
349
+ params.append(param_info)
350
+ except (ValueError, TypeError):
351
+ # If we can't inspect the signature, use an empty parameter list
352
+ pass
353
+
354
+ self.mcp_tools[name] = {
355
+ "name": name,
356
+ "description": description,
357
+ "parameters": params,
358
+ "function": func,
359
+ }
360
+
361
+ def get_tools_for_node(self) -> List:
362
+ """
363
+ Get all MCP tools formatted for use in a LangGraph node.
364
+
365
+ Returns:
366
+ List of LangChain tool objects
367
+ """
368
+ return [
369
+ tool_info["function"]
370
+ for tool_info in self.mcp_tools.values()
371
+ ]
372
+
373
+ # MCP Context Tool Implementations
374
+ def _mcp_context_get(self, key: str) -> Dict:
375
+ """
376
+ Get a context item by key.
377
+
378
+ Args:
379
+ key: The key of the context item to retrieve
380
+
381
+ Returns:
382
+ Dict containing the value or an error message
383
+ """
384
+ if self._use_shared_context:
385
+ if self._shared_context.has(key):
386
+ return {"status": "success", "value": self._shared_context.get(key)}
387
+ return {"status": "error", "message": f"Key '{key}' not found in shared context"}
388
+ else:
389
+ if key in self.context_store:
390
+ return {"status": "success", "value": self.context_store[key]}
391
+ return {"status": "error", "message": f"Key '{key}' not found in context"}
392
+
393
+ def _mcp_context_set(self, key: str, value: Any) -> Dict:
394
+ """
395
+ Set a context item with the given key and value.
396
+
397
+ Args:
398
+ key: The key to store the value under
399
+ value: The value to store
400
+
401
+ Returns:
402
+ Dict indicating success or failure
403
+ """
404
+ if self._use_shared_context:
405
+ self._shared_context.set(key, value)
406
+ return {"status": "success", "message": f"Shared context key '{key}' set successfully"}
407
+ else:
408
+ self.context_store[key] = value
409
+ return {"status": "success", "message": f"Context key '{key}' set successfully"}
410
+
411
+ def _mcp_context_list(self) -> Dict:
412
+ """
413
+ List all available context keys.
414
+
415
+ Returns:
416
+ Dict containing the list of context keys
417
+ """
418
+ if self._use_shared_context:
419
+ return {"status": "success", "keys": self._shared_context.list_keys()}
420
+ else:
421
+ return {"status": "success", "keys": list(self.context_store.keys())}
422
+
423
+ def _mcp_context_remove(self, key: str) -> Dict:
424
+ """
425
+ Remove a context item by key.
426
+
427
+ Args:
428
+ key: The key of the context item to remove
429
+
430
+ Returns:
431
+ Dict indicating success or failure
432
+ """
433
+ if self._use_shared_context:
434
+ if self._shared_context.has(key):
435
+ self._shared_context.remove(key)
436
+ return {"status": "success", "message": f"Shared context key '{key}' removed successfully"}
437
+ return {"status": "error", "message": f"Key '{key}' not found in shared context"}
438
+ else:
439
+ if key in self.context_store:
440
+ del self.context_store[key]
441
+ return {"status": "success", "message": f"Context key '{key}' removed successfully"}
442
+ return {"status": "error", "message": f"Key '{key}' not found in context"}
443
+
444
+ def _mcp_info(self) -> Dict:
445
+ """
446
+ Get information about this MCP node's capabilities.
447
+
448
+ Returns:
449
+ Dict containing MCP node information
450
+ """
451
+ return {
452
+ "id": self.mcp_id,
453
+ "name": self.name,
454
+ "version": self.mcp_version,
455
+ "tools": [
456
+ {
457
+ "name": name,
458
+ "description": tool["description"],
459
+ "parameters": tool["parameters"]
460
+ }
461
+ for name, tool in self.mcp_tools.items()
462
+ ]
463
+ }
464
+
465
+ def update_context(self, key: str, value: Any) -> None:
466
+ """
467
+ Update the MCP context with a new key-value pair.
468
+
469
+ Args:
470
+ key: The context key
471
+ value: The context value
472
+ """
473
+ if self._use_shared_context:
474
+ self._shared_context.set(key, value)
475
+ else:
476
+ self.context_store[key] = value
477
+
478
+ def get_context(self, key: str) -> Any:
479
+ """
480
+ Get a value from the MCP context.
481
+
482
+ Args:
483
+ key: The context key to retrieve
484
+
485
+ Returns:
486
+ The context value or None if not found
487
+ """
488
+ if self._use_shared_context:
489
+ return self._shared_context.get(key)
490
+ else:
491
+ return self.context_store.get(key)
492
+
493
+ def has_context(self, key: str) -> bool:
494
+ """
495
+ Check if a key exists in the context.
496
+
497
+ Args:
498
+ key: The key to check for
499
+
500
+ Returns:
501
+ True if the key exists, False otherwise
502
+ """
503
+ if self._use_shared_context:
504
+ return self._shared_context.has(key)
505
+ else:
506
+ return key in self.context_store
507
+
508
+ def list_available_tools(self) -> List[Dict]:
509
+ """
510
+ Get a list of all available MCP tools.
511
+
512
+ Returns:
513
+ List of tool definitions
514
+ """
515
+ return [
516
+ {
517
+ "name": name,
518
+ "description": tool["description"]
519
+ }
520
+ for name, tool in self.mcp_tools.items()
521
+ ]
522
+
523
+ def add_tool(self, tool_func: Callable) -> None:
524
+ """
525
+ Add a tool to this MCPNode.
526
+
527
+ This is a convenience method that calls register_mcp_tool
528
+
529
+ Args:
530
+ tool_func: The tool function to add
531
+ """
532
+ self.register_mcp_tool(tool_func)
533
+
534
+ def execute_tool(self, tool_name: str, **kwargs) -> Any:
535
+ """
536
+ Execute an MCP tool by name with the provided arguments.
537
+
538
+ Args:
539
+ tool_name: The name of the tool to execute
540
+ **kwargs: Arguments to pass to the tool
541
+
542
+ Returns:
543
+ The result of the tool execution
544
+
545
+ Raises:
546
+ ValueError: If the tool is not found
547
+ """
548
+ if tool_name not in self.mcp_tools:
549
+ raise ValueError(f"Tool '{tool_name}' not found")
550
+
551
+ tool_func = self.mcp_tools[tool_name]["function"]
552
+
553
+ # Handle both old-style and new-style tool calling
554
+ try:
555
+ if hasattr(tool_func, "invoke"):
556
+ # New-style (LangChain 0.1.47+)
557
+ return tool_func.invoke(input=kwargs if kwargs else "")
558
+ else:
559
+ # Build a proper tool_input string for old-style tools
560
+ # For tools with no arguments, pass empty string
561
+ if not kwargs:
562
+ return tool_func("")
563
+ # For tools with arguments, format it properly
564
+ tool_input = ""
565
+ for k, v in kwargs.items():
566
+ tool_input += f"{k}: {v}, "
567
+ tool_input = tool_input.rstrip(", ")
568
+ return tool_func(tool_input)
569
+ except Exception as e:
570
+ # Fallback method - direct function call
571
+ # This works for simple Python functions that don't use the tool interface
572
+ if callable(tool_func) and not isinstance(tool_func, type):
573
+ return tool_func(**kwargs)
574
+ raise e
575
+
576
+ def get_system_message(self) -> str:
577
+ """
578
+ Get the system message for this node, including context summary.
579
+
580
+ Returns:
581
+ The full system message with context
582
+ """
583
+ base_message = self.get_context("system_message") or "You are an AI assistant."
584
+ context_summary = self._generate_context_summary()
585
+
586
+ if context_summary:
587
+ return f"{base_message}\n\nAvailable context:\n{context_summary}"
588
+ else:
589
+ return base_message
590
+
591
+ def _generate_context_summary(self) -> str:
592
+ """
593
+ Generate a summary of available context for inclusion in the system message.
594
+
595
+ Returns:
596
+ String summary of available context
597
+ """
598
+ # Get the context to summarize - either shared or local
599
+ if self._use_shared_context:
600
+ context_keys = self._shared_context.list_keys()
601
+ # Skip non-essential keys to prevent overwhelming the context
602
+ context_keys = [k for k in context_keys if not k.startswith("node_")]
603
+
604
+ if not context_keys:
605
+ return ""
606
+
607
+ summary_parts = []
608
+ for key in context_keys:
609
+ # Skip the system message in the summary
610
+ if key == "system_message":
611
+ continue
612
+
613
+ value = self._shared_context.get(key)
614
+
615
+ # For complex objects, just indicate their type
616
+ if isinstance(value, dict):
617
+ summary_parts.append(f"- {key}: Dictionary with {len(value)} items")
618
+ elif isinstance(value, list):
619
+ summary_parts.append(f"- {key}: List with {len(value)} items")
620
+ elif isinstance(value, str) and len(value) > 100:
621
+ summary_parts.append(f"- {key}: Text ({len(value)} chars)")
622
+ else:
623
+ summary_parts.append(f"- {key}: {value}")
624
+ else:
625
+ # Local context
626
+ if not self.context_store:
627
+ return ""
628
+
629
+ summary_parts = []
630
+ for key, value in self.context_store.items():
631
+ # Skip the system message in the summary
632
+ if key == "system_message":
633
+ continue
634
+
635
+ # For complex objects, just indicate their type
636
+ if isinstance(value, dict):
637
+ summary_parts.append(f"- {key}: Dictionary with {len(value)} items")
638
+ elif isinstance(value, list):
639
+ summary_parts.append(f"- {key}: List with {len(value)} items")
640
+ elif isinstance(value, str) and len(value) > 100:
641
+ summary_parts.append(f"- {key}: Text ({len(value)} chars)")
642
+ else:
643
+ summary_parts.append(f"- {key}: {value}")
644
+
645
+ return "\n".join(summary_parts)
646
+
647
+
648
+ class MCPReactAgent(MCPNode):
649
+ """An implementation of MCP for LangGraph's ReAct agent pattern.
650
+
651
+ This class extends MCPNode to work specifically with ReAct agents,
652
+ providing a seamless integration of the Model Context Protocol with
653
+ LangGraph's agent architecture. It handles:
654
+
655
+ - Agent Creation: Creates ReAct agents with MCP context integration
656
+ - Tool Management: Combines MCP tools with custom agent tools
657
+ - Context Integration: Injects MCP context into agent's system messages
658
+ - LLM Compatibility: Handles different LLM implementations and versions
659
+
660
+ Example:
661
+ >>> agent = MCPReactAgent(name="my_agent")
662
+ >>> react_agent = agent.create_agent(llm, tools=[my_tool])
663
+ """
664
+
665
+ def create_mcp_langgraph(
666
+ llm,
667
+ name: str = "MCPGraph",
668
+ system_message: Optional[str] = None,
669
+ tools: Optional[List] = None,
670
+ additional_nodes: Optional[Dict] = None,
671
+ **kwargs
672
+ ) -> StateGraph:
673
+ """Create a LangGraph with MCP capabilities.
674
+
675
+ This function creates a LangGraph that integrates the Model Context Protocol,
676
+ enabling context sharing and standardized tool usage across the graph. It:
677
+
678
+ - Creates an MCP-enabled ReAct agent as the primary node
679
+ - Configures the graph with proper routing and tool nodes
680
+ - Supports additional custom nodes and tools
681
+ - Handles LLM integration and system messages
682
+
683
+ Args:
684
+ llm: The language model to use
685
+ name: Name of the graph
686
+ system_message: System message for the agent
687
+ tools: Additional tools to provide to the agent
688
+ additional_nodes: Optional additional nodes to add to the graph
689
+ **kwargs: Additional keyword arguments
690
+
691
+ Returns:
692
+ A configured StateGraph with MCP capabilities
693
+ """
694
+ # Create MCP node
695
+ mcp_agent = MCPReactAgent(name=name, system_message=system_message)
696
+
697
+ # Create agent node
698
+ agent = mcp_agent.create_agent(llm, tools)
699
+
700
+ # Initialize the state graph
701
+ builder = StateGraph(cast(Type, Dict))
702
+
703
+ # Add the agent node
704
+ builder.add_node("agent", agent)
705
+
706
+ # Add any additional nodes
707
+ if additional_nodes:
708
+ for node_name, node in additional_nodes.items():
709
+ builder.add_node(node_name, node)
710
+
711
+ # Set the entry point
712
+ builder.set_entry_point("agent")
713
+
714
+ # Add conditional edges
715
+ # This simpler routing approach works better with the latest LangGraph
716
+ builder.add_edge("agent", END)
717
+
718
+ # Add any needed tools as nodes
719
+ tool_nodes = {}
720
+
721
+ # Skip adding tool nodes for now as they're causing compatibility issues
722
+ # LangGraph will handle tools internally within the agent
723
+
724
+ # Skip adding additional tool nodes for now
725
+ # The tools are already passed to the agent when it's created
726
+
727
+ # Compile the graph
728
+ graph = builder.compile()
729
+
730
+ # Store the MCP agent for later access
731
+ graph.mcp_agent = mcp_agent
732
+
733
+ return graph