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
@@ -15,15 +15,23 @@ import logging
15
15
  from pymongo import MongoClient
16
16
  from pymongo import server_api
17
17
 
18
- # Import for embeddings
19
- from .embeddings import get_embedding_service
20
-
21
18
  # Get environment variables
22
19
  CLUSTER_URI = "traia-iatp-cluster.yzwjvgd.mongodb.net/?retryWrites=true&w=majority&appName=Traia-IATP-Cluster"
23
20
  DATABASE_NAME = "iatp"
24
21
 
25
22
  logger = logging.getLogger(__name__)
26
23
 
24
+ # Projection to exclude large embedding fields from query responses
25
+ # These fields are used internally for vector search but not needed by clients
26
+ MCP_PROJECTION_EXCLUDE_EMBEDDINGS = {
27
+ "description_embedding": 0,
28
+ "capabilities_embedding": 0
29
+ }
30
+
31
+ UTILITY_AGENT_PROJECTION_EXCLUDE_EMBEDDINGS = {
32
+ "embeddings": 0 # Excludes embeddings.description, embeddings.agent_card, embeddings.search_text
33
+ }
34
+
27
35
 
28
36
  @dataclass
29
37
  class MCPServerInfo:
@@ -36,10 +44,25 @@ class MCPServerInfo:
36
44
  capabilities: List[str]
37
45
  metadata: Dict[str, Any]
38
46
  tags: List[str]
47
+ is_active: bool = False
48
+ core_tests_passed: bool = False
49
+ crewai_tests_passed: bool = False
50
+ user_uuid: Optional[str] = None
51
+ # Blockchain fields (from PostgreSQL, synced to MongoDB)
52
+ operator_address: Optional[str] = None
53
+ server_address: Optional[str] = None
54
+ server_network: Optional[str] = None
55
+ server_chain_id: Optional[int] = None
56
+ # Token acceptance information
57
+ accepts: Optional[Dict[str, Any]] = None
39
58
 
40
59
  @classmethod
41
60
  def from_registry_doc(cls, doc: Dict[str, Any]) -> 'MCPServerInfo':
42
- """Create MCPServerInfo from MongoDB document."""
61
+ """Create MCPServerInfo from MongoDB document.
62
+
63
+ Note: Embedding fields (description_embedding, capabilities_embedding)
64
+ should be excluded from queries via projection - they are not needed by clients.
65
+ """
43
66
  # Extract tags from metadata if present
44
67
  metadata = doc.get('metadata', {})
45
68
  tags = metadata.get('tags', [])
@@ -52,7 +75,18 @@ class MCPServerInfo:
52
75
  server_type=doc.get('server_type', 'streamable-http'),
53
76
  capabilities=doc.get('capabilities', []),
54
77
  metadata=metadata,
55
- tags=tags
78
+ tags=tags,
79
+ is_active=doc.get('is_active', False),
80
+ core_tests_passed=doc.get('core_tests_passed', False),
81
+ crewai_tests_passed=doc.get('crewai_tests_passed', False),
82
+ user_uuid=doc.get('user_uuid'),
83
+ # Blockchain fields
84
+ operator_address=doc.get('operator_address'),
85
+ server_address=doc.get('server_address'),
86
+ server_network=doc.get('server_network'),
87
+ server_chain_id=doc.get('server_chain_id'),
88
+ # Token acceptance
89
+ accepts=doc.get('accepts')
56
90
  )
57
91
 
58
92
 
@@ -69,10 +103,18 @@ class UtilityAgentInfo:
69
103
  metadata: Dict[str, Any]
70
104
  skills: List[Dict[str, Any]]
71
105
  endpoints: Optional[Dict[str, Any]] = None
106
+ # D402 payment fields
107
+ d402_enabled: bool = False
108
+ d402_payment_info: Optional[Dict[str, Any]] = None
72
109
 
73
110
  @classmethod
74
111
  def from_registry_doc(cls, doc: Dict[str, Any]) -> 'UtilityAgentInfo':
