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.
- traia_iatp/__init__.py +105 -8
- traia_iatp/cli/main.py +85 -1
- traia_iatp/client/__init__.py +28 -3
- traia_iatp/client/crewai_a2a_tools.py +32 -12
- traia_iatp/client/d402_a2a_client.py +348 -0
- traia_iatp/contracts/__init__.py +11 -0
- traia_iatp/contracts/data/abis/contract-abis-localhost.json +4091 -0
- traia_iatp/contracts/data/abis/contract-abis-sepolia.json +4890 -0
- traia_iatp/contracts/data/addresses/contract-addresses.json +17 -0
- traia_iatp/contracts/data/addresses/contract-proxies.json +12 -0
- traia_iatp/contracts/iatp_contracts_config.py +263 -0
- traia_iatp/contracts/wallet_creator.py +369 -0
- traia_iatp/core/models.py +17 -3
- traia_iatp/d402/MIDDLEWARE_ARCHITECTURE.md +205 -0
- traia_iatp/d402/PRICE_BUILDER_USAGE.md +249 -0
- traia_iatp/d402/README.md +489 -0
- traia_iatp/d402/__init__.py +54 -0
- traia_iatp/d402/asgi_wrapper.py +469 -0
- traia_iatp/d402/chains.py +102 -0
- traia_iatp/d402/client.py +150 -0
- traia_iatp/d402/clients/__init__.py +7 -0
- traia_iatp/d402/clients/base.py +218 -0
- traia_iatp/d402/clients/httpx.py +266 -0
- traia_iatp/d402/common.py +114 -0
- traia_iatp/d402/encoding.py +28 -0
- traia_iatp/d402/examples/client_example.py +197 -0
- traia_iatp/d402/examples/server_example.py +171 -0
- traia_iatp/d402/facilitator.py +481 -0
- traia_iatp/d402/mcp_middleware.py +296 -0
- traia_iatp/d402/models.py +116 -0
- traia_iatp/d402/networks.py +98 -0
- traia_iatp/d402/path.py +43 -0
- traia_iatp/d402/payment_introspection.py +126 -0
- traia_iatp/d402/payment_signing.py +183 -0
- traia_iatp/d402/price_builder.py +164 -0
- traia_iatp/d402/servers/__init__.py +61 -0
- traia_iatp/d402/servers/base.py +139 -0
- traia_iatp/d402/servers/example_general_server.py +140 -0
- traia_iatp/d402/servers/fastapi.py +253 -0
- traia_iatp/d402/servers/mcp.py +304 -0
- traia_iatp/d402/servers/starlette.py +878 -0
- traia_iatp/d402/starlette_middleware.py +529 -0
- traia_iatp/d402/types.py +300 -0
- traia_iatp/mcp/D402_MCP_ADAPTER_FLOW.md +357 -0
- traia_iatp/mcp/__init__.py +3 -0
- traia_iatp/mcp/d402_mcp_tool_adapter.py +526 -0
- traia_iatp/mcp/mcp_agent_template.py +78 -13
- traia_iatp/mcp/templates/Dockerfile.j2 +27 -4
- traia_iatp/mcp/templates/README.md.j2 +104 -8
- traia_iatp/mcp/templates/cursor-rules.md.j2 +194 -0
- traia_iatp/mcp/templates/deployment_params.json.j2 +1 -2
- traia_iatp/mcp/templates/docker-compose.yml.j2 +13 -3
- traia_iatp/mcp/templates/env.example.j2 +60 -0
- traia_iatp/mcp/templates/mcp_health_check.py.j2 +2 -2
- traia_iatp/mcp/templates/pyproject.toml.j2 +11 -5
- traia_iatp/mcp/templates/pyrightconfig.json.j2 +22 -0
- traia_iatp/mcp/templates/run_local_docker.sh.j2 +320 -10
- traia_iatp/mcp/templates/server.py.j2 +174 -197
- traia_iatp/mcp/traia_mcp_adapter.py +182 -20
- traia_iatp/registry/__init__.py +47 -12
- traia_iatp/registry/atlas_search_indexes.json +108 -54
- traia_iatp/registry/iatp_search_api.py +169 -39
- traia_iatp/registry/mongodb_registry.py +241 -69
- traia_iatp/registry/readmes/EMBEDDINGS_SETUP.md +1 -1
- traia_iatp/registry/readmes/IATP_SEARCH_API_GUIDE.md +8 -8
- traia_iatp/registry/readmes/MONGODB_X509_AUTH.md +1 -1
- traia_iatp/registry/readmes/README.md +3 -3
- traia_iatp/registry/readmes/REFACTORING_SUMMARY.md +6 -6
- traia_iatp/scripts/__init__.py +2 -0
- traia_iatp/scripts/create_wallet.py +244 -0
- traia_iatp/server/a2a_server.py +22 -7
- traia_iatp/server/iatp_server_template_generator.py +23 -0
- traia_iatp/server/templates/.dockerignore.j2 +48 -0
- traia_iatp/server/templates/Dockerfile.j2 +23 -1
- traia_iatp/server/templates/README.md +2 -2
- traia_iatp/server/templates/README.md.j2 +5 -5
- traia_iatp/server/templates/__main__.py.j2 +374 -66
- traia_iatp/server/templates/agent.py.j2 +12 -11
- traia_iatp/server/templates/agent_config.json.j2 +3 -3
- traia_iatp/server/templates/agent_executor.py.j2 +45 -27
- traia_iatp/server/templates/env.example.j2 +32 -4
- traia_iatp/server/templates/gitignore.j2 +7 -0
- traia_iatp/server/templates/pyproject.toml.j2 +13 -12
- traia_iatp/server/templates/run_local_docker.sh.j2 +143 -11
- traia_iatp/server/templates/server.py.j2 +197 -10
- traia_iatp/special_agencies/registry_search_agency.py +1 -1
- traia_iatp/utils/iatp_utils.py +6 -6
- traia_iatp-0.1.67.dist-info/METADATA +320 -0
- traia_iatp-0.1.67.dist-info/RECORD +117 -0
- traia_iatp-0.1.2.dist-info/METADATA +0 -414
- traia_iatp-0.1.2.dist-info/RECORD +0 -72
- {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/WHEEL +0 -0
- {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/entry_points.txt +0 -0
- {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/licenses/LICENSE +0 -0
- {traia_iatp-0.1.2.dist-info → traia_iatp-0.1.67.dist-info}/top_level.txt +0 -0
|
@@ -26,13 +26,11 @@ if __name__ == "__main__":
|
|
|
26
26
|
# When running as a script, import directly
|
|
27
27
|
import sys
|
|
28
28
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
|
29
|
-
from
|
|
30
|
-
from iatp.registry.embeddings import get_embedding_service
|
|
29
|
+
from src.traia_iatp.core.models import UtilityAgentRegistryEntry, UtilityAgent
|
|
31
30
|
from ..utils import get_now_in_utc
|
|
32
31
|
else:
|
|
33
32
|
# When imported as a module
|
|
34
33
|
from ..core.models import UtilityAgentRegistryEntry, UtilityAgent
|
|
35
|
-
from .embeddings import get_embedding_service
|
|
36
34
|
from ..utils import get_now_in_utc
|
|
37
35
|
|
|
38
36
|
logger = logging.getLogger(__name__)
|
|
@@ -127,34 +125,51 @@ class UtilityAgentRegistry:
|
|
|
127
125
|
if connection_string:
|
|
128
126
|
self.connection_string = connection_string
|
|
129
127
|
else:
|
|
130
|
-
#
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
#
|
|
135
|
-
#
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
128
|
+
# Check if running locally or in cloud environment
|
|
129
|
+
is_local = os.getenv("LOCAL_EXECUTION", "false").lower() == "true"
|
|
130
|
+
|
|
131
|
+
if is_local:
|
|
132
|
+
# Local execution - require explicit credentials
|
|
133
|
+
# Try X.509 certificate authentication first
|
|
134
|
+
cert_file = os.getenv("MONGODB_X509_CERT_FILE")
|
|
135
|
+
if cert_file and os.path.exists(cert_file):
|
|
136
|
+
# For X.509 authentication, we need to extract the subject from the certificate
|
|
137
|
+
# to use as the username. MongoDB Atlas typically uses the full DN as username.
|
|
138
|
+
# The connection string format for X.509 is:
|
|
139
|
+
# mongodb+srv://cluster.mongodb.net/?authSource=$external&authMechanism=MONGODB-X509
|
|
140
|
+
# Extract just the cluster hostname without query parameters
|
|
141
|
+
cluster_host = CLUSTER_URI.split('?')[0]
|
|
142
|
+
self.connection_string = f"mongodb+srv://{cluster_host}?authSource=$external&authMechanism=MONGODB-X509&tls=true&tlsCertificateKeyFile={cert_file}"
|
|
143
|
+
logger.info(f"Using X.509 certificate authentication from {cert_file}")
|
|
144
|
+
else:
|
|
145
|
+
# Fallback to username/password authentication
|
|
146
|
+
user = os.getenv("MONGODB_USER")
|
|
147
|
+
password = os.getenv("MONGODB_PASSWORD")
|
|
148
|
+
if user and password:
|
|
149
|
+
self.connection_string = f"mongodb+srv://{user}:{password}@{CLUSTER_URI}"
|
|
150
|
+
logger.info("Using username/password authentication")
|
|
151
|
+
else:
|
|
152
|
+
# Try connection string as last resort
|
|
153
|
+
self.connection_string = os.getenv("MONGODB_CONNECTION_STRING")
|
|
154
|
+
if not self.connection_string:
|
|
155
|
+
raise ValueError(
|
|
156
|
+
"MongoDB authentication required for local execution. Please provide either:\n"
|
|
157
|
+
"1. MONGODB_X509_CERT_FILE - Path to X.509 certificate file\n"
|
|
158
|
+
"2. MONGODB_USER and MONGODB_PASSWORD - Username and password\n"
|
|
159
|
+
"3. MONGODB_CONNECTION_STRING - Full connection string"
|
|
160
|
+
)
|
|
141
161
|
else:
|
|
142
|
-
#
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if
|
|
146
|
-
|
|
147
|
-
|
|
162
|
+
# Cloud execution (Lambda) - use IAM role authentication
|
|
163
|
+
cluster_host = CLUSTER_URI.split('?')[0].rstrip('/')
|
|
164
|
+
# Preserve useful query parameters from original CLUSTER_URI
|
|
165
|
+
original_params = CLUSTER_URI.split('?')[1] if '?' in CLUSTER_URI else ""
|
|
166
|
+
# Combine AWS auth params with original params
|
|
167
|
+
auth_params = "authSource=$external&authMechanism=MONGODB-AWS"
|
|
168
|
+
if original_params:
|
|
169
|
+
self.connection_string = f"mongodb+srv://{cluster_host}?{auth_params}&{original_params}"
|
|
148
170
|
else:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if not self.connection_string:
|
|
152
|
-
raise ValueError(
|
|
153
|
-
"MongoDB authentication required. Please provide either:\n"
|
|
154
|
-
"1. MONGODB_X509_CERT_FILE - Path to X.509 certificate file\n"
|
|
155
|
-
"2. MONGODB_USER and MONGODB_PASSWORD - Username and password\n"
|
|
156
|
-
"3. MONGODB_CONNECTION_STRING - Full connection string"
|
|
157
|
-
)
|
|
171
|
+
self.connection_string = f"mongodb+srv://{cluster_host}?{auth_params}"
|
|
172
|
+
logger.info("Using IAM role authentication for cloud execution")
|
|
158
173
|
|
|
159
174
|
self.database_name = database_name
|
|
160
175
|
self.client = _create_mongodb_client_with_retry(self.connection_string)
|
|
@@ -196,7 +211,7 @@ class UtilityAgentRegistry:
|
|
|
196
211
|
except Exception as e:
|
|
197
212
|
logger.warning(f"Could not create index {index_name}: {e}")
|
|
198
213
|
|
|
199
|
-
async def add_utility_agent(self, agent: UtilityAgent, tags: List[str] = None) -> UtilityAgentRegistryEntry:
|
|
214
|
+
async def add_utility_agent(self, agent: UtilityAgent, tags: List[str] = None, endpoints: List[Dict[str, Any]] = None) -> UtilityAgentRegistryEntry:
|
|
200
215
|
"""Add a utility agent to the cloud registry.
|
|
201
216
|
|
|
202
217
|
Args:
|
|
@@ -225,6 +240,8 @@ class UtilityAgentRegistry:
|
|
|
225
240
|
|
|
226
241
|
if os.getenv("ENABLE_EMBEDDINGS", "true").lower() == "true":
|
|
227
242
|
try:
|
|
243
|
+
# Lazy import to avoid loading OpenAI/Cohere unless embeddings are enabled
|
|
244
|
+
from .embeddings import get_embedding_service
|
|
228
245
|
embedding_service = get_embedding_service()
|
|
229
246
|
|
|
230
247
|
# Generate embedding for description
|
|
@@ -266,14 +283,19 @@ class UtilityAgentRegistry:
|
|
|
266
283
|
except Exception as e:
|
|
267
284
|
logger.warning(f"Failed to generate embeddings: {e}. Proceeding without embeddings.")
|
|
268
285
|
|
|
269
|
-
# Create entry with enhanced fields
|
|
286
|
+
# Create entry with enhanced fields including d402 payment info
|
|
270
287
|
entry = UtilityAgentRegistryEntry(
|
|
271
288
|
agent_id=agent_id,
|
|
272
289
|
name=agent.name,
|
|
273
290
|
description=agent.description,
|
|
291
|
+
mcp_server_id=agent.mcp_server_id, # MCP server reference
|
|
274
292
|
capabilities=agent.capabilities,
|
|
275
293
|
tags=tags or agent.tags,
|
|
276
|
-
metadata=agent.metadata
|
|
294
|
+
metadata=agent.metadata,
|
|
295
|
+
# D402 payment fields (must be at root level)
|
|
296
|
+
contract_address=agent.contract_address,
|
|
297
|
+
d402_enabled=agent.d402_enabled,
|
|
298
|
+
d402_payment_info=agent.d402_config # d402_config becomes d402_payment_info in registry
|
|
277
299
|
)
|
|
278
300
|
|
|
279
301
|
# Add agent card if available
|
|
@@ -281,9 +303,8 @@ class UtilityAgentRegistry:
|
|
|
281
303
|
entry.agent_card = agent.agent_card
|
|
282
304
|
entry.skills = agent.agent_card.skills
|
|
283
305
|
|
|
284
|
-
# Add
|
|
306
|
+
# Add base_url if available
|
|
285
307
|
if agent.endpoints:
|
|
286
|
-
entry.endpoints = agent.endpoints
|
|
287
308
|
# Store base_url for indexing since all endpoints are derived from it
|
|
288
309
|
entry.base_url = agent.endpoints.base_url
|
|
289
310
|
|
|
@@ -308,6 +329,8 @@ class UtilityAgentRegistry:
|
|
|
308
329
|
# Generate embedding for the search text we just created
|
|
309
330
|
if os.getenv("ENABLE_EMBEDDINGS", "true").lower() == "true" and entry.search_text:
|
|
310
331
|
try:
|
|
332
|
+
# Lazy import to avoid loading OpenAI/Cohere unless embeddings are enabled
|
|
333
|
+
from .embeddings import get_embedding_service
|
|
311
334
|
embedding_service = get_embedding_service()
|
|
312
335
|
search_text_embedding = await embedding_service.generate_embedding(entry.search_text)
|
|
313
336
|
except Exception as e:
|
|
@@ -317,6 +340,43 @@ class UtilityAgentRegistry:
|
|
|
317
340
|
# Convert to dict and ensure all values are JSON-serializable
|
|
318
341
|
entry_dict = entry.model_dump(mode='json')
|
|
319
342
|
|
|
343
|
+
# Add iatp_endpoints (protocol structure) and endpoints array (payment info) separately
|
|
344
|
+
if agent.endpoints:
|
|
345
|
+
# Store IATPEndpoints as iatp_endpoints (protocol info)
|
|
346
|
+
entry_dict["iatp_endpoints"] = agent.endpoints.model_dump(mode='json')
|
|
347
|
+
|
|
348
|
+
# Store endpoints as array matching MCP server format (payment info)
|
|
349
|
+
# Use endpoints parameter if provided (includes actual payment configuration)
|
|
350
|
+
if endpoints and len(endpoints) > 0:
|
|
351
|
+
# Use endpoints array (includes actual payment configuration)
|
|
352
|
+
entry_dict["endpoints"] = endpoints
|
|
353
|
+
logger.info(f"Using {len(endpoints)} endpoints with payment configuration")
|
|
354
|
+
else:
|
|
355
|
+
# Fallback: Create endpoint from d402_config (for backward compatibility)
|
|
356
|
+
logger.info("Creating endpoint from d402_config (PostgreSQL endpoints not provided)")
|
|
357
|
+
endpoints_array = [
|
|
358
|
+
{
|
|
359
|
+
"endpoint_path": "/a2a",
|
|
360
|
+
"endpoint_name": "a2a",
|
|
361
|
+
"endpoint_method": "POST",
|
|
362
|
+
"endpoint_description": "A2A protocol JSON-RPC endpoint",
|
|
363
|
+
"icon_url": None,
|
|
364
|
+
"endpoint_input_schema": None, # A2A protocol defines this
|
|
365
|
+
"endpoint_output_schema": None, # A2A protocol defines this
|
|
366
|
+
# Payment info from d402_config
|
|
367
|
+
"payment_price_float": float(agent.d402_config.get("price_usd", 0.01)) if agent.d402_config else 0.01,
|
|
368
|
+
"payment_price_wei": agent.d402_config.get("price_wei") if agent.d402_config else None,
|
|
369
|
+
"settlement_token_address": agent.d402_config.get("token_address") if agent.d402_config else None,
|
|
370
|
+
"settlement_token_symbol": agent.d402_config.get("token_symbol") if agent.d402_config else None,
|
|
371
|
+
"settlement_token_network": agent.d402_config.get("network", "sepolia") if agent.d402_config else "sepolia",
|
|
372
|
+
"settlement_token_chain_id": agent.d402_config.get("chain_id") if agent.d402_config else None
|
|
373
|
+
}
|
|
374
|
+
]
|
|
375
|
+
entry_dict["endpoints"] = endpoints_array
|
|
376
|
+
else:
|
|
377
|
+
entry_dict["iatp_endpoints"] = None
|
|
378
|
+
entry_dict["endpoints"] = []
|
|
379
|
+
|
|
320
380
|
# Add embeddings if available
|
|
321
381
|
embeddings = {}
|
|
322
382
|
if description_embedding:
|
|
@@ -333,9 +393,13 @@ class UtilityAgentRegistry:
|
|
|
333
393
|
if embeddings:
|
|
334
394
|
entry_dict["embeddings"] = embeddings
|
|
335
395
|
|
|
336
|
-
#
|
|
337
|
-
|
|
338
|
-
|
|
396
|
+
# Do NOT store utility_agent_data as a nested structure - all fields are already at root level
|
|
397
|
+
# The entry_dict from UtilityAgentRegistryEntry already contains all necessary fields:
|
|
398
|
+
# - agent_id, name, description, capabilities, tags, metadata (at root)
|
|
399
|
+
# - agent_card, skills, endpoints (at root)
|
|
400
|
+
# - contract_address, d402_enabled, d402_payment_info (at root)
|
|
401
|
+
# - base_url, search_text, is_active, registered_at (at root)
|
|
402
|
+
# No utility_agent_data nesting is created
|
|
339
403
|
|
|
340
404
|
result = self.registry.replace_one(
|
|
341
405
|
{"agent_id": agent_id}, # Use agent_id as the key for upsert
|
|
@@ -401,7 +465,7 @@ class UtilityAgentRegistry:
|
|
|
401
465
|
return False
|
|
402
466
|
|
|
403
467
|
# Create new endpoints based on the new base URL
|
|
404
|
-
from
|
|
468
|
+
from ..utils.iatp_utils import create_iatp_endpoints
|
|
405
469
|
supports_streaming = doc.get("endpoints", {}).get("streaming_endpoint") is not None
|
|
406
470
|
new_endpoints = create_iatp_endpoints(new_base_url, supports_streaming)
|
|
407
471
|
|
|
@@ -456,6 +520,58 @@ class UtilityAgentRegistry:
|
|
|
456
520
|
logger.warning(f"No utility agent found with ID {agent_id} or no changes made")
|
|
457
521
|
return False
|
|
458
522
|
|
|
523
|
+
async def update_utility_agent_status(
|
|
524
|
+
self,
|
|
525
|
+
agent_id: str,
|
|
526
|
+
is_active: Optional[bool] = None,
|
|
527
|
+
core_tests_passed: Optional[bool] = None,
|
|
528
|
+
crewai_tests_passed: Optional[bool] = None
|
|
529
|
+
) -> Dict[str, Any]:
|
|
530
|
+
"""
|
|
531
|
+
Update utility agent status and test results in MongoDB.
|
|
532
|
+
|
|
533
|
+
Similar to MCP server status updates, allows updating:
|
|
534
|
+
- is_active: Whether the agent is active and available
|
|
535
|
+
- core_tests_passed: Whether core (direct A2A) tests passed
|
|
536
|
+
- crewai_tests_passed: Whether CrewAI agency tests passed
|
|
537
|
+
|
|
538
|
+
Args:
|
|
539
|
+
agent_id: ID of the utility agent
|
|
540
|
+
is_active: Optional new active status
|
|
541
|
+
core_tests_passed: Optional core test status
|
|
542
|
+
crewai_tests_passed: Optional CrewAI test status
|
|
543
|
+
|
|
544
|
+
Returns:
|
|
545
|
+
Dict with success status and updated fields
|
|
546
|
+
"""
|
|
547
|
+
update_data = {}
|
|
548
|
+
|
|
549
|
+
if is_active is not None:
|
|
550
|
+
update_data["metadata.is_active"] = is_active
|
|
551
|
+
|
|
552
|
+
if core_tests_passed is not None:
|
|
553
|
+
update_data["metadata.core_tests_passed"] = core_tests_passed
|
|
554
|
+
|
|
555
|
+
if crewai_tests_passed is not None:
|
|
556
|
+
update_data["metadata.crewai_tests_passed"] = crewai_tests_passed
|
|
557
|
+
|
|
558
|
+
if not update_data:
|
|
559
|
+
return {"success": False, "error": "No fields to update"}
|
|
560
|
+
|
|
561
|
+
update_data["updated_at"] = get_now_in_utc()
|
|
562
|
+
|
|
563
|
+
result = self.registry.update_one(
|
|
564
|
+
{"agent_id": agent_id},
|
|
565
|
+
{"$set": update_data}
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
if result.modified_count > 0:
|
|
569
|
+
logger.info(f"Updated utility agent {agent_id} - {list(update_data.keys())}")
|
|
570
|
+
return {"success": True, "modified_count": result.modified_count, "fields": list(update_data.keys())}
|
|
571
|
+
else:
|
|
572
|
+
logger.warning(f"No documents modified for agent_id: {agent_id}")
|
|
573
|
+
return {"success": False, "error": "No documents modified"}
|
|
574
|
+
|
|
459
575
|
async def get_statistics(self) -> Dict[str, Any]:
|
|
460
576
|
"""Get registry statistics."""
|
|
461
577
|
total_agents = self.registry.count_documents({})
|
|
@@ -499,34 +615,51 @@ class MCPServerRegistry:
|
|
|
499
615
|
if connection_string:
|
|
500
616
|
self.connection_string = connection_string
|
|
501
617
|
else:
|
|
502
|
-
#
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
#
|
|
507
|
-
#
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
618
|
+
# Check if running locally or in cloud environment
|
|
619
|
+
is_local = os.getenv("LOCAL_EXECUTION", "false").lower() == "true"
|
|
620
|
+
|
|
621
|
+
if is_local:
|
|
622
|
+
# Local execution - require explicit credentials
|
|
623
|
+
# Try X.509 certificate authentication first
|
|
624
|
+
cert_file = os.getenv("MONGODB_X509_CERT_FILE")
|
|
625
|
+
if cert_file and os.path.exists(cert_file):
|
|
626
|
+
# For X.509 authentication, we need to extract the subject from the certificate
|
|
627
|
+
# to use as the username. MongoDB Atlas typically uses the full DN as username.
|
|
628
|
+
# The connection string format for X.509 is:
|
|
629
|
+
# mongodb+srv://cluster.mongodb.net/?authSource=$external&authMechanism=MONGODB-X509
|
|
630
|
+
# Extract just the cluster hostname without query parameters
|
|
631
|
+
cluster_host = CLUSTER_URI.split('?')[0]
|
|
632
|
+
self.connection_string = f"mongodb+srv://{cluster_host}?authSource=$external&authMechanism=MONGODB-X509&tls=true&tlsCertificateKeyFile={cert_file}"
|
|
633
|
+
logger.info(f"Using X.509 certificate authentication from {cert_file}")
|
|
634
|
+
else:
|
|
635
|
+
# Fallback to username/password authentication
|
|
636
|
+
user = os.getenv("MONGODB_USER")
|
|
637
|
+
password = os.getenv("MONGODB_PASSWORD")
|
|
638
|
+
if user and password:
|
|
639
|
+
self.connection_string = f"mongodb+srv://{user}:{password}@{CLUSTER_URI}"
|
|
640
|
+
logger.info("Using username/password authentication")
|
|
641
|
+
else:
|
|
642
|
+
# Try connection string as last resort
|
|
643
|
+
self.connection_string = os.getenv("MONGODB_CONNECTION_STRING")
|
|
644
|
+
if not self.connection_string:
|
|
645
|
+
raise ValueError(
|
|
646
|
+
"MongoDB authentication required for local execution. Please provide either:\n"
|
|
647
|
+
"1. MONGODB_X509_CERT_FILE - Path to X.509 certificate file\n"
|
|
648
|
+
"2. MONGODB_USER and MONGODB_PASSWORD - Username and password\n"
|
|
649
|
+
"3. MONGODB_CONNECTION_STRING - Full connection string"
|
|
650
|
+
)
|
|
513
651
|
else:
|
|
514
|
-
#
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
if
|
|
518
|
-
|
|
519
|
-
|
|
652
|
+
# Cloud execution (Lambda) - use IAM role authentication
|
|
653
|
+
cluster_host = CLUSTER_URI.split('?')[0].rstrip('/')
|
|
654
|
+
# Preserve useful query parameters from original CLUSTER_URI
|
|
655
|
+
original_params = CLUSTER_URI.split('?')[1] if '?' in CLUSTER_URI else ""
|
|
656
|
+
# Combine AWS auth params with original params
|
|
657
|
+
auth_params = "authSource=$external&authMechanism=MONGODB-AWS"
|
|
658
|
+
if original_params:
|
|
659
|
+
self.connection_string = f"mongodb+srv://{cluster_host}?{auth_params}&{original_params}"
|
|
520
660
|
else:
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
if not self.connection_string:
|
|
524
|
-
raise ValueError(
|
|
525
|
-
"MongoDB authentication required. Please provide either:\n"
|
|
526
|
-
"1. MONGODB_X509_CERT_FILE - Path to X.509 certificate file\n"
|
|
527
|
-
"2. MONGODB_USER and MONGODB_PASSWORD - Username and password\n"
|
|
528
|
-
"3. MONGODB_CONNECTION_STRING - Full connection string"
|
|
529
|
-
)
|
|
661
|
+
self.connection_string = f"mongodb+srv://{cluster_host}?{auth_params}"
|
|
662
|
+
logger.info("Using IAM role authentication for cloud execution")
|
|
530
663
|
|
|
531
664
|
self.client = _create_mongodb_client_with_retry(self.connection_string)
|
|
532
665
|
self.db: Database = self.client[database_name]
|
|
@@ -557,7 +690,16 @@ class MCPServerRegistry:
|
|
|
557
690
|
description: str,
|
|
558
691
|
server_type: str = "streamable-http",
|
|
559
692
|
capabilities: List[str] = None,
|
|
560
|
-
metadata: Dict[str, Any] = None
|
|
693
|
+
metadata: Dict[str, Any] = None,
|
|
694
|
+
core_tests_passed: bool = False,
|
|
695
|
+
crewai_tests_passed: bool = False,
|
|
696
|
+
endpoints: Optional[List[Dict[str, Any]]] = None,
|
|
697
|
+
user_uuid: Optional[str] = None,
|
|
698
|
+
operator_address: Optional[str] = None,
|
|
699
|
+
server_address: Optional[str] = None,
|
|
700
|
+
server_network: Optional[str] = None,
|
|
701
|
+
server_chain_id: Optional[int] = None,
|
|
702
|
+
accepts: Optional[Dict[str, Any]] = None
|
|
561
703
|
) -> str:
|
|
562
704
|
"""Add an MCP server to the registry with retry logic."""
|
|
563
705
|
# Generate embeddings if enabled
|
|
@@ -566,6 +708,8 @@ class MCPServerRegistry:
|
|
|
566
708
|
|
|
567
709
|
if os.getenv("ENABLE_EMBEDDINGS", "true").lower() == "true":
|
|
568
710
|
try:
|
|
711
|
+
# Lazy import to avoid loading OpenAI/Cohere unless embeddings are enabled
|
|
712
|
+
from .embeddings import get_embedding_service
|
|
569
713
|
embedding_service = get_embedding_service()
|
|
570
714
|
|
|
571
715
|
# Generate embedding for description
|
|
@@ -587,11 +731,29 @@ class MCPServerRegistry:
|
|
|
587
731
|
"description": description,
|
|
588
732
|
"server_type": server_type,
|
|
589
733
|
"capabilities": capabilities or [],
|
|
734
|
+
"endpoints": endpoints or [],
|
|
590
735
|
"metadata": metadata or {},
|
|
591
736
|
"registered_at": get_now_in_utc(),
|
|
592
|
-
"
|
|
737
|
+
"core_tests_passed": core_tests_passed,
|
|
738
|
+
"crewai_tests_passed": crewai_tests_passed,
|
|
739
|
+
"is_active": False,
|
|
740
|
+
"user_uuid": user_uuid
|
|
593
741
|
}
|
|
594
742
|
|
|
743
|
+
# Add blockchain fields if provided (from PostgreSQL database)
|
|
744
|
+
if operator_address:
|
|
745
|
+
doc["operator_address"] = operator_address
|
|
746
|
+
if server_address:
|
|
747
|
+
doc["server_address"] = server_address
|
|
748
|
+
if server_network:
|
|
749
|
+
doc["server_network"] = server_network
|
|
750
|
+
if server_chain_id:
|
|
751
|
+
doc["server_chain_id"] = server_chain_id
|
|
752
|
+
|
|
753
|
+
# Add token acceptance information if provided
|
|
754
|
+
if accepts:
|
|
755
|
+
doc["accepts"] = accepts
|
|
756
|
+
|
|
595
757
|
# Add embeddings if available
|
|
596
758
|
if description_embedding:
|
|
597
759
|
doc["description_embedding"] = description_embedding
|
|
@@ -638,8 +800,18 @@ class MCPServerRegistry:
|
|
|
638
800
|
|
|
639
801
|
|
|
640
802
|
async def get_mcp_server(self, name: str) -> Optional[Dict[str, Any]]:
|
|
641
|
-
"""Get an MCP server by name.
|
|
642
|
-
|
|
803
|
+
"""Get an MCP server by name.
|
|
804
|
+
|
|
805
|
+
Note: Excludes embedding fields to reduce response size and improve performance.
|
|
806
|
+
Embeddings are only used internally for vector search.
|
|
807
|
+
"""
|
|
808
|
+
# Exclude embedding fields from response (large arrays not needed by clients)
|
|
809
|
+
projection = {
|
|
810
|
+
"description_embedding": 0,
|
|
811
|
+
"capabilities_embedding": 0
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
doc = self.collection.find_one({"name": name}, projection)
|
|
643
815
|
if doc:
|
|
644
816
|
doc["_id"] = str(doc["_id"])
|
|
645
817
|
return doc
|
|
@@ -700,7 +872,7 @@ if __name__ == "__main__":
|
|
|
700
872
|
)
|
|
701
873
|
|
|
702
874
|
# Create test agent with endpoints
|
|
703
|
-
from
|
|
875
|
+
from ..utils.iatp_utils import create_iatp_endpoints
|
|
704
876
|
test_agent.endpoints = create_iatp_endpoints("http://weather-agent:8100", supports_streaming=True)
|
|
705
877
|
|
|
706
878
|
# Add to registry
|
|
@@ -116,7 +116,7 @@ import os
|
|
|
116
116
|
os.environ["ENABLE_EMBEDDINGS"] = "true"
|
|
117
117
|
os.environ["OPENAI_API_KEY"] = "your-key"
|
|
118
118
|
|
|
119
|
-
from
|
|
119
|
+
from traia_iatp.registry.mongodb_registry import UtilityAgencyRegistry
|
|
120
120
|
|
|
121
121
|
# Create registry (embeddings will be auto-generated)
|
|
122
122
|
registry = UtilityAgencyRegistry()
|
|
@@ -22,7 +22,7 @@ The API automatically detects and uses the appropriate authentication method:
|
|
|
22
22
|
Find a single utility agent by specific criteria.
|
|
23
23
|
|
|
24
24
|
```python
|
|
25
|
-
from
|
|
25
|
+
from traia_iatp.registry.iatp_search_api import find_utility_agent
|
|
26
26
|
|
|
27
27
|
# Find by exact name
|
|
28
28
|
agent = find_utility_agent(name="hyperliquid-mcp-traia-utility-agency")
|
|
@@ -41,7 +41,7 @@ agent = find_utility_agent(query="trading bot")
|
|
|
41
41
|
List utility agents with optional filters.
|
|
42
42
|
|
|
43
43
|
```python
|
|
44
|
-
from
|
|
44
|
+
from traia_iatp.registry.iatp_search_api import list_utility_agents
|
|
45
45
|
|
|
46
46
|
# List all active agents
|
|
47
47
|
agents = list_utility_agents(limit=20)
|
|
@@ -57,7 +57,7 @@ agents = list_utility_agents(capabilities=["market_info", "trading_orders"])
|
|
|
57
57
|
Search utility agents using vector search.
|
|
58
58
|
|
|
59
59
|
```python
|
|
60
|
-
from
|
|
60
|
+
from traia_iatp.registry.iatp_search_api import search_utility_agents
|
|
61
61
|
|
|
62
62
|
# Search using default search_text embedding (recommended for best performance)
|
|
63
63
|
agents = await search_utility_agents("trading hyperliquid", limit=5)
|
|
@@ -83,7 +83,7 @@ agents = await search_utility_agents(
|
|
|
83
83
|
Find a single MCP server by specific criteria.
|
|
84
84
|
|
|
85
85
|
```python
|
|
86
|
-
from
|
|
86
|
+
from traia_iatp.registry.iatp_search_api import find_mcp_server
|
|
87
87
|
|
|
88
88
|
# Find by exact name
|
|
89
89
|
server = find_mcp_server(name="hyperliquid-mcp")
|
|
@@ -96,7 +96,7 @@ server = find_mcp_server(capability="trading_orders")
|
|
|
96
96
|
List MCP servers with optional filters.
|
|
97
97
|
|
|
98
98
|
```python
|
|
99
|
-
from
|
|
99
|
+
from traia_iatp.registry.iatp_search_api import list_mcp_servers
|
|
100
100
|
|
|
101
101
|
# List all servers
|
|
102
102
|
servers = list_mcp_servers(limit=10)
|
|
@@ -109,7 +109,7 @@ servers = list_mcp_servers(capabilities=["market_info"])
|
|
|
109
109
|
Search MCP servers using vector search.
|
|
110
110
|
|
|
111
111
|
```python
|
|
112
|
-
from
|
|
112
|
+
from traia_iatp.registry.iatp_search_api import search_mcp_servers
|
|
113
113
|
|
|
114
114
|
# Search using default embedding fields (description and capabilities)
|
|
115
115
|
servers = await search_mcp_servers("trading", limit=5)
|
|
@@ -126,7 +126,7 @@ servers = await search_mcp_servers(
|
|
|
126
126
|
Get detailed MCP server information by name (returns raw MongoDB document).
|
|
127
127
|
|
|
128
128
|
```python
|
|
129
|
-
from
|
|
129
|
+
from traia_iatp.registry.iatp_search_api import get_mcp_server
|
|
130
130
|
|
|
131
131
|
# Get full server details
|
|
132
132
|
server_doc = get_mcp_server("hyperliquid-mcp")
|
|
@@ -172,7 +172,7 @@ class MCPServerInfo:
|
|
|
172
172
|
#!/usr/bin/env python
|
|
173
173
|
import asyncio
|
|
174
174
|
import os
|
|
175
|
-
from
|
|
175
|
+
from traia_iatp.registry.iatp_search_api import (
|
|
176
176
|
find_utility_agent,
|
|
177
177
|
list_utility_agents,
|
|
178
178
|
search_utility_agents,
|
|
@@ -132,7 +132,7 @@ openssl s_client -connect your-cluster.mongodb.net:27017 -CAfile cert.pem
|
|
|
132
132
|
The registry automatically detects and uses X.509 authentication when configured:
|
|
133
133
|
|
|
134
134
|
```python
|
|
135
|
-
from
|
|
135
|
+
from traia_iatp.registry.mongodb_registry import UtilityAgentRegistry
|
|
136
136
|
|
|
137
137
|
# No need to pass credentials - uses MONGODB_X509_CERT_FILE automatically
|
|
138
138
|
registry = UtilityAgentRegistry()
|
|
@@ -65,7 +65,7 @@ export MONGODB_CONNECTION_STRING=mongodb+srv://user:pass@cluster.mongodb.net/...
|
|
|
65
65
|
### Using the High-Level API (No MongoDB Required)
|
|
66
66
|
|
|
67
67
|
```python
|
|
68
|
-
from
|
|
68
|
+
from traia_iatp.registry.iatp_search_api import find_utility_agent, list_mcp_servers
|
|
69
69
|
|
|
70
70
|
# Find a specific utility agent
|
|
71
71
|
agent = await find_utility_agent(name="trading-agent")
|
|
@@ -77,7 +77,7 @@ servers = list_mcp_servers(limit=10)
|
|
|
77
77
|
### Direct MongoDB Access
|
|
78
78
|
|
|
79
79
|
```python
|
|
80
|
-
from
|
|
80
|
+
from traia_iatp.registry.mongodb_registry import UtilityAgentRegistry
|
|
81
81
|
|
|
82
82
|
# Registry will automatically use configured authentication
|
|
83
83
|
registry = UtilityAgentRegistry()
|
|
@@ -168,7 +168,7 @@ See [atlas_search_indexes.json](./atlas_search_indexes.json) for index definitio
|
|
|
168
168
|
### Register a New Utility Agent
|
|
169
169
|
|
|
170
170
|
```python
|
|
171
|
-
from
|
|
171
|
+
from traia_iatp.core.models import UtilityAgent, AgentEndpoints
|
|
172
172
|
|
|
173
173
|
agent = UtilityAgent(
|
|
174
174
|
name="My Trading Bot",
|
|
@@ -95,7 +95,7 @@ This refactoring separates the concerns between registry management (write opera
|
|
|
95
95
|
|
|
96
96
|
### Registry Operations (Write)
|
|
97
97
|
```python
|
|
98
|
-
from
|
|
98
|
+
from traia_iatp.registry.mongodb_registry import UtilityAgentRegistry
|
|
99
99
|
|
|
100
100
|
# Add new agent
|
|
101
101
|
registry = UtilityAgentRegistry()
|
|
@@ -108,7 +108,7 @@ await registry.update_agent_base_url(agent_id, new_url)
|
|
|
108
108
|
|
|
109
109
|
### Search Operations (Read)
|
|
110
110
|
```python
|
|
111
|
-
from
|
|
111
|
+
from traia_iatp.registry.iatp_search_api import search_utility_agents, find_utility_agent
|
|
112
112
|
|
|
113
113
|
# Search for agents
|
|
114
114
|
agents = await search_utility_agents("hyperliquid trading", limit=5)
|
|
@@ -120,7 +120,7 @@ agent = find_utility_agent(capability="market_data")
|
|
|
120
120
|
### Discovery Test
|
|
121
121
|
```python
|
|
122
122
|
# The discovery test now uses the search API
|
|
123
|
-
from
|
|
123
|
+
from traia_iatp.registry.iatp_search_api import search_utility_agents
|
|
124
124
|
|
|
125
125
|
# Search for Hyperliquid agent
|
|
126
126
|
search_results = await search_utility_agents(
|
|
@@ -137,19 +137,19 @@ search_results = await search_utility_agents(
|
|
|
137
137
|
1. **If using registry for search/query:**
|
|
138
138
|
```python
|
|
139
139
|
# OLD
|
|
140
|
-
from
|
|
140
|
+
from traia_iatp.registry.mongodb_registry import UtilityAgentRegistry
|
|
141
141
|
registry = UtilityAgentRegistry()
|
|
142
142
|
agents = await registry.query_agents(query="trading")
|
|
143
143
|
|
|
144
144
|
# NEW
|
|
145
|
-
from
|
|
145
|
+
from traia_iatp.registry.iatp_search_api import search_utility_agents
|
|
146
146
|
agents = await search_utility_agents("trading")
|
|
147
147
|
```
|
|
148
148
|
|
|
149
149
|
2. **If using registry for write operations:**
|
|
150
150
|
```python
|
|
151
151
|
# No changes needed - these methods are still available
|
|
152
|
-
from
|
|
152
|
+
from traia_iatp.registry.mongodb_registry import UtilityAgentRegistry
|
|
153
153
|
registry = UtilityAgentRegistry()
|
|
154
154
|
await registry.add_utility_agent(agent)
|
|
155
155
|
```
|