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.
Files changed (57) hide show
  1. agent_mcp/__init__.py +66 -12
  2. agent_mcp/a2a_protocol.py +316 -0
  3. agent_mcp/agent_lightning_library.py +214 -0
  4. agent_mcp/camel_mcp_adapter.py +521 -0
  5. agent_mcp/claude_mcp_adapter.py +195 -0
  6. agent_mcp/cli.py +47 -0
  7. agent_mcp/google_ai_mcp_adapter.py +183 -0
  8. agent_mcp/heterogeneous_group_chat.py +412 -38
  9. agent_mcp/langchain_mcp_adapter.py +176 -43
  10. agent_mcp/llamaindex_mcp_adapter.py +410 -0
  11. agent_mcp/mcp_agent.py +26 -0
  12. agent_mcp/mcp_transport.py +11 -5
  13. agent_mcp/microsoft_agent_framework.py +591 -0
  14. agent_mcp/missing_frameworks.py +435 -0
  15. agent_mcp/openapi_protocol.py +616 -0
  16. agent_mcp/payments.py +804 -0
  17. agent_mcp/pydantic_ai_mcp_adapter.py +628 -0
  18. agent_mcp/registry.py +768 -0
  19. agent_mcp/security.py +864 -0
  20. {agent_mcp-0.1.3.dist-info → agent_mcp-0.1.5.dist-info}/METADATA +173 -49
  21. agent_mcp-0.1.5.dist-info/RECORD +62 -0
  22. {agent_mcp-0.1.3.dist-info → agent_mcp-0.1.5.dist-info}/WHEEL +1 -1
  23. agent_mcp-0.1.5.dist-info/entry_points.txt +4 -0
  24. agent_mcp-0.1.5.dist-info/top_level.txt +3 -0
  25. demos/__init__.py +1 -0
  26. demos/basic/__init__.py +1 -0
  27. demos/basic/framework_examples.py +108 -0
  28. demos/basic/langchain_camel_demo.py +272 -0
  29. demos/basic/simple_chat.py +355 -0
  30. demos/basic/simple_integration_example.py +51 -0
  31. demos/collaboration/collaborative_task_example.py +437 -0
  32. demos/collaboration/group_chat_example.py +130 -0
  33. demos/collaboration/simplified_crewai_example.py +39 -0
  34. demos/comprehensive_framework_demo.py +202 -0
  35. demos/langgraph/autonomous_langgraph_network.py +808 -0
  36. demos/langgraph/langgraph_agent_network.py +415 -0
  37. demos/langgraph/langgraph_collaborative_task.py +619 -0
  38. demos/langgraph/langgraph_example.py +227 -0
  39. demos/langgraph/run_langgraph_examples.py +213 -0
  40. demos/network/agent_network_example.py +381 -0
  41. demos/network/email_agent.py +130 -0
  42. demos/network/email_agent_demo.py +46 -0
  43. demos/network/heterogeneous_network_example.py +216 -0
  44. demos/network/multi_framework_example.py +199 -0
  45. demos/utils/check_imports.py +49 -0
  46. demos/workflows/autonomous_agent_workflow.py +248 -0
  47. demos/workflows/mcp_features_demo.py +353 -0
  48. demos/workflows/run_agent_collaboration_demo.py +63 -0
  49. demos/workflows/run_agent_collaboration_with_logs.py +396 -0
  50. demos/workflows/show_agent_interactions.py +107 -0
  51. demos/workflows/simplified_autonomous_demo.py +74 -0
  52. functions/main.py +144 -0
  53. functions/mcp_network_server.py +513 -0
  54. functions/utils.py +47 -0
  55. agent_mcp-0.1.3.dist-info/RECORD +0 -18
  56. agent_mcp-0.1.3.dist-info/entry_points.txt +0 -2
  57. agent_mcp-0.1.3.dist-info/top_level.txt +0 -1