75
- """Create UtilityAgentInfo from MongoDB document."""
112
+ """Create UtilityAgentInfo from MongoDB document.
113
+
114
+ Note: The "embeddings" object (containing embeddings.description,
115
+ embeddings.agent_card, embeddings.search_text) should be excluded from
116
+ queries via projection - they are not needed by clients.
117
+ """
76
118
  return cls(
77
119
  agent_id=doc.get('agent_id', ''),
78
120
  name=doc.get('name', ''),
@@ -83,13 +125,21 @@ class UtilityAgentInfo:
83
125
  is_active=doc.get('is_active', True),
84
126
  metadata=doc.get('metadata', {}),
85
127
  skills=doc.get('skills', []),
86
- endpoints=doc.get('endpoints') # Get endpoints from root level
128
+ endpoints=doc.get('endpoints'), # Get endpoints from root level
129
+ d402_enabled=doc.get('d402_enabled', False),
130
+ d402_payment_info=doc.get('d402_payment_info')
87
131
  )
88
132
 
89
133
 
90
134
  def get_readonly_connection_string() -> str:
91
135
  """Get read-only MongoDB connection string."""
92
- # Try X.509 certificate authentication first
136
+
137
+ # Try IAM access
138
+ cluster_host_name = os.environ.get('CLUSTER_HOST_NAME', None)
139
+ if cluster_host_name:
140
+ return f"mongodb+srv://{cluster_host_name}/?authMechanism=MONGODB-AWS&authSource=%24external&retryWrites=true&w=majority&appName=Traia-IATP-Cluster"
141
+
142
+ # Try X.509 certificate authentication next
93
143
  cert_file = os.getenv("MONGODB_X509_CERT_FILE")
94
144
  if cert_file and os.path.exists(cert_file):
95
145
  # Extract just the cluster hostname without query parameters
@@ -110,9 +160,10 @@ def get_readonly_connection_string() -> str:
110
160
 
111
161
  raise ValueError(
112
162
  "MongoDB authentication required. Please provide either:\n"
113
- "1. MONGODB_X509_CERT_FILE - Path to X.509 certificate file\n"
114
- "2. MONGODB_USER and MONGODB_PASSWORD - Username and password\n"
115
- "3. MONGODB_CONNECTION_STRING - Full connection string"
163
+ "1. MONGODB_IAM_ACCESS - Cluster Host Required\n"
164
+ "2. MONGODB_X509_CERT_FILE - Path to X.509 certificate file\n"
165
+ "3. MONGODB_USER and MONGODB_PASSWORD - Username and password\n"
166
+ "4. MONGODB_CONNECTION_STRING - Full connection string"
116
167
  )
117
168
 
118
169
 
@@ -178,14 +229,20 @@ class IATPSearchAPI:
178
229
  collection = db[collection_names["utility_agent"]]
179
230
 
180
231
  if agent_id:
181
- # Direct lookup by agent_id
182
- doc = collection.find_one({"agent_id": agent_id, "is_active": True})
232
+ # Direct lookup by agent_id (exclude embeddings for performance)
233
+ doc = collection.find_one(
234
+ {"agent_id": agent_id, "is_active": True},
235
+ UTILITY_AGENT_PROJECTION_EXCLUDE_EMBEDDINGS
236
+ )
183
237
  if doc:
184
238
  return UtilityAgentInfo.from_registry_doc(doc)
185
239
 
186
240
  if name:
187
- # Direct lookup by name
188
- doc = collection.find_one({"name": name, "is_active": True})
241
+ # Direct lookup by name (exclude embeddings for performance)
242
+ doc = collection.find_one(
243
+ {"name": name, "is_active": True},
244
+ UTILITY_AGENT_PROJECTION_EXCLUDE_EMBEDDINGS
245
+ )
189
246
  if doc:
190
247
  return UtilityAgentInfo.from_registry_doc(doc)
191
248
 
@@ -232,8 +289,8 @@ class IATPSearchAPI:
232
289
  if tag:
