alita-sdk 0.3.428.post2__py3-none-any.whl → 0.3.429__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.
- alita_sdk/runtime/clients/client.py +12 -31
- alita_sdk/runtime/clients/mcp_discovery.py +342 -0
- alita_sdk/runtime/clients/mcp_manager.py +262 -0
- alita_sdk/runtime/langchain/constants.py +1 -1
- alita_sdk/runtime/models/mcp_models.py +57 -0
- alita_sdk/runtime/toolkits/__init__.py +24 -0
- alita_sdk/runtime/toolkits/mcp.py +667 -0
- alita_sdk/runtime/toolkits/tools.py +8 -1
- alita_sdk/runtime/tools/mcp_inspect_tool.py +248 -0
- alita_sdk/runtime/tools/mcp_server_tool.py +52 -2
- alita_sdk/runtime/utils/streamlit.py +34 -3
- alita_sdk/runtime/utils/toolkit_utils.py +5 -2
- alita_sdk/tools/__init__.py +11 -0
- alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
- alita_sdk/tools/figma/__init__.py +3 -49
- alita_sdk/tools/figma/api_wrapper.py +123 -1157
- alita_sdk/tools/qtest/api_wrapper.py +52 -1236
- alita_sdk/tools/utils/content_parser.py +1 -36
- {alita_sdk-0.3.428.post2.dist-info → alita_sdk-0.3.429.dist-info}/METADATA +1 -1
- {alita_sdk-0.3.428.post2.dist-info → alita_sdk-0.3.429.dist-info}/RECORD +23 -20
- alita_sdk/tools/figma/figma_client.py +0 -73
- alita_sdk/tools/figma/toon_tools.py +0 -2748
- {alita_sdk-0.3.428.post2.dist-info → alita_sdk-0.3.429.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.428.post2.dist-info → alita_sdk-0.3.429.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.428.post2.dist-info → alita_sdk-0.3.429.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,667 @@
|
|
|
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 requests
|
|
9
|
+
from typing import List, Optional, Any, Dict, Literal, ClassVar
|
|
10
|
+
|
|
11
|
+
from langchain_core.tools import BaseToolkit, BaseTool
|
|
12
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
13
|
+
|
|
14
|
+
from ..tools.mcp_server_tool import McpServerTool
|
|
15
|
+
from ..tools.mcp_inspect_tool import McpInspectTool
|
|
16
|
+
from ...tools.utils import TOOLKIT_SPLITTER, clean_string
|
|
17
|
+
from ..models.mcp_models import McpConnectionConfig
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
name = "mcp"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class McpToolkit(BaseToolkit):
|
|
25
|
+
"""
|
|
26
|
+
MCP Toolkit for connecting to a single remote MCP server and exposing its tools.
|
|
27
|
+
Each toolkit instance represents one MCP server connection.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
tools: List[BaseTool] = []
|
|
31
|
+
server_name: Optional[str] = None
|
|
32
|
+
|
|
33
|
+
# Class variable (not Pydantic field) for tool name length limit
|
|
34
|
+
toolkit_max_length: ClassVar[int] = 0 # No limit for MCP tool names
|
|
35
|
+
|
|
36
|
+
def __getstate__(self):
|
|
37
|
+
"""Custom serialization for pickle compatibility."""
|
|
38
|
+
state = self.__dict__.copy()
|
|
39
|
+
# The tools list should already be pickle-safe due to individual tool fixes
|
|
40
|
+
# Just return the state as-is since tools handle their own serialization
|
|
41
|
+
return state
|
|
42
|
+
|
|
43
|
+
def __setstate__(self, state):
|
|
44
|
+
"""Custom deserialization for pickle compatibility."""
|
|
45
|
+
# Initialize Pydantic internal attributes if needed
|
|
46
|
+
if '__pydantic_fields_set__' not in state:
|
|
47
|
+
state['__pydantic_fields_set__'] = set(state.keys())
|
|
48
|
+
if '__pydantic_extra__' not in state:
|
|
49
|
+
state['__pydantic_extra__'] = None
|
|
50
|
+
if '__pydantic_private__' not in state:
|
|
51
|
+
state['__pydantic_private__'] = None
|
|
52
|
+
|
|
53
|
+
# Update object state
|
|
54
|
+
self.__dict__.update(state)
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def toolkit_config_schema() -> BaseModel:
|
|
58
|
+
"""
|
|
59
|
+
Generate the configuration schema for MCP toolkit.
|
|
60
|
+
Following MCP specification for connection parameters.
|
|
61
|
+
"""
|
|
62
|
+
from pydantic import create_model
|
|
63
|
+
|
|
64
|
+
return create_model(
|
|
65
|
+
'mcp',
|
|
66
|
+
server_name=(
|
|
67
|
+
str,
|
|
68
|
+
Field(
|
|
69
|
+
description="MCP server name/identifier",
|
|
70
|
+
json_schema_extra={
|
|
71
|
+
'tooltip': 'Unique identifier for this MCP server'
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
),
|
|
75
|
+
url=(
|
|
76
|
+
str,
|
|
77
|
+
Field(
|
|
78
|
+
description="MCP server HTTP URL",
|
|
79
|
+
json_schema_extra={
|
|
80
|
+
'tooltip': 'HTTP URL for the MCP server (http:// or https://)',
|
|
81
|
+
'example': 'https://your-mcp-server.com/mcp'
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
),
|
|
85
|
+
headers=(
|
|
86
|
+
Optional[Dict[str, str]],
|
|
87
|
+
Field(
|
|
88
|
+
default=None,
|
|
89
|
+
description="HTTP headers for authentication and configuration",
|
|
90
|
+
json_schema_extra={
|
|
91
|
+
'tooltip': 'HTTP headers to send with requests (e.g. Authorization)',
|
|
92
|
+
'example': {'Authorization': 'Bearer your-api-token'}
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
),
|
|
96
|
+
timeout=(
|
|
97
|
+
int,
|
|
98
|
+
Field(
|
|
99
|
+
default=60,
|
|
100
|
+
ge=1, le=300,
|
|
101
|
+
description="Request timeout in seconds"
|
|
102
|
+
)
|
|
103
|
+
),
|
|
104
|
+
discovery_mode=(
|
|
105
|
+
Literal['static', 'dynamic', 'hybrid'],
|
|
106
|
+
Field(
|
|
107
|
+
default="hybrid",
|
|
108
|
+
description="Discovery mode",
|
|
109
|
+
json_schema_extra={
|
|
110
|
+
'tooltip': 'static: use registry, dynamic: live discovery, hybrid: try dynamic first'
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
),
|
|
114
|
+
discovery_interval=(
|
|
115
|
+
int,
|
|
116
|
+
Field(
|
|
117
|
+
default=300,
|
|
118
|
+
ge=60, le=3600,
|
|
119
|
+
description="Discovery interval in seconds (for periodic discovery)"
|
|
120
|
+
)
|
|
121
|
+
),
|
|
122
|
+
selected_tools=(
|
|
123
|
+
List[str],
|
|
124
|
+
Field(
|
|
125
|
+
default=[],
|
|
126
|
+
description="Specific tools to enable (empty = all tools)",
|
|
127
|
+
json_schema_extra={
|
|
128
|
+
'tooltip': 'Leave empty to enable all tools from the MCP server'
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
),
|
|
132
|
+
enable_caching=(
|
|
133
|
+
bool,
|
|
134
|
+
Field(
|
|
135
|
+
default=True,
|
|
136
|
+
description="Enable caching of tool schemas and responses"
|
|
137
|
+
)
|
|
138
|
+
),
|
|
139
|
+
cache_ttl=(
|
|
140
|
+
int,
|
|
141
|
+
Field(
|
|
142
|
+
default=300,
|
|
143
|
+
ge=60, le=3600,
|
|
144
|
+
description="Cache TTL in seconds"
|
|
145
|
+
)
|
|
146
|
+
),
|
|
147
|
+
__config__=ConfigDict(
|
|
148
|
+
json_schema_extra={
|
|
149
|
+
'metadata': {
|
|
150
|
+
"label": "mcp",
|
|
151
|
+
"icon_url": "mcp-icon.svg",
|
|
152
|
+
"categories": ["integration", "tools"],
|
|
153
|
+
"extra_categories": ["mcp", "remote tools", "protocol", "http"],
|
|
154
|
+
"description": "Connect to a remote Model Context Protocol (MCP) server via HTTP to access tools"
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
@classmethod
|
|
161
|
+
def get_toolkit(
|
|
162
|
+
cls,
|
|
163
|
+
server_name: str,
|
|
164
|
+
url: str,
|
|
165
|
+
headers: Optional[Dict[str, str]] = None,
|
|
166
|
+
timeout: int = 60,
|
|
167
|
+
discovery_mode: str = "hybrid",
|
|
168
|
+
discovery_interval: int = 300,
|
|
169
|
+
selected_tools: List[str] = None,
|
|
170
|
+
enable_caching: bool = True,
|
|
171
|
+
cache_ttl: int = 300,
|
|
172
|
+
toolkit_name: Optional[str] = None,
|
|
173
|
+
client = None,
|
|
174
|
+
**kwargs
|
|
175
|
+
) -> 'McpToolkit':
|
|
176
|
+
"""
|
|
177
|
+
Create an MCP toolkit instance for a single MCP server.
|
|
178
|
+
|
|
179
|
+
When valid connection configuration (url + headers) is provided, the toolkit will:
|
|
180
|
+
1. Immediately perform live discovery from the MCP server
|
|
181
|
+
2. Create BaseTool instances for all discovered tools with complete schemas
|
|
182
|
+
3. Include an inspection tool for server exploration
|
|
183
|
+
4. Return all tools via get_tools() method
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
server_name: MCP server name/identifier
|
|
187
|
+
url: MCP server HTTP URL
|
|
188
|
+
headers: HTTP headers for authentication
|
|
189
|
+
timeout: Request timeout in seconds
|
|
190
|
+
discovery_mode: Discovery mode ('static', 'dynamic', 'hybrid')
|
|
191
|
+
discovery_interval: Discovery interval in seconds (for periodic discovery)
|
|
192
|
+
selected_tools: List of specific tools to enable (empty = all tools)
|
|
193
|
+
enable_caching: Whether to enable caching
|
|
194
|
+
cache_ttl: Cache TTL in seconds
|
|
195
|
+
toolkit_name: Optional name prefix for tools
|
|
196
|
+
client: Alita client for MCP communication
|
|
197
|
+
**kwargs: Additional configuration options
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Configured McpToolkit instance with all available tools discovered
|
|
201
|
+
"""
|
|
202
|
+
if selected_tools is None:
|
|
203
|
+
selected_tools = []
|
|
204
|
+
|
|
205
|
+
logger.info(f"Creating MCP toolkit for server: {server_name}")
|
|
206
|
+
|
|
207
|
+
# Parse headers if they're provided as a JSON string
|
|
208
|
+
parsed_headers = headers
|
|
209
|
+
if isinstance(headers, str) and headers.strip():
|
|
210
|
+
try:
|
|
211
|
+
import json
|
|
212
|
+
logger.debug(f"Raw headers string length: {len(headers)} chars")
|
|
213
|
+
logger.debug(f"Raw headers string (first 100 chars): {headers[:100]}")
|
|
214
|
+
logger.debug(f"Raw headers string (last 100 chars): {headers[-100:]}")
|
|
215
|
+
parsed_headers = json.loads(headers)
|
|
216
|
+
logger.info(f"Parsed headers from JSON string successfully")
|
|
217
|
+
logger.debug(f"Parsed headers: {parsed_headers}")
|
|
218
|
+
except json.JSONDecodeError as e:
|
|
219
|
+
logger.error(f"Failed to parse headers JSON: {e}")
|
|
220
|
+
logger.error(f"Headers string length: {len(headers)}")
|
|
221
|
+
logger.error(f"Headers string content: {repr(headers)}")
|
|
222
|
+
raise ValueError(f"Invalid headers JSON format: {e}")
|
|
223
|
+
elif headers is not None and not isinstance(headers, dict):
|
|
224
|
+
logger.error(f"Headers must be a dictionary or JSON string, got: {type(headers)}")
|
|
225
|
+
raise ValueError(f"Headers must be a dictionary or JSON string, got: {type(headers)}")
|
|
226
|
+
|
|
227
|
+
# Create MCP connection configuration
|
|
228
|
+
try:
|
|
229
|
+
connection_config = McpConnectionConfig(url=url, headers=parsed_headers)
|
|
230
|
+
except Exception as e:
|
|
231
|
+
logger.error(f"Invalid MCP connection configuration: {e}")
|
|
232
|
+
raise ValueError(f"Invalid MCP connection configuration: {e}")
|
|
233
|
+
|
|
234
|
+
# Create toolkit instance
|
|
235
|
+
toolkit = cls(server_name=server_name)
|
|
236
|
+
|
|
237
|
+
# Generate tools from the MCP server
|
|
238
|
+
toolkit.tools = cls._create_tools_from_server(
|
|
239
|
+
server_name=server_name,
|
|
240
|
+
connection_config=connection_config,
|
|
241
|
+
timeout=timeout,
|
|
242
|
+
selected_tools=selected_tools,
|
|
243
|
+
toolkit_name=toolkit_name,
|
|
244
|
+
client=client,
|
|
245
|
+
discovery_mode=discovery_mode
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
return toolkit
|
|
249
|
+
|
|
250
|
+
@classmethod
|
|
251
|
+
def _create_tools_from_server(
|
|
252
|
+
cls,
|
|
253
|
+
server_name: str,
|
|
254
|
+
connection_config: McpConnectionConfig,
|
|
255
|
+
timeout: int,
|
|
256
|
+
selected_tools: List[str],
|
|
257
|
+
toolkit_name: Optional[str],
|
|
258
|
+
client,
|
|
259
|
+
discovery_mode: str = "hybrid"
|
|
260
|
+
) -> List[BaseTool]:
|
|
261
|
+
"""
|
|
262
|
+
Create tools from a single MCP server. Always performs live discovery when connection config is provided.
|
|
263
|
+
"""
|
|
264
|
+
tools = []
|
|
265
|
+
|
|
266
|
+
# First, try direct HTTP discovery since we have valid connection config
|
|
267
|
+
try:
|
|
268
|
+
logger.info(f"Discovering tools from MCP server '{server_name}' at {connection_config.url}")
|
|
269
|
+
|
|
270
|
+
# Use synchronous HTTP discovery for toolkit initialization
|
|
271
|
+
tool_metadata_list = cls._discover_tools_sync(
|
|
272
|
+
server_name=server_name,
|
|
273
|
+
connection_config=connection_config,
|
|
274
|
+
timeout=timeout
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Filter tools if specific ones are selected
|
|
278
|
+
selected_tools_lower = [tool.lower() for tool in selected_tools] if selected_tools else []
|
|
279
|
+
if selected_tools_lower:
|
|
280
|
+
tool_metadata_list = [
|
|
281
|
+
tool for tool in tool_metadata_list
|
|
282
|
+
if tool.get('name', '').lower() in selected_tools_lower
|
|
283
|
+
]
|
|
284
|
+
|
|
285
|
+
# Create BaseTool instances from discovered metadata
|
|
286
|
+
for tool_metadata in tool_metadata_list:
|
|
287
|
+
server_tool = cls._create_tool_from_dict(
|
|
288
|
+
tool_dict=tool_metadata,
|
|
289
|
+
server_name=server_name,
|
|
290
|
+
toolkit_name=toolkit_name or server_name,
|
|
291
|
+
timeout=timeout,
|
|
292
|
+
client=client
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
if server_tool:
|
|
296
|
+
tools.append(server_tool)
|
|
297
|
+
|
|
298
|
+
logger.info(f"Successfully created {len(tools)} MCP tools from server '{server_name}' via direct discovery")
|
|
299
|
+
|
|
300
|
+
except Exception as e:
|
|
301
|
+
logger.error(f"Direct discovery failed for MCP server '{server_name}': {e}")
|
|
302
|
+
|
|
303
|
+
# Fallback to static mode if available and not already static
|
|
304
|
+
if client and discovery_mode != "static":
|
|
305
|
+
logger.info(f"Falling back to static discovery for server '{server_name}'")
|
|
306
|
+
tools = cls._create_tools_static(server_name, selected_tools, toolkit_name, timeout, client)
|
|
307
|
+
else:
|
|
308
|
+
logger.warning(f"No fallback available for server '{server_name}' - returning empty tools list")
|
|
309
|
+
|
|
310
|
+
# Always add the inspection tool (not subject to selected_tools filtering)
|
|
311
|
+
inspection_tool = cls._create_inspection_tool(
|
|
312
|
+
server_name=server_name,
|
|
313
|
+
connection_config=connection_config,
|
|
314
|
+
toolkit_name=toolkit_name or server_name
|
|
315
|
+
)
|
|
316
|
+
if inspection_tool:
|
|
317
|
+
tools.append(inspection_tool)
|
|
318
|
+
logger.info(f"Added MCP inspection tool for server '{server_name}'")
|
|
319
|
+
|
|
320
|
+
return tools
|
|
321
|
+
|
|
322
|
+
@classmethod
|
|
323
|
+
def _discover_tools_sync(
|
|
324
|
+
cls,
|
|
325
|
+
server_name: str,
|
|
326
|
+
connection_config: McpConnectionConfig,
|
|
327
|
+
timeout: int
|
|
328
|
+
) -> List[Dict[str, Any]]:
|
|
329
|
+
"""
|
|
330
|
+
Synchronously discover tools from MCP server using HTTP requests.
|
|
331
|
+
Returns list of tool dictionaries with name, description, and inputSchema.
|
|
332
|
+
"""
|
|
333
|
+
import time
|
|
334
|
+
|
|
335
|
+
# MCP protocol: list_tools request
|
|
336
|
+
mcp_request = {
|
|
337
|
+
"jsonrpc": "2.0",
|
|
338
|
+
"id": f"discover_{int(time.time())}",
|
|
339
|
+
"method": "tools/list",
|
|
340
|
+
"params": {}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
headers = {"Content-Type": "application/json"}
|
|
344
|
+
if connection_config.headers:
|
|
345
|
+
headers.update(connection_config.headers)
|
|
346
|
+
|
|
347
|
+
try:
|
|
348
|
+
response = requests.post(
|
|
349
|
+
connection_config.url,
|
|
350
|
+
json=mcp_request,
|
|
351
|
+
headers=headers,
|
|
352
|
+
timeout=timeout
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
if response.status_code != 200:
|
|
356
|
+
raise Exception(f"HTTP {response.status_code}: {response.text}")
|
|
357
|
+
|
|
358
|
+
data = response.json()
|
|
359
|
+
|
|
360
|
+
if "error" in data:
|
|
361
|
+
raise Exception(f"MCP Error: {data['error']}")
|
|
362
|
+
|
|
363
|
+
# Parse MCP response and extract tools
|
|
364
|
+
tools_data = data.get("result", {}).get("tools", [])
|
|
365
|
+
logger.info(f"Discovered {len(tools_data)} tools from MCP server '{server_name}'")
|
|
366
|
+
|
|
367
|
+
return tools_data
|
|
368
|
+
|
|
369
|
+
except Exception as e:
|
|
370
|
+
logger.error(f"Failed to discover tools from MCP server '{server_name}': {e}")
|
|
371
|
+
raise
|
|
372
|
+
|
|
373
|
+
@classmethod
|
|
374
|
+
def _create_tool_from_dict(
|
|
375
|
+
cls,
|
|
376
|
+
tool_dict: Dict[str, Any],
|
|
377
|
+
server_name: str,
|
|
378
|
+
toolkit_name: str,
|
|
379
|
+
timeout: int,
|
|
380
|
+
client
|
|
381
|
+
) -> Optional[BaseTool]:
|
|
382
|
+
"""Create a BaseTool from a tool dictionary (from direct HTTP discovery)."""
|
|
383
|
+
try:
|
|
384
|
+
# Store toolkit_max_length in local variable to avoid contextual access issues
|
|
385
|
+
max_length_value = cls.toolkit_max_length
|
|
386
|
+
|
|
387
|
+
# Clean server name for prefixing (use server_name instead of toolkit_name)
|
|
388
|
+
clean_prefix = clean_string(server_name, max_length_value)
|
|
389
|
+
|
|
390
|
+
full_tool_name = f'{clean_prefix}{TOOLKIT_SPLITTER}{tool_dict.get("name", "unknown")}'
|
|
391
|
+
|
|
392
|
+
return McpServerTool(
|
|
393
|
+
name=full_tool_name,
|
|
394
|
+
description=f"MCP tool '{tool_dict.get('name')}' from server '{server_name}': {tool_dict.get('description', '')}",
|
|
395
|
+
args_schema=McpServerTool.create_pydantic_model_from_schema(
|
|
396
|
+
tool_dict.get("inputSchema", {})
|
|
397
|
+
),
|
|
398
|
+
client=client,
|
|
399
|
+
server=server_name,
|
|
400
|
+
tool_timeout_sec=timeout
|
|
401
|
+
)
|
|
402
|
+
except Exception as e:
|
|
403
|
+
logger.error(f"Failed to create MCP tool '{tool_dict.get('name')}' from server '{server_name}': {e}")
|
|
404
|
+
return None
|
|
405
|
+
|
|
406
|
+
@classmethod
|
|
407
|
+
def _create_tools_static(
|
|
408
|
+
cls,
|
|
409
|
+
server_name: str,
|
|
410
|
+
selected_tools: List[str],
|
|
411
|
+
toolkit_name: Optional[str],
|
|
412
|
+
timeout: int,
|
|
413
|
+
client
|
|
414
|
+
) -> List[BaseTool]:
|
|
415
|
+
"""Fallback static tool creation using the original method."""
|
|
416
|
+
tools = []
|
|
417
|
+
|
|
418
|
+
if not client or not hasattr(client, 'get_mcp_toolkits'):
|
|
419
|
+
logger.warning("Alita client does not support MCP toolkit discovery")
|
|
420
|
+
return tools
|
|
421
|
+
|
|
422
|
+
try:
|
|
423
|
+
all_toolkits = client.get_mcp_toolkits()
|
|
424
|
+
server_toolkit = next((tk for tk in all_toolkits if tk.get('name') == server_name), None)
|
|
425
|
+
|
|
426
|
+
if not server_toolkit:
|
|
427
|
+
logger.warning(f"MCP server '{server_name}' not found in available toolkits")
|
|
428
|
+
return tools
|
|
429
|
+
|
|
430
|
+
# Extract tools from the toolkit
|
|
431
|
+
available_tools = server_toolkit.get('tools', [])
|
|
432
|
+
selected_tools_lower = [tool.lower() for tool in selected_tools] if selected_tools else []
|
|
433
|
+
|
|
434
|
+
for available_tool in available_tools:
|
|
435
|
+
tool_name = available_tool.get("name", "").lower()
|
|
436
|
+
|
|
437
|
+
# Filter tools if specific tools are selected
|
|
438
|
+
if selected_tools_lower and tool_name not in selected_tools_lower:
|
|
439
|
+
continue
|
|
440
|
+
|
|
441
|
+
# Create the tool
|
|
442
|
+
server_tool = cls._create_single_tool(
|
|
443
|
+
server_name=server_name,
|
|
444
|
+
toolkit_name=toolkit_name or server_name,
|
|
445
|
+
available_tool=available_tool,
|
|
446
|
+
timeout=timeout,
|
|
447
|
+
client=client
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
if server_tool:
|
|
451
|
+
tools.append(server_tool)
|
|
452
|
+
|
|
453
|
+
logger.info(f"Successfully created {len(tools)} MCP tools from server '{server_name}' using static mode")
|
|
454
|
+
|
|
455
|
+
except Exception as e:
|
|
456
|
+
logger.error(f"Error in static tool creation: {e}")
|
|
457
|
+
|
|
458
|
+
# Always add the inspection tool (not subject to selected_tools filtering)
|
|
459
|
+
# For static mode, we need to create a basic connection config from the server info
|
|
460
|
+
try:
|
|
461
|
+
# We don't have full connection config in static mode, so create a basic one
|
|
462
|
+
# The inspection tool will work as long as the server is accessible
|
|
463
|
+
inspection_tool = McpInspectTool(
|
|
464
|
+
name=f"{clean_string(server_name, 50)}{TOOLKIT_SPLITTER}mcp_inspect",
|
|
465
|
+
server_name=server_name,
|
|
466
|
+
server_url="", # Will be populated by the client if available
|
|
467
|
+
description=f"Inspect available tools, prompts, and resources from MCP server '{server_name}'"
|
|
468
|
+
)
|
|
469
|
+
tools.append(inspection_tool)
|
|
470
|
+
logger.info(f"Added MCP inspection tool for server '{server_name}' (static mode)")
|
|
471
|
+
except Exception as e:
|
|
472
|
+
logger.warning(f"Failed to create inspection tool for {server_name}: {e}")
|
|
473
|
+
|
|
474
|
+
return tools
|
|
475
|
+
|
|
476
|
+
@classmethod
|
|
477
|
+
def _create_tool_from_metadata(
|
|
478
|
+
cls,
|
|
479
|
+
tool_metadata,
|
|
480
|
+
toolkit_name: str,
|
|
481
|
+
timeout: int,
|
|
482
|
+
client
|
|
483
|
+
) -> Optional[BaseTool]:
|
|
484
|
+
"""Create a BaseTool from discovered metadata."""
|
|
485
|
+
try:
|
|
486
|
+
# Store toolkit_max_length in local variable to avoid contextual access issues
|
|
487
|
+
max_length_value = cls.toolkit_max_length
|
|
488
|
+
|
|
489
|
+
# Clean server name for prefixing (use tool_metadata.server instead of toolkit_name)
|
|
490
|
+
clean_prefix = clean_string(tool_metadata.server, max_length_value)
|
|
491
|
+
full_tool_name = f'{clean_prefix}{TOOLKIT_SPLITTER}{tool_metadata.name}'
|
|
492
|
+
|
|
493
|
+
return McpServerTool(
|
|
494
|
+
name=full_tool_name,
|
|
495
|
+
description=f"MCP tool '{tool_metadata.name}' from server '{tool_metadata.server}': {tool_metadata.description}",
|
|
496
|
+
args_schema=McpServerTool.create_pydantic_model_from_schema(tool_metadata.schema),
|
|
497
|
+
client=client,
|
|
498
|
+
server=tool_metadata.server,
|
|
499
|
+
tool_timeout_sec=timeout
|
|
500
|
+
)
|
|
501
|
+
except Exception as e:
|
|
502
|
+
logger.error(f"Failed to create MCP tool '{tool_metadata.name}' from server '{tool_metadata.server}': {e}")
|
|
503
|
+
return None
|
|
504
|
+
|
|
505
|
+
@classmethod
|
|
506
|
+
def _create_single_tool(
|
|
507
|
+
cls,
|
|
508
|
+
server_name: str,
|
|
509
|
+
toolkit_name: str,
|
|
510
|
+
available_tool: Dict[str, Any],
|
|
511
|
+
timeout: int,
|
|
512
|
+
client
|
|
513
|
+
) -> Optional[BaseTool]:
|
|
514
|
+
"""Create a single MCP tool."""
|
|
515
|
+
try:
|
|
516
|
+
# Store toolkit_max_length in local variable to avoid contextual access issues
|
|
517
|
+
max_length_value = cls.toolkit_max_length
|
|
518
|
+
|
|
519
|
+
# Clean server name for prefixing (use server_name instead of toolkit_name)
|
|
520
|
+
clean_prefix = clean_string(server_name, max_length_value)
|
|
521
|
+
|
|
522
|
+
full_tool_name = f'{clean_prefix}{TOOLKIT_SPLITTER}{available_tool["name"]}'
|
|
523
|
+
|
|
524
|
+
return McpServerTool(
|
|
525
|
+
name=full_tool_name,
|
|
526
|
+
description=f"MCP tool '{available_tool['name']}' from server '{server_name}': {available_tool.get('description', '')}",
|
|
527
|
+
args_schema=McpServerTool.create_pydantic_model_from_schema(
|
|
528
|
+
available_tool.get("inputSchema", {})
|
|
529
|
+
),
|
|
530
|
+
client=client,
|
|
531
|
+
server=server_name,
|
|
532
|
+
tool_timeout_sec=timeout
|
|
533
|
+
)
|
|
534
|
+
except Exception as e:
|
|
535
|
+
logger.error(f"Failed to create MCP tool '{available_tool.get('name')}' from server '{server_name}': {e}")
|
|
536
|
+
return None
|
|
537
|
+
|
|
538
|
+
@classmethod
|
|
539
|
+
def _create_inspection_tool(
|
|
540
|
+
cls,
|
|
541
|
+
server_name: str,
|
|
542
|
+
connection_config: McpConnectionConfig,
|
|
543
|
+
toolkit_name: str
|
|
544
|
+
) -> Optional[BaseTool]:
|
|
545
|
+
"""Create the inspection tool for the MCP server."""
|
|
546
|
+
try:
|
|
547
|
+
# Store toolkit_max_length in local variable to avoid contextual access issues
|
|
548
|
+
max_length_value = cls.toolkit_max_length
|
|
549
|
+
|
|
550
|
+
# Clean server name for prefixing (use server_name instead of toolkit_name)
|
|
551
|
+
clean_prefix = clean_string(server_name, max_length_value)
|
|
552
|
+
|
|
553
|
+
full_tool_name = f'{clean_prefix}{TOOLKIT_SPLITTER}mcp_inspect'
|
|
554
|
+
|
|
555
|
+
return McpInspectTool(
|
|
556
|
+
name=full_tool_name,
|
|
557
|
+
server_name=server_name,
|
|
558
|
+
server_url=connection_config.url,
|
|
559
|
+
server_headers=connection_config.headers,
|
|
560
|
+
description=f"Inspect available tools, prompts, and resources from MCP server '{server_name}'"
|
|
561
|
+
)
|
|
562
|
+
except Exception as e:
|
|
563
|
+
logger.error(f"Failed to create MCP inspection tool for server '{server_name}': {e}")
|
|
564
|
+
return None
|
|
565
|
+
|
|
566
|
+
def get_tools(self) -> List[BaseTool]:
|
|
567
|
+
"""Get the list of tools provided by this toolkit."""
|
|
568
|
+
return self.tools
|
|
569
|
+
|
|
570
|
+
async def refresh_tools(self):
|
|
571
|
+
"""Manually refresh tools from the MCP server."""
|
|
572
|
+
if not self.server_name:
|
|
573
|
+
logger.warning("Cannot refresh tools: server_name not set")
|
|
574
|
+
return
|
|
575
|
+
|
|
576
|
+
try:
|
|
577
|
+
from ..clients.mcp_manager import get_mcp_manager
|
|
578
|
+
manager = get_mcp_manager()
|
|
579
|
+
await manager.refresh_server(self.server_name)
|
|
580
|
+
logger.info(f"Successfully refreshed tools for server {self.server_name}")
|
|
581
|
+
except Exception as e:
|
|
582
|
+
logger.error(f"Failed to refresh tools for server {self.server_name}: {e}")
|
|
583
|
+
|
|
584
|
+
async def get_server_health(self) -> Dict[str, Any]:
|
|
585
|
+
"""Get health status of the configured MCP server."""
|
|
586
|
+
if not self.server_name:
|
|
587
|
+
return {"status": "not_configured"}
|
|
588
|
+
|
|
589
|
+
try:
|
|
590
|
+
from ..clients.mcp_manager import get_mcp_manager
|
|
591
|
+
manager = get_mcp_manager()
|
|
592
|
+
health_info = await manager.get_server_health(self.server_name)
|
|
593
|
+
return health_info
|
|
594
|
+
except Exception as e:
|
|
595
|
+
logger.error(f"Failed to get server health for {self.server_name}: {e}")
|
|
596
|
+
return {"status": "error", "error": str(e)}
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
def get_tools(tool_config: dict, alita_client, llm=None, memory_store=None) -> List[BaseTool]:
|
|
600
|
+
"""
|
|
601
|
+
Create MCP tools from configuration.
|
|
602
|
+
This function is called by the main tool loading system.
|
|
603
|
+
|
|
604
|
+
Args:
|
|
605
|
+
tool_config: Tool configuration dictionary
|
|
606
|
+
alita_client: Alita client instance
|
|
607
|
+
llm: Language model (not used by MCP tools)
|
|
608
|
+
memory_store: Memory store (not used by MCP tools)
|
|
609
|
+
|
|
610
|
+
Returns:
|
|
611
|
+
List of configured MCP tools
|
|
612
|
+
"""
|
|
613
|
+
settings = tool_config.get('settings', {})
|
|
614
|
+
|
|
615
|
+
# Extract required fields
|
|
616
|
+
server_name = settings.get('server_name')
|
|
617
|
+
url = settings.get('url')
|
|
618
|
+
headers = settings.get('headers')
|
|
619
|
+
|
|
620
|
+
if not server_name:
|
|
621
|
+
logger.error("MCP toolkit configuration missing required 'server_name'")
|
|
622
|
+
return []
|
|
623
|
+
|
|
624
|
+
if not url:
|
|
625
|
+
logger.error("MCP toolkit configuration missing required 'url'")
|
|
626
|
+
return []
|
|
627
|
+
|
|
628
|
+
return McpToolkit.get_toolkit(
|
|
629
|
+
server_name=server_name,
|
|
630
|
+
url=url,
|
|
631
|
+
headers=headers,
|
|
632
|
+
timeout=settings.get('timeout', 60),
|
|
633
|
+
discovery_mode=settings.get('discovery_mode', 'hybrid'),
|
|
634
|
+
discovery_interval=settings.get('discovery_interval', 300),
|
|
635
|
+
selected_tools=settings.get('selected_tools', []),
|
|
636
|
+
enable_caching=settings.get('enable_caching', True),
|
|
637
|
+
cache_ttl=settings.get('cache_ttl', 300),
|
|
638
|
+
toolkit_name=tool_config.get('toolkit_name'),
|
|
639
|
+
client=alita_client
|
|
640
|
+
).get_tools()
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
# Utility functions for managing MCP discovery
|
|
644
|
+
async def start_global_discovery():
|
|
645
|
+
"""Start the global MCP discovery service."""
|
|
646
|
+
from ..clients.mcp_discovery import init_discovery_service
|
|
647
|
+
await init_discovery_service()
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
async def stop_global_discovery():
|
|
651
|
+
"""Stop the global MCP discovery service."""
|
|
652
|
+
from ..clients.mcp_discovery import shutdown_discovery_service
|
|
653
|
+
await shutdown_discovery_service()
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
async def register_mcp_server_for_discovery(server_name: str, connection_config):
|
|
657
|
+
"""Register an MCP server for global discovery."""
|
|
658
|
+
from ..clients.mcp_discovery import get_discovery_service
|
|
659
|
+
service = get_discovery_service()
|
|
660
|
+
await service.register_server(server_name, connection_config)
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def get_all_discovered_servers():
|
|
664
|
+
"""Get status of all discovered servers."""
|
|
665
|
+
from ..clients.mcp_discovery import get_discovery_service
|
|
666
|
+
service = get_discovery_service()
|
|
667
|
+
return service.get_server_health()
|
|
@@ -12,6 +12,7 @@ from .datasource import DatasourcesToolkit
|
|
|
12
12
|
from .prompt import PromptToolkit
|
|
13
13
|
from .subgraph import SubgraphToolkit
|
|
14
14
|
from .vectorstore import VectorStoreToolkit
|
|
15
|
+
from .mcp import McpToolkit
|
|
15
16
|
from ..tools.mcp_server_tool import McpServerTool
|
|
16
17
|
from ..tools.sandbox import SandboxToolkit
|
|
17
18
|
from ..tools.image_generation import ImageGenerationToolkit
|
|
@@ -29,7 +30,8 @@ def get_toolkits():
|
|
|
29
30
|
MemoryToolkit.toolkit_config_schema(),
|
|
30
31
|
VectorStoreToolkit.toolkit_config_schema(),
|
|
31
32
|
SandboxToolkit.toolkit_config_schema(),
|
|
32
|
-
ImageGenerationToolkit.toolkit_config_schema()
|
|
33
|
+
ImageGenerationToolkit.toolkit_config_schema(),
|
|
34
|
+
McpToolkit.toolkit_config_schema()
|
|
33
35
|
]
|
|
34
36
|
|
|
35
37
|
return core_toolkits + community_toolkits() + alita_toolkits()
|
|
@@ -106,6 +108,11 @@ def get_tools(tools_list: list, alita_client, llm, memory_store: BaseStore = Non
|
|
|
106
108
|
llm=llm,
|
|
107
109
|
toolkit_name=tool.get('toolkit_name', ''),
|
|
108
110
|
**tool['settings']).get_tools())
|
|
111
|
+
elif tool['type'] == 'mcp':
|
|
112
|
+
tools.extend(McpToolkit.get_toolkit(
|
|
113
|
+
toolkit_name=tool.get('toolkit_name', ''),
|
|
114
|
+
client=alita_client,
|
|
115
|
+
**tool['settings']).get_tools())
|
|
109
116
|
except Exception as e:
|
|
110
117
|
logger.error(f"Error initializing toolkit for tool '{tool.get('name', 'unknown')}': {e}", exc_info=True)
|
|
111
118
|
if debug_mode:
|