@@ -0,0 +1,591 @@
1
+ """
2
+ Microsoft Agent Framework Integration
3
+ Combines Semantic Kernel + AutoGen with MCP support
4
+
5
+ This module provides integration with Microsoft's unified agent framework
6
+ that brings together Semantic Kernel and AutoGen capabilities.
7
+ """
8
+
9
+ import asyncio
10
+ import json
11
+ import uuid
12
+ from typing import Dict, Any, List, Optional, Callable, Union
13
+ from dataclasses import dataclass, asdict
14
+ from datetime import datetime, timezone
15
+ import logging
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ # Try to import Microsoft frameworks
20
+ try:
21
+ import semantic_kernel as sk
22
+ from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, OpenAIChatCompletion
23
+ from semantic_kernel.kernel import Kernel
24
+ from semantic_kernel.planning import SequentialPlanner
25
+ from semantic_kernel.skills.core import TimeSkill
26
+ from semantic_kernel.orchestration import SkillContext
27
+ SEMANTIC_KERNEL_AVAILABLE = True
28
+ except ImportError:
29
+ SEMANTIC_KERNEL_AVAILABLE = False
30
+ sk = None
31
+ Kernel = None
32
+ logger.warning("Semantic Kernel not available. Install with: pip install semantic-kernel")
33
+
34
+ try:
35
+ from autogen import ConversableAgent, Agent, GroupChat, GroupChatManager
36
+ AUTOGEN_AVAILABLE = True
37
+ except ImportError:
38
+ AUTOGEN_AVAILABLE = False
39
+ ConversableAgent = None
40
+ Agent = None
41
+ logger.warning("AutoGen not available. Install with: pip install pyautogen")
42
+
43
+ from .mcp_transport import HTTPTransport
44
+
45
+ @dataclass
46
+ class MicrosoftAgentConfig:
47
+ """Configuration for Microsoft Agent Framework integration"""
48
+ agent_id: str
49
+ name: str
50
+ description: str
51
+ framework: str = "unified" # "semantic_kernel", "autogen", "unified"
52
+ llm_config: Dict[str, Any] = None
53
+ skills: List[str] = None
54
+ mcp_server_url: str = "https://mcp-server-ixlfhxquwq-ew.a.run.app"
55
+ enable_monitoring: bool = True
56
+ enable_compliance: bool = True
57
+
58
+ def __post_init__(self):
59
+ if self.skills is None:
60
+ self.skills = []
61
+ if self.llm_config is None:
62
+ self.llm_config = {}
63
+
64
+ class MicrosoftMCPAgent:
65
+ """Microsoft Agent with MCP capabilities"""
66
+
67
+ def __init__(
68
+ self,
69
+ config: MicrosoftAgentConfig,
70
+ transport: HTTPTransport = None
71
+ ):
72
+ self.config = config
73
+ self.transport = transport or HTTPTransport.from_url(config.mcp_server_url)
74
+ self.mcp_id = config.agent_id
75
+ self.mcp_version = "0.1.0"
76
+ self.mcp_tools = {}
77
+
78
+ # Initialize Microsoft frameworks
79
+ self.kernel = None
80
+ self.autogen_agent = None
81
+ self.group_chat = None
82
+
83
+ # Setup based on framework choice
84
+ if config.framework in ["semantic_kernel", "unified"] and SEMANTIC_KERNEL_AVAILABLE:
85
+ self._setup_semantic_kernel()
86
+
87
+ if config.framework in ["autogen", "unified"] and AUTOGEN_AVAILABLE:
88
+ self._setup_autogen()
89
+
90
+ # Register MCP tools
91
+ self._register_default_mcp_tools()
92
+
93
+ def _setup_semantic_kernel(self):
94
+ """Setup Semantic Kernel"""
95
+ try:
96
+ self.kernel = Kernel()
97
+
98
+ # Add LLM based on config
99
+ llm_config = self.config.llm_config
100
+ if llm_config.get("api_type") == "azure":
101
+ self.kernel.add_chat_service(
102
+ "azure_openai",
103
+ AzureChatCompletion(
104
+ deployment_name=llm_config.get("deployment_name", "gpt-35-turbo"),
105
+ endpoint=llm_config.get("endpoint"),
106
+ api_key=llm_config.get("api_key")
107
+ )
108
+ )
109
+ else:
110
+ self.kernel.add_chat_service(
111
+ "openai",
112
+ OpenAIChatCompletion(
113
+ ai_model_id=llm_config.get("model", "gpt-3.5-turbo"),
114
+ api_key=llm_config.get("api_key")
115
+ )
116
+ )
117
+
118
+ # Add default skills
119
+ self.kernel.import_skill(TimeSkill(), "TimeSkill")
120
+
121
+ # Add custom skills from config
122
+ for skill_name in self.config.skills:
123
+ try:
124
+ # Try to import and add skill
125
+ skill = self._load_skill(skill_name)
126
+ if skill:
127
+ self.kernel.import_skill(skill, skill_name)
128
+ logger.info(f"Added Semantic Kernel skill: {skill_name}")
129
+ except Exception as e:
130
+ logger.error(f"Failed to load skill {skill_name}: {e}")
131
+
132
+ except Exception as e:
133
+ logger.error(f"Error setting up Semantic Kernel: {e}")
134
+
135
+ def _setup_autogen(self):
136
+ """Setup AutoGen agent"""
137
+ try:
138
+ llm_config = self.config.llm_config
139
+
140
+ # AutoGen LLM config format
141
+ autogen_config = {
142
+ "model": llm_config.get("model", "gpt-3.5-turbo"),
143
+ "api_key": llm_config.get("api_key"),
144
+ "temperature": llm_config.get("temperature", 0.7)
145
+ }
146
+
147
+ if llm_config.get("api_type") == "azure":
148
+ autogen_config.update({
149
+ "base_url": llm_config.get("endpoint"),
150
+ "api_version": "2023-12-01-preview",
151
+ "deployment_name": llm_config.get("deployment_name")
152
+ })
153
+
154
+ self.autogen_agent = ConversableAgent(
155
+ name=self.config.name,
156
+ llm_config=autogen_config,
157
+ system_message=self.config.description,
158
+ human_input_mode="NEVER",
159
+ code_execution_config=False,
160
+ max_consecutive_auto_reply=3
161
+ )
162
+
163
+ logger.info(f"Created AutoGen agent: {self.config.name}")
164
+
165
+ except Exception as e:
166
+ logger.error(f"Error setting up AutoGen: {e}")
167
+
168
+ def _load_skill(self, skill_name: str):
169
+ """Load a Semantic Kernel skill by name"""
170
+ # This is a placeholder for skill loading logic
171
+ # In practice, this would load from various sources
172
+
173
+ if skill_name == "WebSearch":
174
+ return self._create_web_search_skill()
175
+ elif skill_name == "FileIO":
176
+ return self._create_file_io_skill()
177
+ else:
178
+ return None
179
+
180
+ def _create_web_search_skill(self):
181
+ """Create a web search skill for Semantic Kernel"""
182
+ @sk.sk_function(description="Search the web for information")
183
+ async def search_web(query: str) -> str:
184
+ try:
185
+ import aiohttp
186
+ async with aiohttp.ClientSession() as session:
187
+ # Use a search API (placeholder)
188
+ url = f"https://api.duckduckgo.com/?q={query}&format=json"
189
+ async with session.get(url) as response:
190
+ data = await response.json()
191
+ results = data.get("AbstractText", "No results found")
192
+ return str(results)
193
+ except Exception as e:
194
+ return f"Search error: {e}"
195
+
196
+ return search_web
197
+
198
+ def _create_file_io_skill(self):
199
+ """Create a file I/O skill for Semantic Kernel"""
200
+ @sk.sk_function(description="Read a file")
201
+ def read_file(file_path: str) -> str:
202
+ try:
203
+ with open(file_path, 'r') as f:
204
+ return f.read()
205
+ except Exception as e:
206
+ return f"Error reading file: {e}"
207
+
208
+ return read_file
209
+
210
+ def _register_default_mcp_tools(self):
211
+ """Register default MCP tools for Microsoft Agent"""
212
+
213
+ # Semantic Kernel tools
214
+ if self.kernel:
215
+ self._register_kernel_tools_as_mcp()
216
+
217
+ # AutoGen tools
218
+ if self.autogen_agent:
219
+ self._register_autogen_tools_as_mcp()
220
+
221
+ # Unified Microsoft tools
222
+ self._register_microsoft_tools_as_mcp()
223
+
224
+ def _register_kernel_tools_as_mcp(self):
225
+ """Register Semantic Kernel skills as MCP tools"""
226
+ if not self.kernel:
227
+ return
228
+
229
+ # Register each available skill
230
+ for skill_name, skill in self.kernel.skills.items():
231
+ for function_name, function in skill.items():
232
+ mcp_tool_name = f"sk_{skill_name}_{function_name}"
233
+
234
+ async def kernel_tool_wrapper(**kwargs):
235
+ """Wrapper to call Semantic Kernel function"""
236
+ try:
237
+ context = SkillContext()
238
+ result = await function.invoke_async(context=context, **kwargs)
239
+ return {
240
+ "status": "success",
241
+ "result": str(result),
242
+ "tool_name": mcp_tool_name,
243
+ "framework": "semantic_kernel"
244
+ }
245
+ except Exception as e:
246
+ logger.error(f"Error in Semantic Kernel tool {mcp_tool_name}: {e}")
247
+ return {
248
+ "status": "error",
249
+ "message": str(e),
250
+ "tool_name": mcp_tool_name
251
+ }
252
+
253
+ self.mcp_tools[mcp_tool_name] = {
254
+ "name": mcp_tool_name,
255
+ "description": function.description or f"Semantic Kernel {function_name}",
256
+ "parameters": self._extract_kernel_function_parameters(function),
257
+ "function": kernel_tool_wrapper
258
+ }
259
+
260
+ def _register_autogen_tools_as_mcp(self):
261
+ """Register AutoGen agent capabilities as MCP tools"""
262
+ if not self.autogen_agent:
263
+ return
264
+
265
+ async def autogen_send_message(message: str, target_agent: str = None) -> Dict[str, Any]:
266
+ """Send a message through AutoGen"""
267
+ try:
268
+ if target_agent:
269
+ # Send to specific agent
270
+ response = await self.autogen_agent.a_generate_reply(
271
+ [{"content": message, "role": "user"}]
272
+ )
273
+ else:
274
+ # Internal message processing
275
+ response = await self.autogen_agent.a_generate_reply(
276
+ [{"content": message, "role": "user"}]
277
+ )
278
+
279
+ return {
280
+ "status": "success",
281
+ "result": response,
282
+ "tool_name": "autogen_send_message",
283
+ "framework": "autogen"
284
+ }
285
+ except Exception as e:
286
+ logger.error(f"Error in AutoGen tool: {e}")
287
+ return {
288
+ "status": "error",
289
+ "message": str(e),
290
+ "tool_name": "autogen_send_message"
291
+ }
292
+
293
+ self.mcp_tools["autogen_send_message"] = {
294
+ "name": "autogen_send_message",
295
+ "description": "Send a message using AutoGen framework",
296
+ "parameters": [
297
+ {
298
+ "name": "message",
299
+ "description": "The message to send",
300
+ "type": "string",
301
+ "required": True
302
+ },
303
+ {
304
+ "name": "target_agent",
305
+ "description": "Optional target agent name",
306
+ "type": "string",
307
+ "required": False
308
+ }
309
+ ],
310
+ "function": autogen_send_message
311
+ }
312
+
313
+ def _register_microsoft_tools_as_mcp(self):
314
+ """Register Microsoft-specific MCP tools"""
315
+
316
+ async def get_agent_capabilities() -> Dict[str, Any]:
317
+ """Get capabilities of this Microsoft Agent"""
318
+ capabilities = {
319
+ "framework": self.config.framework,
320
+ "semantic_kernel_available": self.kernel is not None,
321
+ "autogen_available": self.autogen_agent is not None,
322
+ "skills": self.config.skills,
323
+ "tools": list(self.mcp_tools.keys())
324
+ }
325
+ return {
326
+ "status": "success",
327
+ "result": capabilities,
328
+ "agent_id": self.mcp_id
329
+ }
330
+
331
+ self.mcp_tools["get_agent_capabilities"] = {
332
+ "name": "get_agent_capabilities",
333
+ "description": "Get capabilities and configuration of this Microsoft Agent",
334
+ "parameters": [],
335
+ "function": get_agent_capabilities
336
+ }
337
+
338
+ async def execute_skill(skill_name: str, parameters: Dict[str, Any] = None) -> Dict[str, Any]:
339
+ """Execute a Semantic Kernel skill"""
340
+ if not self.kernel:
341
+ return {"status": "error", "message": "Semantic Kernel not available"}
342
+
343
+ try:
344
+ context = SkillContext()
345
+ skill_function = self.kernel.skills.get_function(skill_name)
346
+ if not skill_function:
347
+ return {"status": "error", "message": f"Skill {skill_name} not found"}
348
+
349
+ result = await skill_function.invoke_async(
350
+ context=context,
351
+ **(parameters or {})
352
+ )
353
+
354
+ return {
355
+ "status": "success",
356
+ "result": str(result),
357
+ "skill_name": skill_name
358
+ }
359
+ except Exception as e:
360
+ logger.error(f"Error executing skill {skill_name}: {e}")
361
+ return {
362
+ "status": "error",
363
+ "message": str(e),
364
+ "skill_name": skill_name
365
+ }
366
+
367
+ self.mcp_tools["execute_skill"] = {
368
+ "name": "execute_skill",
369
+ "description": "Execute a Semantic Kernel skill",
370
+ "parameters": [
371
+ {
372
+ "name": "skill_name",
373
+ "description": "Name of the skill to execute",
374
+ "type": "string",
375
+ "required": True
376
+ },
377
+ {
378
+ "name": "parameters",
379
+ "description": "Parameters for the skill",
380
+ "type": "object",
381
+ "required": False
382
+ }
383
+ ],
384
+ "function": execute_skill
385
+ }
386
+
387
+ def _extract_kernel_function_parameters(self, function) -> List[Dict[str, Any]]:
388
+ """Extract parameters from Semantic Kernel function"""
389
+ parameters = []
390
+
391
+ if hasattr(function, 'parameters') and function.parameters:
392
+ for param_name, param_info in function.parameters.items():
393
+ parameter = {
394
+ "name": param_name,
395
+ "description": param_info.description or f"Parameter {param_name}",
396
+ "type": "string", # Default to string
397
+ "required": param_info.default_value is None
398
+ }
399
+
400
+ # Try to determine type
401
+ if hasattr(param_info, 'type_info'):
402
+ type_str = str(param_info.type_info)
403
+ if "int" in type_str.lower():
404
+ parameter["type"] = "number"
405
+ elif "bool" in type_str.lower():
406
+ parameter["type"] = "boolean"
407
+
408
+ parameters.append(parameter)
409
+
410
+ return parameters
411
+
412
+ async def register_with_mcp_server(self) -> Dict[str, Any]:
413
+ """Register this Microsoft Agent with MCP server"""
414
+ registration_data = {
415
+ "agent_id": self.mcp_id,
416
+ "info": {
417
+ "name": self.config.name,
418
+ "description": self.config.description,
419
+ "framework": "Microsoft Agent Framework",
420
+ "framework_type": self.config.framework,
421
+ "capabilities": {
422
+ "semantic_kernel": self.kernel is not None,
423
+ "autogen": self.autogen_agent is not None,
424
+ "skills": self.config.skills,
425
+ "tools": list(self.mcp_tools.keys())
426
+ },
427
+ "version": self.mcp_version,
428
+ "microsoft_features": {
429
+ "monitoring": self.config.enable_monitoring,
430
+ "compliance": self.config.enable_compliance
431
+ }
432
+ }
433
+ }
434
+
435
+ return await self.transport.register_agent(self)
436
+
437
+ async def execute_mcp_tool(self, tool_name: str, **kwargs) -> Dict[str, Any]:
438
+ """Execute an MCP tool"""
439
+ if tool_name in self.mcp_tools:
440
+ tool_func = self.mcp_tools[tool_name]["function"]
441
+ return await tool_func(**kwargs)
442
+ else:
443
+ return {
444
+ "status": "error",
445
+ "message": f"Tool {tool_name} not found",
446
+ "available_tools": list(self.mcp_tools.keys())
447
+ }
448
+
449
+ async def create_agent_collaboration(
450
+ self,
451
+ other_agents: List['MicrosoftMCPAgent'],
452
+ collaboration_type: str = "group_chat"
453
+ ) -> 'AgentGroup':
454
+ """Create collaboration between Microsoft Agents"""
455
+
456
+ if collaboration_type == "group_chat" and AUTOGEN_AVAILABLE:
457
+ # Create AutoGen group chat
458
+ agents = [self.autogen_agent] + [agent.autogen_agent for agent in other_agents if agent.autogen_agent]
459
+
460
+ if len(agents) > 1:
461
+ self.group_chat = GroupChat(
462
+ agents=agents,
463
+ messages=[],
464
+ max_round=10
465
+ )
466
+
467
+ group_manager = GroupChatManager(
468
+ groupchat=self.group_chat,
469
+ llm_config=self.config.llm_config
470
+ )
471
+
472
+ return AgentGroup(
473
+ type="autogen_group_chat",
474
+ manager=group_manager,
475
+ agents=agents
476
+ )
477
+
478
+ elif collaboration_type == "semantic_kernel_orchestration":
479
+ # Use Semantic Kernel for orchestration
480
+ if self.kernel:
481
+ planner = SequentialPlanner(self.kernel)
482
+
483
+ async def orchestrate_task(task: str) -> Dict[str, Any]:
484
+ try:
485
+ plan = await planner.create_plan_async(task)
486
+ result = await plan.invoke_async()
487
+ return {
488
+ "status": "success",
489
+ "result": str(result),
490
+ "plan_steps": len(plan._steps)
491
+ }
492
+ except Exception as e:
493
+ return {
494
+ "status": "error",
495
+ "message": str(e)
496
+ }
497
+
498
+ return AgentGroup(
499
+ type="semantic_kernel_orchestration",
500
+ planner=planner,
501
+ execute_function=orchestrate_task
502
+ )
503
+
504
+ return None
505
+
506
+ @dataclass
507
+ class AgentGroup:
508
+ """Represents a group of collaborating agents"""
509
+ type: str
510
+ agents: List[Any] = None
511
+ manager: Any = None
512
+ planner: Any = None
513
+ execute_function: Callable = None
514
+
515
+ class MicrosoftAgentBridge:
516
+ """Bridge for Microsoft Agent Framework integration with MCP"""
517
+
518
+ def __init__(self, mcp_server_url: str = "https://mcp-server-ixlfhxquwq-ew.a.run.app"):
519
+ self.mcp_server_url = mcp_server_url
520
+ self.agents = {}
521
+ self.groups = {}
522
+
523
+ async def create_agent(
524
+ self,
525
+ agent_id: str,
526
+ name: str,
527
+ description: str,
528
+ framework: str = "unified",
529
+ llm_config: Dict[str, Any] = None,
530
+ skills: List[str] = None
531
+ ) -> MicrosoftMCPAgent:
532
+ """Create and register a Microsoft Agent"""
533
+
534
+ config = MicrosoftAgentConfig(
535
+ agent_id=agent_id,
536
+ name=name,
537
+ description=description,
538
+ framework=framework,
539
+ llm_config=llm_config or {},
540
+ skills=skills or [],
541
+ mcp_server_url=self.mcp_server_url
542
+ )
543
+
544
+ agent = MicrosoftMCPAgent(config)
545
+
546
+ # Register with MCP server
547
+ registration_result = await agent.register_with_mcp_server()
548
+
549
+ if registration_result.get("status") == "registered":
550
+ logger.info(f"Microsoft Agent {agent_id} registered with MCP server")
551
+ self.agents[agent_id] = agent
552
+ else:
553
+ logger.error(f"Failed to register Microsoft Agent {agent_id}: {registration_result}")
554
+
555
+ return agent
556
+
557
+ async def create_collaboration(
558
+ self,
559
+ agent_ids: List[str],
560
+ collaboration_type: str = "group_chat"
561
+ ) -> Optional[AgentGroup]:
562
+ """Create collaboration between agents"""
563
+
564
+ agents = [self.agents[aid] for aid in agent_ids if aid in self.agents]
565
+
566
+ if len(agents) < 2:
567
+ logger.error("Need at least 2 agents for collaboration")
568
+ return None
569
+
570
+ # Use the first agent to create the group
571
+ lead_agent = agents[0]
572
+ other_agents = agents[1:]
573
+
574
+ group = await lead_agent.create_agent_collaboration(
575
+ other_agents, collaboration_type
576
+ )
577
+
578
+ if group:
579
+ group_id = str(uuid.uuid4())
580
+ self.groups[group_id] = group
581
+ logger.info(f"Created {collaboration_type} group with {len(agents)} agents")
582
+
583
+ return group
584
+
585
+ # Export classes for easy importing
586
+ __all__ = [
587
+ 'MicrosoftAgentConfig',
588
+ 'MicrosoftMCPAgent',
589
+ 'AgentGroup',
590
+ 'MicrosoftAgentBridge'
591
+ ]