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,78 @@
1
+ [project]
2
+ name = "{{ package_name }}"
3
+ version = "{{ agent_version }}"
4
+ description = "{{ agent_description }}"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "crewai>=0.203.1",
9
+ "crewai-tools[mcp]>=0.76.0",
10
+ "agentops>=0.4.21",
11
+ "a2a-sdk>=0.2.6",
12
+ "fastmcp>=2.12.5",
13
+ "hypercorn>=0.17.3", # HTTP/2 support via h2 dependency
14
+ "httpx[http2]>=0.28.1", # HTTP/2 support with http2 extra
15
+ "starlette>=0.48.0", # For SSE streaming support
16
+ "sse-starlette>=3.0.2", # Server-Sent Events support
17
+ "pydantic>=2.12.2",
18
+ "python-dotenv>=1.1.1",
19
+ "pymongo>=4.15.3", # Required for MCP registry
20
+ "traia-iatp>=0.1.27", # Core IATP functionality with d402 support
21
+ "eth-account>=0.11.0", # For x402 signing
22
+ "web3>=6.15.0", # For x402 blockchain interaction
23
+ {% for dep in additional_dependencies %}
24
+ "{{ dep }}",
25
+ {% endfor %}
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ grpc = [
30
+ "grpcio>=1.73.0",
31
+ "grpcio-tools>=1.71.0",
32
+ "grpcio-reflection>=1.71.0",
33
+ ]
34
+
35
+ dev = [
36
+ "pytest>=7.0.0",
37
+ "pytest-asyncio>=0.21.0",
38
+ "black>=23.0.0",
39
+ "ruff>=0.1.0",
40
+ "mypy>=1.0.0",
41
+ ]
42
+
43
+ [build-system]
44
+ requires = ["setuptools>=64", "wheel"]
45
+ build-backend = "setuptools.build_meta"
46
+
47
+ [tool.black]
48
+ line-length = 100
49
+ target-version = ['py312']
50
+ include = '\.pyi?$'
51
+
52
+ [tool.ruff]
53
+ line-length = 100
54
+ target-version = "py312"
55
+ select = [
56
+ "E", # pycodestyle errors
57
+ "W", # pycodestyle warnings
58
+ "F", # pyflakes
59
+ "I", # isort
60
+ "B", # flake8-bugbear
61
+ "C4", # flake8-comprehensions
62
+ "UP", # pyupgrade
63
+ ]
64
+ ignore = [
65
+ "E501", # line too long
66
+ "B008", # do not perform function calls in argument defaults
67
+ "C901", # too complex
68
+ ]
69
+
70
+ [tool.ruff.isort]
71
+ known-first-party = ["{{ package_name }}"]
72
+
73
+ [tool.mypy]
74
+ python_version = "3.12"
75
+ warn_return_any = true
76
+ warn_unused_configs = true
77
+ disallow_untyped_defs = true
78
+ ignore_missing_imports = true
@@ -0,0 +1,103 @@
1
+ #!/bin/bash
2
+
3
+ # Script to build and run the {{ agent_name }} locally in Docker
4
+
5
+ set -e # Exit on error
6
+
7
+ # Color codes for output
8
+ GREEN='\033[0;32m'
9
+ BLUE='\033[0;34m'
10
+ YELLOW='\033[1;33m'
11
+ RED='\033[0;31m'
12
+ NC='\033[0m' # No Color
13
+
14
+ # Load environment variables from .env if it exists
15
+ if [ -f .env ]; then
16
+ echo -e "${BLUE}📋 Loading environment variables from .env file...${NC}"
17
+ set -a # Export all variables
18
+ source .env
19
+ set +a # Stop exporting
20
+ else
21
+ echo -e "${YELLOW}⚠️ No .env file found. Using defaults.${NC}"
22
+ echo -e "${YELLOW} Copy .env.example to .env and configure as needed.${NC}"
23
+ fi
24
+
25
+ # Configuration with defaults
26
+ IMAGE_NAME="{{ agent_id }}"
27
+ CONTAINER_NAME="{{ agent_id }}-local"
28
+ HOST_PORT=${PORT:-8000}
29
+ CONTAINER_PORT=${PORT:-8000}
30
+
31
+ echo -e "${BLUE}🚀 Building and running {{ agent_name }}...${NC}"
32
+ echo -e "${BLUE} Using port: ${HOST_PORT}${NC}"
33
+ echo
34
+
35
+ # Check if Docker is installed
36
+ if ! command -v docker &> /dev/null; then
37
+ echo -e "${RED}❌ Docker is not installed. Please install Docker first.${NC}"
38
+ exit 1
39
+ fi
40
+
41
+ # Stop and remove existing container if it exists
42
+ if docker ps -a | grep -q $CONTAINER_NAME; then
43
+ echo -e "${YELLOW}🛑 Stopping existing container...${NC}"
44
+ docker stop $CONTAINER_NAME >/dev/null 2>&1 || true
45
+ docker rm $CONTAINER_NAME >/dev/null 2>&1 || true
46
+ fi
47
+
48
+ # Build the Docker image
49
+ echo -e "${BLUE}🔨 Building Docker image...${NC}"
50
+ docker build -t $IMAGE_NAME .
51
+
52
+ # Run the container
53
+ echo -e "${BLUE}🏃 Starting container...${NC}"
54
+ docker run -d \
55
+ --name $CONTAINER_NAME \
56
+ -p $HOST_PORT:$CONTAINER_PORT \
57
+ --env-file .env \
58
+ -e DEBUG_PROTOCOL=${DEBUG_PROTOCOL:-false} \
59
+ $IMAGE_NAME
60
+
61
+ # Wait for the server to start
62
+ echo -e "${YELLOW}⏳ Waiting for server to start...${NC}"
63
+ sleep 3
64
+
65
+ # Check if container is running
66
+ if ! docker ps | grep -q $CONTAINER_NAME; then
67
+ echo -e "${RED}❌ Container failed to start. Checking logs:${NC}"
68
+ docker logs $CONTAINER_NAME
69
+ exit 1
70
+ fi
71
+
72
+ # Get container info
73
+ CONTAINER_ID=$(docker ps -q -f name=$CONTAINER_NAME)
74
+
75
+ # Output connection information
76
+ echo
77
+ echo -e "${GREEN}✅ {{ agent_name }} is running!${NC}"
78
+ echo
79
+ echo -e "${BLUE}📍 Connection Information:${NC}"
80
+ echo -e " A2A Endpoint: ${GREEN}http://localhost:${HOST_PORT}/a2a${NC}"
81
+ echo -e " Agent Info: ${GREEN}http://localhost:${HOST_PORT}/.well-known/agent.json${NC}"
82
+ echo -e " Container Name: ${GREEN}${CONTAINER_NAME}${NC}"
83
+ echo -e " Container ID: ${GREEN}${CONTAINER_ID:0:12}${NC}"
84
+ echo
85
+ echo -e "${BLUE}📝 Useful commands:${NC}"
86
+ echo -e " View logs: ${YELLOW}docker logs -f ${CONTAINER_NAME}${NC}"
87
+ echo -e " Stop server: ${YELLOW}docker stop ${CONTAINER_NAME}${NC}"
88
+ echo -e " Remove container: ${YELLOW}docker rm ${CONTAINER_NAME}${NC}"
89
+ echo -e " Shell access: ${YELLOW}docker exec -it ${CONTAINER_NAME} /bin/bash${NC}"
90
+ echo
91
+
92
+ # Check if the server is responding
93
+ echo -e "${YELLOW}🔍 Checking server health...${NC}"
94
+ if curl -s -o /dev/null -w "%{http_code}" "http://localhost:${HOST_PORT}/.well-known/agent.json" | grep -q "200"; then
95
+ echo -e "${GREEN}✅ Server is healthy!${NC}"
96
+
97
+ # Get and display agency info
98
+ echo
99
+ echo -e "${BLUE}📋 Agency Information:${NC}"
100
+ curl -s "http://localhost:${HOST_PORT}/.well-known/agent.json" | python -m json.tool || echo "Could not fetch agency info"
101
+ else
102
+ echo -e "${YELLOW}⚠️ Server may still be starting up. Check logs with: docker logs -f ${CONTAINER_NAME}${NC}"
103
+ fi
@@ -0,0 +1,243 @@
1
+ """
2
+ {{ agent_name }} - A2A Server Implementation
3
+
4
+ Auto-generated utility agent that exposes {{ mcp_server_name }} via A2A protocol.
5
+ This is a simplified all-in-one implementation.
6
+ """
7
+
8
+ import asyncio
9
+ import json
10
+ import logging
11
+ import os
12
+ from typing import Dict, Any, Optional
13
+ from a2a.server.apps import A2AStarletteApplication
14
+ from a2a.server.agent_execution import AgentExecutor, RequestContext
15
+ from a2a.server.events.event_queue import EventQueue
16
+ from a2a.server.tasks import InMemoryTaskStore
17
+ from a2a.server.request_handlers import DefaultRequestHandler
18
+ from a2a.types import AgentCard, AgentSkill, AgentCapabilities
19
+ from a2a.utils import new_agent_text_message
20
+ import uvicorn
21
+ from crewai import Task
22
+ from fastapi import Request
23
+
24
+ # Import the MCP agent builder and run function
25
+ from traia_iatp.mcp import MCPServerConfig, MCPAgentBuilder, run_with_mcp_tools, MCPServerInfo
26
+
27
+ # Import D402 payment support
28
+ from traia_iatp.x402 import (
29
+ D402Config,
30
+ D402ServicePrice,
31
+ require_iatp_payment,
32
+ add_x402_info_to_agent_card
33
+ )
34
+
35
+ logging.basicConfig(level=logging.INFO)
36
+ logger = logging.getLogger(__name__)
37
+
38
+ # Load agent configuration
39
+ with open("agent_config.json", "r") as f:
40
+ config = json.load(f)
41
+
42
+ # Create MCP configuration
43
+ mcp_config = MCPServerConfig(
44
+ name=config["mcp_server"]["name"],
45
+ url=config["mcp_server"]["url"],
46
+ description=config["mcp_server"]["description"],
47
+ server_type=config["mcp_server"].get("server_type", "streamable-http"),
48
+ capabilities=config["mcp_server"].get("capabilities", []),
49
+ metadata=config["mcp_server"].get("metadata", {})
50
+ )
51
+
52
+ # Create MCP server info for use with run_with_mcp_tools
53
+ mcp_server_info = MCPServerInfo(
54
+ id="", # Not needed for direct usage
55
+ name=mcp_config.name,
56
+ url=mcp_config.url,
57
+ description=mcp_config.description,
58
+ server_type=mcp_config.server_type,
59
+ capabilities=mcp_config.capabilities,
60
+ metadata=mcp_config.metadata,
61
+ tags=mcp_config.metadata.get("tags", [])
62
+ )
63
+
64
+ # Get MCP server API key if required
65
+ {% if requires_api_key %}
66
+ # Check for API keys required by the MCP server
67
+ MCP_API_KEY = None
68
+ {% for api_key_name in api_keys %}
69
+ if not MCP_API_KEY and os.getenv("{{ api_key_name }}"):
70
+ MCP_API_KEY = os.getenv("{{ api_key_name }}")
71
+ logger.info(f"Using API key from {{ api_key_name }} environment variable")
72
+ {% endfor %}
73
+
74
+ if not MCP_API_KEY:
75
+ logger.warning("No API key found for MCP server authentication.")
76
+ logger.warning("The MCP server requires one of these environment variables to be set:")
77
+ {% for api_key_name in api_keys %}
78
+ logger.warning(" - {{ api_key_name }}")
79
+ {% endfor %}
80
+ {% else %}
81
+ MCP_API_KEY = None
82
+ {% endif %}
83
+
84
+ # D402 Configuration (if enabled)
85
+ x402_enabled = os.getenv("D402_ENABLED", "false").lower() == "true"
86
+ x402_config = None
87
+
88
+ if x402_enabled:
89
+ logger.info("D402 payments enabled")
90
+
91
+ contract_address = os.getenv("UTILITY_AGENT_CONTRACT_ADDRESS")
92
+ if not contract_address:
93
+ logger.error("D402 enabled but UTILITY_AGENT_CONTRACT_ADDRESS not set!")
94
+ x402_enabled = False
95
+ else:
96
+ x402_config = D402Config(
97
+ enabled=True,
98
+ pay_to_address=contract_address,
99
+ default_price=D402ServicePrice(
100
+ usd_amount=os.getenv("D402_PRICE_USD", "0.01"),
101
+ network=os.getenv("D402_NETWORK", "sepolia"),
102
+ asset_address=os.getenv("D402_TOKEN_ADDRESS", ""),
103
+ max_timeout_seconds=300
104
+ ),
105
+ facilitator_url=os.getenv("D402_FACILITATOR_URL", "http://localhost:8080"),
106
+ service_description=config["description"],
107
+ protected_paths=["*"] # Protect all paths except .well-known
108
+ )
109
+ logger.info(f"D402 configured: ${x402_config.default_price.usd_amount} per request")
110
+
111
+
112
+ class {{ class_name }}AgentExecutor(AgentExecutor):
113
+ """Agent executor for {{ agency_name }}."""
114
+
115
+ def __init__(self):
116
+ self.mcp_config = mcp_config
117
+ self.mcp_server_info = mcp_server_info
118
+
119
+ async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
120
+ """Process a request using the {{ agency_name }} capabilities."""
121
+ try:
122
+ # Get the user's request from context
123
+ request_text = context.get_user_input()
124
+ if not request_text:
125
+ await event_queue.enqueue_event(
126
+ new_agent_text_message("No user message provided")
127
+ )
128
+ return
129
+
130
+ # Get additional context if provided
131
+ task_context = {}
132
+ if hasattr(context, 'metadata'):
133
+ task_context = context.metadata or {}
134
+
135
+ # Create an agent for this request
136
+ agent = MCPAgentBuilder.create_agent(
137
+ role=f"{config['name']} Specialist",
138
+ goal=f"Process the request using {self.mcp_config.name} capabilities",
139
+ backstory=f"You are an expert at using {self.mcp_config.name}. {self.mcp_config.description}"
140
+ )
141
+
142
+ # Create a task
143
+ task = Task(
144
+ description=request_text,
145
+ expected_output="The processed result based on the request",
146
+ agent=agent
147
+ )
148
+
149
+ # Run with MCP tools
150
+ result = run_with_mcp_tools(
151
+ tasks=[task],
152
+ mcp_server=self.mcp_server_info,
153
+ inputs=task_context,
154
+ skip_health_check=True, # Skip for production usage
155
+ api_key=MCP_API_KEY # Pass the API key if available
156
+ )
157
+
158
+ # Send the result as agent message
159
+ await event_queue.enqueue_event(
160
+ new_agent_text_message(str(result))
161
+ )
162
+
163
+ except Exception as e:
164
+ logger.error(f"Error processing request: {e}")
165
+ await event_queue.enqueue_event(
166
+ new_agent_text_message(f"Error processing request: {str(e)}")
167
+ )
168
+
169
+
170
+ def create_app():
171
+ """Create the A2A application."""
172
+ # Create agent skill
173
+ skill = AgentSkill(
174
+ id="process_request",
175
+ name=f"Process request using {config['name']}",
176
+ description=config["description"],
177
+ examples=config.get("a2a_config", {}).get("skill_examples", [
178
+ "Help me with this request",
179
+ "Process this data"
180
+ ])
181
+ )
182
+
183
+ # Create capabilities
184
+ capabilities = AgentCapabilities(
185
+ streaming=False,
186
+ pushNotifications=False,
187
+ stateTransitionHistory=False
188
+ )
189
+
190
+ # Create agent card
191
+ agent_card_dict = {
192
+ "name": config["agency_id"],
193
+ "description": config["description"],
194
+ "url": f"http://0.0.0.0:{os.environ.get('PORT', 8000)}",
195
+ "version": config["version"],
196
+ "capabilities": capabilities.model_dump(),
197
+ "skills": [skill.model_dump()]
198
+ }
199
+
200
+ # Add x402 payment info to agent card if enabled
201
+ if x402_enabled and x402_config:
202
+ import asyncio
203
+ agent_card_dict = asyncio.run(add_x402_info_to_agent_card(agent_card_dict, x402_config))
204
+
205
+ # Create agent card from dict
206
+ agent_card = AgentCard(**agent_card_dict)
207
+
208
+ # Create executor
209
+ executor = {{ class_name }}AgentExecutor()
210
+
211
+ # Create task store and request handler
212
+ task_store = InMemoryTaskStore()
213
+ request_handler = DefaultRequestHandler(
214
+ agent_executor=executor,
215
+ task_store=task_store
216
+ )
217
+
218
+ # Create the A2A application
219
+ app = A2AStarletteApplication(
220
+ agent_card=agent_card,
221
+ http_handler=request_handler
222
+ )
223
+
224
+ # Add x402 middleware if enabled
225
+ if x402_enabled and x402_config:
226
+ logger.info("Adding x402 payment middleware")
227
+
228
+ @app.middleware("http")
229
+ async def payment_middleware(request: Request, call_next):
230
+ middleware = require_iatp_payment(x402_config)
231
+ return await middleware(request, call_next)
232
+
233
+ return app
234
+
235
+
236
+ if __name__ == "__main__":
237
+ # Get port from environment variable (for Cloud Run compatibility)
238
+ port = int(os.environ.get("PORT", 8000))
239
+ logger.info(f"Starting {config['name']} on port {port}")
240
+
241
+ # Create and run the app
242
+ app = create_app()
243
+ uvicorn.run(app.build(), host="0.0.0.0", port=port)
@@ -0,0 +1,4 @@
1
+ """Special pre-built agencies for common IATP use cases."""
2
+
3
+ # Module for special agencies - currently empty
4
+ __all__ = []