alita-sdk 0.3.379__py3-none-any.whl → 0.3.627__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 (278) hide show
  1. alita_sdk/cli/__init__.py +10 -0
  2. alita_sdk/cli/__main__.py +17 -0
  3. alita_sdk/cli/agent/__init__.py +5 -0
  4. alita_sdk/cli/agent/default.py +258 -0
  5. alita_sdk/cli/agent_executor.py +156 -0
  6. alita_sdk/cli/agent_loader.py +245 -0
  7. alita_sdk/cli/agent_ui.py +228 -0
  8. alita_sdk/cli/agents.py +3113 -0
  9. alita_sdk/cli/callbacks.py +647 -0
  10. alita_sdk/cli/cli.py +168 -0
  11. alita_sdk/cli/config.py +306 -0
  12. alita_sdk/cli/context/__init__.py +30 -0
  13. alita_sdk/cli/context/cleanup.py +198 -0
  14. alita_sdk/cli/context/manager.py +731 -0
  15. alita_sdk/cli/context/message.py +285 -0
  16. alita_sdk/cli/context/strategies.py +289 -0
  17. alita_sdk/cli/context/token_estimation.py +127 -0
  18. alita_sdk/cli/formatting.py +182 -0
  19. alita_sdk/cli/input_handler.py +419 -0
  20. alita_sdk/cli/inventory.py +1073 -0
  21. alita_sdk/cli/mcp_loader.py +315 -0
  22. alita_sdk/cli/testcases/__init__.py +94 -0
  23. alita_sdk/cli/testcases/data_generation.py +119 -0
  24. alita_sdk/cli/testcases/discovery.py +96 -0
  25. alita_sdk/cli/testcases/executor.py +84 -0
  26. alita_sdk/cli/testcases/logger.py +85 -0
  27. alita_sdk/cli/testcases/parser.py +172 -0
  28. alita_sdk/cli/testcases/prompts.py +91 -0
  29. alita_sdk/cli/testcases/reporting.py +125 -0
  30. alita_sdk/cli/testcases/setup.py +108 -0
  31. alita_sdk/cli/testcases/test_runner.py +282 -0
  32. alita_sdk/cli/testcases/utils.py +39 -0
  33. alita_sdk/cli/testcases/validation.py +90 -0
  34. alita_sdk/cli/testcases/workflow.py +196 -0
  35. alita_sdk/cli/toolkit.py +327 -0
  36. alita_sdk/cli/toolkit_loader.py +85 -0
  37. alita_sdk/cli/tools/__init__.py +43 -0
  38. alita_sdk/cli/tools/approval.py +224 -0
  39. alita_sdk/cli/tools/filesystem.py +1751 -0
  40. alita_sdk/cli/tools/planning.py +389 -0
  41. alita_sdk/cli/tools/terminal.py +414 -0
  42. alita_sdk/community/__init__.py +72 -12
  43. alita_sdk/community/inventory/__init__.py +236 -0
  44. alita_sdk/community/inventory/config.py +257 -0
  45. alita_sdk/community/inventory/enrichment.py +2137 -0
  46. alita_sdk/community/inventory/extractors.py +1469 -0
  47. alita_sdk/community/inventory/ingestion.py +3172 -0
  48. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  49. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  50. alita_sdk/community/inventory/parsers/base.py +295 -0
  51. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  52. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  53. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  54. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  55. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  56. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  57. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  58. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  59. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  60. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  61. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  62. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  63. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  64. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  65. alita_sdk/community/inventory/patterns/loader.py +348 -0
  66. alita_sdk/community/inventory/patterns/registry.py +198 -0
  67. alita_sdk/community/inventory/presets.py +535 -0
  68. alita_sdk/community/inventory/retrieval.py +1403 -0
  69. alita_sdk/community/inventory/toolkit.py +173 -0
  70. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  71. alita_sdk/community/inventory/visualize.py +1370 -0
  72. alita_sdk/configurations/__init__.py +1 -1
  73. alita_sdk/configurations/ado.py +141 -20
  74. alita_sdk/configurations/bitbucket.py +94 -2
  75. alita_sdk/configurations/confluence.py +130 -1
  76. alita_sdk/configurations/figma.py +76 -0
  77. alita_sdk/configurations/gitlab.py +91 -0
  78. alita_sdk/configurations/jira.py +103 -0
  79. alita_sdk/configurations/openapi.py +329 -0
  80. alita_sdk/configurations/qtest.py +72 -1
  81. alita_sdk/configurations/report_portal.py +96 -0
  82. alita_sdk/configurations/sharepoint.py +148 -0
  83. alita_sdk/configurations/testio.py +83 -0
  84. alita_sdk/configurations/testrail.py +88 -0
  85. alita_sdk/configurations/xray.py +93 -0
  86. alita_sdk/configurations/zephyr_enterprise.py +93 -0
  87. alita_sdk/configurations/zephyr_essential.py +75 -0
  88. alita_sdk/runtime/clients/artifact.py +3 -3
  89. alita_sdk/runtime/clients/client.py +388 -46
  90. alita_sdk/runtime/clients/mcp_discovery.py +342 -0
  91. alita_sdk/runtime/clients/mcp_manager.py +262 -0
  92. alita_sdk/runtime/clients/sandbox_client.py +8 -21
  93. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  94. alita_sdk/runtime/langchain/assistant.py +157 -39
  95. alita_sdk/runtime/langchain/constants.py +647 -1
  96. alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
  97. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
  98. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  99. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -4
  100. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
  101. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
  102. alita_sdk/runtime/langchain/document_loaders/constants.py +40 -19
  103. alita_sdk/runtime/langchain/langraph_agent.py +405 -84
  104. alita_sdk/runtime/langchain/utils.py +106 -7
  105. alita_sdk/runtime/llms/preloaded.py +2 -6
  106. alita_sdk/runtime/models/mcp_models.py +61 -0
  107. alita_sdk/runtime/skills/__init__.py +91 -0
  108. alita_sdk/runtime/skills/callbacks.py +498 -0
  109. alita_sdk/runtime/skills/discovery.py +540 -0
  110. alita_sdk/runtime/skills/executor.py +610 -0
  111. alita_sdk/runtime/skills/input_builder.py +371 -0
  112. alita_sdk/runtime/skills/models.py +330 -0
  113. alita_sdk/runtime/skills/registry.py +355 -0
  114. alita_sdk/runtime/skills/skill_runner.py +330 -0
  115. alita_sdk/runtime/toolkits/__init__.py +31 -0
  116. alita_sdk/runtime/toolkits/application.py +29 -10
  117. alita_sdk/runtime/toolkits/artifact.py +20 -11
  118. alita_sdk/runtime/toolkits/datasource.py +13 -6
  119. alita_sdk/runtime/toolkits/mcp.py +783 -0
  120. alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
  121. alita_sdk/runtime/toolkits/planning.py +178 -0
  122. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  123. alita_sdk/runtime/toolkits/subgraph.py +251 -6
  124. alita_sdk/runtime/toolkits/tools.py +356 -69
  125. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  126. alita_sdk/runtime/tools/__init__.py +10 -3
  127. alita_sdk/runtime/tools/application.py +27 -6
  128. alita_sdk/runtime/tools/artifact.py +511 -28
  129. alita_sdk/runtime/tools/data_analysis.py +183 -0
  130. alita_sdk/runtime/tools/function.py +67 -35
  131. alita_sdk/runtime/tools/graph.py +10 -4
  132. alita_sdk/runtime/tools/image_generation.py +148 -46
  133. alita_sdk/runtime/tools/llm.py +1003 -128
  134. alita_sdk/runtime/tools/loop.py +3 -1
  135. alita_sdk/runtime/tools/loop_output.py +3 -1
  136. alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
  137. alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
  138. alita_sdk/runtime/tools/mcp_server_tool.py +8 -5
  139. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  140. alita_sdk/runtime/tools/planning/models.py +246 -0
  141. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  142. alita_sdk/runtime/tools/router.py +2 -4
  143. alita_sdk/runtime/tools/sandbox.py +65 -48
  144. alita_sdk/runtime/tools/skill_router.py +776 -0
  145. alita_sdk/runtime/tools/tool.py +3 -1
  146. alita_sdk/runtime/tools/vectorstore.py +9 -3
  147. alita_sdk/runtime/tools/vectorstore_base.py +70 -14
  148. alita_sdk/runtime/utils/AlitaCallback.py +137 -21
  149. alita_sdk/runtime/utils/constants.py +5 -1
  150. alita_sdk/runtime/utils/mcp_client.py +492 -0
  151. alita_sdk/runtime/utils/mcp_oauth.py +361 -0
  152. alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
  153. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  154. alita_sdk/runtime/utils/serialization.py +155 -0
  155. alita_sdk/runtime/utils/streamlit.py +40 -13
  156. alita_sdk/runtime/utils/toolkit_utils.py +30 -9
  157. alita_sdk/runtime/utils/utils.py +36 -0
  158. alita_sdk/tools/__init__.py +134 -35
  159. alita_sdk/tools/ado/repos/__init__.py +51 -32
  160. alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
  161. alita_sdk/tools/ado/test_plan/__init__.py +25 -9
  162. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
  163. alita_sdk/tools/ado/utils.py +1 -18
  164. alita_sdk/tools/ado/wiki/__init__.py +25 -12
  165. alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
  166. alita_sdk/tools/ado/work_item/__init__.py +26 -13
  167. alita_sdk/tools/ado/work_item/ado_wrapper.py +73 -11
  168. alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
  169. alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
  170. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  171. alita_sdk/tools/azure_ai/search/__init__.py +11 -8
  172. alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
  173. alita_sdk/tools/base/tool.py +5 -1
  174. alita_sdk/tools/base_indexer_toolkit.py +271 -84
  175. alita_sdk/tools/bitbucket/__init__.py +17 -11
  176. alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
  177. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
  178. alita_sdk/tools/browser/__init__.py +5 -4
  179. alita_sdk/tools/carrier/__init__.py +5 -6
  180. alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
  181. alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
  182. alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
  183. alita_sdk/tools/chunkers/__init__.py +3 -1
  184. alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
  185. alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
  186. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  187. alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
  188. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  189. alita_sdk/tools/cloud/aws/__init__.py +10 -7
  190. alita_sdk/tools/cloud/azure/__init__.py +10 -7
  191. alita_sdk/tools/cloud/gcp/__init__.py +10 -7
  192. alita_sdk/tools/cloud/k8s/__init__.py +10 -7
  193. alita_sdk/tools/code/linter/__init__.py +10 -8
  194. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  195. alita_sdk/tools/code/sonar/__init__.py +11 -8
  196. alita_sdk/tools/code_indexer_toolkit.py +82 -22
  197. alita_sdk/tools/confluence/__init__.py +22 -16
  198. alita_sdk/tools/confluence/api_wrapper.py +107 -30
  199. alita_sdk/tools/confluence/loader.py +14 -2
  200. alita_sdk/tools/custom_open_api/__init__.py +12 -5
  201. alita_sdk/tools/elastic/__init__.py +11 -8
  202. alita_sdk/tools/elitea_base.py +493 -30
  203. alita_sdk/tools/figma/__init__.py +58 -11
  204. alita_sdk/tools/figma/api_wrapper.py +1235 -143
  205. alita_sdk/tools/figma/figma_client.py +73 -0
  206. alita_sdk/tools/figma/toon_tools.py +2748 -0
  207. alita_sdk/tools/github/__init__.py +14 -15
  208. alita_sdk/tools/github/github_client.py +224 -100
  209. alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
  210. alita_sdk/tools/github/schemas.py +14 -5
  211. alita_sdk/tools/github/tool.py +5 -1
  212. alita_sdk/tools/github/tool_prompts.py +9 -22
  213. alita_sdk/tools/gitlab/__init__.py +16 -11
  214. alita_sdk/tools/gitlab/api_wrapper.py +218 -48
  215. alita_sdk/tools/gitlab_org/__init__.py +10 -9
  216. alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
  217. alita_sdk/tools/google/bigquery/__init__.py +13 -12
  218. alita_sdk/tools/google/bigquery/tool.py +5 -1
  219. alita_sdk/tools/google_places/__init__.py +11 -8
  220. alita_sdk/tools/google_places/api_wrapper.py +1 -1
  221. alita_sdk/tools/jira/__init__.py +17 -10
  222. alita_sdk/tools/jira/api_wrapper.py +92 -41
  223. alita_sdk/tools/keycloak/__init__.py +11 -8
  224. alita_sdk/tools/localgit/__init__.py +9 -3
  225. alita_sdk/tools/localgit/local_git.py +62 -54
  226. alita_sdk/tools/localgit/tool.py +5 -1
  227. alita_sdk/tools/memory/__init__.py +12 -4
  228. alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
  229. alita_sdk/tools/ocr/__init__.py +11 -8
  230. alita_sdk/tools/openapi/__init__.py +491 -106
  231. alita_sdk/tools/openapi/api_wrapper.py +1368 -0
  232. alita_sdk/tools/openapi/tool.py +20 -0
  233. alita_sdk/tools/pandas/__init__.py +20 -12
  234. alita_sdk/tools/pandas/api_wrapper.py +38 -25
  235. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  236. alita_sdk/tools/postman/__init__.py +10 -9
  237. alita_sdk/tools/pptx/__init__.py +11 -10
  238. alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
  239. alita_sdk/tools/qtest/__init__.py +31 -11
  240. alita_sdk/tools/qtest/api_wrapper.py +2135 -86
  241. alita_sdk/tools/rally/__init__.py +10 -9
  242. alita_sdk/tools/rally/api_wrapper.py +1 -1
  243. alita_sdk/tools/report_portal/__init__.py +12 -8
  244. alita_sdk/tools/salesforce/__init__.py +10 -8
  245. alita_sdk/tools/servicenow/__init__.py +17 -15
  246. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  247. alita_sdk/tools/sharepoint/__init__.py +10 -7
  248. alita_sdk/tools/sharepoint/api_wrapper.py +129 -38
  249. alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
  250. alita_sdk/tools/sharepoint/utils.py +8 -2
  251. alita_sdk/tools/slack/__init__.py +10 -7
  252. alita_sdk/tools/slack/api_wrapper.py +2 -2
  253. alita_sdk/tools/sql/__init__.py +12 -9
  254. alita_sdk/tools/testio/__init__.py +10 -7
  255. alita_sdk/tools/testrail/__init__.py +11 -10
  256. alita_sdk/tools/testrail/api_wrapper.py +1 -1
  257. alita_sdk/tools/utils/__init__.py +9 -4
  258. alita_sdk/tools/utils/content_parser.py +103 -18
  259. alita_sdk/tools/utils/text_operations.py +410 -0
  260. alita_sdk/tools/utils/tool_prompts.py +79 -0
  261. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +30 -13
  262. alita_sdk/tools/xray/__init__.py +13 -9
  263. alita_sdk/tools/yagmail/__init__.py +9 -3
  264. alita_sdk/tools/zephyr/__init__.py +10 -7
  265. alita_sdk/tools/zephyr_enterprise/__init__.py +11 -7
  266. alita_sdk/tools/zephyr_essential/__init__.py +10 -7
  267. alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
  268. alita_sdk/tools/zephyr_essential/client.py +2 -2
  269. alita_sdk/tools/zephyr_scale/__init__.py +11 -8
  270. alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
  271. alita_sdk/tools/zephyr_squad/__init__.py +10 -7
  272. {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +154 -8
  273. alita_sdk-0.3.627.dist-info/RECORD +468 -0
  274. alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
  275. alita_sdk-0.3.379.dist-info/RECORD +0 -360
  276. {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
  277. {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
  278. {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,342 @@
1
+ """
2
+ Dynamic MCP Server Discovery Client.
3
+ Implements the MCP protocol for discovering tools from remote servers.
4
+ """
5
+
6
+ import asyncio
7
+ import json
8
+ import logging
9
+ import time
10
+ from dataclasses import dataclass, field
11
+ from typing import Dict, List, Optional, Any, Set
12
+ from urllib.parse import urlparse
13
+ import aiohttp
14
+ from datetime import datetime, timedelta
15
+
16
+ from ..models.mcp_models import McpConnectionConfig, McpToolMetadata
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ @dataclass
22
+ class McpServerInfo:
23
+ """Information about an MCP server."""
24
+ name: str
25
+ url: str
26
+ headers: Optional[Dict[str, str]] = None
27
+ last_discovery: Optional[datetime] = None
28
+ tools: List[McpToolMetadata] = field(default_factory=list)
29
+ status: str = "unknown" # unknown, online, offline, error
30
+ error: Optional[str] = None
31
+
32
+
33
+ class McpDiscoveryClient:
34
+ """
35
+ Client for dynamically discovering tools from MCP servers.
36
+ Implements the MCP protocol for tool discovery.
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ discovery_interval: int = 300, # 5 minutes
42
+ request_timeout: int = 30,
43
+ max_retries: int = 3,
44
+ cache_ttl: int = 600 # 10 minutes
45
+ ):
46
+ self.discovery_interval = discovery_interval
47
+ self.request_timeout = request_timeout
48
+ self.max_retries = max_retries
49
+ self.cache_ttl = cache_ttl
50
+
51
+ # Server registry
52
+ self.servers: Dict[str, McpServerInfo] = {}
53
+ self.session: Optional[aiohttp.ClientSession] = None
54
+
55
+ # Discovery state
56
+ self._discovery_task: Optional[asyncio.Task] = None
57
+ self._running = False
58
+
59
+ async def __aenter__(self):
60
+ """Async context manager entry."""
61
+ await self.start()
62
+ return self
63
+
64
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
65
+ """Async context manager exit."""
66
+ await self.stop()
67
+
68
+ async def start(self):
69
+ """Start the discovery client."""
70
+ if self._running:
71
+ return
72
+
73
+ self.session = aiohttp.ClientSession(
74
+ timeout=aiohttp.ClientTimeout(total=self.request_timeout)
75
+ )
76
+ self._running = True
77
+
78
+ # Start background discovery task
79
+ self._discovery_task = asyncio.create_task(self._discovery_loop())
80
+ logger.info("MCP Discovery Client started")
81
+
82
+ async def stop(self):
83
+ """Stop the discovery client."""
84
+ if not self._running:
85
+ return
86
+
87
+ self._running = False
88
+
89
+ if self._discovery_task:
90
+ self._discovery_task.cancel()
91
+ try:
92
+ await self._discovery_task
93
+ except asyncio.CancelledError:
94
+ pass
95
+
96
+ if self.session:
97
+ await self.session.close()
98
+
99
+ logger.info("MCP Discovery Client stopped")
100
+
101
+ def add_server(self, server_name: str, connection_config: McpConnectionConfig):
102
+ """Add an MCP server to discovery."""
103
+ server_info = McpServerInfo(
104
+ name=server_name,
105
+ url=connection_config.url,
106
+ headers=connection_config.headers
107
+ )
108
+ self.servers[server_name] = server_info
109
+ logger.info(f"Added MCP server for discovery: {server_name} at {connection_config.url}")
110
+
111
+ def remove_server(self, server_name: str):
112
+ """Remove an MCP server from discovery."""
113
+ if server_name in self.servers:
114
+ del self.servers[server_name]
115
+ logger.info(f"Removed MCP server from discovery: {server_name}")
116
+
117
+ async def discover_server_tools(self, server_name: str, force: bool = False) -> List[McpToolMetadata]:
118
+ """Discover tools from a specific MCP server."""
119
+ if server_name not in self.servers:
120
+ raise ValueError(f"Server {server_name} not registered for discovery")
121
+
122
+ server_info = self.servers[server_name]
123
+
124
+ # Check cache unless force refresh
125
+ if not force and self._is_cache_valid(server_info):
126
+ logger.debug(f"Using cached tools for server {server_name}")
127
+ return server_info.tools
128
+
129
+ try:
130
+ tools = await self._fetch_server_tools(server_info)
131
+ server_info.tools = tools
132
+ server_info.last_discovery = datetime.now()
133
+ server_info.status = "online"
134
+ server_info.error = None
135
+
136
+ logger.info(f"Discovered {len(tools)} tools from server {server_name}")
137
+ return tools
138
+
139
+ except Exception as e:
140
+ error_msg = f"Failed to discover tools from {server_name}: {e}"
141
+ logger.error(error_msg)
142
+ server_info.status = "error"
143
+ server_info.error = str(e)
144
+ return []
145
+
146
+ async def get_all_tools(self) -> Dict[str, List[McpToolMetadata]]:
147
+ """Get all discovered tools from all servers."""
148
+ all_tools = {}
149
+
150
+ for server_name in self.servers:
151
+ tools = await self.discover_server_tools(server_name)
152
+ all_tools[server_name] = tools
153
+
154
+ return all_tools
155
+
156
+ def get_server_status(self, server_name: str) -> Optional[McpServerInfo]:
157
+ """Get status information for a server."""
158
+ return self.servers.get(server_name)
159
+
160
+ def get_all_server_status(self) -> Dict[str, McpServerInfo]:
161
+ """Get status information for all servers."""
162
+ return self.servers.copy()
163
+
164
+ async def _discovery_loop(self):
165
+ """Background task for periodic tool discovery."""
166
+ while self._running:
167
+ try:
168
+ await self._perform_discovery()
169
+ await asyncio.sleep(self.discovery_interval)
170
+ except asyncio.CancelledError:
171
+ break
172
+ except Exception as e:
173
+ logger.error(f"Error in discovery loop: {e}")
174
+ await asyncio.sleep(60) # Wait before retrying
175
+
176
+ async def _perform_discovery(self):
177
+ """Perform discovery on all registered servers."""
178
+ if not self.servers:
179
+ return
180
+
181
+ discovery_tasks = [
182
+ self.discover_server_tools(server_name)
183
+ for server_name in self.servers
184
+ ]
185
+
186
+ results = await asyncio.gather(*discovery_tasks, return_exceptions=True)
187
+
188
+ # Log any errors
189
+ for i, result in enumerate(results):
190
+ if isinstance(result, Exception):
191
+ server_name = list(self.servers.keys())[i]
192
+ logger.error(f"Discovery failed for server {server_name}: {result}")
193
+
194
+ async def _fetch_server_tools(self, server_info: McpServerInfo) -> List[McpToolMetadata]:
195
+ """Fetch tools from an MCP server using HTTP requests."""
196
+ if not self.session:
197
+ raise RuntimeError("Discovery client not started")
198
+
199
+ # MCP protocol: list_tools request
200
+ mcp_request = {
201
+ "jsonrpc": "2.0",
202
+ "id": f"discover_{int(time.time())}",
203
+ "method": "tools/list",
204
+ "params": {}
205
+ }
206
+
207
+ headers = {"Content-Type": "application/json"}
208
+ if server_info.headers:
209
+ headers.update(server_info.headers)
210
+
211
+ async with self.session.post(
212
+ server_info.url,
213
+ json=mcp_request,
214
+ headers=headers
215
+ ) as response:
216
+
217
+ if response.status != 200:
218
+ raise Exception(f"HTTP {response.status}: {await response.text()}")
219
+
220
+ data = await response.json()
221
+
222
+ if "error" in data:
223
+ raise Exception(f"MCP Error: {data['error']}")
224
+
225
+ # Parse MCP response
226
+ tools_data = data.get("result", {}).get("tools", [])
227
+ tools = []
228
+
229
+ for tool_data in tools_data:
230
+ try:
231
+ tool_metadata = McpToolMetadata(
232
+ name=tool_data.get("name", ""),
233
+ description=tool_data.get("description", ""),
234
+ server=server_info.name,
235
+ input_schema=tool_data.get("inputSchema", {}),
236
+ enabled=True
237
+ )
238
+ tools.append(tool_metadata)
239
+ except Exception as e:
240
+ logger.warning(f"Failed to parse tool from {server_info.name}: {e}")
241
+
242
+ return tools
243
+
244
+ def _is_cache_valid(self, server_info: McpServerInfo) -> bool:
245
+ """Check if cached tools are still valid."""
246
+ if not server_info.last_discovery:
247
+ return False
248
+
249
+ cache_age = datetime.now() - server_info.last_discovery
250
+ return cache_age.total_seconds() < self.cache_ttl
251
+
252
+
253
+ class McpDiscoveryService:
254
+ """
255
+ High-level service for managing MCP server discovery.
256
+ Integrates with the existing toolkit system.
257
+ """
258
+
259
+ def __init__(self, discovery_client: Optional[McpDiscoveryClient] = None):
260
+ self.client = discovery_client or McpDiscoveryClient()
261
+ self._started = False
262
+
263
+ async def start(self):
264
+ """Start the discovery service."""
265
+ if not self._started:
266
+ await self.client.start()
267
+ self._started = True
268
+
269
+ async def stop(self):
270
+ """Stop the discovery service."""
271
+ if self._started:
272
+ await self.client.stop()
273
+ self._started = False
274
+
275
+ async def register_server(self, server_name: str, connection_config: McpConnectionConfig):
276
+ """Register an MCP server for discovery."""
277
+ self.client.add_server(server_name, connection_config)
278
+
279
+ # Perform immediate discovery
280
+ await self.client.discover_server_tools(server_name, force=True)
281
+
282
+ def unregister_server(self, server_name: str):
283
+ """Unregister an MCP server."""
284
+ self.client.remove_server(server_name)
285
+
286
+ async def get_server_tools(self, server_name: str) -> List[McpToolMetadata]:
287
+ """Get tools from a specific server."""
288
+ return await self.client.discover_server_tools(server_name)
289
+
290
+ async def get_all_available_tools(self) -> Dict[str, List[McpToolMetadata]]:
291
+ """Get all available tools from all registered servers."""
292
+ return await self.client.get_all_tools()
293
+
294
+ def get_server_health(self) -> Dict[str, Dict[str, Any]]:
295
+ """Get health status of all servers."""
296
+ status_info = {}
297
+
298
+ for name, server_info in self.client.get_all_server_status().items():
299
+ status_info[name] = {
300
+ "status": server_info.status,
301
+ "url": server_info.url,
302
+ "last_discovery": server_info.last_discovery.isoformat() if server_info.last_discovery else None,
303
+ "tool_count": len(server_info.tools),
304
+ "error": server_info.error
305
+ }
306
+
307
+ return status_info
308
+
309
+ async def refresh_server(self, server_name: str):
310
+ """Force refresh tools from a specific server."""
311
+ await self.client.discover_server_tools(server_name, force=True)
312
+
313
+ async def refresh_all_servers(self):
314
+ """Force refresh tools from all servers."""
315
+ for server_name in self.client.servers:
316
+ await self.client.discover_server_tools(server_name, force=True)
317
+
318
+
319
+ # Global discovery service instance
320
+ _discovery_service: Optional[McpDiscoveryService] = None
321
+
322
+
323
+ def get_discovery_service() -> McpDiscoveryService:
324
+ """Get the global MCP discovery service instance."""
325
+ global _discovery_service
326
+ if _discovery_service is None:
327
+ _discovery_service = McpDiscoveryService()
328
+ return _discovery_service
329
+
330
+
331
+ async def init_discovery_service():
332
+ """Initialize the global discovery service."""
333
+ service = get_discovery_service()
334
+ await service.start()
335
+
336
+
337
+ async def shutdown_discovery_service():
338
+ """Shutdown the global discovery service."""
339
+ global _discovery_service
340
+ if _discovery_service:
341
+ await _discovery_service.stop()
342
+ _discovery_service = None
@@ -0,0 +1,262 @@
1
+ """
2
+ MCP Manager - Unified interface for both static and dynamic MCP tool discovery.
3
+ Provides a single API that can work with both registry-based and live discovery.
4
+ """
5
+
6
+ import asyncio
7
+ import logging
8
+ from typing import Dict, List, Optional, Any, Union
9
+ from enum import Enum
10
+
11
+ from ..models.mcp_models import McpConnectionConfig, McpToolMetadata
12
+ from .mcp_discovery import McpDiscoveryService, get_discovery_service
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class DiscoveryMode(Enum):
18
+ """MCP discovery modes."""
19
+ STATIC = "static" # Use alita.get_mcp_toolkits() registry
20
+ DYNAMIC = "dynamic" # Live discovery from MCP servers
21
+ HYBRID = "hybrid" # Try dynamic first, fallback to static
22
+
23
+
24
+ class McpManager:
25
+ """
26
+ Unified manager for MCP tool discovery supporting multiple modes.
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ default_mode: DiscoveryMode = DiscoveryMode.DYNAMIC,
32
+ discovery_service: Optional[McpDiscoveryService] = None
33
+ ):
34
+ self.default_mode = default_mode
35
+ self.discovery_service = discovery_service or get_discovery_service()
36
+ self._static_fallback_enabled = True
37
+
38
+ async def discover_server_tools(
39
+ self,
40
+ server_name: str,
41
+ connection_config: Optional[McpConnectionConfig] = None,
42
+ alita_client=None,
43
+ mode: Optional[DiscoveryMode] = None,
44
+ **kwargs
45
+ ) -> List[McpToolMetadata]:
46
+ """
47
+ Discover tools from an MCP server using the specified mode.
48
+
49
+ Args:
50
+ server_name: Name of the MCP server
51
+ connection_config: Connection configuration (required for dynamic mode)
52
+ alita_client: Alita client (required for static mode)
53
+ mode: Discovery mode to use (defaults to manager's default)
54
+ **kwargs: Additional options
55
+
56
+ Returns:
57
+ List of discovered tool metadata
58
+ """
59
+ discovery_mode = mode or self.default_mode
60
+
61
+ if discovery_mode == DiscoveryMode.DYNAMIC:
62
+ return await self._discover_dynamic(server_name, connection_config)
63
+
64
+ elif discovery_mode == DiscoveryMode.STATIC:
65
+ return await self._discover_static(server_name, alita_client)
66
+
67
+ elif discovery_mode == DiscoveryMode.HYBRID:
68
+ return await self._discover_hybrid(server_name, connection_config, alita_client)
69
+
70
+ else:
71
+ raise ValueError(f"Unknown discovery mode: {discovery_mode}")
72
+
73
+ async def _discover_dynamic(
74
+ self,
75
+ server_name: str,
76
+ connection_config: Optional[McpConnectionConfig]
77
+ ) -> List[McpToolMetadata]:
78
+ """Discover tools using dynamic MCP protocol."""
79
+ if not connection_config:
80
+ raise ValueError("Connection configuration required for dynamic discovery")
81
+
82
+ try:
83
+ # Ensure discovery service is started
84
+ await self.discovery_service.start()
85
+
86
+ # Register and discover
87
+ await self.discovery_service.register_server(server_name, connection_config)
88
+ tools = await self.discovery_service.get_server_tools(server_name)
89
+
90
+ logger.info(f"Dynamic discovery found {len(tools)} tools from {server_name}")
91
+ return tools
92
+
93
+ except Exception as e:
94
+ logger.error(f"Dynamic discovery failed for {server_name}: {e}")
95
+ raise
96
+
97
+ async def _discover_static(
98
+ self,
99
+ server_name: str,
100
+ alita_client
101
+ ) -> List[McpToolMetadata]:
102
+ """Discover tools using static registry."""
103
+ if not alita_client or not hasattr(alita_client, 'get_mcp_toolkits'):
104
+ raise ValueError("Alita client with get_mcp_toolkits() required for static discovery")
105
+
106
+ try:
107
+ # Use existing registry approach
108
+ all_toolkits = alita_client.get_mcp_toolkits()
109
+ server_toolkit = next((tk for tk in all_toolkits if tk.get('name') == server_name), None)
110
+
111
+ if not server_toolkit:
112
+ logger.warning(f"Static registry: Server {server_name} not found")
113
+ return []
114
+
115
+ # Convert to metadata format
116
+ tools = []
117
+ for tool_info in server_toolkit.get('tools', []):
118
+ metadata = McpToolMetadata(
119
+ name=tool_info.get('name', ''),
120
+ description=tool_info.get('description', ''),
121
+ server=server_name,
122
+ input_schema=tool_info.get('inputSchema', {}),
123
+ enabled=True
124
+ )
125
+ tools.append(metadata)
126
+
127
+ logger.info(f"Static discovery found {len(tools)} tools from {server_name}")
128
+ return tools
129
+
130
+ except Exception as e:
131
+ logger.error(f"Static discovery failed for {server_name}: {e}")
132
+ raise
133
+
134
+ async def _discover_hybrid(
135
+ self,
136
+ server_name: str,
137
+ connection_config: Optional[McpConnectionConfig],
138
+ alita_client
139
+ ) -> List[McpToolMetadata]:
140
+ """Discover tools using hybrid approach (dynamic first, static fallback)."""
141
+
142
+ # Try dynamic discovery first
143
+ if connection_config:
144
+ try:
145
+ return await self._discover_dynamic(server_name, connection_config)
146
+ except Exception as e:
147
+ logger.warning(f"Dynamic discovery failed for {server_name}, trying static: {e}")
148
+
149
+ # Fallback to static discovery
150
+ if self._static_fallback_enabled and alita_client:
151
+ try:
152
+ return await self._discover_static(server_name, alita_client)
153
+ except Exception as e:
154
+ logger.error(f"Static fallback also failed for {server_name}: {e}")
155
+
156
+ logger.error(f"All discovery methods failed for {server_name}")
157
+ return []
158
+
159
+ async def get_server_health(
160
+ self,
161
+ server_name: Optional[str] = None
162
+ ) -> Dict[str, Any]:
163
+ """Get health information for servers."""
164
+ try:
165
+ if server_name:
166
+ # Get specific server health from discovery service
167
+ all_health = self.discovery_service.get_server_health()
168
+ return all_health.get(server_name, {"status": "unknown"})
169
+ else:
170
+ # Get all server health
171
+ return self.discovery_service.get_server_health()
172
+ except Exception as e:
173
+ logger.error(f"Failed to get server health: {e}")
174
+ return {"status": "error", "error": str(e)}
175
+
176
+ async def refresh_server(self, server_name: str):
177
+ """Force refresh a specific server's tools."""
178
+ try:
179
+ await self.discovery_service.refresh_server(server_name)
180
+ except Exception as e:
181
+ logger.error(f"Failed to refresh server {server_name}: {e}")
182
+
183
+ async def start(self):
184
+ """Start the MCP manager."""
185
+ await self.discovery_service.start()
186
+
187
+ async def stop(self):
188
+ """Stop the MCP manager."""
189
+ await self.discovery_service.stop()
190
+
191
+ def set_static_fallback(self, enabled: bool):
192
+ """Enable or disable static fallback in hybrid mode."""
193
+ self._static_fallback_enabled = enabled
194
+
195
+
196
+ # Global manager instance
197
+ _mcp_manager: Optional[McpManager] = None
198
+
199
+
200
+ def get_mcp_manager(mode: DiscoveryMode = DiscoveryMode.HYBRID) -> McpManager:
201
+ """Get the global MCP manager instance."""
202
+ global _mcp_manager
203
+ if _mcp_manager is None:
204
+ _mcp_manager = McpManager(default_mode=mode)
205
+ return _mcp_manager
206
+
207
+
208
+ async def discover_mcp_tools(
209
+ server_name: str,
210
+ connection_config: Optional[McpConnectionConfig] = None,
211
+ alita_client=None,
212
+ mode: Optional[DiscoveryMode] = None
213
+ ) -> List[McpToolMetadata]:
214
+ """
215
+ Convenience function for discovering MCP tools.
216
+
217
+ Args:
218
+ server_name: Name of the MCP server
219
+ connection_config: Connection config (for dynamic discovery)
220
+ alita_client: Alita client (for static discovery)
221
+ mode: Discovery mode (defaults to HYBRID)
222
+
223
+ Returns:
224
+ List of discovered tool metadata
225
+ """
226
+ manager = get_mcp_manager()
227
+ return await manager.discover_server_tools(
228
+ server_name=server_name,
229
+ connection_config=connection_config,
230
+ alita_client=alita_client,
231
+ mode=mode or DiscoveryMode.HYBRID
232
+ )
233
+
234
+
235
+ async def init_mcp_manager(mode: DiscoveryMode = DiscoveryMode.HYBRID):
236
+ """Initialize the global MCP manager."""
237
+ manager = get_mcp_manager(mode)
238
+ await manager.start()
239
+
240
+
241
+ async def shutdown_mcp_manager():
242
+ """Shutdown the global MCP manager."""
243
+ global _mcp_manager
244
+ if _mcp_manager:
245
+ await _mcp_manager.stop()
246
+ _mcp_manager = None
247
+
248
+
249
+ # Configuration helpers
250
+ def create_discovery_config(
251
+ mode: str = "hybrid",
252
+ discovery_interval: int = 300,
253
+ enable_static_fallback: bool = True,
254
+ **kwargs
255
+ ) -> Dict[str, Any]:
256
+ """Create a discovery configuration dictionary."""
257
+ return {
258
+ "discovery_mode": mode,
259
+ "discovery_interval": discovery_interval,
260
+ "enable_static_fallback": enable_static_fallback,
261
+ **kwargs
262
+ }
@@ -48,27 +48,6 @@ class SandboxArtifact:
48
48
  return f'{data['error']}. {data['content'] if data['content'] else ''}'
49
49
  detected = chardet.detect(data)
50
50
  return data
51
- # TODO: add proper handling for binary files (images, pdf, etc.) for sandbox
52
- # if detected['encoding'] is not None:
53
- # try:
54
- # return data.decode(detected['encoding'])
55
- # except Exception:
56
- # logger.error('Error while default encoding')
57
- # return parse_file_content(file_name=artifact_name,
58
- # file_content=data,
59
- # is_capture_image=is_capture_image,
60
- # page_number=page_number,
61
- # sheet_name=sheet_name,
62
- # excel_by_sheets=excel_by_sheets,
63
- # llm=llm)
64
- # else:
65
- # return parse_file_content(file_name=artifact_name,
66
- # file_content=data,
67
- # is_capture_image=is_capture_image,
68
- # page_number=page_number,
69
- # sheet_name=sheet_name,
70
- # excel_by_sheets=excel_by_sheets,
71
- # llm=llm)
72
51
 
73
52
  def delete(self, artifact_name: str, bucket_name=None):
74
53
  if not bucket_name:
@@ -143,6 +122,7 @@ class SandboxClient:
143
122
  self.configurations_url = f'{self.base_url}{self.api_path}/integrations/integrations/default/{self.project_id}?section=configurations&unsecret=true'
144
123
  self.ai_section_url = f'{self.base_url}{self.api_path}/integrations/integrations/default/{self.project_id}?section=ai'
145
124
  self.image_generation_url = f'{self.base_url}{self.llm_path}/images/generations'
125
+ self.auth_user_url = f'{self.base_url}{self.api_path}/auth/user'
146
126
  self.configurations: list = configurations or []
147
127
  self.model_timeout = kwargs.get('model_timeout', 120)
148
128
  self.model_image_generation = kwargs.get('model_image_generation')
@@ -363,3 +343,10 @@ class SandboxClient:
363
343
  url = f'{self.artifact_url}/{bucket_name}'
364
344
  data = requests.delete(url, headers=self.headers, verify=False, params={'filename': quote(artifact_name)})
365
345
  return self._process_requst(data)
346
+
347
+ def get_user_data(self) -> Dict[str, Any]:
348
+ resp = requests.get(self.auth_user_url, headers=self.headers, verify=False)
349
+ if resp.ok:
350
+ return resp.json()
351
+ logger.error(f'Failed to fetch user data: {resp.status_code} - {resp.text}')
352
+ raise ApiDetailsRequestError(f'Failed to fetch user data with status code {resp.status_code}.')