233
290
  filters["tags"] = tag
234
291
 
235
- # Find first matching document
236
- doc = collection.find_one(filters)
292
+ # Find first matching document (exclude embeddings for performance)
293
+ doc = collection.find_one(filters, UTILITY_AGENT_PROJECTION_EXCLUDE_EMBEDDINGS)
237
294
  if doc:
238
295
  return UtilityAgentInfo.from_registry_doc(doc)
239
296
 
@@ -272,8 +329,11 @@ class IATPSearchAPI:
272
329
  if capabilities:
273
330
  filters["capabilities"] = {"$in": capabilities}
274
331
 
275
- # Query with limit
276
- cursor = collection.find(filters).limit(limit).sort("registered_at", -1)
332
+ # Query with limit (exclude embeddings for performance)
333
+ cursor = collection.find(
334
+ filters,
335
+ UTILITY_AGENT_PROJECTION_EXCLUDE_EMBEDDINGS
336
+ ).limit(limit).sort("registered_at", -1)
277
337
 
278
338
  return [UtilityAgentInfo.from_registry_doc(doc) for doc in cursor]
279
339
 
@@ -298,8 +358,9 @@ class IATPSearchAPI:
298
358
  Returns:
299
359
  List of UtilityAgentInfo objects
300
360
  """
301
- # Generate query embedding
361
+ # Generate query embedding (lazy import to avoid loading OpenAI/Cohere unless needed)
302
362
  try:
363
+ from .embeddings import get_embedding_service
303
364
  embedding_service = get_embedding_service()
304
365
  query_embedding = await embedding_service.generate_query_embedding(query)
305
366
  except Exception as e:
@@ -469,7 +530,7 @@ class IATPSearchAPI:
469
530
 
470
531
  if name:
471
532
  # Direct lookup by name
472
- doc = collection.find_one({"name": name, "is_active": True})
533
+ doc = collection.find_one({"name": name, "is_active": True, "core_tests_passed": True})
473
534
  if doc:
474
535
  return MCPServerInfo.from_registry_doc(doc)
475
536
 
@@ -488,7 +549,7 @@ class IATPSearchAPI:
488
549
  }
489
550
  }
490
551
  },
491
- {"$match": {"is_active": True}},
552
+ {"$match": {"is_active": True, "core_tests_passed": True}},
492
553
  ]
493
554
 
494
555
  # Add additional filters if provided
@@ -502,6 +563,8 @@ class IATPSearchAPI:
502
563
  if match_filters:
503
564
  pipeline.append({"$match": match_filters})
504
565
 
566
+ # Exclude embedding fields from results
567
+ pipeline.append({"$project": MCP_PROJECTION_EXCLUDE_EMBEDDINGS})
505
568
  pipeline.append({"$limit": 1})
506
569
 
507
570
  results = list(collection.aggregate(pipeline))
@@ -509,7 +572,7 @@ class IATPSearchAPI:
509
572
  return MCPServerInfo.from_registry_doc(results[0])
510
573
  else:
511
574
  # Build standard query filters
512
- filters = {"is_active": True}
575
+ filters = {"is_active": True, "core_tests_passed": True}
513
576
 
514
577
  if capability:
515
578
  filters["capabilities"] = capability
@@ -518,8 +581,8 @@ class IATPSearchAPI:
518
581
  # Tags are stored in metadata
519
582
  filters["metadata.tags"] = tag
520
583
 
521
- # Find first matching document
522
- doc = collection.find_one(filters)
584
+ # Find first matching document (exclude embeddings for performance)
585
+ doc = collection.find_one(filters, MCP_PROJECTION_EXCLUDE_EMBEDDINGS)
523
586
  if doc:
524
587
  return MCPServerInfo.from_registry_doc(doc)
525
588
 
@@ -529,31 +592,58 @@ class IATPSearchAPI:
529
592
  def list_mcp_servers(
530
593
  cls,
531
594
  limit: int = 10,
595
+ page: int = 1,
532
596
  tags: Optional[List[str]] = None,
533
- capabilities: Optional[List[str]] = None
597
+ capabilities: Optional[List[str]] = None,
598
+ user_uuid: Optional[str] = None
534
599
  ) -> List[MCPServerInfo]:
535
600
  """
