alita-sdk 0.3.462__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 (261) hide show
  1. alita_sdk/cli/agent/__init__.py +5 -0
  2. alita_sdk/cli/agent/default.py +258 -0
  3. alita_sdk/cli/agent_executor.py +15 -3
  4. alita_sdk/cli/agent_loader.py +56 -8
  5. alita_sdk/cli/agent_ui.py +93 -31
  6. alita_sdk/cli/agents.py +2274 -230
  7. alita_sdk/cli/callbacks.py +96 -25
  8. alita_sdk/cli/cli.py +10 -1
  9. alita_sdk/cli/config.py +162 -9
  10. alita_sdk/cli/context/__init__.py +30 -0
  11. alita_sdk/cli/context/cleanup.py +198 -0
  12. alita_sdk/cli/context/manager.py +731 -0
  13. alita_sdk/cli/context/message.py +285 -0
  14. alita_sdk/cli/context/strategies.py +289 -0
  15. alita_sdk/cli/context/token_estimation.py +127 -0
  16. alita_sdk/cli/input_handler.py +419 -0
  17. alita_sdk/cli/inventory.py +1073 -0
  18. alita_sdk/cli/testcases/__init__.py +94 -0
  19. alita_sdk/cli/testcases/data_generation.py +119 -0
  20. alita_sdk/cli/testcases/discovery.py +96 -0
  21. alita_sdk/cli/testcases/executor.py +84 -0
  22. alita_sdk/cli/testcases/logger.py +85 -0
  23. alita_sdk/cli/testcases/parser.py +172 -0
  24. alita_sdk/cli/testcases/prompts.py +91 -0
  25. alita_sdk/cli/testcases/reporting.py +125 -0
  26. alita_sdk/cli/testcases/setup.py +108 -0
  27. alita_sdk/cli/testcases/test_runner.py +282 -0
  28. alita_sdk/cli/testcases/utils.py +39 -0
  29. alita_sdk/cli/testcases/validation.py +90 -0
  30. alita_sdk/cli/testcases/workflow.py +196 -0
  31. alita_sdk/cli/toolkit.py +14 -17
  32. alita_sdk/cli/toolkit_loader.py +35 -5
  33. alita_sdk/cli/tools/__init__.py +36 -2
  34. alita_sdk/cli/tools/approval.py +224 -0
  35. alita_sdk/cli/tools/filesystem.py +910 -64
  36. alita_sdk/cli/tools/planning.py +389 -0
  37. alita_sdk/cli/tools/terminal.py +414 -0
  38. alita_sdk/community/__init__.py +72 -12
  39. alita_sdk/community/inventory/__init__.py +236 -0
  40. alita_sdk/community/inventory/config.py +257 -0
  41. alita_sdk/community/inventory/enrichment.py +2137 -0
  42. alita_sdk/community/inventory/extractors.py +1469 -0
  43. alita_sdk/community/inventory/ingestion.py +3172 -0
  44. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  45. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  46. alita_sdk/community/inventory/parsers/base.py +295 -0
  47. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  48. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  49. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  50. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  51. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  52. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  53. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  54. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  55. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  56. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  57. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  58. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  59. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  60. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  61. alita_sdk/community/inventory/patterns/loader.py +348 -0
  62. alita_sdk/community/inventory/patterns/registry.py +198 -0
  63. alita_sdk/community/inventory/presets.py +535 -0
  64. alita_sdk/community/inventory/retrieval.py +1403 -0
  65. alita_sdk/community/inventory/toolkit.py +173 -0
  66. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  67. alita_sdk/community/inventory/visualize.py +1370 -0
  68. alita_sdk/configurations/__init__.py +1 -1
  69. alita_sdk/configurations/ado.py +141 -20
  70. alita_sdk/configurations/bitbucket.py +0 -3
  71. alita_sdk/configurations/confluence.py +76 -42
  72. alita_sdk/configurations/figma.py +76 -0
  73. alita_sdk/configurations/gitlab.py +17 -5
  74. alita_sdk/configurations/openapi.py +329 -0
  75. alita_sdk/configurations/qtest.py +72 -1
  76. alita_sdk/configurations/report_portal.py +96 -0
  77. alita_sdk/configurations/sharepoint.py +148 -0
  78. alita_sdk/configurations/testio.py +83 -0
  79. alita_sdk/runtime/clients/artifact.py +3 -3
  80. alita_sdk/runtime/clients/client.py +353 -48
  81. alita_sdk/runtime/clients/sandbox_client.py +0 -21
  82. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  83. alita_sdk/runtime/langchain/assistant.py +123 -26
  84. alita_sdk/runtime/langchain/constants.py +642 -1
  85. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
  86. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  87. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +6 -3
  88. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
  89. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
  90. alita_sdk/runtime/langchain/document_loaders/constants.py +12 -7
  91. alita_sdk/runtime/langchain/langraph_agent.py +279 -73
  92. alita_sdk/runtime/langchain/utils.py +82 -15
  93. alita_sdk/runtime/llms/preloaded.py +2 -6
  94. alita_sdk/runtime/skills/__init__.py +91 -0
  95. alita_sdk/runtime/skills/callbacks.py +498 -0
  96. alita_sdk/runtime/skills/discovery.py +540 -0
  97. alita_sdk/runtime/skills/executor.py +610 -0
  98. alita_sdk/runtime/skills/input_builder.py +371 -0
  99. alita_sdk/runtime/skills/models.py +330 -0
  100. alita_sdk/runtime/skills/registry.py +355 -0
  101. alita_sdk/runtime/skills/skill_runner.py +330 -0
  102. alita_sdk/runtime/toolkits/__init__.py +7 -0
  103. alita_sdk/runtime/toolkits/application.py +21 -9
  104. alita_sdk/runtime/toolkits/artifact.py +15 -5
  105. alita_sdk/runtime/toolkits/datasource.py +13 -6
  106. alita_sdk/runtime/toolkits/mcp.py +139 -251
  107. alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
  108. alita_sdk/runtime/toolkits/planning.py +178 -0
  109. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  110. alita_sdk/runtime/toolkits/subgraph.py +251 -6
  111. alita_sdk/runtime/toolkits/tools.py +238 -32
  112. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  113. alita_sdk/runtime/tools/__init__.py +3 -1
  114. alita_sdk/runtime/tools/application.py +20 -6
  115. alita_sdk/runtime/tools/artifact.py +511 -28
  116. alita_sdk/runtime/tools/data_analysis.py +183 -0
  117. alita_sdk/runtime/tools/function.py +43 -15
  118. alita_sdk/runtime/tools/image_generation.py +50 -44
  119. alita_sdk/runtime/tools/llm.py +852 -67
  120. alita_sdk/runtime/tools/loop.py +3 -1
  121. alita_sdk/runtime/tools/loop_output.py +3 -1
  122. alita_sdk/runtime/tools/mcp_remote_tool.py +25 -10
  123. alita_sdk/runtime/tools/mcp_server_tool.py +7 -6
  124. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  125. alita_sdk/runtime/tools/planning/models.py +246 -0
  126. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  127. alita_sdk/runtime/tools/router.py +2 -4
  128. alita_sdk/runtime/tools/sandbox.py +9 -6
  129. alita_sdk/runtime/tools/skill_router.py +776 -0
  130. alita_sdk/runtime/tools/tool.py +3 -1
  131. alita_sdk/runtime/tools/vectorstore.py +7 -2
  132. alita_sdk/runtime/tools/vectorstore_base.py +51 -11
  133. alita_sdk/runtime/utils/AlitaCallback.py +137 -21
  134. alita_sdk/runtime/utils/constants.py +5 -1
  135. alita_sdk/runtime/utils/mcp_client.py +492 -0
  136. alita_sdk/runtime/utils/mcp_oauth.py +202 -5
  137. alita_sdk/runtime/utils/mcp_sse_client.py +36 -7
  138. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  139. alita_sdk/runtime/utils/serialization.py +155 -0
  140. alita_sdk/runtime/utils/streamlit.py +6 -10
  141. alita_sdk/runtime/utils/toolkit_utils.py +16 -5
  142. alita_sdk/runtime/utils/utils.py +36 -0
  143. alita_sdk/tools/__init__.py +113 -29
  144. alita_sdk/tools/ado/repos/__init__.py +51 -33
  145. alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
  146. alita_sdk/tools/ado/test_plan/__init__.py +25 -9
  147. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
  148. alita_sdk/tools/ado/utils.py +1 -18
  149. alita_sdk/tools/ado/wiki/__init__.py +25 -8
  150. alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
  151. alita_sdk/tools/ado/work_item/__init__.py +26 -9
  152. alita_sdk/tools/ado/work_item/ado_wrapper.py +56 -3
  153. alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
  154. alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
  155. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  156. alita_sdk/tools/azure_ai/search/__init__.py +11 -8
  157. alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
  158. alita_sdk/tools/base/tool.py +5 -1
  159. alita_sdk/tools/base_indexer_toolkit.py +170 -45
  160. alita_sdk/tools/bitbucket/__init__.py +17 -12
  161. alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
  162. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
  163. alita_sdk/tools/browser/__init__.py +5 -4
  164. alita_sdk/tools/carrier/__init__.py +5 -6
  165. alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
  166. alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
  167. alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
  168. alita_sdk/tools/chunkers/__init__.py +3 -1
  169. alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
  170. alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
  171. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  172. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  173. alita_sdk/tools/cloud/aws/__init__.py +10 -7
  174. alita_sdk/tools/cloud/azure/__init__.py +10 -7
  175. alita_sdk/tools/cloud/gcp/__init__.py +10 -7
  176. alita_sdk/tools/cloud/k8s/__init__.py +10 -7
  177. alita_sdk/tools/code/linter/__init__.py +10 -8
  178. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  179. alita_sdk/tools/code/sonar/__init__.py +10 -7
  180. alita_sdk/tools/code_indexer_toolkit.py +73 -23
  181. alita_sdk/tools/confluence/__init__.py +21 -15
  182. alita_sdk/tools/confluence/api_wrapper.py +78 -23
  183. alita_sdk/tools/confluence/loader.py +4 -2
  184. alita_sdk/tools/custom_open_api/__init__.py +12 -5
  185. alita_sdk/tools/elastic/__init__.py +11 -8
  186. alita_sdk/tools/elitea_base.py +493 -30
  187. alita_sdk/tools/figma/__init__.py +58 -11
  188. alita_sdk/tools/figma/api_wrapper.py +1235 -143
  189. alita_sdk/tools/figma/figma_client.py +73 -0
  190. alita_sdk/tools/figma/toon_tools.py +2748 -0
  191. alita_sdk/tools/github/__init__.py +13 -14
  192. alita_sdk/tools/github/github_client.py +224 -100
  193. alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
  194. alita_sdk/tools/github/schemas.py +14 -5
  195. alita_sdk/tools/github/tool.py +5 -1
  196. alita_sdk/tools/github/tool_prompts.py +9 -22
  197. alita_sdk/tools/gitlab/__init__.py +15 -11
  198. alita_sdk/tools/gitlab/api_wrapper.py +207 -41
  199. alita_sdk/tools/gitlab_org/__init__.py +10 -8
  200. alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
  201. alita_sdk/tools/google/bigquery/__init__.py +13 -12
  202. alita_sdk/tools/google/bigquery/tool.py +5 -1
  203. alita_sdk/tools/google_places/__init__.py +10 -8
  204. alita_sdk/tools/google_places/api_wrapper.py +1 -1
  205. alita_sdk/tools/jira/__init__.py +17 -11
  206. alita_sdk/tools/jira/api_wrapper.py +91 -40
  207. alita_sdk/tools/keycloak/__init__.py +11 -8
  208. alita_sdk/tools/localgit/__init__.py +9 -3
  209. alita_sdk/tools/localgit/local_git.py +62 -54
  210. alita_sdk/tools/localgit/tool.py +5 -1
  211. alita_sdk/tools/memory/__init__.py +11 -3
  212. alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
  213. alita_sdk/tools/ocr/__init__.py +11 -8
  214. alita_sdk/tools/openapi/__init__.py +490 -114
  215. alita_sdk/tools/openapi/api_wrapper.py +1368 -0
  216. alita_sdk/tools/openapi/tool.py +20 -0
  217. alita_sdk/tools/pandas/__init__.py +20 -12
  218. alita_sdk/tools/pandas/api_wrapper.py +38 -25
  219. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  220. alita_sdk/tools/postman/__init__.py +11 -11
  221. alita_sdk/tools/pptx/__init__.py +10 -9
  222. alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
  223. alita_sdk/tools/qtest/__init__.py +30 -10
  224. alita_sdk/tools/qtest/api_wrapper.py +430 -13
  225. alita_sdk/tools/rally/__init__.py +10 -8
  226. alita_sdk/tools/rally/api_wrapper.py +1 -1
  227. alita_sdk/tools/report_portal/__init__.py +12 -9
  228. alita_sdk/tools/salesforce/__init__.py +10 -9
  229. alita_sdk/tools/servicenow/__init__.py +17 -14
  230. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  231. alita_sdk/tools/sharepoint/__init__.py +10 -8
  232. alita_sdk/tools/sharepoint/api_wrapper.py +4 -4
  233. alita_sdk/tools/slack/__init__.py +10 -8
  234. alita_sdk/tools/slack/api_wrapper.py +2 -2
  235. alita_sdk/tools/sql/__init__.py +11 -9
  236. alita_sdk/tools/testio/__init__.py +10 -8
  237. alita_sdk/tools/testrail/__init__.py +11 -8
  238. alita_sdk/tools/testrail/api_wrapper.py +1 -1
  239. alita_sdk/tools/utils/__init__.py +9 -4
  240. alita_sdk/tools/utils/content_parser.py +77 -3
  241. alita_sdk/tools/utils/text_operations.py +410 -0
  242. alita_sdk/tools/utils/tool_prompts.py +79 -0
  243. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +17 -13
  244. alita_sdk/tools/xray/__init__.py +12 -9
  245. alita_sdk/tools/yagmail/__init__.py +9 -3
  246. alita_sdk/tools/zephyr/__init__.py +9 -7
  247. alita_sdk/tools/zephyr_enterprise/__init__.py +11 -8
  248. alita_sdk/tools/zephyr_essential/__init__.py +10 -8
  249. alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
  250. alita_sdk/tools/zephyr_essential/client.py +2 -2
  251. alita_sdk/tools/zephyr_scale/__init__.py +11 -9
  252. alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
  253. alita_sdk/tools/zephyr_squad/__init__.py +10 -8
  254. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +147 -7
  255. alita_sdk-0.3.627.dist-info/RECORD +468 -0
  256. alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
  257. alita_sdk-0.3.462.dist-info/RECORD +0 -384
  258. alita_sdk-0.3.462.dist-info/entry_points.txt +0 -2
  259. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
  260. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
  261. {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
@@ -6,19 +6,17 @@ Following MCP specification: https://modelcontextprotocol.io/specification/2025-
6
6
 
7
7
  import logging
8
8
  import re
9
- import requests
10
9
  import asyncio
11
10
  from typing import List, Optional, Any, Dict, Literal, ClassVar, Union
12
11
 
13
12
  from langchain_core.tools import BaseToolkit, BaseTool
14
- from pydantic import BaseModel, ConfigDict, Field
13
+ from pydantic import BaseModel, ConfigDict, Field, SecretStr
15
14
 
16
15
  from ..tools.mcp_server_tool import McpServerTool
17
16
  from ..tools.mcp_remote_tool import McpRemoteTool
18
17
  from ..tools.mcp_inspect_tool import McpInspectTool
19
- from ...tools.utils import TOOLKIT_SPLITTER, clean_string
20
18
  from ..models.mcp_models import McpConnectionConfig
21
- from ..utils.mcp_sse_client import McpSseClient
19
+ from ..utils.mcp_client import McpClient
22
20
  from ..utils.mcp_oauth import (
23
21
  McpAuthorizationRequired,
24
22
  canonical_resource,
@@ -41,110 +39,6 @@ def safe_int(value, default):
41
39
  logger.warning(f"Invalid integer value '{value}', using default {default}")
42
40
  return default
43
41
 
44
- def optimize_tool_name(prefix: str, tool_name: str, max_total_length: int = 64) -> str:
45
- """
46
- Optimize tool name to fit within max_total_length while preserving meaning.
47
-
48
- Args:
49
- prefix: The toolkit prefix (already cleaned)
50
- tool_name: The original tool name
51
- max_total_length: Maximum total length for the full tool name (default: 64)
52
-
53
- Returns:
54
- Optimized full tool name in format: prefix___tool_name
55
- """
56
- splitter = TOOLKIT_SPLITTER
57
- splitter_len = len(splitter)
58
- prefix_len = len(prefix)
59
-
60
- # Calculate available space for tool name
61
- available_space = max_total_length - prefix_len - splitter_len
62
-
63
- if available_space <= 0:
64
- logger.error(f"Prefix '{prefix}' is too long ({prefix_len} chars), cannot create valid tool name")
65
- # Fallback: truncate prefix itself
66
- prefix = prefix[:max_total_length - splitter_len - 10] # Leave 10 chars for tool name
67
- available_space = max_total_length - len(prefix) - splitter_len
68
-
69
- # If tool name fits, use it as-is
70
- if len(tool_name) <= available_space:
71
- return f'{prefix}{splitter}{tool_name}'
72
-
73
- # Tool name is too long, need to optimize
74
- logger.debug(f"Tool name '{tool_name}' is too long ({len(tool_name)} chars), optimizing to fit {available_space} chars")
75
-
76
- # Split tool name into parts (handle camelCase, snake_case, and mixed)
77
- # First, split by underscores and hyphens
78
- parts = re.split(r'[_-]', tool_name)
79
-
80
- # Further split camelCase within each part
81
- all_parts = []
82
- for part in parts:
83
- # Insert underscore before uppercase letters (camelCase to snake_case)
84
- snake_case_part = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', part)
85
- all_parts.extend(snake_case_part.split('_'))
86
-
87
- # Filter out empty parts
88
- all_parts = [p for p in all_parts if p]
89
-
90
- # Remove redundant prefix words (case-insensitive comparison)
91
- # Only remove if prefix is meaningful (>= 3 chars) to avoid over-filtering
92
- prefix_lower = prefix.lower()
93
- filtered_parts = []
94
- for part in all_parts:
95
- part_lower = part.lower()
96
- # Skip if this part contains the prefix or the prefix contains this part
97
- # But only if both are meaningful (>= 3 chars)
98
- should_remove = False
99
- if len(prefix_lower) >= 3 and len(part_lower) >= 3:
100
- if part_lower in prefix_lower or prefix_lower in part_lower:
101
- should_remove = True
102
- logger.debug(f"Removing redundant part '{part}' (matches prefix '{prefix}')")
103
-
104
- if not should_remove:
105
- filtered_parts.append(part)
106
-
107
- # If we removed all parts, keep the original parts
108
- if not filtered_parts:
109
- filtered_parts = all_parts
110
-
111
- # Reconstruct tool name with filtered parts
112
- optimized_name = '_'.join(filtered_parts)
113
-
114
- # If still too long, truncate intelligently
115
- if len(optimized_name) > available_space:
116
- # Strategy: Keep beginning and end, as they often contain the most important info
117
- # For example: "projectalita_github_io_list_branches" -> "projectalita_list_branches"
118
-
119
- # Try removing middle parts first
120
- if len(filtered_parts) > 2:
121
- # Keep first and last parts, remove middle
122
- kept_parts = [filtered_parts[0], filtered_parts[-1]]
123
- optimized_name = '_'.join(kept_parts)
124
-
125
- # If still too long, add parts from the end until we run out of space
126
- if len(optimized_name) <= available_space and len(filtered_parts) > 2:
127
- for i in range(len(filtered_parts) - 2, 0, -1):
128
- candidate = '_'.join([filtered_parts[0]] + filtered_parts[i:])
129
- if len(candidate) <= available_space:
130
- optimized_name = candidate
131
- break
132
-
133
- # If still too long, just truncate
134
- if len(optimized_name) > available_space:
135
- # Try to truncate at word boundary
136
- truncated = optimized_name[:available_space]
137
- last_underscore = truncated.rfind('_')
138
- if last_underscore > available_space * 0.7: # Keep if we're not losing too much
139
- optimized_name = truncated[:last_underscore]
140
- else:
141
- optimized_name = truncated
142
-
143
- full_name = f'{prefix}{splitter}{optimized_name}'
144
- logger.info(f"Optimized tool name: '{tool_name}' ({len(tool_name)} chars) -> '{optimized_name}' ({len(optimized_name)} chars), full: '{full_name}' ({len(full_name)} chars)")
145
-
146
- return full_name
147
-
148
42
  class McpToolkit(BaseToolkit):
149
43
  """
150
44
  MCP Toolkit for connecting to a single remote MCP server and exposing its tools.
@@ -154,9 +48,6 @@ class McpToolkit(BaseToolkit):
154
48
  tools: List[BaseTool] = []
155
49
  toolkit_name: Optional[str] = None
156
50
 
157
- # Class variable (not Pydantic field) for tool name length limit
158
- toolkit_max_length: ClassVar[int] = 0 # No limit for MCP tool names
159
-
160
51
  def __getstate__(self):
161
52
  """Custom serialization for pickle compatibility."""
162
53
  state = self.__dict__.copy()
@@ -208,28 +99,32 @@ class McpToolkit(BaseToolkit):
208
99
  }
209
100
  )
210
101
  ),
211
- timeout=(
212
- Union[int, str], # TODO: remove one I will figure out why UI sends str
102
+ client_id=(
103
+ Optional[str],
213
104
  Field(
214
- default=300,
215
- description="Request timeout in seconds (1-3600)"
105
+ default=None,
106
+ description="OAuth Client ID (if applicable)"
216
107
  )
217
108
  ),
218
- discovery_mode=(
219
- Literal['static', 'dynamic', 'hybrid'],
109
+ client_secret=(
110
+ Optional[SecretStr],
220
111
  Field(
221
- default="dynamic",
222
- description="Discovery mode",
223
- json_schema_extra={
224
- 'tooltip': 'static: use registry, dynamic: live discovery, hybrid: try dynamic first'
225
- }
112
+ default=None,
113
+ description="OAuth Client Secret (if applicable)"
226
114
  )
227
115
  ),
228
- discovery_interval=(
229
- Union[int, str],
116
+ scopes=(
117
+ Optional[List[str]],
118
+ Field(
119
+ default=None,
120
+ description="OAuth Scopes (if applicable)"
121
+ )
122
+ ),
123
+ timeout=(
124
+ Union[int, str], # TODO: remove one I will figure out why UI sends str
230
125
  Field(
231
126
  default=300,
232
- description="Discovery interval in seconds (60-3600, for periodic discovery)"
127
+ description="Request timeout in seconds (1-3600)"
233
128
  )
234
129
  ),
235
130
  selected_tools=(
@@ -259,7 +154,7 @@ class McpToolkit(BaseToolkit):
259
154
  __config__=ConfigDict(
260
155
  json_schema_extra={
261
156
  'metadata': {
262
- "label": "Remove MCP",
157
+ "label": "Remote MCP",
263
158
  "icon_url": None,
264
159
  "categories": ["other"],
265
160
  "extra_categories": ["remote tools", "sse", "http"],
@@ -275,8 +170,6 @@ class McpToolkit(BaseToolkit):
275
170
  url: str,
276
171
  headers: Optional[Dict[str, str]] = None,
277
172
  timeout: int = 60,
278
- discovery_mode: str = "hybrid",
279
- discovery_interval: int = 300,
280
173
  selected_tools: List[str] = None,
281
174
  enable_caching: bool = True,
282
175
  cache_ttl: int = 300,
@@ -297,8 +190,6 @@ class McpToolkit(BaseToolkit):
297
190
  url: MCP server HTTP URL
298
191
  headers: HTTP headers for authentication
299
192
  timeout: Request timeout in seconds
300
- discovery_mode: Discovery mode ('static', 'dynamic', 'hybrid')
301
- discovery_interval: Discovery interval in seconds (for periodic discovery)
302
193
  selected_tools: List of specific tools to enable (empty = all tools)
303
194
  enable_caching: Whether to enable caching
304
195
  cache_ttl: Cache TTL in seconds
@@ -317,7 +208,6 @@ class McpToolkit(BaseToolkit):
317
208
 
318
209
  # Convert numeric parameters that may come as strings from UI
319
210
  timeout = safe_int(timeout, 60)
320
- discovery_interval = safe_int(discovery_interval, 300)
321
211
  cache_ttl = safe_int(cache_ttl, 300)
322
212
 
323
213
  logger.info(f"Creating MCP toolkit: {toolkit_name}")
@@ -363,8 +253,7 @@ class McpToolkit(BaseToolkit):
363
253
  connection_config=connection_config,
364
254
  timeout=timeout,
365
255
  selected_tools=selected_tools,
366
- client=client,
367
- discovery_mode=discovery_mode
256
+ client=client
368
257
  )
369
258
 
370
259
  return toolkit
@@ -376,8 +265,7 @@ class McpToolkit(BaseToolkit):
376
265
  connection_config: McpConnectionConfig,
377
266
  timeout: int,
378
267
  selected_tools: List[str],
379
- client,
380
- discovery_mode: str = "dynamic"
268
+ client
381
269
  ) -> List[BaseTool]:
382
270
  """
383
271
  Create tools from a single MCP server. Always performs live discovery when connection config is provided.
@@ -423,19 +311,23 @@ class McpToolkit(BaseToolkit):
423
311
 
424
312
  logger.info(f"Successfully created {len(tools)} MCP tools from toolkit '{toolkit_name}' via direct discovery")
425
313
 
314
+ except McpAuthorizationRequired:
315
+ # Authorization is required; surface upstream so the caller can prompt the user
316
+ logger.info(f"MCP toolkit '{toolkit_name}' requires OAuth authorization")
317
+ raise
426
318
  except Exception as e:
427
319
  logger.error(f"Direct discovery failed for MCP toolkit '{toolkit_name}': {e}", exc_info=True)
428
320
  logger.error(f"Discovery error details - URL: {connection_config.url}, Timeout: {timeout}s")
429
-
430
- # Fallback to static mode if available and not already static
431
- if isinstance(e, McpAuthorizationRequired):
432
- # Authorization is required; surface upstream so the caller can prompt the user
321
+
322
+ # For new MCP toolkits (no client), don't silently return empty - surface the error
323
+ # This helps users understand why tool discovery failed
324
+ if not client:
325
+ logger.warning(f"No fallback available for toolkit '{toolkit_name}' - re-raising discovery error")
433
326
  raise
434
- if client and discovery_mode != "static":
435
- logger.info(f"Falling back to static discovery for toolkit '{toolkit_name}'")
436
- tools = cls._create_tools_static(toolkit_name, selected_tools, timeout, client)
437
- else:
438
- logger.warning(f"No fallback available for toolkit '{toolkit_name}' - returning empty tools list")
327
+
328
+ # Only fall back to static discovery for existing toolkits with a client
329
+ logger.info(f"Falling back to static discovery for toolkit '{toolkit_name}'")
330
+ tools = cls._create_tools_static(toolkit_name, selected_tools, timeout, client)
439
331
 
440
332
  # Don't add inspection tool to agent - it's only for internal use by toolkit
441
333
  # inspection_tool = cls._create_inspection_tool(
@@ -459,28 +351,34 @@ class McpToolkit(BaseToolkit):
459
351
  toolkit_name: str,
460
352
  connection_config: McpConnectionConfig,
461
353
  timeout: int
462
- ) -> List[Dict[str, Any]]:
354
+ ) -> tuple[List[Dict[str, Any]], Optional[str]]:
463
355
  """
464
356
  Discover tools and prompts from MCP server using SSE client.
465
- Returns list of tool/prompt dictionaries with name, description, and inputSchema.
466
- Prompts are converted to tools that can be invoked.
357
+
358
+ Returns:
359
+ Tuple of (tool_list, server_session_id) - session_id may be server-provided
467
360
  """
