traia-iatp 0.1.1__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.

Potentially problematic release.


This version of traia-iatp might be problematic. Click here for more details.

Files changed (72) hide show
  1. traia_iatp/README.md +368 -0
  2. traia_iatp/__init__.py +30 -0
  3. traia_iatp/cli/__init__.py +5 -0
  4. traia_iatp/cli/main.py +483 -0
  5. traia_iatp/client/__init__.py +10 -0
  6. traia_iatp/client/a2a_client.py +274 -0
  7. traia_iatp/client/crewai_a2a_tools.py +335 -0
  8. traia_iatp/client/grpc_a2a_tools.py +349 -0
  9. traia_iatp/client/root_path_a2a_client.py +1 -0
  10. traia_iatp/core/__init__.py +43 -0
  11. traia_iatp/core/models.py +161 -0
  12. traia_iatp/mcp/__init__.py +15 -0
  13. traia_iatp/mcp/client.py +201 -0
  14. traia_iatp/mcp/mcp_agent_template.py +422 -0
  15. traia_iatp/mcp/templates/Dockerfile.j2 +56 -0
  16. traia_iatp/mcp/templates/README.md.j2 +212 -0
  17. traia_iatp/mcp/templates/cursor-rules.md.j2 +326 -0
  18. traia_iatp/mcp/templates/deployment_params.json.j2 +20 -0
  19. traia_iatp/mcp/templates/docker-compose.yml.j2 +23 -0
  20. traia_iatp/mcp/templates/dockerignore.j2 +47 -0
  21. traia_iatp/mcp/templates/gitignore.j2 +77 -0
  22. traia_iatp/mcp/templates/mcp_health_check.py.j2 +150 -0
  23. traia_iatp/mcp/templates/pyproject.toml.j2 +26 -0
  24. traia_iatp/mcp/templates/run_local_docker.sh.j2 +94 -0
  25. traia_iatp/mcp/templates/server.py.j2 +240 -0
  26. traia_iatp/mcp/traia_mcp_adapter.py +381 -0
  27. traia_iatp/preview_diagrams.html +181 -0
  28. traia_iatp/registry/__init__.py +26 -0
  29. traia_iatp/registry/atlas_search_indexes.json +280 -0
  30. traia_iatp/registry/embeddings.py +298 -0
  31. traia_iatp/registry/iatp_search_api.py +839 -0
  32. traia_iatp/registry/mongodb_registry.py +771 -0
  33. traia_iatp/registry/readmes/ATLAS_SEARCH_INDEXES.md +252 -0
  34. traia_iatp/registry/readmes/ATLAS_SEARCH_SETUP.md +134 -0
  35. traia_iatp/registry/readmes/AUTHENTICATION_UPDATE.md +124 -0
  36. traia_iatp/registry/readmes/EMBEDDINGS_SETUP.md +172 -0
  37. traia_iatp/registry/readmes/IATP_SEARCH_API_GUIDE.md +257 -0
  38. traia_iatp/registry/readmes/MONGODB_X509_AUTH.md +208 -0
  39. traia_iatp/registry/readmes/README.md +251 -0
  40. traia_iatp/registry/readmes/REFACTORING_SUMMARY.md +191 -0
  41. traia_iatp/server/__init__.py +15 -0
  42. traia_iatp/server/a2a_server.py +215 -0
  43. traia_iatp/server/example_template_usage.py +72 -0
  44. traia_iatp/server/iatp_server_agent_generator.py +237 -0
  45. traia_iatp/server/iatp_server_template_generator.py +235 -0
  46. traia_iatp/server/templates/Dockerfile.j2 +49 -0
  47. traia_iatp/server/templates/README.md +137 -0
  48. traia_iatp/server/templates/README.md.j2 +425 -0
  49. traia_iatp/server/templates/__init__.py +1 -0
  50. traia_iatp/server/templates/__main__.py.j2 +450 -0
  51. traia_iatp/server/templates/agent.py.j2 +80 -0
  52. traia_iatp/server/templates/agent_config.json.j2 +22 -0
  53. traia_iatp/server/templates/agent_executor.py.j2 +264 -0
  54. traia_iatp/server/templates/docker-compose.yml.j2 +23 -0
  55. traia_iatp/server/templates/env.example.j2 +67 -0
  56. traia_iatp/server/templates/gitignore.j2 +78 -0
  57. traia_iatp/server/templates/grpc_server.py.j2 +218 -0
  58. traia_iatp/server/templates/pyproject.toml.j2 +76 -0
  59. traia_iatp/server/templates/run_local_docker.sh.j2 +103 -0
  60. traia_iatp/server/templates/server.py.j2 +190 -0
  61. traia_iatp/special_agencies/__init__.py +4 -0
  62. traia_iatp/special_agencies/registry_search_agency.py +392 -0
  63. traia_iatp/utils/__init__.py +10 -0
  64. traia_iatp/utils/docker_utils.py +251 -0
  65. traia_iatp/utils/general.py +64 -0
  66. traia_iatp/utils/iatp_utils.py +126 -0
  67. traia_iatp-0.1.1.dist-info/METADATA +414 -0
  68. traia_iatp-0.1.1.dist-info/RECORD +72 -0
  69. traia_iatp-0.1.1.dist-info/WHEEL +5 -0
  70. traia_iatp-0.1.1.dist-info/entry_points.txt +2 -0
  71. traia_iatp-0.1.1.dist-info/licenses/LICENSE +21 -0
  72. traia_iatp-0.1.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,201 @@
