alita-sdk 0.3.257__py3-none-any.whl → 0.3.584__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of alita-sdk might be problematic. Click here for more details.

Files changed (281) 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 +155 -0
  6. alita_sdk/cli/agent_loader.py +215 -0
  7. alita_sdk/cli/agent_ui.py +228 -0
  8. alita_sdk/cli/agents.py +3794 -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/toolkit.py +327 -0
  23. alita_sdk/cli/toolkit_loader.py +85 -0
  24. alita_sdk/cli/tools/__init__.py +43 -0
  25. alita_sdk/cli/tools/approval.py +224 -0
  26. alita_sdk/cli/tools/filesystem.py +1751 -0
  27. alita_sdk/cli/tools/planning.py +389 -0
  28. alita_sdk/cli/tools/terminal.py +414 -0
  29. alita_sdk/community/__init__.py +72 -12
  30. alita_sdk/community/inventory/__init__.py +236 -0
  31. alita_sdk/community/inventory/config.py +257 -0
  32. alita_sdk/community/inventory/enrichment.py +2137 -0
  33. alita_sdk/community/inventory/extractors.py +1469 -0
  34. alita_sdk/community/inventory/ingestion.py +3172 -0
  35. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  36. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  37. alita_sdk/community/inventory/parsers/base.py +295 -0
  38. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  39. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  40. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  41. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  42. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  43. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  44. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  45. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  46. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  47. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  48. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  49. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  50. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  51. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  52. alita_sdk/community/inventory/patterns/loader.py +348 -0
  53. alita_sdk/community/inventory/patterns/registry.py +198 -0
  54. alita_sdk/community/inventory/presets.py +535 -0
  55. alita_sdk/community/inventory/retrieval.py +1403 -0
  56. alita_sdk/community/inventory/toolkit.py +173 -0
  57. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  58. alita_sdk/community/inventory/visualize.py +1370 -0
  59. alita_sdk/configurations/__init__.py +11 -0
  60. alita_sdk/configurations/ado.py +148 -2
  61. alita_sdk/configurations/azure_search.py +1 -1
  62. alita_sdk/configurations/bigquery.py +1 -1
  63. alita_sdk/configurations/bitbucket.py +94 -2
  64. alita_sdk/configurations/browser.py +18 -0
  65. alita_sdk/configurations/carrier.py +19 -0
  66. alita_sdk/configurations/confluence.py +130 -1
  67. alita_sdk/configurations/delta_lake.py +1 -1
  68. alita_sdk/configurations/figma.py +76 -5
  69. alita_sdk/configurations/github.py +65 -1
  70. alita_sdk/configurations/gitlab.py +81 -0
  71. alita_sdk/configurations/google_places.py +17 -0
  72. alita_sdk/configurations/jira.py +103 -0
  73. alita_sdk/configurations/openapi.py +323 -0
  74. alita_sdk/configurations/postman.py +1 -1
  75. alita_sdk/configurations/qtest.py +72 -3
  76. alita_sdk/configurations/report_portal.py +115 -0
  77. alita_sdk/configurations/salesforce.py +19 -0
  78. alita_sdk/configurations/service_now.py +1 -12
  79. alita_sdk/configurations/sharepoint.py +167 -0
  80. alita_sdk/configurations/sonar.py +18 -0
  81. alita_sdk/configurations/sql.py +20 -0
  82. alita_sdk/configurations/testio.py +101 -0
  83. alita_sdk/configurations/testrail.py +88 -0
  84. alita_sdk/configurations/xray.py +94 -1
  85. alita_sdk/configurations/zephyr_enterprise.py +94 -1
  86. alita_sdk/configurations/zephyr_essential.py +95 -0
  87. alita_sdk/runtime/clients/artifact.py +21 -4
  88. alita_sdk/runtime/clients/client.py +458 -67
  89. alita_sdk/runtime/clients/mcp_discovery.py +342 -0
  90. alita_sdk/runtime/clients/mcp_manager.py +262 -0
  91. alita_sdk/runtime/clients/sandbox_client.py +352 -0
  92. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  93. alita_sdk/runtime/langchain/assistant.py +183 -43
  94. alita_sdk/runtime/langchain/constants.py +647 -1
  95. alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
  96. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +209 -31
  97. alita_sdk/runtime/langchain/document_loaders/AlitaImageLoader.py +1 -1
  98. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  99. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -3
  100. alita_sdk/runtime/langchain/document_loaders/AlitaMarkdownLoader.py +66 -0
  101. alita_sdk/runtime/langchain/document_loaders/AlitaPDFLoader.py +79 -10
  102. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +52 -15
  103. alita_sdk/runtime/langchain/document_loaders/AlitaPythonLoader.py +9 -0
  104. alita_sdk/runtime/langchain/document_loaders/AlitaTableLoader.py +1 -4
  105. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +15 -2
  106. alita_sdk/runtime/langchain/document_loaders/ImageParser.py +30 -0
  107. alita_sdk/runtime/langchain/document_loaders/constants.py +189 -41
  108. alita_sdk/runtime/langchain/interfaces/llm_processor.py +4 -2
  109. alita_sdk/runtime/langchain/langraph_agent.py +493 -105
  110. alita_sdk/runtime/langchain/utils.py +118 -8
  111. alita_sdk/runtime/llms/preloaded.py +2 -6
  112. alita_sdk/runtime/models/mcp_models.py +61 -0
  113. alita_sdk/runtime/skills/__init__.py +91 -0
  114. alita_sdk/runtime/skills/callbacks.py +498 -0
  115. alita_sdk/runtime/skills/discovery.py +540 -0
  116. alita_sdk/runtime/skills/executor.py +610 -0
  117. alita_sdk/runtime/skills/input_builder.py +371 -0
  118. alita_sdk/runtime/skills/models.py +330 -0
  119. alita_sdk/runtime/skills/registry.py +355 -0
  120. alita_sdk/runtime/skills/skill_runner.py +330 -0
  121. alita_sdk/runtime/toolkits/__init__.py +28 -0
  122. alita_sdk/runtime/toolkits/application.py +14 -4
  123. alita_sdk/runtime/toolkits/artifact.py +25 -9
  124. alita_sdk/runtime/toolkits/datasource.py +13 -6
  125. alita_sdk/runtime/toolkits/mcp.py +782 -0
  126. alita_sdk/runtime/toolkits/planning.py +178 -0
  127. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  128. alita_sdk/runtime/toolkits/subgraph.py +11 -6
  129. alita_sdk/runtime/toolkits/tools.py +314 -70
  130. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  131. alita_sdk/runtime/tools/__init__.py +24 -0
  132. alita_sdk/runtime/tools/application.py +16 -4
  133. alita_sdk/runtime/tools/artifact.py +367 -33
  134. alita_sdk/runtime/tools/data_analysis.py +183 -0
  135. alita_sdk/runtime/tools/function.py +100 -4
  136. alita_sdk/runtime/tools/graph.py +81 -0
  137. alita_sdk/runtime/tools/image_generation.py +218 -0
  138. alita_sdk/runtime/tools/llm.py +1032 -177
  139. alita_sdk/runtime/tools/loop.py +3 -1
  140. alita_sdk/runtime/tools/loop_output.py +3 -1
  141. alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
  142. alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
  143. alita_sdk/runtime/tools/mcp_server_tool.py +3 -1
  144. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  145. alita_sdk/runtime/tools/planning/models.py +246 -0
  146. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  147. alita_sdk/runtime/tools/router.py +2 -1
  148. alita_sdk/runtime/tools/sandbox.py +375 -0
  149. alita_sdk/runtime/tools/skill_router.py +776 -0
  150. alita_sdk/runtime/tools/tool.py +3 -1
  151. alita_sdk/runtime/tools/vectorstore.py +69 -65
  152. alita_sdk/runtime/tools/vectorstore_base.py +163 -90
  153. alita_sdk/runtime/utils/AlitaCallback.py +137 -21
  154. alita_sdk/runtime/utils/constants.py +5 -1
  155. alita_sdk/runtime/utils/mcp_client.py +492 -0
  156. alita_sdk/runtime/utils/mcp_oauth.py +361 -0
  157. alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
  158. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  159. alita_sdk/runtime/utils/streamlit.py +41 -14
  160. alita_sdk/runtime/utils/toolkit_utils.py +28 -9
  161. alita_sdk/runtime/utils/utils.py +48 -0
  162. alita_sdk/tools/__init__.py +135 -37
  163. alita_sdk/tools/ado/__init__.py +2 -2
  164. alita_sdk/tools/ado/repos/__init__.py +16 -19
  165. alita_sdk/tools/ado/repos/repos_wrapper.py +12 -20
  166. alita_sdk/tools/ado/test_plan/__init__.py +27 -8
  167. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +56 -28
  168. alita_sdk/tools/ado/wiki/__init__.py +28 -12
  169. alita_sdk/tools/ado/wiki/ado_wrapper.py +114 -40
  170. alita_sdk/tools/ado/work_item/__init__.py +28 -12
  171. alita_sdk/tools/ado/work_item/ado_wrapper.py +95 -11
  172. alita_sdk/tools/advanced_jira_mining/__init__.py +13 -8
  173. alita_sdk/tools/aws/delta_lake/__init__.py +15 -11
  174. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  175. alita_sdk/tools/azure_ai/search/__init__.py +14 -8
  176. alita_sdk/tools/base/tool.py +5 -1
  177. alita_sdk/tools/base_indexer_toolkit.py +454 -110
  178. alita_sdk/tools/bitbucket/__init__.py +28 -19
  179. alita_sdk/tools/bitbucket/api_wrapper.py +285 -27
  180. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +5 -5
  181. alita_sdk/tools/browser/__init__.py +41 -16
  182. alita_sdk/tools/browser/crawler.py +3 -1
  183. alita_sdk/tools/browser/utils.py +15 -6
  184. alita_sdk/tools/carrier/__init__.py +18 -17
  185. alita_sdk/tools/carrier/backend_reports_tool.py +8 -4
  186. alita_sdk/tools/carrier/excel_reporter.py +8 -4
  187. alita_sdk/tools/chunkers/__init__.py +3 -1
  188. alita_sdk/tools/chunkers/code/codeparser.py +1 -1
  189. alita_sdk/tools/chunkers/sematic/json_chunker.py +2 -1
  190. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  191. alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
  192. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  193. alita_sdk/tools/cloud/aws/__init__.py +12 -7
  194. alita_sdk/tools/cloud/azure/__init__.py +12 -7
  195. alita_sdk/tools/cloud/gcp/__init__.py +12 -7
  196. alita_sdk/tools/cloud/k8s/__init__.py +12 -7
  197. alita_sdk/tools/code/linter/__init__.py +10 -8
  198. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  199. alita_sdk/tools/code/sonar/__init__.py +21 -13
  200. alita_sdk/tools/code_indexer_toolkit.py +199 -0
  201. alita_sdk/tools/confluence/__init__.py +22 -14
  202. alita_sdk/tools/confluence/api_wrapper.py +197 -58
  203. alita_sdk/tools/confluence/loader.py +14 -2
  204. alita_sdk/tools/custom_open_api/__init__.py +12 -5
  205. alita_sdk/tools/elastic/__init__.py +11 -8
  206. alita_sdk/tools/elitea_base.py +546 -64
  207. alita_sdk/tools/figma/__init__.py +60 -11
  208. alita_sdk/tools/figma/api_wrapper.py +1400 -167
  209. alita_sdk/tools/figma/figma_client.py +73 -0
  210. alita_sdk/tools/figma/toon_tools.py +2748 -0
  211. alita_sdk/tools/github/__init__.py +18 -17
  212. alita_sdk/tools/github/api_wrapper.py +9 -26
  213. alita_sdk/tools/github/github_client.py +81 -12
  214. alita_sdk/tools/github/schemas.py +2 -1
  215. alita_sdk/tools/github/tool.py +5 -1
  216. alita_sdk/tools/gitlab/__init__.py +19 -13
  217. alita_sdk/tools/gitlab/api_wrapper.py +256 -80
  218. alita_sdk/tools/gitlab_org/__init__.py +14 -10
  219. alita_sdk/tools/google/bigquery/__init__.py +14 -13
  220. alita_sdk/tools/google/bigquery/tool.py +5 -1
  221. alita_sdk/tools/google_places/__init__.py +21 -11
  222. alita_sdk/tools/jira/__init__.py +22 -11
  223. alita_sdk/tools/jira/api_wrapper.py +315 -168
  224. alita_sdk/tools/keycloak/__init__.py +11 -8
  225. alita_sdk/tools/localgit/__init__.py +9 -3
  226. alita_sdk/tools/localgit/local_git.py +62 -54
  227. alita_sdk/tools/localgit/tool.py +5 -1
  228. alita_sdk/tools/memory/__init__.py +38 -14
  229. alita_sdk/tools/non_code_indexer_toolkit.py +7 -2
  230. alita_sdk/tools/ocr/__init__.py +11 -8
  231. alita_sdk/tools/openapi/__init__.py +491 -106
  232. alita_sdk/tools/openapi/api_wrapper.py +1357 -0
  233. alita_sdk/tools/openapi/tool.py +20 -0
  234. alita_sdk/tools/pandas/__init__.py +20 -12
  235. alita_sdk/tools/pandas/api_wrapper.py +40 -45
  236. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  237. alita_sdk/tools/postman/__init__.py +11 -11
  238. alita_sdk/tools/postman/api_wrapper.py +19 -8
  239. alita_sdk/tools/postman/postman_analysis.py +8 -1
  240. alita_sdk/tools/pptx/__init__.py +11 -10
  241. alita_sdk/tools/qtest/__init__.py +22 -14
  242. alita_sdk/tools/qtest/api_wrapper.py +1784 -88
  243. alita_sdk/tools/rally/__init__.py +13 -10
  244. alita_sdk/tools/report_portal/__init__.py +23 -16
  245. alita_sdk/tools/salesforce/__init__.py +22 -16
  246. alita_sdk/tools/servicenow/__init__.py +21 -16
  247. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  248. alita_sdk/tools/sharepoint/__init__.py +17 -14
  249. alita_sdk/tools/sharepoint/api_wrapper.py +179 -39
  250. alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
  251. alita_sdk/tools/sharepoint/utils.py +8 -2
  252. alita_sdk/tools/slack/__init__.py +13 -8
  253. alita_sdk/tools/sql/__init__.py +22 -19
  254. alita_sdk/tools/sql/api_wrapper.py +71 -23
  255. alita_sdk/tools/testio/__init__.py +21 -13
  256. alita_sdk/tools/testrail/__init__.py +13 -11
  257. alita_sdk/tools/testrail/api_wrapper.py +214 -46
  258. alita_sdk/tools/utils/__init__.py +28 -4
  259. alita_sdk/tools/utils/content_parser.py +241 -55
  260. alita_sdk/tools/utils/text_operations.py +254 -0
  261. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +83 -27
  262. alita_sdk/tools/xray/__init__.py +18 -14
  263. alita_sdk/tools/xray/api_wrapper.py +58 -113
  264. alita_sdk/tools/yagmail/__init__.py +9 -3
  265. alita_sdk/tools/zephyr/__init__.py +12 -7
  266. alita_sdk/tools/zephyr_enterprise/__init__.py +16 -9
  267. alita_sdk/tools/zephyr_enterprise/api_wrapper.py +30 -15
  268. alita_sdk/tools/zephyr_essential/__init__.py +16 -10
  269. alita_sdk/tools/zephyr_essential/api_wrapper.py +297 -54
  270. alita_sdk/tools/zephyr_essential/client.py +6 -4
  271. alita_sdk/tools/zephyr_scale/__init__.py +13 -8
  272. alita_sdk/tools/zephyr_scale/api_wrapper.py +39 -31
  273. alita_sdk/tools/zephyr_squad/__init__.py +12 -7
  274. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.dist-info}/METADATA +184 -37
  275. alita_sdk-0.3.584.dist-info/RECORD +452 -0
  276. alita_sdk-0.3.584.dist-info/entry_points.txt +2 -0
  277. alita_sdk/tools/bitbucket/tools.py +0 -304
  278. alita_sdk-0.3.257.dist-info/RECORD +0 -343
  279. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.dist-info}/WHEEL +0 -0
  280. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.dist-info}/licenses/LICENSE +0 -0
  281. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,315 @@