468
- session_id = connection_config.session_id
361
+ initial_session_id = connection_config.session_id
469
362
 
470
- if not session_id:
471
- logger.warning(f"[MCP Session] No session_id provided for '{toolkit_name}' - server may require it")
472
- logger.warning(f"[MCP Session] Frontend should generate a UUID and include it with mcp_tokens")
363
+ if not initial_session_id:
364
+ logger.warning(f"[MCP Session] No session_id provided for '{toolkit_name}' - will generate one")
473
365
 
474
366
  # Run async discovery in sync context
475
367
  try:
476
- all_tools = asyncio.run(
368
+ all_tools, server_session_id = asyncio.run(
477
369
  cls._discover_tools_async(
478
370
  toolkit_name=toolkit_name,
479
371
  connection_config=connection_config,
480
372
  timeout=timeout
481
373
  )
482
374
  )
483
- return all_tools, session_id
375
+ # Return tools and the session_id (server-provided or generated)
376
+ logger.info(f"[MCP Session] Final session_id for '{toolkit_name}': {server_session_id}")
377
+ return all_tools, server_session_id
378
+ except McpAuthorizationRequired:
379
+ # Re-raise auth required exceptions directly
380
+ logger.info(f"[MCP SSE] Authorization required for '{toolkit_name}'")
381
+ raise
484
382
  except Exception as e:
485
383
  logger.error(f"[MCP SSE] Discovery failed for '{toolkit_name}': {e}")
486
384
  raise
@@ -491,9 +389,12 @@ class McpToolkit(BaseToolkit):
491
389
  toolkit_name: str,
492
390
  connection_config: McpConnectionConfig,
493
391
  timeout: int
494
- ) -> List[Dict[str, Any]]:
392
+ ) -> tuple[List[Dict[str, Any]], Optional[str]]:
495
393
  """
496
394
  Async implementation of tool discovery using SSE client.
395
+
396
+ Returns:
397
+ Tuple of (tool_list, server_session_id)
497
398
  """
