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,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,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 }}"]
@@ -0,0 +1,137 @@
1
+ # A2A Utility Agency Templates
2
+
3
+ This directory contains Jinja2 templates for generating utility agencies that wrap MCP servers and expose them via the A2A (Agent-to-Agent) protocol.
4
+
5
+ ## Overview
6
+
7
+ These templates follow the pattern established by the [A2A CrewAI examples](https://github.com/google-a2a/a2a-samples/tree/main/samples/python/agents/crewai) to create standardized A2A servers that:
8
+
9
+ 1. **Wrap MCP Servers**: Connect to any MCP (Model Context Protocol) server
10
+ 2. **Use CrewAI**: Leverage CrewAI agents to process requests intelligently
11
+ 3. **Expose A2A Interface**: Provide a standard A2A protocol interface for agent-to-agent communication
12
+
13
+ ## Templates
14
+
15
+ ### Core Templates
16
+
17
+ - **`agent.py.j2`** - CrewAI agent implementation that wraps the MCP server
18
+ - **`agent_executor.py.j2`** - A2A executor that interfaces the CrewAI agent with A2A protocol
19
+ - **`__main__.py.j2`** - Main entry point that initializes and starts the A2A server
20
+
21
+ ### Simple Server Template
22
+
23
+ - **`server.py.j2`** - All-in-one simplified implementation combining agent, executor, and server
24
+
25
+ ### Configuration Templates
26
+
27
+ - **`agency_config.json.j2`** - Agency configuration file
28
+ - **`pyproject.toml.j2`** - Python project dependencies
29
+ - **`Dockerfile.j2`** - Docker container configuration
30
+ - **`README.md.j2`** - Documentation for the generated agency
31
+
32
+ ## Template Variables
33
+
34
+ The templates expect the following variables:
35
+
36
+ ### Required Variables
37
+ - `agency_name` - Human-readable name of the agency
38
+ - `agency_id` - Unique identifier for the agency
39
+ - `mcp_server_name` - Name of the MCP server
40
+ - `mcp_server_url` - URL or path to the MCP server
41
+ - `mcp_server_description` - Description of the MCP server
42
+ - `mcp_server_capabilities` - List of capabilities provided
43
+
44
+ ### Optional Variables
45
+ - `agency_description` - Description of the agency (auto-generated if not provided)
46
+ - `agency_version` - Version of the agency (default: "0.1.0")
47
+ - `mcp_server_type` - Type of MCP server: "stdio", "http", etc. (default: "stdio")
48
+ - `expose_individual_tools` - Whether to expose each MCP tool as a separate A2A skill
49
+ - `auth_required` - Whether authentication is required
50
+ - `auth_schemes` - List of auth schemes if auth is required
51
+ - `skill_examples` - Example prompts for the main skill
52
+ - `additional_dependencies` - Extra Python dependencies
53
+ - `use_simple_server` - Whether to use the simplified server.py template
54
+
55
+ ### Auto-generated Variables
56
+ - `class_name` - PascalCase class name derived from agency_name
57
+ - `package_name` - Python package name (snake_case)
58
+ - `module_name` - Python module name
59
+ - `docker_image` - Docker image name
60
+
61
+ ## Usage
62
+
63
+ Use the `UtilityAgencyTemplateGenerator` class to generate agencies:
64
+
65
+ ```python
66
+ from iatp.server.template_generator import UtilityAgencyTemplateGenerator
67
+
68
+ generator = UtilityAgencyTemplateGenerator()
69
+ output_path = generator.generate_agency(
70
+ output_dir="path/to/output",
71
+ agency_name="My MCP Agency",
72
+ agency_id="my-mcp-agency",
73
+ mcp_server_name="my-mcp-server",
74
+ mcp_server_url="http://localhost:3000",
75
+ mcp_server_description="Description of the MCP server",
76
+ mcp_server_capabilities=["capability1", "capability2"],
77
+ use_simple_server=True # For simpler deployments
78
+ )
79
+ ```
80
+
81
+ ## Generated Structure
82
+
83
+ ### Simple Server (use_simple_server=True)
84
+ ```
85
+ output_dir/
86
+ ├── server.py # All-in-one A2A server
87
+ ├── agency_config.json # Configuration
88
+ ├── pyproject.toml # Dependencies
89
+ ├── Dockerfile # Container config
90
+ ├── README.md # Documentation
91
+ ├── docker-compose.yml # Local development
92
+ ├── .gitignore # Git ignore rules
93
+ └── .env.example # Environment variables example
94
+ ```
95
+
96
+ ### Modular Structure (use_simple_server=False)
97
+ ```
98
+ output_dir/
99
+ ├── package_name/
100
+ │ ├── __init__.py
101
+ │ ├── agent.py # CrewAI agent
102
+ │ ├── agent_executor.py # A2A executor
103
+ │ └── __main__.py # Entry point
104
+ ├── agency_config.json
105
+ ├── pyproject.toml
106
+ ├── Dockerfile
107
+ ├── README.md
108
+ ├── docker-compose.yml
109
+ ├── .gitignore
110
+ └── .env.example
111
+ ```
112
+
113
+ ## A2A Protocol Compliance
114
+
115
+ The generated agencies follow the A2A protocol by:
116
+
117
+ 1. **Agent Card**: Exposing agent capabilities at `/.well-known/agent.json`
118
+ 2. **Task Processing**: Handling tasks via POST to `/tasks/send`
119
+ 3. **Text I/O**: Supporting text input and output by default
120
+ 4. **Request Context**: Processing request context and metadata
121
+ 5. **Error Handling**: Proper error responses in A2A format
122
+
123
+ ## Next Steps
124
+
125
+ After generating an agency:
126
+
127
+ 1. **Test Locally**: Run with `uv run python -m package_name` or `python server.py`
128
+ 2. **Build Docker**: `docker build -t agency-name .`
129
+ 3. **Push to GitHub**: Use the push_agency_to_repo functionality
130
+ 4. **Deploy to Cloud**: Use cloudrun_deployer for Google Cloud Run
131
+ 5. **Register**: Add to MongoDB registry for discovery
132
+
133
+ ## References
134
+
135
+ - [A2A Protocol Documentation](https://github.com/google-a2a/A2A)
136
+ - [A2A CrewAI Examples](https://github.com/google-a2a/a2a-samples/tree/main/samples/python/agents/crewai)
137
+ - [MCP Protocol](https://github.com/anthropics/model-context-protocol)