536
601
  List available MCP servers from the registry.
537
602
 
538
603
  Args:
539
- limit: Maximum number of servers to return
604
+ limit: Maximum number of servers to return (default: 10)
605
+ page: Page number for pagination, 1-indexed (default: 1)
540
606
  tags: Filter by tags
541
607
  capabilities: Filter by capabilities
608
+ user_uuid: Filter by user UUID
542
609
 
543
610
  Returns:
544
611
  List of MCPServerInfo objects
612
+
613
+ Examples:
614
+ list_mcp_servers(limit=10, page=1) # First 10 servers
615
+ list_mcp_servers(limit=10, page=2) # Next 10 servers
545
616
  """
617
+ if page < 1:
618
+ page = 1
619
+
620
+ skip = (page - 1) * limit
621
+ logger.info(f"list_mcp_servers: limit={limit}, page={page}, tags={tags}, capabilities={capabilities}, user_uuid={user_uuid}")
622
+
546
623
  db = cls._get_connection()
547
624
  collection_names = get_collection_names()
548
625
  collection = db[collection_names["mcp_server"]]
549
626
 
627
+ logger.info(f"Using collection: {collection_names['mcp_server']}")
628
+
550
629
  # Build query filters
551
- filters = {"is_active": True}
630
+ filters = {"is_active": True, "core_tests_passed": True}
552
631
  if capabilities:
553
632
  filters["capabilities"] = {"$in": capabilities}
633
+ if user_uuid:
634
+ filters["user_uuid"] = user_uuid
554
635
 
555
- # Query
556
- cursor = collection.find(filters).limit(limit).sort("registered_at", -1)
636
+ logger.info(f"Query filters: {filters}")
637
+
638
+ # Count and log total
639
+ total_count = collection.count_documents(filters)
640
+ logger.info(f"Total documents matching filters: {total_count}")
641
+
642
+ # Query with pagination (exclude embeddings for performance)
643
+ cursor = collection.find(
644
+ filters,
645
+ MCP_PROJECTION_EXCLUDE_EMBEDDINGS
646
+ ).skip(skip).limit(limit).sort("registered_at", -1)
557
647
 
558
648
  # Manual filtering for tags if needed
559
649
  servers = []
@@ -562,9 +652,11 @@ class IATPSearchAPI:
562
652
  doc_tags = doc.get("metadata", {}).get("tags", [])
563
653
  if not any(tag in doc_tags for tag in tags):
564
654
  continue
655
+
565
656
  servers.append(MCPServerInfo.from_registry_doc(doc))
566
657
 
567
- return servers[:limit]
658
+ logger.info(f"Returning {len(servers)} servers")
659
+ return servers
568
660
 
569
661
  @classmethod
570
662
  def get_mcp_server(cls, name: str) -> Optional[Dict[str, Any]]:
@@ -581,7 +673,8 @@ class IATPSearchAPI:
581
673
  collection_names = get_collection_names()
582
674
  collection = db[collection_names["mcp_server"]]
583
675
 
584
- doc = collection.find_one({"name": name})
676
+ # Exclude embeddings from response (not needed by clients)
677
+ doc = collection.find_one({"name": name}, MCP_PROJECTION_EXCLUDE_EMBEDDINGS)
585
678
  if doc:
586
679
  doc["_id"] = str(doc["_id"])
587
680
  return doc
@@ -615,8 +708,9 @@ class IATPSearchAPI:
615
708
  Returns:
616
709
  List of MCPServerInfo objects
617
710
  """
618
- # Generate query embedding
711
+ # Generate query embedding (lazy import to avoid loading OpenAI/Cohere unless needed)
619
712
  try:
713
+ from .embeddings import get_embedding_service
620
714
  embedding_service = get_embedding_service()