498
399
  all_tools = []
499
400
  session_id = connection_config.session_id
@@ -505,65 +406,74 @@ class McpToolkit(BaseToolkit):
505
406
  session_id = str(uuid.uuid4())
506
407
  logger.info(f"[MCP SSE] Generated temporary session_id for OAuth: {session_id}")
507
408
 
508
- logger.info(f"[MCP SSE] Discovering from {connection_config.url} with session {session_id}")
409
+ logger.info(f"[MCP] Discovering from {connection_config.url} with session {session_id}")
509
410
 
510
411
  # Prepare headers
511
412
  headers = {}
512
413
  if connection_config.headers:
513
414
  headers.update(connection_config.headers)
514
415
 
515
- # Create SSE client
516
- client = McpSseClient(
416
+ # Create unified MCP client (auto-detects SSE vs Streamable HTTP)
417
+ client = McpClient(
517
418
  url=connection_config.url,
518
419
  session_id=session_id,
519
420
  headers=headers,
520
421
  timeout=timeout
521
422
  )
522
423
 
523
- # Initialize MCP session
524
- await client.initialize()
525
- logger.info(f"[MCP SSE] Session initialized for '{toolkit_name}'")
526
-
527
- # Discover tools
528
- tools = await client.list_tools()
529
- all_tools.extend(tools)
530
- logger.info(f"[MCP SSE] Discovered {len(tools)} tools from '{toolkit_name}'")
531
-
532
- # Discover prompts
533
- try:
534
- prompts = await client.list_prompts()
535
- # Convert prompts to tool format
536
- for prompt in prompts:
537
- prompt_tool = {
538
- "name": f"prompt_{prompt.get('name', 'unnamed')}",
539
- "description": prompt.get('description', f"Execute prompt: {prompt.get('name')}"),
540
- "inputSchema": {
541
- "type": "object",
542
- "properties": {
543
- "arguments": {
544
- "type": "object",
545
- "description": "Arguments for the prompt template",
546
- "properties": {
547
- arg.get("name"): {
548
- "type": "string",
549
- "description": arg.get("description", ""),
550
- "required": arg.get("required", False)
424
+ server_session_id = None
425
+ async with client:
426
+ # Initialize MCP session
427
+ await client.initialize()
428
+ logger.info(f"[MCP] Session initialized for '{toolkit_name}' (transport={client.detected_transport})")
429
+
430
+ # Capture server-provided session_id (from mcp-session-id header)
431
+ server_session_id = client.server_session_id
432
+ if server_session_id:
433
+ logger.info(f"[MCP] Server provided session_id: {server_session_id}")
434
+
435
+ # Discover tools
436
+ tools = await client.list_tools()
437
+ all_tools.extend(tools)
438
+ logger.info(f"[MCP] Discovered {len(tools)} tools from '{toolkit_name}'")
439
+
440
+ # Discover prompts
441
+ try:
442
+ prompts = await client.list_prompts()
443
+ # Convert prompts to tool format
444
+ for prompt in prompts:
445
+ prompt_tool = {
446
+ "name": f"prompt_{prompt.get('name', 'unnamed')}",
447
+ "description": prompt.get('description', f"Execute prompt: {prompt.get('name')}"),
448
+ "inputSchema": {
449
+ "type": "object",
450
+ "properties": {
451
+ "arguments": {
452
+ "type": "object",
453
+ "description": "Arguments for the prompt template",
454
+ "properties": {
455
+ arg.get("name"): {
456
+ "type": "string",
457
+ "description": arg.get("description", ""),
458
+ "required": arg.get("required", False)
459
+ }
460
+ for arg in prompt.get("arguments", [])
551
461
  }
552
- for arg in prompt.get("arguments", [])
553
462
  }
554
463
  }
555
- }
556
- },
557
- "_mcp_type": "prompt",
558
- "_mcp_prompt_name": prompt.get('name')
559
- }
560
- all_tools.append(prompt_tool)
561
- logger.info(f"[MCP SSE] Discovered {len(prompts)} prompts from '{toolkit_name}'")
562
- except Exception as e:
563
- logger.warning(f"[MCP SSE] Failed to discover prompts: {e}")
464
+ },
465
+ "_mcp_type": "prompt",
466
+ "_mcp_prompt_name": prompt.get('name')
467
+ }
468
+ all_tools.append(prompt_tool)
469
+ logger.info(f"[MCP] Discovered {len(prompts)} prompts from '{toolkit_name}'")
470
+ except Exception as e:
471
+ logger.warning(f"[MCP] Failed to discover prompts: {e}")
564
472
 
565
- logger.info(f"[MCP SSE] Total discovered {len(all_tools)} items from '{toolkit_name}'")
566
- return all_tools
473
+ logger.info(f"[MCP] Total discovered {len(all_tools)} items from '{toolkit_name}'")
474
+ # Return tools and server-provided session_id (use server's if available, else the one we sent)
475
+ final_session_id = server_session_id or session_id
476
+ return all_tools, final_session_id
567
477
 
568
478
  @classmethod
569
479
  def _create_tool_from_dict(
@@ -577,28 +487,23 @@ class McpToolkit(BaseToolkit):
577
487
  ) -> Optional[BaseTool]:
578
488
  """Create a BaseTool from a tool/prompt dictionary (from direct HTTP discovery)."""
579
489
  try:
580
- # Store toolkit_max_length in local variable to avoid contextual access issues
581
- max_length_value = cls.toolkit_max_length
582
-
583
- # Clean toolkit name for prefixing
584
- clean_prefix = clean_string(toolkit_name, max_length_value)
585
-
586
- # Optimize tool name to fit within 64 character limit
587
- full_tool_name = optimize_tool_name(clean_prefix, tool_dict.get("name", "unknown"))
490
+ # Use original tool name directly
491
+ tool_name = tool_dict.get("name", "unknown")
588
492
 
589
493
  # Check if this is a prompt (converted to tool)
590
494
  is_prompt = tool_dict.get("_mcp_type") == "prompt"
591
495
  item_type = "prompt" if is_prompt else "tool"
592
496
 
593
- # Build description and ensure it doesn't exceed 1000 characters
594
- description = f"MCP {item_type} '{tool_dict.get('name')}' from toolkit '{toolkit_name}': {tool_dict.get('description', '')}"
497
+ # Build description with toolkit context and ensure it doesn't exceed 1000 characters
498
+ base_description = tool_dict.get('description', '')
499
+ description = f"{base_description}\nToolkit: {toolkit_name} ({connection_config.url})"
595
500
  if len(description) > 1000:
596
501
  description = description[:997] + "..."
597
- logger.debug(f"Trimmed description for tool '{tool_dict.get('name')}' from {len(description)} to 1000 chars")
502
+ logger.debug(f"Trimmed description for tool '{tool_name}' to 1000 chars")
598
503
 
599
504
  # Use McpRemoteTool for remote MCP servers (HTTP/SSE)
600
505
  return McpRemoteTool(
601
- name=full_tool_name,
506
+ name=tool_name,
602
507
  description=description,
603
508
  args_schema=McpServerTool.create_pydantic_model_from_schema(
604
509
  tool_dict.get("inputSchema", {})
@@ -610,11 +515,12 @@ class McpToolkit(BaseToolkit):
610
515
  tool_timeout_sec=timeout,
611
516
  is_prompt=is_prompt,
612
517
  prompt_name=tool_dict.get("_mcp_prompt_name") if is_prompt else None,
613
- original_tool_name=tool_dict.get('name'), # Store original name for MCP server invocation
614
- session_id=session_id # Pass session ID for stateful SSE servers
518
+ original_tool_name=tool_name, # Store original name for MCP server invocation
519
+ session_id=session_id, # Pass session ID for stateful SSE servers
520
+ metadata={"toolkit_name": toolkit_name, "toolkit_type": "mcp"}
615
521
  )
616
522
  except Exception as e:
617
- logger.error(f"Failed to create MCP tool '{tool_dict.get('name')}' from toolkit '{toolkit_name}': {e}")
523
+ logger.error(f"Failed to create MCP tool '{tool_name}' from toolkit '{toolkit_name}': {e}")
618
524
  return None
619
525
 
620
526
  @classmethod
@@ -673,7 +579,7 @@ class McpToolkit(BaseToolkit):
673
579
  # We don't have full connection config in static mode, so create a basic one
674
580
  # The inspection tool will work as long as the server is accessible
675
581
  inspection_tool = McpInspectTool(
676
- name=f"{clean_string(toolkit_name, 50)}{TOOLKIT_SPLITTER}mcp_inspect",
582
+ name="mcp_inspect",
677
583
  server_name=toolkit_name,
678
584
  server_url="", # Will be populated by the client if available
679
585
  description=f"Inspect available tools, prompts, and resources from MCP toolkit '{toolkit_name}'"
@@ -695,30 +601,26 @@ class McpToolkit(BaseToolkit):
695
601
  ) -> Optional[BaseTool]:
696
602
  """Create a BaseTool from discovered metadata."""
697
603
  try:
698
- # Store toolkit_max_length in local variable to avoid contextual access issues
699
- max_length_value = cls.toolkit_max_length
700
-
701
- # Clean server name for prefixing (use tool_metadata.server instead of toolkit_name)
702
- clean_prefix = clean_string(tool_metadata.server, max_length_value)
703
- # Optimize tool name to fit within 64 character limit
704
- full_tool_name = optimize_tool_name(clean_prefix, tool_metadata.name)
604
+ # Use original tool name directly
605
+ tool_name = tool_metadata.name
705
606
 
706
- # Build description and ensure it doesn't exceed 1000 characters
707
- description = f"MCP tool '{tool_metadata.name}' from server '{tool_metadata.server}': {tool_metadata.description}"
607
+ # Build description with toolkit context and ensure it doesn't exceed 1000 characters
608
+ description = f"{tool_metadata.description}\nToolkit: {toolkit_name}"
708
609
  if len(description) > 1000:
709
610
  description = description[:997] + "..."
710
- logger.debug(f"Trimmed description for tool '{tool_metadata.name}' from {len(description)} to 1000 chars")
611
+ logger.debug(f"Trimmed description for tool '{tool_name}' to 1000 chars")
711
612
 
712
613
  return McpServerTool(
713
- name=full_tool_name,
614
+ name=tool_name,
714
615
  description=description,
715
616
  args_schema=McpServerTool.create_pydantic_model_from_schema(tool_metadata.input_schema),
716
617
  client=client,
717
618
  server=tool_metadata.server,
718
- tool_timeout_sec=timeout
619
+ tool_timeout_sec=timeout,
620
+ metadata={"toolkit_name": toolkit_name, "toolkit_type": name}
719
621
  )
720
622
  except Exception as e:
721
- logger.error(f"Failed to create MCP tool '{tool_metadata.name}' from server '{tool_metadata.server}': {e}")
623
+ logger.error(f"Failed to create MCP tool '{tool_name}' from server '{tool_metadata.server}': {e}")
722
624
  return None
723
625
 
724
626
  @classmethod
@@ -731,33 +633,29 @@ class McpToolkit(BaseToolkit):
731
633
  ) -> Optional[BaseTool]:
732
634
  """Create a single MCP tool."""
733
635
  try:
734
- # Store toolkit_max_length in local variable to avoid contextual access issues
735
- max_length_value = cls.toolkit_max_length
736
-
737
- # Clean toolkit name for prefixing
738
- clean_prefix = clean_string(toolkit_name, max_length_value)
739
-
740
- # Optimize tool name to fit within 64 character limit
741
- full_tool_name = optimize_tool_name(clean_prefix, available_tool["name"])
636
+ # Use original tool name directly
637
+ tool_name = available_tool["name"]
742
638
 
743
- # Build description and ensure it doesn't exceed 1000 characters
744
- description = f"MCP tool '{available_tool['name']}' from toolkit '{toolkit_name}': {available_tool.get('description', '')}"
639
+ # Build description with toolkit context and ensure it doesn't exceed 1000 characters
640
+ base_description = available_tool.get('description', '')
641
+ description = f"{base_description}\nToolkit: {toolkit_name}"
745
642
  if len(description) > 1000:
746
643
  description = description[:997] + "..."
747
- logger.debug(f"Trimmed description for tool '{available_tool['name']}' from {len(description)} to 1000 chars")
644
+ logger.debug(f"Trimmed description for tool '{tool_name}' to 1000 chars")
748
645
 
749
646
  return McpServerTool(
750
- name=full_tool_name,
647
+ name=tool_name,
751
648
  description=description,
752
649
  args_schema=McpServerTool.create_pydantic_model_from_schema(
753
650
  available_tool.get("inputSchema", {})
754
651
  ),
755
652
  client=client,
756
653
  server=toolkit_name,
757
- tool_timeout_sec=timeout
654
+ tool_timeout_sec=timeout,
655
+ metadata={"toolkit_name": toolkit_name, "toolkit_type": name}
758
656
  )
759
657
  except Exception as e:
760
- logger.error(f"Failed to create MCP tool '{available_tool.get('name')}' from toolkit '{toolkit_name}': {e}")
658
+ logger.error(f"Failed to create MCP tool '{tool_name}' from toolkit '{toolkit_name}': {e}")
761
659
  return None
762
660
 
763
661
  @classmethod
@@ -768,16 +666,8 @@ class McpToolkit(BaseToolkit):
768
666
  ) -> Optional[BaseTool]:
769
667
  """Create the inspection tool for the MCP toolkit."""
770
668
  try:
771
- # Store toolkit_max_length in local variable to avoid contextual access issues
772
- max_length_value = cls.toolkit_max_length
773
-
774
- # Clean toolkit name for prefixing
775
- clean_prefix = clean_string(toolkit_name, max_length_value)
776
-
777
- full_tool_name = f'{clean_prefix}{TOOLKIT_SPLITTER}mcp_inspect'
778
-
779
669
  return McpInspectTool(
780
- name=full_tool_name,
670
+ name="mcp_inspect",
781
671
  server_name=toolkit_name,
782
672
  server_url=connection_config.url,
783
673
  server_headers=connection_config.headers,
@@ -858,8 +748,6 @@ def get_tools(tool_config: dict, alita_client, llm=None, memory_store=None) -> L
858
748
  url=url,
859
749
  headers=headers,
860
750
  timeout=safe_int(settings.get('timeout'), 60),
861
- discovery_mode=settings.get('discovery_mode', 'dynamic'),
862
- discovery_interval=safe_int(settings.get('discovery_interval'), 300),
863
751
  selected_tools=settings.get('selected_tools', []),
864
752
  enable_caching=settings.get('enable_caching', True),
865
753
  cache_ttl=safe_int(settings.get('cache_ttl'), 300),