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,783 @@
1
+ """
2
+ MCP (Model Context Protocol) Toolkit for Alita SDK.
3
+ This toolkit enables connection to a single remote MCP server and exposes its tools.
4
+ Following MCP specification: https://modelcontextprotocol.io/specification/2025-06-18
5
+ """
6
+
7
+ import logging
8
+ import re
9
+ import asyncio
10
+ from typing import List, Optional, Any, Dict, Literal, ClassVar, Union
11
+
12
+ from langchain_core.tools import BaseToolkit, BaseTool
13
+ from pydantic import BaseModel, ConfigDict, Field, SecretStr
14
+
15
+ from ..tools.mcp_server_tool import McpServerTool
16
+ from ..tools.mcp_remote_tool import McpRemoteTool
17
+ from ..tools.mcp_inspect_tool import McpInspectTool
18
+ from ..models.mcp_models import McpConnectionConfig
19
+ from ..utils.mcp_client import McpClient
20
+ from ..utils.mcp_oauth import (
21
+ McpAuthorizationRequired,
22
+ canonical_resource,
23
+ extract_resource_metadata_url,
24
+ fetch_resource_metadata,
25
+ infer_authorization_servers_from_realm,
26
+ )
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ name = "mcp"
31
+
32
+ def safe_int(value, default):
33
+ """Convert value to int, handling string inputs."""
34
+ if value is None:
35
+ return default
36
+ try:
37
+ return int(value)
38
+ except (ValueError, TypeError):
39
+ logger.warning(f"Invalid integer value '{value}', using default {default}")
40
+ return default
41
+
42
+ class McpToolkit(BaseToolkit):
43
+ """
44
+ MCP Toolkit for connecting to a single remote MCP server and exposing its tools.
45
+ Each toolkit instance represents one MCP server connection.
46
+ """
47
+
48
+ tools: List[BaseTool] = []
49
+ toolkit_name: Optional[str] = None
50
+
51
+ def __getstate__(self):
52
+ """Custom serialization for pickle compatibility."""
53
+ state = self.__dict__.copy()
54
+ # The tools list should already be pickle-safe due to individual tool fixes
55
+ # Just return the state as-is since tools handle their own serialization
56
+ return state
57
+
58
+ def __setstate__(self, state):
59
+ """Custom deserialization for pickle compatibility."""
60
+ # Initialize Pydantic internal attributes if needed
61
+ if '__pydantic_fields_set__' not in state:
62
+ state['__pydantic_fields_set__'] = set(state.keys())
63
+ if '__pydantic_extra__' not in state:
64
+ state['__pydantic_extra__'] = None
65
+ if '__pydantic_private__' not in state:
66
+ state['__pydantic_private__'] = None
67
+
68
+ # Update object state
69
+ self.__dict__.update(state)
70
+
71
+ @staticmethod
72
+ def toolkit_config_schema() -> BaseModel:
73
+ """
74
+ Generate the configuration schema for MCP toolkit.
75
+ Following MCP specification for connection parameters.
76
+ """
77
+ from pydantic import create_model
78
+
79
+ return create_model(
80
+ 'mcp',
81
+ url=(
82
+ str,
83
+ Field(
84
+ description="MCP server HTTP URL",
85
+ json_schema_extra={
86
+ 'tooltip': 'HTTP URL for the MCP server (http:// or https://)',
87
+ 'example': 'https://your-mcp-server.com/mcp'
88
+ }
89
+ )
90
+ ),
91
+ headers=(
92
+ Optional[Dict[str, str]],
93
+ Field(
94
+ default=None,
95
+ description="HTTP headers for authentication and configuration",
96
+ json_schema_extra={
97
+ 'tooltip': 'HTTP headers to send with requests (e.g. Authorization)',
98
+ 'example': {'Authorization': 'Bearer your-api-token'}
99
+ }
100
+ )
101
+ ),
102
+ client_id=(
103
+ Optional[str],
104
+ Field(
105
+ default=None,
106
+ description="OAuth Client ID (if applicable)"
107
+ )
108
+ ),
109
+ client_secret=(
110
+ Optional[SecretStr],
111
+ Field(
112
+ default=None,
113
+ description="OAuth Client Secret (if applicable)"
114
+ )
115
+ ),
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
125
+ Field(
126
+ default=300,
127
+ description="Request timeout in seconds (1-3600)"
128
+ )
129
+ ),
130
+ selected_tools=(
131
+ List[str],
132
+ Field(
133
+ default=[],
134
+ description="Specific tools to enable (empty = all tools)",
135
+ json_schema_extra={
136
+ 'tooltip': 'Leave empty to enable all tools from the MCP server'
137
+ }
138
+ )
139
+ ),
140
+ enable_caching=(
141
+ bool,
142
+ Field(
143
+ default=True,
144
+ description="Enable caching of tool schemas and responses"
145
+ )
146
+ ),
147
+ cache_ttl=(
148
+ Union[int, str],
149
+ Field(
150
+ default=300,
151
+ description="Cache TTL in seconds (60-3600)"
152
+ )
153
+ ),
154
+ __config__=ConfigDict(
155
+ json_schema_extra={
156
+ 'metadata': {
157
+ "label": "Remote MCP",
158
+ "icon_url": None,
159
+ "categories": ["other"],
160
+ "extra_categories": ["remote tools", "sse", "http"],
161
+ "description": "Connect to a remote Model Context Protocol (MCP) server via HTTP to access tools"
162
+ }
163
+ }
164
+ )
165
+ )
166
+
167
+ @classmethod
168
+ def get_toolkit(
169
+ cls,
170
+ url: str,
171
+ headers: Optional[Dict[str, str]] = None,
172
+ timeout: int = 60,
173
+ selected_tools: List[str] = None,
174
+ enable_caching: bool = True,
175
+ cache_ttl: int = 300,
176
+ toolkit_name: str = None,
177
+ client = None,
178
+ **kwargs
179
+ ) -> 'McpToolkit':
180
+ """
181
+ Create an MCP toolkit instance for a single MCP server.
182
+
183
+ When valid connection configuration (url + headers) is provided, the toolkit will:
184
+ 1. Immediately perform live discovery from the MCP server
185
+ 2. Create BaseTool instances for all discovered tools with complete schemas
186
+ 3. Include an inspection tool for server exploration
187
+ 4. Return all tools via get_tools() method
188
+
189
+ Args:
190
+ url: MCP server HTTP URL
191
+ headers: HTTP headers for authentication
192
+ timeout: Request timeout in seconds
193
+ selected_tools: List of specific tools to enable (empty = all tools)
194
+ enable_caching: Whether to enable caching
195
+ cache_ttl: Cache TTL in seconds
196
+ toolkit_name: Toolkit name/identifier and prefix for tools
197
+ client: Alita client for MCP communication
198
+ **kwargs: Additional configuration options
199
+
200
+ Returns:
201
+ Configured McpToolkit instance with all available tools discovered
202
+ """
203
+ if selected_tools is None:
204
+ selected_tools = []
205
+
206
+ if not toolkit_name:
207
+ raise ValueError("toolkit_name is required")
208
+
209
+ # Convert numeric parameters that may come as strings from UI
210
+ timeout = safe_int(timeout, 60)
211
+ cache_ttl = safe_int(cache_ttl, 300)
212
+
213
+ logger.info(f"Creating MCP toolkit: {toolkit_name}")
214
+
215
+ # Parse headers if they're provided as a JSON string
216
+ parsed_headers = headers
217
+ if isinstance(headers, str) and headers.strip():
218
+ try:
219
+ import json
220
+ logger.debug(f"Raw headers string length: {len(headers)} chars")
221
+ logger.debug(f"Raw headers string (first 100 chars): {headers[:100]}")
222
+ logger.debug(f"Raw headers string (last 100 chars): {headers[-100:]}")
223
+ parsed_headers = json.loads(headers)
224
+ logger.info(f"Parsed headers from JSON string successfully")
225
+ logger.debug(f"Parsed headers: {parsed_headers}")
226
+ except json.JSONDecodeError as e:
227
+ logger.error(f"Failed to parse headers JSON: {e}")
228
+ logger.error(f"Headers string length: {len(headers)}")
229
+ logger.error(f"Headers string content: {repr(headers)}")
230
+ raise ValueError(f"Invalid headers JSON format: {e}")
231
+ elif headers is not None and not isinstance(headers, dict):
232
+ logger.error(f"Headers must be a dictionary or JSON string, got: {type(headers)}")
233
+ raise ValueError(f"Headers must be a dictionary or JSON string, got: {type(headers)}")
234
+
235
+ # Extract session_id from kwargs if provided
236
+ session_id = kwargs.get('session_id')
237
+ if session_id:
238
+ logger.info(f"[MCP Session] Using provided session ID for toolkit '{toolkit_name}': {session_id}")
239
+
240
+ # Create MCP connection configuration
241
+ try:
242
+ connection_config = McpConnectionConfig(url=url, headers=parsed_headers, session_id=session_id)
243
+ except Exception as e:
244
+ logger.error(f"Invalid MCP connection configuration: {e}")
245
+ raise ValueError(f"Invalid MCP connection configuration: {e}")
246
+
247
+ # Create toolkit instance
248
+ toolkit = cls(toolkit_name=toolkit_name)
249
+
250
+ # Generate tools from the MCP server
251
+ toolkit.tools = cls._create_tools_from_server(
252
+ toolkit_name=toolkit_name,
253
+ connection_config=connection_config,
254
+ timeout=timeout,
255
+ selected_tools=selected_tools,
256
+ client=client
257
+ )
258
+
259
+ return toolkit
260
+
261
+ @classmethod
262
+ def _create_tools_from_server(
263
+ cls,
264
+ toolkit_name: str,
265
+ connection_config: McpConnectionConfig,
266
+ timeout: int,
267
+ selected_tools: List[str],
268
+ client
269
+ ) -> List[BaseTool]:
270
+ """
271
+ Create tools from a single MCP server. Always performs live discovery when connection config is provided.
272
+ """
273
+ tools = []
274
+
275
+ # First, try direct HTTP discovery since we have valid connection config
276
+ try:
277
+ logger.info(f"Discovering tools from MCP toolkit '{toolkit_name}' at {connection_config.url}")
278
+
279
+ # Use synchronous HTTP discovery for toolkit initialization
280
+ tool_metadata_list, session_id = cls._discover_tools_sync(
281
+ toolkit_name=toolkit_name,
282
+ connection_config=connection_config,
283
+ timeout=timeout
284
+ )
285
+
286
+ # Filter tools if specific ones are selected
287
+ selected_tools_lower = [tool.lower() for tool in selected_tools] if selected_tools else []
288
+ if selected_tools_lower:
289
+ tool_metadata_list = [
290
+ tool for tool in tool_metadata_list
291
+ if tool.get('name', '').lower() in selected_tools_lower
292
+ ]
293
+
294
+ # Create BaseTool instances from discovered metadata
295
+ # Use session_id from frontend (passed via connection_config)
296
+ if session_id:
297
+ logger.info(f"[MCP Session] Using session_id from frontend: {session_id}")
298
+
299
+ for tool_metadata in tool_metadata_list:
300
+ server_tool = cls._create_tool_from_dict(
301
+ tool_dict=tool_metadata,
302
+ toolkit_name=toolkit_name,
303
+ connection_config=connection_config,
304
+ timeout=timeout,
305
+ client=client,
306
+ session_id=session_id # Use session from discovery
307
+ )
308
+
309
+ if server_tool:
310
+ tools.append(server_tool)
311
+
312
+ logger.info(f"Successfully created {len(tools)} MCP tools from toolkit '{toolkit_name}' via direct discovery")
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
318
+ except Exception as e:
319
+ logger.error(f"Direct discovery failed for MCP toolkit '{toolkit_name}': {e}", exc_info=True)
320
+ logger.error(f"Discovery error details - URL: {connection_config.url}, Timeout: {timeout}s")
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")
326
+ raise
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)
331
+
332
+ # Don't add inspection tool to agent - it's only for internal use by toolkit
333
+ # inspection_tool = cls._create_inspection_tool(
334
+ # toolkit_name=toolkit_name,
335
+ # connection_config=connection_config
336
+ # )
337
+ # if inspection_tool:
338
+ # tools.append(inspection_tool)
339
+ # logger.info(f"Added MCP inspection tool for toolkit '{toolkit_name}'")
340
+
341
+ # Log final tool count before returning
342
+ logger.info(f"MCP toolkit '{toolkit_name}' will provide {len(tools)} tools to agent")
343
+ if len(tools) == 0:
344
+ logger.warning(f"MCP toolkit '{toolkit_name}' has no tools - discovery may have failed")
345
+
346
+ return tools
347
+
348
+ @classmethod
349
+ def _discover_tools_sync(
350
+ cls,
351
+ toolkit_name: str,
352
+ connection_config: McpConnectionConfig,
353
+ timeout: int
354
+ ) -> tuple[List[Dict[str, Any]], Optional[str]]:
355
+ """
356
+ Discover tools and prompts from MCP server using SSE client.
357
+
358
+ Returns:
359
+ Tuple of (tool_list, server_session_id) - session_id may be server-provided
360
+ """
361
+ initial_session_id = connection_config.session_id
362
+
363
+ if not initial_session_id:
364
+ logger.warning(f"[MCP Session] No session_id provided for '{toolkit_name}' - will generate one")
365
+
366
+ # Run async discovery in sync context
367
+ try:
368
+ all_tools, server_session_id = asyncio.run(
369
+ cls._discover_tools_async(
370
+ toolkit_name=toolkit_name,
371
+ connection_config=connection_config,
372
+ timeout=timeout
373
+ )
374
+ )
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
382
+ except Exception as e:
383
+ logger.error(f"[MCP SSE] Discovery failed for '{toolkit_name}': {e}")
384
+ raise
385
+
386
+ @classmethod
387
+ async def _discover_tools_async(
388
+ cls,
389
+ toolkit_name: str,
390
+ connection_config: McpConnectionConfig,
391
+ timeout: int
392
+ ) -> tuple[List[Dict[str, Any]], Optional[str]]:
393
+ """
394
+ Async implementation of tool discovery using SSE client.
395
+
396
+ Returns:
397
+ Tuple of (tool_list, server_session_id)
398
+ """
399
+ all_tools = []
400
+ session_id = connection_config.session_id
401
+
402
+ # Generate temporary session_id if not provided (for OAuth flow)
403
+ # The real session_id should come from frontend after OAuth completes
404
+ if not session_id:
405
+ import uuid
406
+ session_id = str(uuid.uuid4())
407
+ logger.info(f"[MCP SSE] Generated temporary session_id for OAuth: {session_id}")
408
+
409
+ logger.info(f"[MCP] Discovering from {connection_config.url} with session {session_id}")
410
+
411
+ # Prepare headers
412
+ headers = {}
413
+ if connection_config.headers:
414
+ headers.update(connection_config.headers)
415
+
416
+ # Create unified MCP client (auto-detects SSE vs Streamable HTTP)
417
+ client = McpClient(
418
+ url=connection_config.url,
419
+ session_id=session_id,
420
+ headers=headers,
421
+ timeout=timeout
422
+ )
423
+
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", [])
461
+ }
462
+ }
463
+ }
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}")
472
+
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
477
+
478
+ @classmethod
479
+ def _create_tool_from_dict(
480
+ cls,
481
+ tool_dict: Dict[str, Any],
482
+ toolkit_name: str,
483
+ connection_config: McpConnectionConfig,
484
+ timeout: int,
485
+ client,
486
+ session_id: Optional[str] = None
487
+ ) -> Optional[BaseTool]:
488
+ """Create a BaseTool from a tool/prompt dictionary (from direct HTTP discovery)."""
489
+ try:
490
+ # Use original tool name directly
491
+ tool_name = tool_dict.get("name", "unknown")
492
+
493
+ # Check if this is a prompt (converted to tool)
494
+ is_prompt = tool_dict.get("_mcp_type") == "prompt"
495
+ item_type = "prompt" if is_prompt else "tool"
496
+
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})"
500
+ if len(description) > 1000:
501
+ description = description[:997] + "..."
502
+ logger.debug(f"Trimmed description for tool '{tool_name}' to 1000 chars")
503
+
504
+ # Use McpRemoteTool for remote MCP servers (HTTP/SSE)
505
+ return McpRemoteTool(
506
+ name=tool_name,
507
+ description=description,
508
+ args_schema=McpServerTool.create_pydantic_model_from_schema(
509
+ tool_dict.get("inputSchema", {})
510
+ ),
511
+ client=client,
512
+ server=toolkit_name,
513
+ server_url=connection_config.url,
514
+ server_headers=connection_config.headers,
515
+ tool_timeout_sec=timeout,
516
+ is_prompt=is_prompt,
517
+ prompt_name=tool_dict.get("_mcp_prompt_name") if is_prompt else None,
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"}
521
+ )
522
+ except Exception as e:
523
+ logger.error(f"Failed to create MCP tool '{tool_name}' from toolkit '{toolkit_name}': {e}")
524
+ return None
525
+
526
+ @classmethod
527
+ def _create_tools_static(
528
+ cls,
529
+ toolkit_name: str,
530
+ selected_tools: List[str],
531
+ timeout: int,
532
+ client
533
+ ) -> List[BaseTool]:
534
+ """Fallback static tool creation using the original method."""
535
+ tools = []
536
+
537
+ if not client or not hasattr(client, 'get_mcp_toolkits'):
538
+ logger.warning("Alita client does not support MCP toolkit discovery")
539
+ return tools
540
+
541
+ try:
542
+ all_toolkits = client.get_mcp_toolkits()
543
+ server_toolkit = next((tk for tk in all_toolkits if tk.get('name') == toolkit_name), None)
544
+
545
+ if not server_toolkit:
546
+ logger.warning(f"MCP toolkit '{toolkit_name}' not found in available toolkits")
547
+ return tools
548
+
549
+ # Extract tools from the toolkit
550
+ available_tools = server_toolkit.get('tools', [])
551
+ selected_tools_lower = [tool.lower() for tool in selected_tools] if selected_tools else []
552
+
553
+ for available_tool in available_tools:
554
+ tool_name = available_tool.get("name", "").lower()
555
+
556
+ # Filter tools if specific tools are selected
557
+ if selected_tools_lower and tool_name not in selected_tools_lower:
558
+ continue
559
+
560
+ # Create the tool
561
+ server_tool = cls._create_single_tool(
562
+ toolkit_name=toolkit_name,
563
+ available_tool=available_tool,
564
+ timeout=timeout,
565
+ client=client
566
+ )
567
+
568
+ if server_tool:
569
+ tools.append(server_tool)
570
+
571
+ logger.info(f"Successfully created {len(tools)} MCP tools from toolkit '{toolkit_name}' using static mode")
572
+
573
+ except Exception as e:
574
+ logger.error(f"Error in static tool creation: {e}")
575
+
576
+ # Always add the inspection tool (not subject to selected_tools filtering)
577
+ # For static mode, we need to create a basic connection config from the server info
578
+ try:
579
+ # We don't have full connection config in static mode, so create a basic one
580
+ # The inspection tool will work as long as the server is accessible
581
+ inspection_tool = McpInspectTool(
582
+ name="mcp_inspect",
583
+ server_name=toolkit_name,
584
+ server_url="", # Will be populated by the client if available
585
+ description=f"Inspect available tools, prompts, and resources from MCP toolkit '{toolkit_name}'"
586
+ )
587
+ tools.append(inspection_tool)
588
+ logger.info(f"Added MCP inspection tool for toolkit '{toolkit_name}' (static mode)")
589
+ except Exception as e:
590
+ logger.warning(f"Failed to create inspection tool for {toolkit_name}: {e}")
591
+
592
+ return tools
593
+
594
+ @classmethod
595
+ def _create_tool_from_metadata(
596
+ cls,
597
+ tool_metadata,
598
+ toolkit_name: str,
599
+ timeout: int,
600
+ client
601
+ ) -> Optional[BaseTool]:
602
+ """Create a BaseTool from discovered metadata."""
603
+ try:
604
+ # Use original tool name directly
605
+ tool_name = tool_metadata.name
606
+
607
+ # Build description with toolkit context and ensure it doesn't exceed 1000 characters
608
+ description = f"{tool_metadata.description}\nToolkit: {toolkit_name}"
609
+ if len(description) > 1000:
610
+ description = description[:997] + "..."
611
+ logger.debug(f"Trimmed description for tool '{tool_name}' to 1000 chars")
612
+
613
+ return McpServerTool(
614
+ name=tool_name,
615
+ description=description,
616
+ args_schema=McpServerTool.create_pydantic_model_from_schema(tool_metadata.input_schema),
617
+ client=client,
618
+ server=tool_metadata.server,
619
+ tool_timeout_sec=timeout,
620
+ metadata={"toolkit_name": toolkit_name, "toolkit_type": name}
621
+ )
622
+ except Exception as e:
623
+ logger.error(f"Failed to create MCP tool '{tool_name}' from server '{tool_metadata.server}': {e}")
624
+ return None
625
+
626
+ @classmethod
627
+ def _create_single_tool(
628
+ cls,
629
+ toolkit_name: str,
630
+ available_tool: Dict[str, Any],
631
+ timeout: int,
632
+ client
633
+ ) -> Optional[BaseTool]:
634
+ """Create a single MCP tool."""
635
+ try:
636
+ # Use original tool name directly
637
+ tool_name = available_tool["name"]
638
+
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}"
642
+ if len(description) > 1000:
643
+ description = description[:997] + "..."
644
+ logger.debug(f"Trimmed description for tool '{tool_name}' to 1000 chars")
645
+
646
+ return McpServerTool(
647
+ name=tool_name,
648
+ description=description,
649
+ args_schema=McpServerTool.create_pydantic_model_from_schema(
650
+ available_tool.get("inputSchema", {})
651
+ ),
652
+ client=client,
653
+ server=toolkit_name,
654
+ tool_timeout_sec=timeout,
655
+ metadata={"toolkit_name": toolkit_name, "toolkit_type": name}
656
+ )
657
+ except Exception as e:
658
+ logger.error(f"Failed to create MCP tool '{tool_name}' from toolkit '{toolkit_name}': {e}")
659
+ return None
660
+
661
+ @classmethod
662
+ def _create_inspection_tool(
663
+ cls,
664
+ toolkit_name: str,
665
+ connection_config: McpConnectionConfig
666
+ ) -> Optional[BaseTool]:
667
+ """Create the inspection tool for the MCP toolkit."""
668
+ try:
669
+ return McpInspectTool(
670
+ name="mcp_inspect",
671
+ server_name=toolkit_name,
672
+ server_url=connection_config.url,
673
+ server_headers=connection_config.headers,
674
+ description=f"Inspect available tools, prompts, and resources from MCP toolkit '{toolkit_name}'"
675
+ )
676
+ except Exception as e:
677
+ logger.error(f"Failed to create MCP inspection tool for toolkit '{toolkit_name}': {e}")
678
+ return None
679
+
680
+ def get_tools(self) -> List[BaseTool]:
681
+ """Get the list of tools provided by this toolkit."""
682
+ logger.info(f"MCP toolkit '{self.toolkit_name}' returning {len(self.tools)} tools")
683
+ if len(self.tools) > 0:
684
+ tool_names = [t.name if hasattr(t, 'name') else str(t) for t in self.tools]
685
+ logger.info(f"MCP toolkit '{self.toolkit_name}' tools: {tool_names}")
686
+ return self.tools
687
+
688
+ async def refresh_tools(self):
689
+ """Manually refresh tools from the MCP toolkit."""
690
+ if not self.toolkit_name:
691
+ logger.warning("Cannot refresh tools: toolkit_name not set")
692
+ return
693
+
694
+ try:
695
+ from ..clients.mcp_manager import get_mcp_manager
696
+ manager = get_mcp_manager()
697
+ await manager.refresh_server(self.toolkit_name)
698
+ logger.info(f"Successfully refreshed tools for toolkit {self.toolkit_name}")
699
+ except Exception as e:
700
+ logger.error(f"Failed to refresh tools for toolkit {self.toolkit_name}: {e}")
701
+
702
+ async def get_server_health(self) -> Dict[str, Any]:
703
+ """Get health status of the configured MCP toolkit."""
704
+ if not self.toolkit_name:
705
+ return {"status": "not_configured"}
706
+
707
+ try:
708
+ from ..clients.mcp_manager import get_mcp_manager
709
+ manager = get_mcp_manager()
710
+ health_info = await manager.get_server_health(self.toolkit_name)
711
+ return health_info
712
+ except Exception as e:
713
+ logger.error(f"Failed to get server health for {self.toolkit_name}: {e}")
714
+ return {"status": "error", "error": str(e)}
715
+
716
+
717
+ def get_tools(tool_config: dict, alita_client, llm=None, memory_store=None) -> List[BaseTool]:
718
+ """
719
+ Create MCP tools from configuration.
720
+ This function is called by the main tool loading system.
721
+
722
+ Args:
723
+ tool_config: Tool configuration dictionary
724
+ alita_client: Alita client instance
725
+ llm: Language model (not used by MCP tools)
726
+ memory_store: Memory store (not used by MCP tools)
727
+
728
+ Returns:
729
+ List of configured MCP tools
730
+ """
731
+ settings = tool_config.get('settings', {})
732
+ toolkit_name = tool_config.get('toolkit_name')
733
+
734
+ # Extract required fields
735
+ url = settings.get('url')
736
+ headers = settings.get('headers')
737
+
738
+ if not toolkit_name:
739
+ logger.error("MCP toolkit configuration missing required 'toolkit_name'")
740
+ return []
741
+
742
+ if not url:
743
+ logger.error("MCP toolkit configuration missing required 'url'")
744
+ return []
745
+
746
+ # Type conversion for numeric settings that may come as strings from config
747
+ return McpToolkit.get_toolkit(
748
+ url=url,
749
+ headers=headers,
750
+ timeout=safe_int(settings.get('timeout'), 60),
751
+ selected_tools=settings.get('selected_tools', []),
752
+ enable_caching=settings.get('enable_caching', True),
753
+ cache_ttl=safe_int(settings.get('cache_ttl'), 300),
754
+ toolkit_name=toolkit_name,
755
+ client=alita_client
756
+ ).get_tools()
757
+
758
+
759
+ # Utility functions for managing MCP discovery
760
+ async def start_global_discovery():
761
+ """Start the global MCP discovery service."""
762
+ from ..clients.mcp_discovery import init_discovery_service
763
+ await init_discovery_service()
764
+
765
+
766
+ async def stop_global_discovery():
767
+ """Stop the global MCP discovery service."""
768
+ from ..clients.mcp_discovery import shutdown_discovery_service
769
+ await shutdown_discovery_service()
770
+
771
+
772
+ async def register_mcp_server_for_discovery(toolkit_name: str, connection_config):
773
+ """Register an MCP server for global discovery."""
774
+ from ..clients.mcp_discovery import get_discovery_service
775
+ service = get_discovery_service()
776
+ await service.register_server(toolkit_name, connection_config)
777
+
778
+
779
+ def get_all_discovered_servers():
780
+ """Get status of all discovered servers."""
781
+ from ..clients.mcp_discovery import get_discovery_service
782
+ service = get_discovery_service()
783
+ return service.get_server_health()