621
715
  query_embedding = await embedding_service.generate_query_embedding(query)
622
716
  except Exception as e:
@@ -652,7 +746,7 @@ class IATPSearchAPI:
652
746
  "queryVector": query_embedding,
653
747
  "numCandidates": limit * 5,
654
748
  "limit": limit,
655
- "filter": {"is_active": True} if active_only else {}
749
+ "filter": {"is_active": True, "core_tests_passed": True} if active_only else {"core_tests_passed": True}
656
750
  }
657
751
  },
658
752
  {
@@ -666,6 +760,14 @@ class IATPSearchAPI:
666
760
  "metadata": 1,
667
761
  "registered_at": 1,
668
762
  "is_active": 1,
763
+ "core_tests_passed": 1,
764
+ "crewai_tests_passed": 1,
765
+ "user_uuid": 1,
766
+ "operator_address": 1,
767
+ "server_address": 1,
768
+ "server_network": 1,
769
+ "server_chain_id": 1,
770
+ "accepts": 1,
669
771
  "score": {"$meta": "vectorSearchScore"}
670
772
  }
671
773
  }
@@ -687,7 +789,7 @@ class IATPSearchAPI:
687
789
  "queryVector": query_embedding,
688
790
  "numCandidates": limit * 5,
689
791
  "limit": limit,
690
- "filter": {"is_active": True} if active_only else {}
792
+ "filter": {"is_active": True, "core_tests_passed": True} if active_only else {"core_tests_passed": True}
691
793
  }
692
794
  },
693
795
  {
@@ -701,6 +803,14 @@ class IATPSearchAPI:
701
803
  "metadata": 1,
702
804
  "registered_at": 1,
703
805
  "is_active": 1,
806
+ "core_tests_passed": 1,
807
+ "crewai_tests_passed": 1,
808
+ "user_uuid": 1,
809
+ "operator_address": 1,
810
+ "server_address": 1,
811
+ "server_network": 1,
812
+ "server_chain_id": 1,
813
+ "accepts": 1,
704
814
  "score": {"$meta": "vectorSearchScore"}
705
815
  }
706
816
  }
@@ -749,7 +859,9 @@ class IATPSearchAPI:
749
859
  ]
750
860
 
751
861
  if active_only:
752
- pipeline.append({"$match": {"is_active": True}})
862
+ pipeline.append({"$match": {"is_active": True, "core_tests_passed": True}})
863
+ else:
864
+ pipeline.append({"$match": {"core_tests_passed": True}})
753
865
 
754
866
  pipeline.append({"$limit": limit})
755
867
 
@@ -807,11 +919,29 @@ def find_mcp_server(
807
919
 
808
920
  def list_mcp_servers(
809
921
  limit: int = 10,
922
+ page: int = 1,
810
923
  tags: Optional[List[str]] = None,
811
- capabilities: Optional[List[str]] = None
924
+ capabilities: Optional[List[str]] = None,
925
+ user_uuid: Optional[str] = None
812
926
  ) -> List[MCPServerInfo]:
813
- """List available MCP servers from the registry."""
814
- return IATPSearchAPI.list_mcp_servers(limit, tags, capabilities)
927
+ """
928
+ List available MCP servers from the registry.
929
+
930
+ Args:
931
+ limit: Maximum number of servers to return (default: 10)
932
+ page: Page number for pagination, 1-indexed (default: 1)
933
+ tags: Filter by tags
934
+ capabilities: Filter by capabilities
935
+ user_uuid: Filter by user UUID
936
+
937
+ Returns:
938
+ List of MCPServerInfo objects
939
+
940
+ Examples:
941
+ servers = list_mcp_servers(limit=10, page=1) # First page
942
+ servers = list_mcp_servers(limit=10, page=2) # Second page
943
+ """
944
+ return IATPSearchAPI.list_mcp_servers(limit, page, tags, capabilities, user_uuid)
815
945
 
816
946
 
817
947
  async def search_mcp_servers(