traia-iatp 0.1.2__py3-none-any.whl → 0.1.67__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. traia_iatp/__init__.py +105 -8
  2. traia_iatp/cli/main.py +85 -1
  3. traia_iatp/client/__init__.py +28 -3
  4. traia_iatp/client/crewai_a2a_tools.py +32 -12
  5. traia_iatp/client/d402_a2a_client.py +348 -0
  6. traia_iatp/contracts/__init__.py +11 -0
  7. traia_iatp/contracts/data/abis/contract-abis-localhost.json +4091 -0
  8. traia_iatp/contracts/data/abis/contract-abis-sepolia.json +4890 -0
  9. traia_iatp/contracts/data/addresses/contract-addresses.json +17 -0
  10. traia_iatp/contracts/data/addresses/contract-proxies.json +12 -0
  11. traia_iatp/contracts/iatp_contracts_config.py +263 -0
  12. traia_iatp/contracts/wallet_creator.py +369 -0
  13. traia_iatp/core/models.py +17 -3
  14. traia_iatp/d402/MIDDLEWARE_ARCHITECTURE.md +205 -0
  15. traia_iatp/d402/PRICE_BUILDER_USAGE.md +249 -0
  16. traia_iatp/d402/README.md +489 -0
  17. traia_iatp/d402/__init__.py +54 -0
  18. traia_iatp/d402/asgi_wrapper.py +469 -0
  19. traia_iatp/d402/chains.py +102 -0
  20. traia_iatp/d402/client.py +150 -0
  21. traia_iatp/d402/clients/__init__.py +7 -0
  22. traia_iatp/d402/clients/base.py +218 -0
  23. traia_iatp/d402/clients/httpx.py +266 -0
  24. traia_iatp/d402/common.py +114 -0
  25. traia_iatp/d402/encoding.py +28 -0
  26. traia_iatp/d402/examples/client_example.py +197 -0
  27. traia_iatp/d402/examples/server_example.py +171 -0
  28. traia_iatp/d402/facilitator.py +481 -0
  29. traia_iatp/d402/mcp_middleware.py +296 -0
  30. traia_iatp/d402/models.py +116 -0
  31. traia_iatp/d402/networks.py +98 -0
  32. traia_iatp/d402/path.py +43 -0
  33. traia_iatp/d402/payment_introspection.py +126 -0
  34. traia_iatp/d402/payment_signing.py +183 -0
  35. traia_iatp/d402/price_builder.py +164 -0
  36. traia_iatp/d402/servers/__init__.py +61 -0
  37. traia_iatp/d402/servers/base.py +139 -0
  38. traia_iatp/d402/servers/example_general_server.py +140 -0
  39. traia_iatp/d402/servers/fastapi.py +253 -0
  40. traia_iatp/d402/servers/mcp.py +304 -0
  41. traia_iatp/d402/servers/starlette.py +878 -0
  42. traia_iatp/d402/starlette_middleware.py +529 -0
  43. traia_iatp/d402/types.py +300 -0
  44. traia_iatp/mcp/D402_MCP_ADAPTER_FLOW.md +357 -0
  45. traia_iatp/mcp/__init__.py +3 -0
  46. traia_iatp/mcp/d402_mcp_tool_adapter.py +526 -0
  47. traia_iatp/mcp/mcp_agent_template.py +78 -13
  48. traia_iatp/mcp/templates/Dockerfile.j2 +27 -4
  49. traia_iatp/mcp/templates/README.md.j2 +104 -8
  50. traia_iatp/mcp/templates/cursor-rules.md.j2 +194 -0
  51. traia_iatp/mcp/templates/deployment_params.json.j2 +1 -2
  52. traia_iatp/mcp/templates/docker-compose.yml.j2 +13 -3
  53. traia_iatp/mcp/templates/env.example.j2 +60 -0
  54. traia_iatp/mcp/templates/mcp_health_check.py.j2 +2 -2
  55. traia_iatp/mcp/templates/pyproject.toml.j2 +11 -5
  56. traia_iatp/mcp/templates/pyrightconfig.json.j2 +22 -0
  57. traia_iatp/mcp/templates/run_local_docker.sh.j2 +320 -10
  58. traia_iatp/mcp/templates/server.py.j2 +174 -197
  59. traia_iatp/mcp/traia_mcp_adapter.py +182 -20
  60. traia_iatp/registry/__init__.py +47 -12
  61. traia_iatp/registry/atlas_search_indexes.json +108 -54
  62. traia_iatp/registry/iatp_search_api.py +169 -39
  63. traia_iatp/registry/mongodb_registry.py +241 -69
  64. traia_iatp/registry/readmes/EMBEDDINGS_SETUP.md +1 -1
  65. traia_iatp/registry/readmes/IATP_SEARCH_API_GUIDE.md +8 -8
  66. traia_iatp/registry/readmes/MONGODB_X509_AUTH.md +1 -1
  67. traia_iatp/registry/readmes/README.md +3 -3
  68. traia_iatp/registry/readmes/REFACTORING_SUMMARY.md +6 -6
  69. traia_iatp/scripts/__init__.py +2 -0
  70. traia_iatp/scripts/create_wallet.py +244 -0
  71. traia_iatp/server/a2a_server.py +22 -7
  72. traia_iatp/server/iatp_server_template_generator.py +23 -0
  73. traia_iatp/server/templates/.dockerignore.j2 +48 -0
  74. traia_iatp/server/templates/Dockerfile.j2 +23 -1
  75. traia_iatp/server/templates/README.md +2 -2
  76. traia_iatp/server/templates/README.md.j2 +5 -5
  77. traia_iatp/server/templates/__main__.py.j2 +374 -66
  78. traia_iatp/server/templates/agent.py.j2 +12 -11
  79. traia_iatp/server/templates/agent_config.json.j2 +3 -3
  80. traia_iatp/server/templates/agent_executor.py.j2 +45 -27
  81. traia_iatp/server/templates/env.example.j2 +32 -4
  82. traia_iatp/server/templates/gitignore.j2 +7 -0
  83. traia_iatp/server/templates/pyproject.toml.j2 +13 -12
  84. traia_iatp/server/templates/run_local_docker.sh.j2 +143 -11
  85. traia_iatp/server/templates/server.py.j2 +197 -10
  86. traia_iatp/special_agencies/registry_search_agency.py +1 -1
  87. traia_iatp/utils/iatp_utils.py +6 -6
  88. traia_iatp-0.1.67.dist-info/METADATA +320 -0
  89. traia_iatp-0.1.67.dist-info/RECORD +117 -0
  90. traia_iatp-0.1.2.dist-info/METADATA +0 -414
  91. traia_iatp-0.1.2.dist-info/RECORD +0 -72
  92. {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/WHEEL +0 -0
  93. {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/entry_points.txt +0 -0
  94. {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/licenses/LICENSE +0 -0
  95. {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,244 @@
1
+ #!/usr/bin/env python3
2
+ """Create an IATPWallet for a user.
3
+
4
+ This script:
5
+ 1. Takes an owner private key as input
6
+ 2. Generates a new operator keypair
7
+ 3. Calls IATPWalletFactory.createWallet() to deploy an IATPWallet
8
+ 4. Returns the operator keys and wallet address
9
+
10
+ Usage:
11
+ uv run python -m traia_iatp.scripts.create_wallet \\
12
+ --owner-key 0x... \\
13
+ --network sepolia \\
14
+ [--wait-confirmations 2]
15
+ """
16
+
17
+ import argparse
18
+ import json
19
+ import logging
20
+ import sys
21
+ from eth_account import Account
22
+ from web3 import Web3
23
+
24
+ from traia_iatp.contracts import (
25
+ get_contract_address,
26
+ load_contract,
27
+ ContractName
28
+ )
29
+
30
+ logging.basicConfig(
31
+ level=logging.INFO,
32
+ format='%(asctime)s - %(levelname)s - %(message)s'
33
+ )
34
+ logger = logging.getLogger(__name__)
35
+
36
+
37
+ def create_wallet(
38
+ owner_private_key: str,
39
+ network: str = "sepolia",
40
+ wait_confirmations: int = 2
41
+ ) -> dict:
42
+ """Create an IATP Wallet for an owner.
43
+
44
+ Args:
45
+ owner_private_key: Owner's private key (0x...)
46
+ network: Network name (default: "sepolia")
47
+ wait_confirmations: Number of confirmations to wait (default: 2)
48
+
49
+ Returns:
50
+ Dict with operator keys and wallet address:
51
+ {
52
+ "owner_address": "0x...",
53
+ "operator_address": "0x...",
54
+ "operator_private_key": "0x...",
55
+ "wallet_address": "0x...",
56
+ "transaction_hash": "0x...",
57
+ "network": "sepolia"
58
+ }
59
+ """
60
+ logger.info(f"Creating IATP Wallet on {network}")
61
+
62
+ # Create owner account
63
+ if not owner_private_key.startswith("0x"):
64
+ owner_private_key = f"0x{owner_private_key}"
65
+
66
+ owner_account = Account.from_key(owner_private_key)
67
+ logger.info(f"Owner address: {owner_account.address}")
68
+
69
+ # Generate new operator keypair
70
+ operator_account = Account.create()
71
+ logger.info(f"Generated operator address: {operator_account.address}")
72
+ logger.info(f"Operator private key: {operator_account.key.hex()}")
73
+
74
+ # Load IATPWalletFactory contract
75
+ factory_contract, w3 = load_contract(
76
+ ContractName.IATP_WALLET_FACTORY,
77
+ network=network
78
+ )
79
+
80
+ factory_address = factory_contract.address
81
+ logger.info(f"IATPWalletFactory address: {factory_address}")
82
+
83
+ # Check owner balance
84
+ owner_balance = w3.eth.get_balance(owner_account.address)
85
+ logger.info(f"Owner balance: {w3.from_wei(owner_balance, 'ether')} ETH")
86
+
87
+ if owner_balance == 0:
88
+ logger.error("Owner has no ETH for gas. Please fund the account.")
89
+ raise ValueError("Insufficient balance for gas")
90
+
91
+ # Build transaction to create wallet
92
+ logger.info("Building createWallet transaction...")
93
+
94
+ tx = factory_contract.functions.createWallet(
95
+ operator_account.address
96
+ ).build_transaction({
97
+ 'from': owner_account.address,
98
+ 'nonce': w3.eth.get_transaction_count(owner_account.address),
99
+ 'gas': 2000000, # Estimate gas
100
+ 'gasPrice': w3.eth.gas_price,
101
+ 'chainId': w3.eth.chain_id
102
+ })
103
+
104
+ # Sign transaction
105
+ signed_tx = owner_account.sign_transaction(tx)
106
+
107
+ # Send transaction
108
+ logger.info("Sending transaction...")
109
+ tx_hash = w3.eth.send_raw_transaction(signed_tx.raw_transaction)
110
+ logger.info(f"Transaction hash: {tx_hash.hex()}")
111
+
112
+ # Wait for confirmation
113
+ logger.info(f"Waiting for {wait_confirmations} confirmation(s)...")
114
+ tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=300)
115
+
116
+ if tx_receipt['status'] != 1:
117
+ logger.error("Transaction failed!")
118
+ raise Exception(f"Transaction failed: {tx_hash.hex()}")
119
+
120
+ logger.info(f"Transaction confirmed in block {tx_receipt['blockNumber']}")
121
+
122
+ # Parse WalletCreated event to get wallet address
123
+ wallet_address = None
124
+ for log in tx_receipt['logs']:
125
+ try:
126
+ event = factory_contract.events.WalletCreated().process_log(log)
127
+ wallet_address = event['args']['wallet']
128
+ logger.info(f"✅ IATPWallet created: {wallet_address}")
129
+ break
130
+ except:
131
+ continue
132
+
133
+ if not wallet_address:
134
+ logger.error("Could not find WalletCreated event in logs")
135
+ raise Exception("Wallet address not found in transaction logs")
136
+
137
+ # Return result
138
+ result = {
139
+ "owner_address": owner_account.address,
140
+ "operator_address": operator_account.address,
141
+ "operator_private_key": operator_account.key.hex(),
142
+ "wallet_address": wallet_address,
143
+ "transaction_hash": tx_hash.hex(),
144
+ "block_number": tx_receipt['blockNumber'],
145
+ "network": network,
146
+ "chain_id": w3.eth.chain_id
147
+ }
148
+
149
+ return result
150
+
151
+
152
+ def main():
153
+ """Main CLI entry point."""
154
+ parser = argparse.ArgumentParser(
155
+ description="Create an IATP Wallet via IATPWalletFactory",
156
+ formatter_class=argparse.RawDescriptionHelpFormatter,
157
+ epilog="""
158
+ Examples:
159
+ # Create wallet on Sepolia
160
+ uv run python -m traia_iatp.scripts.create_wallet \\
161
+ --owner-key 0x0cdda2d9d744d6d2a3ba4b30ac51227590f0e8f44cf5251bb1ed0941d677c0a4 \\
162
+ --network sepolia
163
+
164
+ # Create wallet with output to JSON file
165
+ uv run python -m traia_iatp.scripts.create_wallet \\
166
+ --owner-key 0x... \\
167
+ --network sepolia \\
168
+ --output wallet_config.json
169
+ """
170
+ )
171
+
172
+ parser.add_argument(
173
+ "--owner-key",
174
+ required=True,
175
+ help="Owner's private key (0x...)"
176
+ )
177
+
178
+ parser.add_argument(
179
+ "--network",
180
+ default="sepolia",
181
+ choices=["sepolia", "base-sepolia", "arbitrum-sepolia", "localhost"],
182
+ help="Network to deploy on (default: sepolia)"
183
+ )
184
+
185
+ parser.add_argument(
186
+ "--wait-confirmations",
187
+ type=int,
188
+ default=2,
189
+ help="Number of confirmations to wait (default: 2)"
190
+ )
191
+
192
+ parser.add_argument(
193
+ "--output",
194
+ "-o",
195
+ type=str,
196
+ help="Output JSON file path (prints to stdout if not specified)"
197
+ )
198
+
199
+ parser.add_argument(
200
+ "--verbose",
201
+ "-v",
202
+ action="store_true",
203
+ help="Enable verbose logging"
204
+ )
205
+
206
+ args = parser.parse_args()
207
+
208
+ if args.verbose:
209
+ logging.getLogger().setLevel(logging.DEBUG)
210
+
211
+ try:
212
+ # Create wallet
213
+ result = create_wallet(
214
+ owner_private_key=args.owner_key,
215
+ network=args.network,
216
+ wait_confirmations=args.wait_confirmations
217
+ )
218
+
219
+ # Output result
220
+ output_json = json.dumps(result, indent=2)
221
+
222
+ if args.output:
223
+ with open(args.output, 'w') as f:
224
+ f.write(output_json)
225
+ logger.info(f"✅ Configuration saved to: {args.output}")
226
+ else:
227
+ print("\n" + "=" * 80)
228
+ print("IATP Wallet Created Successfully!")
229
+ print("=" * 80)
230
+ print(output_json)
231
+ print("=" * 80)
232
+
233
+ return 0
234
+
235
+ except Exception as e:
236
+ logger.error(f"Failed to create wallet: {e}")
237
+ import traceback
238
+ traceback.print_exc()
239
+ return 1
240
+
241
+
242
+ if __name__ == "__main__":
243
+ sys.exit(main())
244
+
@@ -39,7 +39,7 @@ class UtilityAgencyExecutor(AgentExecutor):
39
39
  # Get the user's request from context
40
40
  user_message = context.get_last_user_message()
41
41
  if not user_message:
42
- event_queue.enqueue_event(
42
+ await event_queue.enqueue_event(
43
43
  new_agent_text_message("No user message provided")
44
44
  )
45
45
  return
@@ -73,6 +73,10 @@ class UtilityAgencyExecutor(AgentExecutor):
73
73
  new_agent_text_message(f"Error processing request: {str(e)}")
74
74
  )
75
75
 
76
+ async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
77
+ """Cancel all tasks."""
78
+ pass
79
+
76
80
 
77
81
  class MCPToolExecutor(AgentExecutor):
78
82
  """Agent executor for individual MCP tool execution."""
@@ -89,7 +93,7 @@ class MCPToolExecutor(AgentExecutor):
89
93
  # Get tool arguments from context
90
94
  user_message = context.get_last_user_message()
91
95
  if not user_message:
92
- event_queue.enqueue_event(
96
+ await event_queue.enqueue_event(
93
97
  new_agent_text_message("No arguments provided")
94
98
  )
95
99
  return
@@ -110,7 +114,7 @@ class MCPToolExecutor(AgentExecutor):
110
114
 
111
115
  try:
112
116
  result = await mcp_client.call_tool(self.tool_name, arguments)
113
- event_queue.enqueue_event(
117
+ await event_queue.enqueue_event(
114
118
  new_agent_text_message(str(result))
115
119
  )
116
120
  finally:
@@ -118,7 +122,7 @@ class MCPToolExecutor(AgentExecutor):
118
122
 
119
123
  except Exception as e:
120
124
  logger.error(f"Error executing MCP tool {self.tool_name}: {e}")
121
- event_queue.enqueue_event(
125
+ await event_queue.enqueue_event(
122
126
  new_agent_text_message(f"Error executing tool: {str(e)}")
123
127
  )
124
128
 
@@ -162,11 +166,22 @@ def create_a2a_server(
162
166
  stateTransitionHistory=False
163
167
  )
164
168
 
165
- # Create agent card
169
+ # Create agent card with proper URL
170
+ # For Cloud Run: use PUBLIC_URL or SERVICE_URL environment variable
171
+ # For local: use http://{host}:{port}
172
+ import os
173
+ public_url = os.getenv("PUBLIC_URL") or os.getenv("SERVICE_URL")
174
+ if public_url:
175
+ # Cloud Run deployment - use the public URL
176
+ agent_url = public_url
177
+ else:
178
+ # Local deployment - use host:port
179
+ agent_url = f"http://{host}:{port}"
180
+
166
181
  agent_card = AgentCard(
167
182
  name=agency.name.replace(" ", "_").lower(),
168
183
  description=agency.description,
169
- url=f"http://{host}:{port}",
184
+ url=agent_url,
170
185
  version="1.0.0",
171
186
  capabilities=capabilities,
172
187
  skills=skills,
@@ -194,7 +209,7 @@ def create_a2a_server(
194
209
  # Create the A2A application
195
210
  app = A2AStarletteApplication(
196
211
  agent_card=agent_card,
197
- request_handler=request_handler
212
+ http_handler=request_handler
198
213
  )
199
214
 
200
215
  return app
@@ -117,6 +117,18 @@ class IATPServerTemplateGenerator:
117
117
  module_name = package_name
118
118
  docker_image = f"traia/{agent_id}:latest"
119
119
 
120
+ # Extract D402 parameters from kwargs (populated by deploy_utility_agent)
121
+ d402_enabled = kwargs.get("d402_enabled", False)
122
+ d402_contract_address = kwargs.get("d402_contract_address", "")
123
+ d402_operator_private_key = kwargs.get("d402_operator_private_key", "")
124
+ d402_price_usd = kwargs.get("d402_price_usd", "0.01")
125
+ d402_token_symbol = kwargs.get("d402_token_symbol", "USDC")
126
+ d402_token_address = kwargs.get("d402_token_address", "")
127
+ d402_token_decimals = kwargs.get("d402_token_decimals", 6)
128
+ d402_network = kwargs.get("d402_network", "sepolia")
129
+ d402_facilitator_url = kwargs.get("d402_facilitator_url", "http://localhost:7070")
130
+ d402_testing_mode = kwargs.get("d402_testing_mode", "false")
131
+
120
132
  # Prepare template context
121
133
  context = {
122
134
  "agent_name": agent_name,
@@ -141,6 +153,17 @@ class IATPServerTemplateGenerator:
141
153
  "environment_variables": environment_variables,
142
154
  "additional_ignores": additional_ignores,
143
155
  "use_uv_lock": False, # Will be true after first uv sync
156
+ # D402 payment configuration
157
+ "d402_enabled": d402_enabled,
158
+ "d402_contract_address": d402_contract_address,
159
+ "d402_operator_private_key": d402_operator_private_key,
160
+ "d402_price_usd": d402_price_usd,
161
+ "d402_token_symbol": d402_token_symbol,
162
+ "d402_token_address": d402_token_address,
163
+ "d402_token_decimals": d402_token_decimals,
164
+ "d402_network": d402_network,
165
+ "d402_facilitator_url": d402_facilitator_url,
166
+ "d402_testing_mode": d402_testing_mode,
144
167
  **kwargs
145
168
  }
146
169
 
@@ -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
@@ -24,6 +24,28 @@ USER appuser
24
24
  # Copy project files
25
25
  COPY --chown=appuser:appuser . .
26
26
 
27
+ # Copy IATP package if available (for local development only)
28
+ # This Dockerfile works for both local and remote deployments:
29
+ #
30
+ # LOCAL DEPLOYMENT (via run_local_docker.sh):
31
+ # - run_local_docker.sh detects local IATP path in pyproject.toml
32
+ # - Copies IATP to .docker-iatp/IATP before building
33
+ # - Temporarily modifies pyproject.toml to use file:///tmp/IATP
34
+ # - Dockerfile finds .docker-iatp/IATP and copies it to /tmp/IATP
35
+ # - uv install uses the local IATP from /tmp/IATP
36
+ #
37
+ # REMOTE DEPLOYMENT (Cloud Run, etc.):
38
+ # - .docker-iatp/IATP does NOT exist in build context
39
+ # - pyproject.toml has published version (traia-iatp>=0.1.40)
40
+ # - Dockerfile skips IATP copy, uv install uses published package
41
+ #
42
+ RUN if [ -d .docker-iatp/IATP ]; then \
43
+ cp -r .docker-iatp/IATP /tmp/IATP && \
44
+ echo "Using local IATP package (local development mode)"; \
45
+ else \
46
+ echo "Using published IATP package (remote deployment mode)"; \
47
+ fi
48
+
27
49
  # Install Python dependencies
28
50
  RUN uv venv .venv && \
29
51
  uv pip install -r pyproject.toml
@@ -40,7 +62,7 @@ ENV {{ env_var.name }}="{{ env_var.value }}"
40
62
 
41
63
  # Health check
42
64
  HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
43
- CMD curl -f http://localhost:${PORT:-8000}/.well-known/agent.json || exit 1
65
+ CMD curl -f http://localhost:${PORT:-8000}/.well-known/agent-card.json || exit 1
44
66
 
45
67
  # Expose port (uses PORT environment variable with default)
46
68
  EXPOSE ${PORT:-8000}
@@ -63,7 +63,7 @@ The templates expect the following variables:
63
63
  Use the `UtilityAgencyTemplateGenerator` class to generate agencies:
64
64
 
65
65
  ```python
66
- from iatp.server.template_generator import UtilityAgencyTemplateGenerator
66
+ from traia_iatp.server.template_generator import UtilityAgencyTemplateGenerator
67
67
 
68
68
  generator = UtilityAgencyTemplateGenerator()
69
69
  output_path = generator.generate_agency(
@@ -114,7 +114,7 @@ output_dir/
114
114
 
115
115
  The generated agencies follow the A2A protocol by:
116
116
 
117
- 1. **Agent Card**: Exposing agent capabilities at `/.well-known/agent.json`
117
+ 1. **Agent Card**: Exposing agent capabilities at `/.well-known/agent-card.json`
118
118
  2. **Task Processing**: Handling tasks via POST to `/tasks/send`
119
119
  3. **Text I/O**: Supporting text input and output by default
120
120
  4. **Request Context**: Processing request context and metadata
@@ -73,8 +73,8 @@ All configuration is managed through environment variables. See `.env.example` f
73
73
 
74
74
  The A2A (Agent-to-Agent) protocol defines a minimal set of endpoints for agent communication:
75
75
 
76
- #### 1. **`POST /` (Root Path)** - Main JSON-RPC Endpoint
77
- This is the primary endpoint for all A2A communication. Despite what you might expect, the A2A library creates the JSON-RPC endpoint at the root path (`/`), not at `/a2a`.
76
+ #### 1. **`POST /a2a`** - Main JSON-RPC Endpoint
77
+ This is the primary endpoint for all A2A communication. The JSON-RPC endpoint is configured at `/a2a` to avoid conflicts with middleware and root path handling.
78
78
 
79
79
  **Supported Methods:**
80
80
  - `message/send` - Send a message to the agent
@@ -97,7 +97,7 @@ This is the primary endpoint for all A2A communication. Despite what you might e
97
97
  }
98
98
  ```
99
99
 
100
- #### 2. **`GET /.well-known/agent.json`** - Agent Card
100
+ #### 2. **`GET /.well-known/agent-card.json`** - Agent Card
101
101
  Returns the agent's capabilities, skills, and metadata following the A2A standard.
102
102
 
103
103
  **Example Response:**
@@ -135,7 +135,7 @@ Resume a subscription to task events, useful for handling connection drops.
135
135
 
136
136
  ### How Streaming Works
137
137
 
138
- **Important:** Streaming uses the SAME root endpoint (`/`) as regular requests. The difference is in the request parameters:
138
+ **Important:** Streaming uses the SAME endpoint (`/a2a`) as regular requests. The difference is in the request parameters:
139
139
 
140
140
  **Regular Request:**
141
141
  ```json
@@ -311,7 +311,7 @@ This will log:
311
311
  import httpx
312
312
 
313
313
  # Get agent info
314
- response = httpx.get("http://localhost:8000/.well-known/agent.json")
314
+ response = httpx.get("http://localhost:8000/.well-known/agent-card.json")
315
315
  info = response.json()
316
316
 
317
317
  # Send a message via A2A (note: endpoint is at root "/", not "/a2a")