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.
@@ -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: