traia-iatp 0.1.29__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 (107) hide show
  1. traia_iatp/README.md +368 -0
  2. traia_iatp/__init__.py +54 -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/d402_a2a_client.py +293 -0
  9. traia_iatp/client/grpc_a2a_tools.py +349 -0
  10. traia_iatp/client/root_path_a2a_client.py +1 -0
  11. traia_iatp/contracts/__init__.py +12 -0
  12. traia_iatp/contracts/iatp_contracts_config.py +263 -0
  13. traia_iatp/contracts/wallet_creator.py +255 -0
  14. traia_iatp/core/__init__.py +43 -0
  15. traia_iatp/core/models.py +172 -0
  16. traia_iatp/d402/__init__.py +55 -0
  17. traia_iatp/d402/chains.py +102 -0
  18. traia_iatp/d402/client.py +150 -0
  19. traia_iatp/d402/clients/__init__.py +7 -0
  20. traia_iatp/d402/clients/base.py +218 -0
  21. traia_iatp/d402/clients/httpx.py +219 -0
  22. traia_iatp/d402/common.py +114 -0
  23. traia_iatp/d402/encoding.py +28 -0
  24. traia_iatp/d402/examples/client_example.py +197 -0
  25. traia_iatp/d402/examples/server_example.py +171 -0
  26. traia_iatp/d402/facilitator.py +453 -0
  27. traia_iatp/d402/fastapi_middleware/__init__.py +6 -0
  28. traia_iatp/d402/fastapi_middleware/middleware.py +225 -0
  29. traia_iatp/d402/fastmcp_middleware.py +147 -0
  30. traia_iatp/d402/mcp_middleware.py +434 -0
  31. traia_iatp/d402/middleware.py +193 -0
  32. traia_iatp/d402/models.py +116 -0
  33. traia_iatp/d402/networks.py +98 -0
  34. traia_iatp/d402/path.py +43 -0
  35. traia_iatp/d402/payment_introspection.py +104 -0
  36. traia_iatp/d402/payment_signing.py +178 -0
  37. traia_iatp/d402/paywall.py +119 -0
  38. traia_iatp/d402/starlette_middleware.py +326 -0
  39. traia_iatp/d402/template.py +1 -0
  40. traia_iatp/d402/types.py +300 -0
  41. traia_iatp/mcp/__init__.py +18 -0
  42. traia_iatp/mcp/client.py +201 -0
  43. traia_iatp/mcp/d402_mcp_tool_adapter.py +361 -0
  44. traia_iatp/mcp/mcp_agent_template.py +481 -0
  45. traia_iatp/mcp/templates/Dockerfile.j2 +80 -0
  46. traia_iatp/mcp/templates/README.md.j2 +310 -0
  47. traia_iatp/mcp/templates/cursor-rules.md.j2 +520 -0
  48. traia_iatp/mcp/templates/deployment_params.json.j2 +20 -0
  49. traia_iatp/mcp/templates/docker-compose.yml.j2 +32 -0
  50. traia_iatp/mcp/templates/dockerignore.j2 +47 -0
  51. traia_iatp/mcp/templates/env.example.j2 +57 -0
  52. traia_iatp/mcp/templates/gitignore.j2 +77 -0
  53. traia_iatp/mcp/templates/mcp_health_check.py.j2 +150 -0
  54. traia_iatp/mcp/templates/pyproject.toml.j2 +32 -0
  55. traia_iatp/mcp/templates/pyrightconfig.json.j2 +22 -0
  56. traia_iatp/mcp/templates/run_local_docker.sh.j2 +390 -0
  57. traia_iatp/mcp/templates/server.py.j2 +175 -0
  58. traia_iatp/mcp/traia_mcp_adapter.py +543 -0
  59. traia_iatp/preview_diagrams.html +181 -0
  60. traia_iatp/registry/__init__.py +26 -0
  61. traia_iatp/registry/atlas_search_indexes.json +280 -0
  62. traia_iatp/registry/embeddings.py +298 -0
  63. traia_iatp/registry/iatp_search_api.py +846 -0
  64. traia_iatp/registry/mongodb_registry.py +771 -0
  65. traia_iatp/registry/readmes/ATLAS_SEARCH_INDEXES.md +252 -0
  66. traia_iatp/registry/readmes/ATLAS_SEARCH_SETUP.md +134 -0
  67. traia_iatp/registry/readmes/AUTHENTICATION_UPDATE.md +124 -0
  68. traia_iatp/registry/readmes/EMBEDDINGS_SETUP.md +172 -0
  69. traia_iatp/registry/readmes/IATP_SEARCH_API_GUIDE.md +257 -0
  70. traia_iatp/registry/readmes/MONGODB_X509_AUTH.md +208 -0
  71. traia_iatp/registry/readmes/README.md +251 -0
  72. traia_iatp/registry/readmes/REFACTORING_SUMMARY.md +191 -0
  73. traia_iatp/scripts/__init__.py +2 -0
  74. traia_iatp/scripts/create_wallet.py +244 -0
  75. traia_iatp/server/__init__.py +15 -0
  76. traia_iatp/server/a2a_server.py +219 -0
  77. traia_iatp/server/example_template_usage.py +72 -0
  78. traia_iatp/server/iatp_server_agent_generator.py +237 -0
  79. traia_iatp/server/iatp_server_template_generator.py +235 -0
  80. traia_iatp/server/templates/.dockerignore.j2 +48 -0
  81. traia_iatp/server/templates/Dockerfile.j2 +49 -0
  82. traia_iatp/server/templates/README.md +137 -0
  83. traia_iatp/server/templates/README.md.j2 +425 -0
  84. traia_iatp/server/templates/__init__.py +1 -0
  85. traia_iatp/server/templates/__main__.py.j2 +565 -0
  86. traia_iatp/server/templates/agent.py.j2 +94 -0
  87. traia_iatp/server/templates/agent_config.json.j2 +22 -0
  88. traia_iatp/server/templates/agent_executor.py.j2 +279 -0
  89. traia_iatp/server/templates/docker-compose.yml.j2 +23 -0
  90. traia_iatp/server/templates/env.example.j2 +84 -0
  91. traia_iatp/server/templates/gitignore.j2 +78 -0
  92. traia_iatp/server/templates/grpc_server.py.j2 +218 -0
  93. traia_iatp/server/templates/pyproject.toml.j2 +78 -0
  94. traia_iatp/server/templates/run_local_docker.sh.j2 +103 -0
  95. traia_iatp/server/templates/server.py.j2 +243 -0
  96. traia_iatp/special_agencies/__init__.py +4 -0
  97. traia_iatp/special_agencies/registry_search_agency.py +392 -0
  98. traia_iatp/utils/__init__.py +10 -0
  99. traia_iatp/utils/docker_utils.py +251 -0
  100. traia_iatp/utils/general.py +64 -0
  101. traia_iatp/utils/iatp_utils.py +126 -0
  102. traia_iatp-0.1.29.dist-info/METADATA +423 -0
  103. traia_iatp-0.1.29.dist-info/RECORD +107 -0
  104. traia_iatp-0.1.29.dist-info/WHEEL +5 -0
  105. traia_iatp-0.1.29.dist-info/entry_points.txt +2 -0
  106. traia_iatp-0.1.29.dist-info/licenses/LICENSE +21 -0
  107. traia_iatp-0.1.29.dist-info/top_level.txt +1 -0
