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,361 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ D402 MCP Tool Adapter
4
+
5
+ A simple adapter for using d402-enabled MCP servers with CrewAI.
6
+ This adapter avoids the complexity of persistent SSE connections and background tasks.
7
+
8
+ Instead, it:
9
+ 1. Lists available tools from the MCP server
10
+ 2. Creates CrewAI BaseTool wrappers for each MCP tool
11
+ 3. Each tool uses httpx with d402 payment hooks for requests
12
+ 4. No persistent connections - simple request/response pattern
13
+
14
+ This is more reliable than MCPServerAdapter for d402 payment scenarios.
15
+ """
16
+
17
+ import json
18
+ import logging
19
+ from typing import Any, Dict, List, Optional, Type
20
+ from pydantic import BaseModel, Field
21
+ from crewai.tools import BaseTool
22
+ import httpx
23
+
24
+ from traia_iatp.d402.clients.httpx import d402_payment_hooks
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ class D402MCPTool(BaseTool):
30
+ """
31
+ CrewAI tool wrapper for a single MCP tool with d402 payment support.
32
+
33
+ Each instance represents one MCP tool and handles d402 payments automatically.
34
+ """
35
+
36
+ name: str = "mcp_tool"
37
+ description: str = "MCP tool with d402 payment"
38
+ mcp_server_url: str = ""
39
+ mcp_tool_name: str = ""
40
+ mcp_session_id: str = ""
41
+ d402_operator_account: Optional[Any] = None # Operator account (EOA) for signing
42
+ d402_wallet_address: Optional[str] = None # IATPWallet contract address
43
+ input_schema: Dict[str, Any] = Field(default_factory=dict)
44
+
45
+ class Config:
46
+ arbitrary_types_allowed = True
47
+
48
+ def __init__(self, **data):
49
+ """Initialize with custom data."""
50
+ super().__init__(**data)
51
+
52
+ def _run(self, **kwargs) -> Dict[str, Any]:
53
+ """Synchronous wrapper that runs async _arun."""
54
+ import asyncio
55
+ try:
56
+ # Run the async method in a new event loop
57
+ return asyncio.run(self._arun(**kwargs))
58
+ except RuntimeError:
59
+ # If we're already in an event loop, use run_until_complete
60
+ loop = asyncio.get_event_loop()
61
+ return loop.run_until_complete(self._arun(**kwargs))
62
+
63
+ async def _arun(self, **kwargs) -> Dict[str, Any]:
64
+ """
65
+ Execute the MCP tool with d402 payment support.
66
+
67
+ This method:
68
+ 1. Creates httpx client with d402 hooks
69
+ 2. Makes MCP tools/call request
70
+ 3. d402 hooks automatically handle any 402 responses
71
+ 4. Returns the result
72
+ """
73
+
74
+ # Create httpx client with d402 payment hooks
75
+ async with httpx.AsyncClient(base_url=self.mcp_server_url, timeout=60.0) as client:
76
+ # Attach d402 payment hooks
77
+ client.event_hooks = d402_payment_hooks(
78
+ self.d402_operator_account,
79
+ wallet_address=self.d402_wallet_address
80
+ )
81
+
82
+ # Create MCP tools/call request
83
+ mcp_request = {
84
+ "jsonrpc": "2.0",
85
+ "id": 1,
86
+ "method": "tools/call",
87
+ "params": {
88
+ "name": self.mcp_tool_name,
89
+ "arguments": kwargs
90
+ }
91
+ }
92
+
93
+ try:
94
+ # Make request - d402 hooks will handle any 402 responses automatically
95
+ response = await client.post(
96
+ "/",
97
+ json=mcp_request,
98
+ headers={
99
+ "Content-Type": "application/json",
100
+ "Accept": "application/json, text/event-stream",
101
+ "mcp-session-id": self.mcp_session_id
102
+ },
103
+ follow_redirects=True
104
+ )
105
+
106
+ # Read response content
107
+ content = await response.aread()
108
+ content_str = content.decode() if content else ""
109
+
110
+ # Parse SSE format if present
111
+ if "data:" in content_str:
112
+ sse_lines = content_str.strip().split('\n')
113
+ for line in sse_lines:
114
+ if line.startswith("data:"):
115
+ content_str = line[5:].strip()
116
+ break
117
+
118
+ # Parse JSON response
119
+ response_data = json.loads(content_str)
120
+
121
+ # Extract result
122
+ if "result" in response_data:
123
+ result = response_data["result"]
124
+ # Return structured content if available, otherwise full result
125
+ if isinstance(result, dict) and "structuredContent" in result:
126
+ return result["structuredContent"].get("result", result)
127
+ return result
128
+ elif "error" in response_data:
129
+ return {"error": response_data["error"]}
130
+ else:
131
+ return response_data
132
+
133
+ except Exception as e:
134
+ logger.error(f"Error calling MCP tool {self.mcp_tool_name}: {e}")
135
+ return {"error": str(e)}
136
+
137
+
138
+ class D402MCPToolAdapter:
139
+ """
140
+ Adapter for using d402-enabled MCP servers with CrewAI.
141
+
142
+ This adapter is simpler and more reliable than MCPServerAdapter for d402:
143
+ - No persistent SSE connections
144
+ - No background tasks
145
+ - Direct request/response with d402 hooks
146
+
147
+ Usage:
148
+ ```python
149
+ from eth_account import Account
150
+ from traia_iatp.mcp import D402MCPToolAdapter
151
+
152
+ account = Account.from_key("0x...")
153
+ adapter = D402MCPToolAdapter(
154
+ url="http://localhost:8000/mcp",
155
+ account=account
156
+ )
157
+
158
+ with adapter as tools:
159
+ agent = Agent(
160
+ role="Analyst",
161
+ goal="Analyze data",
162
+ tools=tools
163
+ )
164
+ ```
165
+ """
166
+
167
+ def __init__(
168
+ self,
169
+ url: str,
170
+ account: Any, # Operator account (EOA) for signing
171
+ wallet_address: str = None, # IATPWallet contract address
172
+ max_value: Optional[int] = None
173
+ ):
174
+ """
175
+ Initialize the d402 MCP adapter.
176
+
177
+ Args:
178
+ url: MCP server URL (e.g., "http://localhost:8000/mcp")
179
+ account: Operator account (EOA) with private key for signing payments
180
+ wallet_address: IATPWallet contract address (if None, uses account.address for testing)
181
+ max_value: Optional maximum payment value in base units
182
+ """
183
+ self.url = url
184
+ self.account = account # Operator EOA
185
+ self.wallet_address = wallet_address or account.address # IATPWallet or EOA for testing
186
+ self.max_value = max_value
187
+ self.tools: List[BaseTool] = []
188
+ self.session_id: Optional[str] = None
189
+
190
+ async def _initialize_session(self) -> str:
191
+ """Initialize MCP session and return session ID."""
192
+ async with httpx.AsyncClient(base_url=self.url, timeout=30.0) as client:
193
+ init_request = {
194
+ "jsonrpc": "2.0",
195
+ "id": "init",
196
+ "method": "initialize",
197
+ "params": {
198
+ "protocolVersion": "2024-11-05",
199
+ "capabilities": {},
200
+ "clientInfo": {"name": "d402-crewai-client", "version": "1.0"}
201
+ }
202
+ }
203
+
204
+ response = await client.post(
205
+ "/",
206
+ json=init_request,
207
+ headers={
208
+ "Content-Type": "application/json",
209
+ "Accept": "application/json, text/event-stream"
210
+ },
211
+ follow_redirects=True
212
+ )
213
+
214
+ session_id = response.headers.get("mcp-session-id")
215
+ if not session_id:
216
+ raise RuntimeError("Failed to establish MCP session")
217
+
218
+ return session_id
219
+
220
+ async def _list_tools(self) -> List[Dict[str, Any]]:
221
+ """List available tools from the MCP server."""
222
+ async with httpx.AsyncClient(base_url=self.url, timeout=30.0) as client:
223
+ mcp_request = {
224
+ "jsonrpc": "2.0",
225
+ "id": 1,
226
+ "method": "tools/list",
227
+ "params": {}
228
+ }
229
+
230
+ response = await client.post(
231
+ "/",
232
+ json=mcp_request,
233
+ headers={
234
+ "Content-Type": "application/json",
235
+ "Accept": "application/json, text/event-stream",
236
+ "mcp-session-id": self.session_id
237
+ },
238
+ follow_redirects=True
239
+ )
240
+
241
+ content = await response.aread()
242
+ content_str = content.decode() if content else ""
243
+
244
+ # Parse SSE format if present
245
+ if "data:" in content_str:
246
+ sse_lines = content_str.strip().split('\n')
247
+ for line in sse_lines:
248
+ if line.startswith("data:"):
249
+ content_str = line[5:].strip()
250
+ break
251
+
252
+ response_data = json.loads(content_str)
253
+
254
+ if "result" in response_data and "tools" in response_data["result"]:
255
+ return response_data["result"]["tools"]
256
+ return []
257
+
258
+ def __enter__(self) -> List[BaseTool]:
259
+ """
260
+ Enter context manager - set up session and create tool wrappers.
261
+
262
+ Returns:
263
+ List of CrewAI BaseTool objects for each MCP tool
264
+ """
265
+ import asyncio
266
+
267
+ # Initialize session
268
+ logger.info(f"🔌 Initializing MCP session with {self.url}")
269
+ self.session_id = asyncio.run(self._initialize_session())
270
+ logger.info(f"✅ Session established: {self.session_id}")
271
+
272
+ # List available tools
273
+ logger.info("📋 Listing available tools...")
274
+ mcp_tools = asyncio.run(self._list_tools())
275
+ logger.info(f"✅ Found {len(mcp_tools)} tools")
276
+
277
+ # Create CrewAI tool wrappers for each MCP tool
278
+ self.tools = []
279
+ for mcp_tool in mcp_tools:
280
+ tool_name = mcp_tool.get("name", "unknown")
281
+ tool_description = mcp_tool.get("description", f"MCP tool: {tool_name}")
282
+
283
+ # Create tool instance with all necessary data
284
+ tool_instance = D402MCPTool(
285
+ name=tool_name,
286
+ description=tool_description,
287
+ mcp_server_url=self.url,
288
+ mcp_tool_name=tool_name,
289
+ mcp_session_id=self.session_id,
290
+ d402_operator_account=self.account, # Operator EOA for signing
291
+ d402_wallet_address=self.wallet_address, # IATPWallet contract (or EOA for testing)
292
+ input_schema=mcp_tool.get("inputSchema", {})
293
+ )
294
+
295
+ self.tools.append(tool_instance)
296
+
297
+ logger.info(f"✅ Created {len(self.tools)} CrewAI tool wrappers")
298
+ return self.tools
299
+
300
+ def __exit__(self, exc_type, exc_val, exc_tb):
301
+ """Exit context manager - cleanup."""
302
+ logger.info("🔌 Closing D402MCPToolAdapter")
303
+ return False
304
+
305
+
306
+ def create_d402_mcp_adapter(
307
+ url: str,
308
+ account: Any, # Operator account (EOA) for signing
309
+ wallet_address: str = None, # IATPWallet contract address
310
+ max_value: Optional[int] = None
311
+ ) -> D402MCPToolAdapter:
312
+ """
313
+ Create a d402 MCP adapter for CrewAI.
314
+
315
+ Args:
316
+ url: MCP server URL
317
+ account: Operator account (EOA) for signing payments
318
+ wallet_address: IATPWallet contract address (if None, uses account.address for testing)
319
+ max_value: Optional maximum payment value in base units
320
+
321
+ Returns:
322
+ D402MCPToolAdapter instance
323
+
324
+ Example:
325
+ ```python
326
+ from eth_account import Account
327
+ from traia_iatp.mcp import create_d402_mcp_adapter
328
+
329
+ # For testing (uses mock wallet address)
330
+ operator_account = Account.from_key("0x...")
331
+ mock_wallet = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8" # Different from operator
332
+ adapter = create_d402_mcp_adapter(
333
+ url="http://localhost:8000/mcp",
334
+ account=operator_account,
335
+ wallet_address=mock_wallet,
336
+ max_value=1_000_000 # $1.00 in USDC
337
+ )
338
+
339
+ # For production (with deployed IATPWallet contract)
340
+ operator_account = Account.from_key("0x...") # Operator EOA
341
+ wallet_address = "0x..." # Deployed IATPWallet contract
342
+ adapter = create_d402_mcp_adapter(
343
+ url="http://localhost:8000/mcp",
344
+ account=operator_account,
345
+ wallet_address=wallet_address,
346
+ max_value=1_000_000
347
+ )
348
+
349
+ with adapter as tools:
350
+ agent = Agent(
351
+ role="Analyst",
352
+ goal="Analyze data",
353
+ tools=tools
354
+ )
355
+ ```
356
+ """
357
+ return D402MCPToolAdapter(url, account, wallet_address, max_value)
358
+
359
+
360
+ __all__ = ["D402MCPTool", "D402MCPToolAdapter", "create_d402_mcp_adapter"]
361
+