1
+ """
2
+ MCP (Model Context Protocol) server integration.
3
+
4
+ Handles loading MCP server configurations and connecting to MCP servers
5
+ using langchain-mcp-adapters to load tools with persistent sessions.
6
+
7
+ Requires: pip install langchain-mcp-adapters
8
+ Documentation: https://docs.langchain.com/oss/python/langchain/mcp
9
+ Issue Reference: https://github.com/langchain-ai/langchain-mcp-adapters/issues/178
10
+ """
11
+
12
+ import json
13
+ import logging
14
+ from pathlib import Path
15
+ from typing import Dict, Any, List, Optional
16
+ from rich.console import Console
17
+
18
+ from .config import substitute_env_vars
19
+
20
+ console = Console()
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ def load_mcp_config(file_path: str) -> Dict[str, Any]:
25
+ """Load MCP configuration from JSON file (VSCode/Copilot format).
26
+
27
+ Args:
28
+ file_path: Path to mcp.json configuration file
29
+
30
+ Returns:
31
+ Dictionary with mcpServers configuration, or empty dict if file doesn't exist
32
+ """
33
+ path = Path(file_path)
34
+
35
+ if not path.exists():
36
+ # Gracefully handle missing file - MCP is optional
37
+ return {}
38
+
39
+ try:
40
+ with open(path) as f:
41
+ content = f.read()
42
+
43
+ # Apply environment variable substitution
44
+ content = substitute_env_vars(content)
45
+ config = json.loads(content)
46
+
47
+ # Validate structure
48
+ if 'mcpServers' not in config:
49
+ console.print(f"[yellow]Warning: {file_path} missing 'mcpServers' key[/yellow]")
50
+ return {}
51
+
52
+ return config
53
+ except json.JSONDecodeError as e:
54
+ console.print(f"[red]Error parsing {file_path}:[/red] {e}")
55
+ return {}
56
+ except Exception as e:
57
+ console.print(f"[red]Error loading {file_path}:[/red] {e}")
58
+ return {}
59
+
60
+
61
+ def load_mcp_tools(agent_def: Dict[str, Any], mcp_config_path: str) -> List[Dict[str, Any]]:
62
+ """Load MCP tools from agent definition with tool-level filtering.
63
+
64
+ This function creates MCP server configuration objects that will be processed
65
+ by the runtime layer using langchain-mcp-adapters to connect to MCP servers
66
+ and load their tools.
67
+
68
+ The actual connection happens in create_mcp_client() which uses:
69
+ - langchain_mcp_adapters.client.MultiServerMCPClient for connection
70
+ - langchain_mcp_adapters.tools.load_mcp_tools for tool loading
71
+
72
+ Args:
73
+ agent_def: Agent definition dictionary containing mcps list
74
+ mcp_config_path: Path to mcp.json configuration file (workspace-level)
75
+
76
+ Returns:
77
+ List of MCP server configurations that will be used by MultiServerMCPClient
78
+ to connect to servers and load tools with filtering applied.
79
+ """
80
+ import fnmatch
81
+
82
+ toolkit_configs = []
83
+
84
+ # Get MCP configuration
85
+ mcps = agent_def.get('mcps', [])
86
+ if not mcps:
87
+ return toolkit_configs
88
+
89
+ # Load mcp.json config file from workspace
90
+ mcp_config = load_mcp_config(mcp_config_path)
91
+ mcp_servers = mcp_config.get('mcpServers', {})
92
+
93
+ # Process each MCP entry
94
+ for mcp_entry in mcps:
95
+ # Handle both string and object formats
96
+ if isinstance(mcp_entry, str):
97
+ server_name = mcp_entry
98
+ agent_selected_tools = None
99
+ agent_excluded_tools = None
100
+ elif isinstance(mcp_entry, dict):
101
+ server_name = mcp_entry.get('name')
102
+ agent_selected_tools = mcp_entry.get('selected_tools')
103
+ agent_excluded_tools = mcp_entry.get('excluded_tools')
104
+ else:
105
+ console.print(f"[yellow]Warning: Invalid MCP entry format: {mcp_entry}[/yellow]")
106
+ continue
107
+
108
+ if not server_name:
109
+ console.print(f"[yellow]Warning: MCP entry missing 'name': {mcp_entry}[/yellow]")
110
+ continue
111
+
112
+ # Get server configuration
113
+ server_config = mcp_servers.get(server_name)
114
+ if not server_config:
115
+ console.print(f"[yellow]Warning: MCP server '{server_name}' not found in MCP configuration[/yellow]")
116
+ continue
117
+
118
+ # Determine tool selection (agent overrides server defaults)
119
+ server_selected_tools = server_config.get('selected_tools')
120
+ server_excluded_tools = server_config.get('excluded_tools')
121
+
122
+ # Agent overrides take precedence
123
+ final_selected_tools = agent_selected_tools if agent_selected_tools is not None else server_selected_tools
124
+ final_excluded_tools = agent_excluded_tools if agent_excluded_tools is not None else server_excluded_tools
125
+
126
+ # Get connection details
127
+ server_type = server_config.get('type') # VSCode format: "stdio" or "streamable_http"
128
+ server_url = server_config.get('url')
129
+ server_command = server_config.get('command')
130
+ server_args = server_config.get('args', [])
131
+ server_env = server_config.get('env', {})
132
+ stateful = server_config.get('stateful', False)
133
+
134
+ # Build MCP server config for langchain-mcp-adapters
135
+ # Support both VSCode format (type: "stdio") and our format (command: "...")
136
+ if server_type == 'stdio' or server_command:
137
+ # stdio transport (subprocess)
138
+ if not server_command:
139
+ console.print(f"[red]Error: MCP server '{server_name}' has type 'stdio' but no 'command'[/red]")
140
+ continue
141
+
142
+ console.print(f"[yellow]Note: MCP server '{server_name}' uses command/stdio connection[/yellow]")
143
+ mcp_server_config = {
144
+ 'transport': 'stdio',
145
+ 'command': server_command,
146
+ 'args': server_args or []
147
+ }
148
+ elif server_type == 'streamable_http' or server_url:
149
+ # HTTP-based transport
150
+ if not server_url:
151
+ console.print(f"[red]Error: MCP server '{server_name}' has type 'streamable_http' but no 'url'[/red]")
152
+ continue
153
+
154
+ mcp_server_config = {
155
+ 'transport': 'streamable_http',
156
+ 'url': server_url
157
+ }
158
+ else:
159
+ console.print(f"[red]Error: MCP server '{server_name}' has neither 'type'/'url' nor 'command'[/red]")
160
+ continue
161
+
162
+ # Add environment variables if specified
163
+ if server_env:
164
+ mcp_server_config['env'] = server_env
165
+
166
+ # Wrap in toolkit config format
167
+ toolkit_config = {
168
+ 'toolkit_type': 'mcp',
169
+ 'name': server_name,
170
+ 'mcp_server_config': mcp_server_config,
171
+ }
172
+
173
+ # Add tool filtering if specified
174
+ if final_selected_tools is not None:
175
+ toolkit_config['selected_tools'] = final_selected_tools
176
+ if final_excluded_tools is not None:
177
+ toolkit_config['excluded_tools'] = final_excluded_tools
178
+
179
+ toolkit_configs.append(toolkit_config)
180
+
181
+ # Display loaded tools info
182
+ if final_selected_tools:
183
+ tools_display = ', '.join(final_selected_tools)
184
+ console.print(f"✓ Loaded MCP server: [cyan]{server_name}[/cyan] (selected: {tools_display})")
185
+ elif final_excluded_tools:
186
+ console.print(f"✓ Loaded MCP server: [cyan]{server_name}[/cyan] (excluded: {', '.join(final_excluded_tools)})")
187
+ else:
188
+ console.print(f"✓ Loaded MCP server: [cyan]{server_name}[/cyan] (all tools)")
189
+
190
+ return toolkit_configs
191
+
192
+
193
+ class MCPSessionManager:
194
+ """Manages persistent MCP sessions for stateful tools like Playwright.
195
+
196
+ Holds MCP client and session context managers alive for the entire agent lifetime.
197
+ This is critical for stateful MCP servers that maintain state across tool calls
198
+ (e.g., Playwright browser sessions).
199
+
200
+ See: https://github.com/langchain-ai/langchain-mcp-adapters/issues/178
201
+ """
202
+ def __init__(self):
203
+ self.client = None
204
+ self.sessions = {}
205
+ self._session_contexts = {}
206
+
207
+ def __del__(self):
208
+ """Cleanup sessions when manager is garbage collected."""
209
+ # Best effort cleanup - sessions will be closed when process exits anyway
210
+ pass
211
+
212
+
213
+ async def load_mcp_tools_async(mcp_toolkit_configs: List[Dict[str, Any]]):
214
+ """
215
+ Load actual MCP tools from servers with persistent sessions.
216
+
217
+ IMPORTANT: This function returns an MCPSessionManager that MUST be kept alive
218
+ for the entire agent session to maintain stateful MCP server state (e.g., browser).
219
+
220
+ Args:
221
+ mcp_toolkit_configs: List of MCP toolkit configurations with transport details
222
+
223
+ Returns:
224
+ Tuple of (session_manager, list_of_tools) where:
225
+ - session_manager: MCPSessionManager that holds client and persistent sessions
226
+ - list_of_tools: LangChain async tools from MCP that use persistent sessions
227
+ """
228
+ if not mcp_toolkit_configs:
229
+ return None, []
230
+
231
+ try:
232
+ from langchain_mcp_adapters.client import MultiServerMCPClient
233
+ from langchain_mcp_adapters.tools import load_mcp_tools
234
+ except ImportError:
235
+ console.print("[yellow]Warning: langchain-mcp-adapters not installed. MCP tools will not be available.[/yellow]")
236
+ console.print("[yellow]Install with: pip install langchain-mcp-adapters[/yellow]")
237
+ return None, []
238
+
239
+ # Build server configs for MultiServerMCPClient
240
+ server_configs = {}
241
+ for config in mcp_toolkit_configs:
242
+ server_name = config.get('name', 'unknown')
243
+ mcp_server_config = config.get('mcp_server_config', {})
244
+ transport = mcp_server_config.get('transport', 'stdio')
245
+
246
+ if transport == 'stdio':
247
+ command = mcp_server_config.get('command')
248
+ args = mcp_server_config.get('args', [])
249
+ env = mcp_server_config.get('env')
250
+
251
+ # Ensure command is a string
252
+ if isinstance(command, list):
253
+ command = command[0] if command else None
254
+
255
+ # Build config dict - only include env if it's set
256
+ config_dict = {
257
+ 'transport': 'stdio',
258
+ 'command': command,
259
+ 'args': args
260
+ }
261
+ if env:
262
+ config_dict['env'] = env
263
+
264
+ server_configs[server_name] = config_dict
265
+ console.print(f"[dim]MCP server {server_name}: {command} {' '.join(args)}[/dim]")
266
+
267
+ elif transport == 'streamable_http':
268
+ server_configs[server_name] = {
269
+ 'transport': 'streamable_http',
270
+ 'url': mcp_server_config.get('url'),
271
+ 'headers': mcp_server_config.get('headers')
272
+ }
273
+
274
+ if not server_configs:
275
+ return None, []
276
+
277
+ try:
278
+ # Create session manager to hold everything alive
279
+ manager = MCPSessionManager()
280
+ manager.client = MultiServerMCPClient(server_configs)
281
+
282
+ # Create PERSISTENT sessions for each server
283
+ # This keeps the MCP server subprocess and state alive for stateful tools
284
+ all_tools = []
285
+
286
+ for server_name in server_configs.keys():
287
+ # Enter the session context and keep it alive for entire agent lifetime
288
+ session_context = manager.client.session(server_name)
289
+ session = await session_context.__aenter__()
290
+
291
+ # Store both for cleanup and reference
292
+ manager.sessions[server_name] = session
293
+ manager._session_contexts[server_name] = session_context
294
+
295
+ # Load tools using the persistent session
296
+ tools = await load_mcp_tools(
297
+ session,
298
+ connection=manager.client.connections[server_name],
299
+ server_name=server_name
300
+ )
301
+ all_tools.extend(tools)
302
+
303
+ console.print(f"[dim]Created persistent session for {server_name}: {len(tools)} tools[/dim]")
304
+
305
+ console.print(f"✓ Loaded {len(all_tools)} MCP tools total from {len(mcp_toolkit_configs)} servers with persistent sessions")
306
+
307
+ return manager, all_tools
308
+
309
+ except ImportError:
310
+ # Already handled above
311
+ raise
312
+ except Exception as e:
313
+ logger.exception(f"Failed to create MCP client and sessions")
314
+ console.print(f"[red]Error: Failed to load MCP tools: {e}[/red]")
315
+ return None, []
@@ -0,0 +1,327 @@
1
+ """
2
+ Toolkit testing commands for Alita CLI.
3
+
4
+ Provides commands to list, inspect, and test toolkits directly from the command line.
5
+ """
6
+
7
+ import click
8
+ import json
9
+ import logging
10
+ from typing import Optional, Dict, Any
11
+
12
+ from .cli import get_client
13
+ from .toolkit_loader import load_toolkit_config
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @click.group()
19
+ def toolkit():
20
+ """Toolkit testing commands."""
21
+ pass
22
+
23
+
24
+ @toolkit.command('list')
25
+ @click.option('--failed', is_flag=True, help='Show failed imports')
26
+ @click.pass_context
27
+ def toolkit_list(ctx, failed: bool):
28
+ """List all available toolkits."""
29
+ formatter = ctx.obj['formatter']
30
+
31
+ try:
32
+ # Import toolkit registry
33
+ from alita_sdk.tools import AVAILABLE_TOOLS, AVAILABLE_TOOLKITS, FAILED_IMPORTS
34
+
35
+ if failed:
36
+ # Show failed imports
37
+ if FAILED_IMPORTS:
38
+ click.echo("\nFailed toolkit imports:\n")
39
+ for name, error in FAILED_IMPORTS.items():
40
+ click.echo(f" - {name}: {error}")
41
+ click.echo(f"\nTotal failed: {len(FAILED_IMPORTS)}")
42
+ else:
43
+ click.echo("\nNo failed imports")
44
+ return
45
+
46
+ # Build toolkit list
47
+ toolkits = []
48
+ for name, toolkit_dict in AVAILABLE_TOOLS.items():
49
+ toolkit_class_name = None
50
+ if 'toolkit_class' in toolkit_dict:
51
+ toolkit_class_name = toolkit_dict['toolkit_class'].__name__
52
+
53
+ toolkits.append({
54
+ 'name': name,
55
+ 'class_name': toolkit_class_name,
56
+ 'has_get_tools': 'get_tools' in toolkit_dict
57
+ })
58
+
59
+ # Format and display
60
+ output = formatter.format_toolkit_list(toolkits)
61
+ click.echo(output)
62
+
63
+ except Exception as e:
64
+ logger.exception("Failed to list toolkits")
65
+ click.echo(formatter.format_error(str(e)), err=True)
66
+ raise click.Abort()
67
+
68
+
69
+ @toolkit.command('schema')
70
+ @click.argument('toolkit_name')
71
+ @click.pass_context
72
+ def toolkit_schema(ctx, toolkit_name: str):
73
+ """
74
+ Show configuration schema for a toolkit.
75
+
76
+ TOOLKIT_NAME: Name of the toolkit (e.g., 'jira', 'github', 'confluence')
77
+ """
78
+ formatter = ctx.obj['formatter']
79
+
80
+ try:
81
+ # Import toolkit registry
82
+ from alita_sdk.tools import AVAILABLE_TOOLKITS
83
+
84
+ # Find toolkit class
85
+ toolkit_class = None
86
+ for name, cls in AVAILABLE_TOOLKITS.items():
87
+ if name.lower().replace('toolkit', '').replace('alita', '').strip() == toolkit_name.lower():
88
+ toolkit_class = cls
89
+ break
90
+
91
+ if not toolkit_class:
92
+ # Try direct match
93
+ for name, cls in AVAILABLE_TOOLKITS.items():
94
+ if toolkit_name.lower() in name.lower():
95
+ toolkit_class = cls
96
+ break
97
+
98
+ if not toolkit_class:
99
+ available = [name.lower().replace('toolkit', '').replace('alita', '').strip()
100
+ for name in AVAILABLE_TOOLKITS.keys()]
101
+ raise click.ClickException(
102
+ f"Toolkit '{toolkit_name}' not found.\n"
103
+ f"Available toolkits: {', '.join(sorted(set(available)))}"
104
+ )
105
+
106
+ # Get schema
107
+ schema_model = toolkit_class.toolkit_config_schema()
108
+ schema = schema_model.model_json_schema()
109
+
110
+ # Format and display
111
+ if formatter.__class__.__name__ == 'JSONFormatter':
112
+ output = formatter.format_toolkit_schema(toolkit_name, schema)
113
+ else:
114
+ output = formatter.format_toolkit_schema(toolkit_name, schema)
115
+
116
+ click.echo(output)
117
+
118
+ except click.ClickException:
119
+ raise
120
+ except Exception as e:
121
+ logger.exception(f"Failed to get schema for toolkit '{toolkit_name}'")
122
+ click.echo(formatter.format_error(str(e)), err=True)
123
+ raise click.Abort()
124
+
125
+
126
+ @toolkit.command('test')
127
+ @click.argument('toolkit_type')
128
+ @click.option('--tool', required=True, help='Tool name to execute')
129
+ @click.option('--config', 'config_file', type=click.File('r'),
130
+ help='Toolkit configuration JSON file')
131
+ @click.option('--params', type=click.File('r'),
132
+ help='Tool parameters JSON file')
133
+ @click.option('--param', multiple=True,
134
+ help='Tool parameter as key=value (can be used multiple times)')
135
+ @click.option('--llm-model', default='gpt-4o-mini',
136
+ help='LLM model to use (default: gpt-4o-mini)')
137
+ @click.option('--temperature', default=0.1, type=float,
138
+ help='LLM temperature (default: 0.1)')
139
+ @click.option('--max-tokens', default=1000, type=int,
140
+ help='LLM max tokens (default: 1000)')
141
+ @click.pass_context
142
+ def toolkit_test(ctx, toolkit_type: str, tool: str, config_file, params,
143
+ param, llm_model: str, temperature: float, max_tokens: int):
144
+ """Test a specific tool from a toolkit.
145
+
146
+ TOOLKIT_TYPE: Type of toolkit (e.g., 'jira', 'github', 'confluence')
147
+
148
+ \b
149
+ Examples:
150
+ alita toolkit test jira --tool get_issue --config jira.json --params params.json
151
+ alita toolkit test jira --tool get_issue --config jira.json --param issue_key=PROJ-123
152
+ alita -o json toolkit test github --tool get_issue --config github.json
153
+ """
154
+ formatter = ctx.obj['formatter']
155
+ client = get_client(ctx)
156
+
157
+ try:
158
+ # Load toolkit configuration
159
+ toolkit_config = {}
160
+ if config_file:
161
+ toolkit_config = load_toolkit_config(config_file.name)
162
+ logger.debug(f"Loaded toolkit config from {config_file.name}")
163
+
164
+ # Add the tool to selected_tools in the config
165
+ if 'selected_tools' not in toolkit_config:
166
+ toolkit_config['selected_tools'] = []
167
+ if tool not in toolkit_config['selected_tools']:
168
+ toolkit_config['selected_tools'].append(tool)
169
+
170
+ # Load tool parameters
171
+ tool_params = {}
172
+ if params:
173
+ tool_params = json.load(params)
174
+ logger.debug(f"Loaded tool params from {params.name}")
175
+
176
+ # Parse inline parameters
177
+ if param:
178
+ for param_pair in param:
179
+ if '=' not in param_pair:
180
+ raise click.ClickException(
181
+ f"Invalid parameter format: '{param_pair}'. "
182
+ "Use --param key=value"
183
+ )
184
+ key, value = param_pair.split('=', 1)
185
+
186
+ # Try to parse as JSON for complex values
187
+ try:
188
+ tool_params[key] = json.loads(value)
189
+ except json.JSONDecodeError:
190
+ tool_params[key] = value
191
+
192
+ logger.debug(f"Parsed {len(param)} inline parameters")
193
+
194
+ # Prepare full toolkit configuration
195
+ full_config = {
196
+ 'toolkit_name': toolkit_type,
197
+ 'type': toolkit_type,
198
+ 'settings': toolkit_config
199
+ }
200
+
201
+ # LLM configuration
202
+ llm_config = {
203
+ 'temperature': temperature,
204
+ 'max_tokens': max_tokens,
205
+ }
206
+
207
+ # Execute test
208
+ logger.info(f"Testing tool '{tool}' from toolkit '{toolkit_type}'")
209
+ result = client.test_toolkit_tool(
210
+ toolkit_config=full_config,
211
+ tool_name=tool,
212
+ tool_params=tool_params,
213
+ llm_model=llm_model,
214
+ llm_config=llm_config
215
+ )
216
+
217
+ # Format and display result
218
+ output = formatter.format_toolkit_result(result)
219
+ click.echo(output)
220
+
221
+ # Exit with error code if test failed
222
+ if not result.get('success', False):
223
+ raise click.Abort()
224
+
225
+ except click.ClickException:
226
+ raise
227
+ except Exception as e:
228
+ logger.exception("Failed to execute toolkit test")
229
+ click.echo(formatter.format_error(str(e)), err=True)
230
+ raise click.Abort()
231
+
232
+
233
+ @toolkit.command('tools')
234
+ @click.argument('toolkit_type')
235
+ @click.option('--config', 'config_file', type=click.File('r'),
236
+ help='Toolkit configuration JSON file (required for some toolkits)')
237
+ @click.pass_context
238
+ def toolkit_tools(ctx, toolkit_type: str, config_file):
239
+ """
240
+ List available tools for a specific toolkit.
241
+
242
+ TOOLKIT_TYPE: Type of toolkit (e.g., 'jira', 'github', 'confluence')
243
+
244
+ Some toolkits require configuration to determine available tools.
245
+ Use --config to provide configuration if needed.
246
+ """
247
+ formatter = ctx.obj['formatter']
248
+ client = get_client(ctx)
249
+
250
+ try:
251
+ # Load toolkit configuration if provided
252
+ toolkit_config = {}
253
+ if config_file:
254
+ toolkit_config = load_toolkit_config(config_file.name)
255
+
256
+ # Import and instantiate toolkit
257
+ from alita_sdk.tools import AVAILABLE_TOOLS
258
+
259
+ if toolkit_type not in AVAILABLE_TOOLS:
260
+ raise click.ClickException(
261
+ f"Toolkit '{toolkit_type}' not found. "
262
+ f"Use 'alita-cli toolkit list' to see available toolkits."
263
+ )
264
+
265
+ toolkit_entry = AVAILABLE_TOOLS[toolkit_type]
266
+
267
+ if 'toolkit_class' not in toolkit_entry:
268
+ raise click.ClickException(
269
+ f"Toolkit '{toolkit_type}' does not support tool listing"
270
+ )
271
+
272
+ # Get toolkit class and instantiate
273
+ toolkit_class = toolkit_entry['toolkit_class']
274
+
275
+ # Create minimal configuration
276
+ full_config = {
277
+ 'toolkit_name': toolkit_type,
278
+ 'type': toolkit_type,
279
+ 'settings': toolkit_config
280
+ }
281
+
282
+ # Try to get available tools via API wrapper
283
+ try:
284
+ # Instantiate API wrapper if possible
285
+ api_wrapper_class = None
286
+
287
+ # Find API wrapper class by inspecting toolkit
288
+ import inspect
289
+ for name, obj in inspect.getmembers(toolkit_class):
290
+ if inspect.isclass(obj) and 'ApiWrapper' in obj.__name__:
291
+ api_wrapper_class = obj
292
+ break
293
+
294
+ if api_wrapper_class:
295
+ try:
296
+ api_wrapper = api_wrapper_class(**toolkit_config)
297
+ available_tools = api_wrapper.get_available_tools()
298
+
299
+ # Format tools list
300
+ click.echo(f"\nAvailable tools for {toolkit_type}:\n")
301
+ for tool_info in available_tools:
302
+ tool_name = tool_info.get('name', 'unknown')
303
+ description = tool_info.get('description', '')
304
+ click.echo(f" - {tool_name}")
305
+ if description:
306
+ click.echo(f" {description}")
307
+
308
+ click.echo(f"\nTotal: {len(available_tools)} tools")
309
+ return
310
+
311
+ except Exception as e:
312
+ logger.debug(f"Could not instantiate API wrapper: {e}")
313
+
314
+ # Fallback: show general info
315
+ click.echo(f"\n{toolkit_type} toolkit is available")
316
+ click.echo("Use 'alita-cli toolkit schema {toolkit_type}' to see configuration options")
317
+
318
+ except Exception as e:
319
+ logger.exception("Failed to list tools")
320
+ raise click.ClickException(str(e))
321
+
322
+ except click.ClickException:
323
+ raise
324
+ except Exception as e:
325
+ logger.exception("Failed to list toolkit tools")
326
+ click.echo(formatter.format_error(str(e)), err=True)
327
+ raise click.Abort()