@@ -0,0 +1,72 @@
1
+ """Example usage of the utility agent template generator.
2
+
3
+ This script demonstrates how to generate a utility agent from an MCP server.
4
+ """
5
+
6
+ from pathlib import Path
7
+ from ..core.models import MCPServer, MCPServerType
8
+ from .iatp_server_agent_generator import IATPServerAgentGenerator
9
+
10
+
11
+ def main():
12
+ """Example of generating a utility agent."""
13
+
14
+ # Create an MCP server specification
15
+ mcp_server = MCPServer(
16
+ id="hyperliquid-mcp-001",
17
+ name="hyperliquid-mcp",
18
+ url="http://localhost:3000/mcp",
19
+ server_type=MCPServerType.STREAMABLE_HTTP,
20
+ description="Implements comprehensive trading tools for Hyperliquid L1",
21
+ capabilities=[
22
+ "market_data",
23
+ "place_order",
24
+ "cancel_order",
25
+ "get_positions",
26
+ "get_account_info"
27
+ ],
28
+ metadata={
29
+ "version": "1.0.0",
30
+ "author": "Traia"
31
+ }
32
+ )
33
+
34
+ # Create the generator
35
+ generator = IATPServerAgentGenerator(
36
+ output_base_dir=Path("generated_agents")
37
+ )
38
+
39
+ # Generate the utility agent
40
+ agent = generator.generate_agent(
41
+ mcp_server=mcp_server,
42
+ agent_name="Hyperliquid Trading",
43
+ agent_id="hyperliquid-mcp-traia-utility-agent",
44
+ agent_description="A utility agent that exposes Hyperliquid trading capabilities via A2A protocol",
45
+ expose_individual_tools=False, # Just expose one main skill
46
+ auth_required=False,
47
+ use_simple_server=True, # Use the simplified server template
48
+ skill_examples=[
49
+ "Get current market data for ETH-USD",
50
+ "Place a limit order to buy 1 ETH at $3000",
51
+ "Show my current positions",
52
+ "Cancel order with ID 12345",
53
+ "Get my account balance and margin"
54
+ ]
55
+ )
56
+
57
+ print(f"Generated utility agent: {agent.name}")
58
+ print(f"ID: {agent.id}")
59
+ print(f"Status: {agent.status}")
60
+ print(f"Code path: {agent.code_path}")
61
+ print(f"Capabilities: {agent.capabilities}")
62
+
63
+ # The generated agent can now be:
64
+ # 1. Pushed to GitHub using push_agency_to_repo
65
+ # 2. Deployed to Cloud Run using cloudrun_deployer
66
+ # 3. Registered in MongoDB for discovery
67
+
68
+ return agent
69
+
70
+
71
+ if __name__ == "__main__":
72
+ main()
@@ -0,0 +1,237 @@
1
+ """Utility agent generator for A2A servers.
2
+
3
+ This module generates utility agents that wrap MCP servers and expose them via A2A protocol.
4
+ """
5
+
6
+ import json
7
+ import logging
8
+ import shutil
9
+ from pathlib import Path
10
+ from typing import Dict, Any, Optional
11
+ from datetime import datetime
12
+
13
+ from ..core.models import MCPServer, UtilityAgent, UtilityAgentStatus
14
+ from .iatp_server_template_generator import IATPServerTemplateGenerator
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class IATPServerAgentGenerator:
20
+ """Generate utility agents from MCP servers using templates."""
21
+
22
+ def __init__(self, output_base_dir: Optional[Path] = None):
23
+ """Initialize the generator.
24
+
25
+ Args:
26
+ output_base_dir: Base directory for generated agents
27
+ """
28
+ self.output_base_dir = output_base_dir or Path("generated_agents")
29
+ self.template_generator = IATPServerTemplateGenerator()
30
+
31
+ def _convert_datetime_to_string(self, obj: Any) -> Any:
32
+ """Convert datetime objects to strings recursively.
33
+
34
+ Args:
35
+ obj: Object that may contain datetime objects
36
+
37
+ Returns:
38
+ Object with datetime objects converted to strings
39
+ """
40
+ if isinstance(obj, datetime):
41
+ return obj.isoformat()
42
+ elif isinstance(obj, dict):
43
+ return {key: self._convert_datetime_to_string(value) for key, value in obj.items()}
44
+ elif isinstance(obj, list):
45
+ return [self._convert_datetime_to_string(item) for item in obj]
46
+ elif isinstance(obj, tuple):
47
+ return tuple(self._convert_datetime_to_string(item) for item in obj)
48
+ else:
49
+ return obj
50
+
51
+ def generate_agent(
52
+ self,
53
+ mcp_server: MCPServer,
54
+ agent_name: Optional[str] = None,
55
+ agent_id: Optional[str] = None,
56
+ agent_description: Optional[str] = None,
57
+ expose_individual_tools: bool = False,
58
+ auth_required: bool = False,
59
+ auth_schemes: Optional[list] = None,
60
+ use_simple_server: bool = True,
61
+ **kwargs
62
+ ) -> UtilityAgent:
63
+ """Generate a utility agent from an MCP server.
64
+
65
+ Args:
66
+ mcp_server: MCP server to wrap
67
+ agent_name: Name for the agent (defaults to MCP server name)
68
+ agent_id: Unique ID for the agent (defaults to generated)
69
+ agent_description: Description of the agent
70
+ expose_individual_tools: Whether to expose individual MCP tools
71
+ auth_required: Whether authentication is required
72
+ auth_schemes: Authentication schemes if required
73
+ use_simple_server: Whether to use simplified server template
74
+ **kwargs: Additional template variables
75
+
76
+ Returns:
77
+ Generated UtilityAgent object
78
+ """
79
+ # Generate defaults
80
+ if agent_name is None:
81
+ agent_name = f"{mcp_server.name} Utility Agent"
82
+
83
+ if agent_id is None:
84
+ # Generate a clean ID based on MCP server name without timestamp
85
+ base_id = mcp_server.name.lower().replace(' ', '-').replace('_', '-')
86
+ agent_id = f"{base_id}-traia-utility-agent"
87
+
88
+ if agent_description is None:
89
+ agent_description = f"A2A utility agent that exposes {mcp_server.name} capabilities"
90
+
91
+ # Prepare capabilities list
92
+ capabilities = []
93
+ if hasattr(mcp_server, 'capabilities') and mcp_server.capabilities:
94
+ if isinstance(mcp_server.capabilities, dict):
95
+ capabilities = list(mcp_server.capabilities.keys())
96
+ elif isinstance(mcp_server.capabilities, list):
97
+ capabilities = mcp_server.capabilities
98
+
99
+ # Prepare skill examples based on MCP server type
100
+ skill_examples = kwargs.get('skill_examples', [])
101
+ if not skill_examples:
102
+ skill_examples = self._generate_skill_examples(mcp_server)
103
+
104
+ # Generate output directory path
105
+ output_dir = self.output_base_dir / agent_id
106
+
107
+ # Get MCP server metadata and convert datetime objects to strings
108
+ mcp_server_metadata = getattr(mcp_server, 'metadata', {})
109
+ mcp_server_metadata_serializable = self._convert_datetime_to_string(mcp_server_metadata)
110
+
111
+ # Use template generator to create the agent
112
+ generated_path = self.template_generator.generate_agent(
113
+ output_dir=output_dir,
114
+ agent_name=agent_name,
115
+ agent_id=agent_id,
116
+ agent_description=agent_description,
117
+ agent_version="0.1.0",
118
+ mcp_server_name=mcp_server.name,
119
+ mcp_server_url=mcp_server.url,
120
+ mcp_server_description=mcp_server.description,
121
+ mcp_server_type=getattr(mcp_server, 'server_type', 'streamable-http'),
122
+ mcp_server_capabilities=capabilities,
123
+ mcp_server_metadata=mcp_server_metadata_serializable,
124
+ expose_individual_tools=expose_individual_tools,
125
+ auth_required=auth_required,
126
+ auth_schemes=auth_schemes or [],
127
+ skill_examples=skill_examples,
128
+ use_simple_server=use_simple_server,
129
+ **kwargs
130
+ )
131
+
132
+ # Create UtilityAgent object
133
+ utility_agent = UtilityAgent(
134
+ id=agent_id,
135
+ name=agent_name,
136
+ description=agent_description,
137
+ mcp_server_id=mcp_server.id,
138
+ capabilities=capabilities,
139
+ status=UtilityAgentStatus.GENERATED,
140
+ code_path=str(generated_path),
141
+ created_at=datetime.now(),
142
+ metadata={
143
+ "generated_from": mcp_server.name,
144
+ "use_simple_server": use_simple_server,
145
+ "auth_required": auth_required,
146
+ }
147
+ )
148
+
149
+ logger.info(f"Generated utility agent '{agent_name}' at {generated_path}")
150
+ return utility_agent
151
+
152
+ def _generate_skill_examples(self, mcp_server: MCPServer) -> list:
153
+ """Generate example prompts based on MCP server type.
154
+
155
+ Args:
156
+ mcp_server: MCP server object
157
+
158
+ Returns:
159
+ List of example prompts
160
+ """
161
+ # Default examples
162
+ examples = [
163
+ f"Help me use {mcp_server.name}",
164
+ f"What can {mcp_server.name} do?",
165
+ ]
166
+
167
+ # Add type-specific examples
168
+ server_type = mcp_server.name.lower()
169
+
170
+ if 'search' in server_type:
171
+ examples.extend([
172
+ "Search for information about AI",
173
+ "Find recent news about technology"
174
+ ])
175
+ elif 'file' in server_type or 'filesystem' in server_type:
176
+ examples.extend([
177
+ "List files in the current directory",
178
+ "Read the contents of a file"
179
+ ])
180
+ elif 'database' in server_type or 'db' in server_type:
181
+ examples.extend([
182
+ "Query the database for user information",
183
+ "Show me the database schema"
184
+ ])
185
+ elif 'api' in server_type:
186
+ examples.extend([
187
+ "Call the API to get data",
188
+ "Send a request to the endpoint"
189
+ ])
190
+ elif 'trading' in server_type or 'finance' in server_type:
191
+ examples.extend([
192
+ "Get the current market data",
193
+ "Show me trading opportunities"
194
+ ])
195
+ else:
196
+ # Generic examples
197
+ examples.extend([
198
+ f"Execute a {mcp_server.name} operation",
199
+ f"Process this request using {mcp_server.name}"
200
+ ])
201
+
202
+ return examples[:5] # Limit to 5 examples
203
+
204
+ def update_agent_files(
205
+ self,
206
+ agent_path: Path,
207
+ updates: Dict[str, Any]
208
+ ) -> None:
209
+ """Update existing agent files with new configuration.
210
+
211
+ Args:
212
+ agent_path: Path to the agent directory
213
+ updates: Dictionary of updates to apply
214
+ """
215
+ # Update agent_config.json if it exists
216
+ config_path = agent_path / "agent_config.json"
217
+ if config_path.exists():
218
+ with open(config_path, 'r') as f:
219
+ config = json.load(f)
220
+
221
+ # Merge updates
222
+ config.update(updates)
223
+
224
+ with open(config_path, 'w') as f:
225
+ json.dump(config, f, indent=2)
226
+
227
+ logger.info(f"Updated agent configuration at {config_path}")
228
+
229
+ def cleanup_agent(self, agent_path: Path) -> None:
230
+ """Clean up generated agent files.
231
+
232
+ Args:
233
+ agent_path: Path to the agent directory
234
+ """
235
+ if agent_path.exists():
236
+ shutil.rmtree(agent_path)
237
+ logger.info(f"Cleaned up agent at {agent_path}")
@@ -0,0 +1,235 @@
1
+ """Template generator for A2A utility agents.
2
+
3
+ This module provides functionality to generate utility agent code from templates.
4
+ """
5
+
6
+ import os
7
+ import json
8
+ import shutil
9
+ from pathlib import Path
10
+ from typing import Dict, Any, List, Optional
11
+ from jinja2 import Environment, FileSystemLoader, select_autoescape
12
+ import logging
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class IATPServerTemplateGenerator:
18
+ """Generate utility agent code from Jinja2 templates."""
19
+
20
+ def __init__(self, templates_dir: Optional[Path] = None):
21
+ """Initialize the template generator.
22
+
23
+ Args:
24
+ templates_dir: Directory containing the Jinja2 templates
25
+ """
26
+ if templates_dir is None:
27
+ templates_dir = Path(__file__).parent / "templates"
28
+
29
+ self.templates_dir = templates_dir
30
+ self.env = Environment(
31
+ loader=FileSystemLoader(self.templates_dir),
32
+ autoescape=select_autoescape(['html', 'xml']),
33
+ trim_blocks=True,
34
+ lstrip_blocks=True
35
+ )
36
+
37
+ def generate_agent(
38
+ self,
39
+ output_dir: Path,
40
+ agent_name: str,
41
+ agent_id: str,
42
+ mcp_server_name: str,
43
+ mcp_server_url: str,
44
+ mcp_server_description: str,
45
+ mcp_server_capabilities: List[str],
46
+ agent_description: Optional[str] = None,
47
+ agent_version: str = "0.1.0",
48
+ mcp_server_type: str = "streamable-http",
49
+ mcp_server_metadata: Optional[Dict[str, Any]] = None,
50
+ expose_individual_tools: bool = False,
51
+ auth_required: bool = False,
52
+ auth_schemes: Optional[List[str]] = None,
53
+ skill_examples: Optional[List[str]] = None,
54
+ additional_dependencies: Optional[List[str]] = None,
55
+ environment_variables: Optional[List[Dict[str, str]]] = None,
56
+ use_simple_server: bool = True,
57
+ additional_ignores: Optional[List[str]] = None,
58
+ **kwargs
59
+ ) -> Path:
60
+ """Generate a complete utility agent from templates.
61
+
62
+ Args:
63
+ output_dir: Directory to write the generated agent
64
+ agent_name: Human-readable name of the agent
65
+ agent_id: Unique identifier for the agent
66
+ mcp_server_name: Name of the MCP server
67
+ mcp_server_url: URL or path to the MCP server
68
+ mcp_server_description: Description of the MCP server
69
+ mcp_server_capabilities: List of capabilities provided by the MCP server
70
+ agent_description: Description of the agent (defaults to auto-generated)
71
+ agent_version: Version of the agent
72
+ mcp_server_type: Type of MCP server (stdio, http, etc.)
73
+ mcp_server_metadata: Additional metadata for the MCP server
74
+ expose_individual_tools: Whether to expose individual MCP tools as A2A skills
75
+ auth_required: Whether authentication is required
76
+ auth_schemes: List of authentication schemes if auth is required
77
+ skill_examples: Example prompts for the main skill
78
+ additional_dependencies: Extra Python dependencies
79
+ environment_variables: Environment variables for Dockerfile
80
+ use_simple_server: Whether to use the simplified server.py template
81
+ additional_ignores: Additional patterns to add to .gitignore
82
+ **kwargs: Additional template variables
83
+
84
+ Returns:
85
+ Path to the generated agency directory
86
+ """
87
+ # Create output directory
88
+ output_dir = Path(output_dir)
89
+ output_dir.mkdir(parents=True, exist_ok=True)
90
+
91
+ # Generate default values
92
+ if agent_description is None:
93
+ agent_description = f"Utility agent that exposes {mcp_server_name} capabilities via A2A protocol"
94
+
95
+ if skill_examples is None:
96
+ skill_examples = [
97
+ f"Help me use {mcp_server_name}",
98
+ f"Process this request with {mcp_server_name}",
99
+ "Execute this operation"
100
+ ]
101
+
102
+ if mcp_server_metadata is None:
103
+ mcp_server_metadata = {}
104
+
105
+ if additional_dependencies is None:
106
+ additional_dependencies = []
107
+
108
+ if environment_variables is None:
109
+ environment_variables = []
110
+
111
+ if additional_ignores is None:
112
+ additional_ignores = []
113
+
114
+ # Convert agent name to valid Python identifiers
115
+ class_name = ''.join(word.capitalize() for word in agent_name.replace('-', ' ').split())
116
+ package_name = agent_name.lower().replace(' ', '_').replace('-', '_')
117
+ module_name = package_name
118
+ docker_image = f"traia/{agent_id}:latest"
119
+
120
+ # Prepare template context
121
+ context = {
122
+ "agent_name": agent_name,
123
+ "agent_id": agent_id,
124
+ "agent_description": agent_description,
125
+ "agent_version": agent_version,
126
+ "mcp_server_name": mcp_server_name,
127
+ "mcp_server_url": mcp_server_url,
128
+ "mcp_server_description": mcp_server_description,
129
+ "mcp_server_type": mcp_server_type,
130
+ "mcp_server_capabilities": mcp_server_capabilities,
131
+ "mcp_server_metadata": mcp_server_metadata,
132
+ "expose_individual_tools": expose_individual_tools,
133
+ "auth_required": auth_required,
134
+ "auth_schemes": auth_schemes or [],
135
+ "skill_examples": skill_examples,
136
+ "class_name": class_name,
137
+ "package_name": package_name,
138
+ "module_name": module_name,
139
+ "docker_image": docker_image,
140
+ "additional_dependencies": additional_dependencies,
141
+ "environment_variables": environment_variables,
142
+ "additional_ignores": additional_ignores,
143
+ "use_uv_lock": False, # Will be true after first uv sync
144
+ **kwargs
145
+ }
146
+
147
+ # Generate files
148
+ files_to_generate = []
149
+
150
+ # Common files for both approaches
151
+ common_files = [
152
+ ("agent_config.json.j2", "agent_config.json"),
153
+ ("pyproject.toml.j2", "pyproject.toml"),
154
+ ("Dockerfile.j2", "Dockerfile"),
155
+ ("README.md.j2", "README.md"),
156
+ ("docker-compose.yml.j2", "docker-compose.yml"),
157
+ ("env.example.j2", ".env.example"),
158
+ ("gitignore.j2", ".gitignore"),
159
+ (".dockerignore.j2", ".dockerignore"),
160
+ ("run_local_docker.sh.j2", "run_local_docker.sh"),
161
+ ]
162
+
163
+ if use_simple_server:
164
+ # Simple single-file approach
165
+ files_to_generate = [
166
+ ("server.py.j2", "server.py"),
167
+ ] + common_files
168
+ else:
169
+ # Modular approach with separate files
170
+ # Create package directory
171
+ pkg_dir = output_dir / package_name
172
+ pkg_dir.mkdir(exist_ok=True)
173
+
174
+ files_to_generate = [
175
+ ("agent.py.j2", f"{package_name}/agent.py"),
176
+ ("agent_executor.py.j2", f"{package_name}/agent_executor.py"),
177
+ ("__main__.py.j2", f"{package_name}/__main__.py"),
178
+ ] + common_files
179
+
180
+ # Create __init__.py
181
+ (output_dir / package_name / "__init__.py").write_text("")
182
+
183
+ # Render and write templates
184
+ for template_name, output_name in files_to_generate:
185
+ try:
186
+ template = self.env.get_template(template_name)
187
+ content = template.render(context)
188
+ output_path = output_dir / output_name
189
+ output_path.parent.mkdir(parents=True, exist_ok=True)
190
+ output_path.write_text(content)
191
+ logger.info(f"Generated {output_path}")
192
+
193
+ # Make run_local_docker.sh executable
194
+ if output_name == "run_local_docker.sh":
195
+ output_path.chmod(0o755)
196
+ logger.info(f"Made {output_path} executable")
197
+ except Exception as e:
198
+ logger.error(f"Error generating {output_name} from {template_name}: {e}")
199
+ raise
200
+
201
+ # # Copy mcp_agent_template.py
202
+ # self._copy_mcp_agent_template(output_dir)
203
+
204
+ logger.info(f"Successfully generated utility agent at {output_dir}")
205
+ return output_dir
206
+
207
+ # def _copy_mcp_agent_template(self, output_dir: Path):
208
+ # """Copy the mcp_agent_template.py file to the generated agent.
209
+
210
+ # Args:
211
+ # output_dir: Output directory for the agent
212
+ # """
213
+ # # Find the mcp_agent_template.py file
214
+ # mcp_template_path = Path(__file__).parent.parent / "mcp" / "mcp_agent_template.py"
215
+ # agent_adapter_path = Path(__file__).parent.parent / "mcp" / "traia_mcp_adapter.py"
216
+
217
+ # if mcp_template_path.exists():
218
+ # # Copy to output directory
219
+ # dest_path_one = output_dir / "mcp_agent_template.py"
220
+ # dest_path_two = output_dir / "traia_mcp_adapter.py"
221
+ # shutil.copy2(mcp_template_path, dest_path_one)
222
+ # shutil.copy2(agent_adapter_path, dest_path_two)
223
+ # logger.info(f"Copied mcp_agent_template.py to {dest_path_one}")
224
+ # logger.info(f"Copied traia_mcp_adapter.py to {dest_path_two}")
225
+ # else:
226
+ # logger.warning(f"Could not find mcp_agent_template.py at {mcp_template_path}")
227
+ # logger.warning(f"Could not find traia_mcp_adapter.py at {agent_adapter_path}")
228
+
229
+ def list_templates(self) -> List[str]:
230
+ """List available templates.
231
+
232
+ Returns:
233
+ List of template file names
234
+ """
235
+ return [f.name for f in self.templates_dir.glob("*.j2")]
@@ -0,0 +1,48 @@
1
+ # Environment files (should be mounted, not built into image)
2
+ .env
3
+ .env.*
4
+ !.env.example
5
+
6
+ # Python
7
+ __pycache__/
8
+ *.pyc
9
+ *.pyo
10
+ *.pyd
11
+ .Python
12
+ *.egg-info/
13
+ dist/
14
+ build/
15
+
16
+ # Git
17
+ .git/
18
+ .gitignore
19
+
20
+ # IDE
21
+ .vscode/
22
+ .idea/
23
+ *.swp
24
+ *.swo
25
+
26
+ # OS
27
+ .DS_Store
28
+ Thumbs.db
29
+
30
+ # UV/Poetry
31
+ .venv/
32
+ venv/
33
+ env/
34
+
35
+ # Logs
36
+ *.log
37
+ logs/
38
+
39
+ # Testing
40
+ .pytest_cache/
41
+ .coverage
42
+ htmlcov/
43
+ .tox/
44
+
45
+ # Documentation
46
+ docs/
47
+ *.md
48
+ !README.md
@@ -0,0 +1,49 @@
1
+ FROM python:3.12-slim
2
+
3
+ # Install system dependencies
4
+ RUN apt-get update && apt-get install -y \
5
+ curl \
6
+ && rm -rf /var/lib/apt/lists/*
7
+
8
+ # Install uv
9
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
10
+ RUN chmod +x /usr/local/bin/uv
11
+
12
+ # Create non-root user
13
+ RUN useradd -m -u 1000 appuser
14
+
15
+ # Create app directory and set ownership
16
+ RUN mkdir -p /app && chown -R appuser:appuser /app
17
+
18
+ # Set working directory
19
+ WORKDIR /app
20
+
21
+ # Switch to non-root user
22
+ USER appuser
23
+
24
+ # Copy project files
25
+ COPY --chown=appuser:appuser . .
26
+
27
+ # Install Python dependencies
28
+ RUN uv venv .venv && \
29
+ uv pip install -r pyproject.toml
30
+
31
+ # Set environment variables
32
+ ENV PATH="/app/.venv/bin:$PATH"
33
+ ENV PYTHONPATH=/app
34
+ ENV HOST=0.0.0.0
35
+ ENV PYTHONUNBUFFERED=1
36
+ ENV UV_SYSTEM_PYTHON=1
37
+ {% for env_var in environment_variables %}
38
+ ENV {{ env_var.name }}="{{ env_var.value }}"
39
+ {% endfor %}
40
+
41
+ # Health check
42
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
43
+ CMD curl -f http://localhost:${PORT:-8000}/.well-known/agent.json || exit 1
44
+
45
+ # Expose port (uses PORT environment variable with default)
46
+ EXPOSE ${PORT:-8000}
47
+
48
+ # Run the application
49
+ CMD ["uv", "run", "python", "-m", "{{ module_name }}"]