1
+ """MCP client wrapper for connecting to MCP servers with streamable-http support."""
2
+
3
+ import asyncio
4
+ import logging
5
+ from typing import Any, Dict, Optional, List, AsyncIterator
6
+ import httpx
7
+ import json
8
+
9
+ from ..core.models import MCPServer, MCPServerType
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class MCPClient:
15
+ """Wrapper for MCP client connections with streamable-http support."""
16
+
17
+ def __init__(self, mcp_server: MCPServer):
18
+ self.mcp_server = mcp_server
19
+ self._available_tools: List[Dict[str, Any]] = []
20
+ self._http_client: Optional[httpx.AsyncClient] = None
21
+ self._connected = False
22
+
23
+ async def connect(self) -> None:
24
+ """Connect to the MCP server using streamable-http."""
25
+ try:
26
+ if self.mcp_server.server_type != MCPServerType.STREAMABLE_HTTP:
27
+ raise ValueError(f"Only streamable-http is supported, got: {self.mcp_server.server_type}")
28
+
29
+ await self._connect_streamable_http()
30
+
31
+ except Exception as e:
32
+ logger.error(f"Failed to connect to MCP server {self.mcp_server.name}: {e}")
33
+ raise
34
+
35
+ async def _connect_streamable_http(self) -> None:
36
+ """Connect using streamable-http for real-time updates."""
37
+ url = str(self.mcp_server.url)
38
+
39
+ logger.info(f"Connecting to MCP server {self.mcp_server.name} via streamable-http at {url}")
40
+
41
+ # Initialize HTTP client for persistent connection
42
+ self._http_client = httpx.AsyncClient(
43
+ timeout=httpx.Timeout(30.0, connect=10.0),
44
+ limits=httpx.Limits(max_keepalive_connections=5, max_connections=10),
45
+ http2=True # Enable HTTP/2 for better streaming support
46
+ )
47
+
48
+ # Test connection and get available tools
49
+ try:
50
+ response = await self._http_client.get(f"{url}/tools")
51
+ response.raise_for_status()
52
+ tools_data = response.json()
53
+ self._available_tools = tools_data.get("tools", [])
54
+ self._connected = True
55
+ logger.info(f"Connected to {self.mcp_server.name}, found {len(self._available_tools)} tools")
56
+
57
+ # Update capabilities in the MCP server model
58
+ self.mcp_server.capabilities = [tool["name"] for tool in self._available_tools]
59
+ except Exception as e:
60
+ await self._http_client.aclose()
61
+ self._http_client = None
62
+ raise RuntimeError(f"Failed to connect to MCP server: {e}")
63
+
64
+ async def call_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Any:
65
+ """Call a tool on the MCP server."""
66
+ if not self._connected or not self._http_client:
67
+ # Reconnect if needed
68
+ await self.connect()
69
+
70
+ # Find the tool
71
+ tool = next((t for t in self._available_tools if t["name"] == tool_name), None)
72
+ if not tool:
73
+ raise ValueError(f"Tool {tool_name} not found")
74
+
75
+ # Call the tool
76
+ url = str(self.mcp_server.url).rstrip('/') + '/call'
77
+
78
+ response = await self._http_client.post(
79
+ url,
80
+ json={"name": tool_name, "input": arguments}
81
+ )
82
+ response.raise_for_status()
83
+ return response.json()
84
+
85
+ async def call_tool_streaming(self, tool_name: str, arguments: Dict[str, Any]) -> AsyncIterator[Any]:
86
+ """Call a tool with streaming response support."""
87
+ if not self._connected:
88
+ await self.connect()
89
+
90
+ # Stream the tool call response
91
+ async for chunk in self._stream_tool_call(tool_name, arguments):
92
+ yield chunk
93
+
94
+ async def _stream_tool_call(self, tool_name: str, arguments: Dict[str, Any]) -> AsyncIterator[Any]:
95
+ """Stream a tool call response for streamable-http connections."""
96
+ if not self._http_client:
97
+ raise RuntimeError("HTTP client not initialized")
98
+
99
+ url = str(self.mcp_server.url).rstrip('/') + '/call'
100
+
101
+ # Make streaming request
102
+ async with self._http_client.stream(
103
+ "POST",
104
+ url,
105
+ json={"name": tool_name, "input": arguments},
106
+ headers={"Accept": "text/event-stream"}
107
+ ) as response:
108
+ async for line in response.aiter_lines():
109
+ if line.startswith("data: "):
110
+ data = line[6:] # Remove "data: " prefix
111
+ if data:
112
+ try:
113
+ yield json.loads(data)
114
+ except json.JSONDecodeError:
115
+ logger.warning(f"Failed to parse streaming data: {data}")
116
+
117
+ async def list_tools(self) -> List[Dict[str, Any]]:
118
+ """List available tools."""
119
+ if not self._connected:
120
+ await self.connect()
121
+
122
+ return self._available_tools
123
+
124
+ async def disconnect(self) -> None:
125
+ """Disconnect from the MCP server."""
126
+ self._connected = False
127
+ self._available_tools = []
128
+
129
+ if self._http_client:
130
+ await self._http_client.aclose()
131
+ self._http_client = None
132
+
133
+ async def health_check(self) -> bool:
134
+ """Check if the MCP server connection is healthy."""
135
+ try:
136
+ if not self._connected or not self._http_client:
137
+ return False
138
+
139
+ # Try to ping the server
140
+ response = await self._http_client.get(f"{self.mcp_server.url}/health")
141
+ return response.status_code == 200
142
+ except Exception as e:
143
+ logger.warning(f"Health check failed for {self.mcp_server.name}: {e}")
144
+ return False
145
+
146
+ async def __aenter__(self):
147
+ """Async context manager entry."""
148
+ await self.connect()
149
+ return self
150
+
151
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
152
+ """Async context manager exit."""
153
+ await self.disconnect()
154
+
155
+
156
+ class MCPToolWrapper:
157
+ """Wrapper to expose MCP tools as CrewAI-compatible tools with connection pooling."""
158
+
159
+ # Class-level connection pool
160
+ _connection_pool: Dict[str, MCPClient] = {}
161
+ _pool_lock = asyncio.Lock()
162
+
163
+ def __init__(self, mcp_server: MCPServer, tool_name: str, tool_description: str):
164
+ self.mcp_server = mcp_server
165
+ self.tool_name = tool_name
166
+ self.description = tool_description
167
+ self.name = tool_name
168
+
169
+ @classmethod
170
+ async def get_or_create_client(cls, mcp_server: MCPServer) -> MCPClient:
171
+ """Get or create a client from the connection pool."""
172
+ server_key = f"{mcp_server.name}:{mcp_server.url}"
173
+
174
+ async with cls._pool_lock:
175
+ if server_key not in cls._connection_pool:
176
+ # Create new client
177
+ client = MCPClient(mcp_server)
178
+ await client.connect()
179
+ cls._connection_pool[server_key] = client
180
+ else:
181
+ # Check if existing client is healthy
182
+ client = cls._connection_pool[server_key]
183
+ if not await client.health_check():
184
+ # Reconnect if unhealthy
185
+ await client.disconnect()
186
+ await client.connect()
187
+
188
+ return cls._connection_pool[server_key]
189
+
190
+ async def __call__(self, **kwargs) -> Any:
191
+ """Execute the MCP tool using pooled connection."""
192
+ client = await self.get_or_create_client(self.mcp_server)
193
+ return await client.call_tool(self.tool_name, kwargs)
194
+
195
+ @classmethod
196
+ async def cleanup_pool(cls):
197
+ """Clean up all connections in the pool."""
198
+ async with cls._pool_lock:
199
+ for client in cls._connection_pool.values():
200
+ await client.disconnect()
201
+ cls._connection_pool.clear()
@@ -0,0 +1,422 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ MCP Agent Template
4
+
5
+ This template provides a structured way to create agents that use MCP server tools.
6
+ It offers a simplified interface for creating specialized agents that can leverage
7
+ any MCP server by providing the server configuration.
8
+
9
+ Features:
10
+ - Automatic detection of authentication requirements
11
+ - Support for both authenticated and non-authenticated MCP servers
12
+ - Flexible agent creation with optional tool filtering
13
+ - Health checks for MCP servers
14
+
15
+ Authentication:
16
+ The template automatically detects if an MCP server requires authentication
17
+ based on metadata fields:
18
+ - requires_api_key: boolean indicating if authentication is needed
19
+ - api_key_header: the header name to use (default: "Authorization")
20
+ - headers: dictionary containing the actual API key
21
+
22
+ Usage for MCP Agents:
23
+ 1. Import the MCPAgentBuilder from this module
24
+ 2. Create your specialized agent(s) with the builder
25
+ 3. Create tasks for your agents
26
+ 4. Run your agents as a CrewAI crew with MCP server configuration
27
+
28
+ Example:
29
+ ```python
30
+ from mcp_agent_template import MCPAgentBuilder, run_with_mcp_tools, MCPServerInfo
31
+
32
+ # Create MCP server info (you would get this from registry or configuration)
33
+ mcp_server = MCPServerInfo(
34
+ id="weather-123",
35
+ name="weather-mcp",
36
+ url="http://localhost:8080",
37
+ description="Weather information MCP server",
38
+ server_type="streamable-http",
39
+ capabilities=["get_weather", "get_forecast"],
40
+ metadata={},
41
+ tags=["weather", "api"]
42
+ )
43
+
44
+ # For authenticated servers, include auth info in metadata:
45
+ # metadata={
46
+ # "requires_api_key": True,
47
+ # "api_key_header": "Authorization",
48
+ # "headers": {"Authorization": "Bearer YOUR_API_KEY"}
49
+ # }
50
+
51
+ # Create an agent for the MCP server
52
+ analyst = MCPAgentBuilder.create_agent(
53
+ role="Weather Analyst",
54
+ goal="Analyze weather conditions and provide forecasts",
55
+ backstory="You are an expert meteorologist...",
56
+ verbose=True
57
+ )
58
+
59
+ # Create task for the agent
60
+ task = Task(
61
+ description="Analyze current weather conditions in New York...",
62
+ expected_output="A comprehensive weather report...",
63
+ agent=analyst
64
+ )
65
+
66
+ # Run the agent with MCP tools
67
+ result = run_with_mcp_tools([task], mcp_server=mcp_server)
68
+ print(result)
69
+ ```
70
+ """
71
+
72
+ import os
73
+ import sys
74
+ import json
75
+ import argparse
76
+ import logging
77
+ from typing import List, Dict, Any, Optional, Union
78
+ from dataclasses import dataclass
79
+ from pathlib import Path
80
+
81
+ # Import CrewAI components
82
+ from crewai import Agent, Task, Crew, Process, LLM
83
+ from crewai_tools import MCPServerAdapter
84
+
85
+ # Import our custom adapter for API key support
86
+ from .traia_mcp_adapter import create_mcp_adapter, create_mcp_adapter_with_auth
87
+
88
+
89
+ logger = logging.getLogger(__name__)
90
+
91
+ # Create default LLM instance
92
+ DEFAULT_LLM = LLM(model="openai/gpt-4.1", temperature=0.7)
93
+
94
+
95
+ @dataclass
96
+ class MCPServerInfo:
97
+ """Information about an MCP server."""
98
+ id: str
99
+ name: str
100
+ url: str
101
+ description: str
102
+ server_type: str
103
+ capabilities: List[str]
104
+ metadata: Dict[str, Any]
105
+ tags: List[str]
106
+
107
+
108
+ class MCPServerConfig:
109
+ """Configuration for an MCP server - used for utility agency creation."""
110
+
111
+ def __init__(
112
+ self,
113
+ name: str,
114
+ url: str,
115
+ description: str,
116
+ server_type: str = "streamable-http", # Only streamable-http is supported
117
+ capabilities: List[str] = None,
118
+ metadata: Dict[str, Any] = None
119
+ ):
120
+ self.name = name
121
+ self.url = url
122
+ self.description = description
123
+ self.server_type = server_type
124
+ self.capabilities = capabilities or []
125
+ self.metadata = metadata or {}
126
+
127
+ def to_dict(self) -> Dict[str, Any]:
128
+ """Convert to dictionary for storage."""
129
+ return {
130
+ "name": self.name,
131
+ "url": self.url,
132
+ "description": self.description,
133
+ "server_type": self.server_type,
134
+ "capabilities": self.capabilities,
135
+ "metadata": self.metadata
136
+ }
137
+
138
+
139
+ class MCPAgentBuilder:
140
+ """
141
+ Builder class for creating agents that use MCP server tools.
142
+ """
143
+
144
+ # Class variable to store tool subsets for agents (using agent id as key)
145
+ _agent_tool_subsets = {}
146
+
147
+ @staticmethod
148
+ def create_agent(
149
+ role: str,
150
+ goal: str,
151
+ backstory: str,
152
+ verbose: bool = True,
153
+ allow_delegation: bool = False,
154
+ llm: LLM = None,
155
+ tools_subset: List[str] = None
156
+ ) -> Agent:
157
+ """
158
+ Create a CrewAI agent for use with MCP tools.
159
+
160
+ Args:
161
+ role: The role of the agent
162
+ goal: The primary goal of the agent
163
+ backstory: Background story for the agent
164
+ verbose: Whether to enable verbose output
165
+ allow_delegation: Whether to allow the agent to delegate tasks
166
+ llm: The LLM instance to use (defaults to gpt-4.1 with temperature 0.7)
167
+ tools_subset: Optional list of specific tool names to include (if None, all tools are included)
168
+
169
+ Returns:
170
+ CrewAI Agent configured for MCP tools
171
+ """
172
+ # Use specified LLM or default
173
+ if llm is None:
174
+ llm = DEFAULT_LLM
175
+
176
+ # We'll set tools later when run_with_mcp_tools is called
177
+ # This is because tools require the MCP server connection
178
+ agent = Agent(
179
+ role=role,
180
+ goal=goal,
181
+ backstory=backstory,
182
+ verbose=verbose,
183
+ allow_delegation=allow_delegation,
184
+ llm=llm
185
+ )
186
+
187
+ # Store the tools_subset in the class dictionary
188
+ if tools_subset is not None:
189
+ MCPAgentBuilder._agent_tool_subsets[id(agent)] = tools_subset
190
+
191
+ return agent
192
+
193
+ @staticmethod
194
+ def get_tools_subset(agent: Agent) -> Optional[List[str]]:
195
+ """Get the tools subset for a specific agent"""
196
+ return MCPAgentBuilder._agent_tool_subsets.get(id(agent))
197
+
198
+
199
+ def check_server_health(server_info: MCPServerInfo, api_key: Optional[str] = None) -> bool:
200
+ """
201
+ Check if the MCP server is running and healthy by attempting to connect
202
+ and list available tools.
203
+
204
+ Args:
205
+ server_info: MCPServerInfo object with server details
206
+ api_key: Optional API key for authenticated servers
207
+
208
+ Returns:
209
+ True if the server is healthy, False otherwise
210
+ """
211
+ try:
212
+ # Check if authentication is required
213
+ requires_api_key = server_info.metadata.get("requires_api_key", False)
214
+ api_key_header = server_info.metadata.get("api_key_header", "Authorization")
215
+
216
+ # Create appropriate adapter
217
+ if requires_api_key and api_key:
218
+ # Use the provided API key directly (user provides raw key without Bearer prefix)
219
+ adapter = create_mcp_adapter_with_auth(
220
+ url=server_info.url,
221
+ api_key=api_key,
222
+ auth_header=api_key_header,
223
+ auth_prefix="Bearer" # We add the Bearer prefix
224
+ )
225
+ else:
226
+ # No authentication required or no API key provided
227
+ adapter = create_mcp_adapter(url=server_info.url)
228
+
229
+ # Try to connect and list tools
230
+ with adapter as mcp_tools:
231
+ tools = list(mcp_tools)
232
+ print(f"✓ MCP server '{server_info.name}' is healthy ({len(tools)} tools available)")
233
+ return True
234
+
235
+ except Exception as e:
236
+ print(f"✗ MCP server '{server_info.name}' health check failed: {e}")
237
+ return False
238
+
239
+
240
+ def run_with_mcp_tools(
241
+ tasks: List[Task],
242
+ mcp_server: MCPServerInfo,
243
+ agents: Optional[List[Agent]] = None,
244
+ process: Process = Process.sequential,
245
+ verbose: bool = True,
246
+ inputs: Optional[Dict[str, Any]] = None,
247
+ skip_health_check: bool = False,
248
+ api_key: Optional[str] = None
249
+ ) -> Any:
250
+ """
251
+ Run tasks with agents that have access to MCP server tools.
252
+
253
+ NOTE ON AUTHENTICATION:
254
+ This function automatically detects if the MCP server requires authentication
255
+ based on the server's metadata. If authentication is required, you must provide
256
+ your API key using the api_key parameter.
257
+
258
+ Args:
259
+ tasks: List of tasks to run
260
+ mcp_server: MCPServerInfo object with server details
261
+ agents: Optional list of agents (if None, will use agents from tasks)
262
+ process: CrewAI process type (sequential or hierarchical)
263
+ verbose: Whether to enable verbose output
264
+ inputs: Optional inputs for the crew
265
+ skip_health_check: Skip server health check
266
+ api_key: Optional API key for authenticated MCP servers
267
+
268
+ Returns:
269
+ Result from the crew execution
270
+ """
271
+ # Check if the server is healthy (unless skipped)
272
+ if not skip_health_check:
273
+ # Pass the API key for health check if authentication is required
274
+ if not check_server_health(mcp_server, api_key):
275
+ print(f"MCP server '{mcp_server.name}' is not healthy.")
276
+ print(f"Server URL: {mcp_server.url}")
277
+ sys.exit(1)
278
+
279
+ # Check if authentication is required
280
+ requires_api_key = mcp_server.metadata.get("requires_api_key", False)
281
+ api_key_header = mcp_server.metadata.get("api_key_header", "Authorization")
282
+
283
+ # Create appropriate adapter
284
+ if requires_api_key:
285
+ if not api_key:
286
+ print(f"\n⚠️ WARNING: MCP server '{mcp_server.name}' requires authentication")
287
+ print(f"Expected header: {api_key_header}")
288
+ print("But no API key was provided.")
289
+ print("\nTo provide authentication:")
290
+ print("Pass your API key using the 'api_key' parameter")
291
+ print("Example: run_with_mcp_tools(tasks, mcp_server, api_key='YOUR_API_KEY')")
292
+ sys.exit(1)
293
+
294
+ # Use the provided API key directly (user provides raw key without Bearer prefix)
295
+ adapter = create_mcp_adapter_with_auth(
296
+ url=mcp_server.url,
297
+ api_key=api_key,
298
+ auth_header=api_key_header,
299
+ auth_prefix="Bearer" # We add the Bearer prefix
300
+ )
301
+ print(f"\n🔐 Using authenticated connection (header: {api_key_header})")
302
+ else:
303
+ # No authentication required
304
+ adapter = create_mcp_adapter(url=mcp_server.url)
305
+ print("\n🔓 Using standard connection (no authentication)")
306
+
307
+ # Get agents from tasks if not provided
308
+ if agents is None:
309
+ agents = [task.agent for task in tasks]
310
+ # Remove duplicates while preserving order
311
+ seen = set()
312
+ agents = [agent for agent in agents if not (agent in seen or seen.add(agent))]
313
+
314
+ try:
315
+ # Use context manager for MCP server connection
316
+ with adapter as all_tools:
317
+ print(f"Connected to MCP server '{mcp_server.name}'")
318
+ print(f"Available tools: {[tool.name for tool in all_tools]}")
319
+
320
+ # Assign tools to each agent based on their tools_subset if defined
321
+ for agent in agents:
322
+ # Get the tools subset from the class dictionary
323
+ tools_subset = MCPAgentBuilder.get_tools_subset(agent)
324
+ if tools_subset:
325
+ # Filter tools by name if a subset is specified
326
+ agent.tools = [tool for tool in all_tools if tool.name in tools_subset]
327
+ print(f"Agent '{agent.role}' assigned tools: {[tool.name for tool in agent.tools]}")
328
+ else:
329
+ # Use all tools if no subset is specified
330
+ agent.tools = all_tools
331
+ print(f"Agent '{agent.role}' assigned all available tools")
332
+
333
+ # Create and run the crew
334
+ crew = Crew(
335
+ agents=agents,
336
+ tasks=tasks,
337
+ verbose=verbose,
338
+ process=process
339
+ )
340
+
341
+ # Kickoff the crew with inputs
342
+ result = crew.kickoff(inputs=inputs or {})
343
+ return result
344
+
345
+ except Exception as e:
346
+ print(f"Error during execution: {e}")
347
+ import traceback
348
+ traceback.print_exc()
349
+ sys.exit(1)
350
+
351
+
352
+ # Example usage when running this file directly
353
+ if __name__ == "__main__":
354
+ # This example shows how to use the template without registry
355
+ print("MCP Agent Template Example")
356
+ print("=" * 80)
357
+
358
+ # Example MCP server configuration (would normally come from registry or config)
359
+ example_server = MCPServerInfo(
360
+ id="example-123",
361
+ name="example-mcp",
362
+ url="http://localhost:8080/mcp/", # Add trailing slash
363
+ description="Example MCP server for demonstration",
364
+ server_type="streamable-http",
365
+ capabilities=["example_tool1", "example_tool2"],
366
+ metadata={},
367
+ tags=["example", "demo"]
368
+ )
369
+
370
+ # Example of MCP server that requires authentication:
371
+ # authenticated_server = MCPServerInfo(
372
+ # id="news-456",
373
+ # name="newsapi-mcp",
374
+ # url="http://localhost:8000/mcp/", # Add trailing slash
375
+ # description="NewsAPI MCP server",
376
+ # server_type="streamable-http",
377
+ # capabilities=["search_news", "get_headlines"],
378
+ # metadata={
379
+ # "requires_api_key": True,
380
+ # "api_key_header": "Authorization",
381
+ # "headers": {
382
+ # "Authorization": "Bearer YOUR_API_KEY" # Client API key
383
+ # }
384
+ # },
385
+ # tags=["news", "api"]
386
+ # )
387
+
388
+ print(f"Using MCP Server: {example_server.name}")
389
+ print(f"Description: {example_server.description}")
390
+ print(f"URL: {example_server.url}")
391
+ print(f"Capabilities: {example_server.capabilities}")
392
+ print()
393
+
394
+ # Create an example agent
395
+ analyst = MCPAgentBuilder.create_agent(
396
+ role="Example Analyst",
397
+ goal="Demonstrate the usage of MCP tools",
398
+ backstory="""
399
+ You are an expert in using MCP server tools.
400
+ Your job is to demonstrate how to use the available tools effectively.
401
+ """,
402
+ verbose=True
403
+ )
404
+
405
+ # Create a task
406
+ demo_task = Task(
407
+ description="""
408
+ Use the available MCP tools to perform a simple demonstration.
409
+ Show what the tools can do and provide a summary.
410
+ """,
411
+ expected_output="""
412
+ A demonstration report showing the capabilities of the MCP tools.
413
+ """,
414
+ agent=analyst
415
+ )
416
+
417
+ print("Note: This is a template example. To run actual MCP tools:")
418
+ print("1. Ensure an MCP server is running at the specified URL")
419
+ print("2. Get the server configuration from the registry or config")
420
+ print("3. Create agents and tasks specific to your use case")
421
+ print("4. Run with: run_with_mcp_tools([task], mcp_server)")
422
+ print("=" * 80)
@@ -0,0 +1,56 @@
1
+ # Use Python 3.12 slim image as base
2
+ FROM python:3.12-slim
3
+
4
+ # Install system dependencies
5
+ RUN apt-get update && apt-get install -y \
6
+ curl \
7
+ && rm -rf /var/lib/apt/lists/*
8
+
9
+ # Install uv
10
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
11
+ RUN chmod +x /usr/local/bin/uv
12
+
13
+ # Create non-root user
14
+ RUN useradd -m -u 1000 appuser
15
+
16
+ # Create app directory and set ownership
17
+ RUN mkdir -p /app && chown -R appuser:appuser /app
18
+
19
+ # Set working directory
20
+ WORKDIR /app
21
+
22
+ # Switch to non-root user
23
+ USER appuser
24
+
25
+ # Copy project files
26
+ COPY --chown=appuser:appuser . .
27
+
28
+ # Install Python dependencies
29
+ RUN uv venv .venv && \
30
+ uv pip install -r pyproject.toml
31
+
32
+ # Set environment variables
33
+ ENV PATH="/app/.venv/bin:$PATH"
34
+ ENV PYTHONPATH=/app
35
+ ENV HOST=0.0.0.0
36
+ ENV PYTHONUNBUFFERED=1
37
+ ENV UV_SYSTEM_PYTHON=1
38
+ ENV STAGE=MAINNET
39
+ ENV PORT=8000
40
+ ENV LOG_LEVEL=INFO
41
+ {% if environment_variables %}
42
+ # Additional environment variables
43
+ {% for env_var in environment_variables %}
44
+ ENV {{ env_var.name }}="{{ env_var.value }}"
45
+ {% endfor %}
46
+ {% endif %}
47
+
48
+ # Health check
49
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
50
+ CMD curl -f http://localhost:${PORT:-8000}/health || exit 1
51
+
52
+ # Expose port (uses PORT environment variable with default)
53
+ EXPOSE ${PORT:-8000}
54
+
55
+ # Run the application
56
+ CMD ["uv", "run", "python", "server.py"]