alita-sdk 0.3.486__py3-none-any.whl → 0.3.515__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (124) hide show
  1. alita_sdk/cli/agent_loader.py +27 -6
  2. alita_sdk/cli/agents.py +10 -1
  3. alita_sdk/cli/inventory.py +12 -195
  4. alita_sdk/cli/tools/filesystem.py +95 -9
  5. alita_sdk/community/inventory/__init__.py +12 -0
  6. alita_sdk/community/inventory/toolkit.py +9 -5
  7. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  8. alita_sdk/configurations/ado.py +144 -0
  9. alita_sdk/configurations/confluence.py +76 -42
  10. alita_sdk/configurations/figma.py +76 -0
  11. alita_sdk/configurations/gitlab.py +2 -0
  12. alita_sdk/configurations/qtest.py +72 -1
  13. alita_sdk/configurations/report_portal.py +96 -0
  14. alita_sdk/configurations/sharepoint.py +148 -0
  15. alita_sdk/configurations/testio.py +83 -0
  16. alita_sdk/runtime/clients/artifact.py +2 -2
  17. alita_sdk/runtime/clients/client.py +64 -40
  18. alita_sdk/runtime/clients/sandbox_client.py +14 -0
  19. alita_sdk/runtime/langchain/assistant.py +48 -2
  20. alita_sdk/runtime/langchain/constants.py +3 -1
  21. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
  22. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  23. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +2 -1
  24. alita_sdk/runtime/langchain/document_loaders/constants.py +12 -7
  25. alita_sdk/runtime/langchain/langraph_agent.py +10 -10
  26. alita_sdk/runtime/langchain/utils.py +6 -1
  27. alita_sdk/runtime/toolkits/artifact.py +14 -5
  28. alita_sdk/runtime/toolkits/datasource.py +13 -6
  29. alita_sdk/runtime/toolkits/mcp.py +94 -219
  30. alita_sdk/runtime/toolkits/planning.py +13 -6
  31. alita_sdk/runtime/toolkits/tools.py +60 -25
  32. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  33. alita_sdk/runtime/tools/artifact.py +185 -23
  34. alita_sdk/runtime/tools/function.py +2 -1
  35. alita_sdk/runtime/tools/llm.py +155 -34
  36. alita_sdk/runtime/tools/mcp_remote_tool.py +25 -10
  37. alita_sdk/runtime/tools/mcp_server_tool.py +2 -4
  38. alita_sdk/runtime/tools/vectorstore_base.py +3 -3
  39. alita_sdk/runtime/utils/AlitaCallback.py +136 -21
  40. alita_sdk/runtime/utils/mcp_client.py +492 -0
  41. alita_sdk/runtime/utils/mcp_oauth.py +125 -8
  42. alita_sdk/runtime/utils/mcp_sse_client.py +35 -6
  43. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  44. alita_sdk/runtime/utils/toolkit_utils.py +7 -13
  45. alita_sdk/runtime/utils/utils.py +2 -0
  46. alita_sdk/tools/__init__.py +15 -0
  47. alita_sdk/tools/ado/repos/__init__.py +10 -12
  48. alita_sdk/tools/ado/test_plan/__init__.py +23 -8
  49. alita_sdk/tools/ado/wiki/__init__.py +24 -8
  50. alita_sdk/tools/ado/wiki/ado_wrapper.py +21 -7
  51. alita_sdk/tools/ado/work_item/__init__.py +24 -8
  52. alita_sdk/tools/advanced_jira_mining/__init__.py +10 -8
  53. alita_sdk/tools/aws/delta_lake/__init__.py +12 -9
  54. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  55. alita_sdk/tools/azure_ai/search/__init__.py +9 -7
  56. alita_sdk/tools/base/tool.py +5 -1
  57. alita_sdk/tools/base_indexer_toolkit.py +26 -1
  58. alita_sdk/tools/bitbucket/__init__.py +14 -10
  59. alita_sdk/tools/bitbucket/api_wrapper.py +50 -2
  60. alita_sdk/tools/browser/__init__.py +5 -4
  61. alita_sdk/tools/carrier/__init__.py +5 -6
  62. alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
  63. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +2 -0
  64. alita_sdk/tools/chunkers/universal_chunker.py +1 -0
  65. alita_sdk/tools/cloud/aws/__init__.py +9 -7
  66. alita_sdk/tools/cloud/azure/__init__.py +9 -7
  67. alita_sdk/tools/cloud/gcp/__init__.py +9 -7
  68. alita_sdk/tools/cloud/k8s/__init__.py +9 -7
  69. alita_sdk/tools/code/linter/__init__.py +9 -8
  70. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  71. alita_sdk/tools/code/sonar/__init__.py +9 -7
  72. alita_sdk/tools/confluence/__init__.py +15 -10
  73. alita_sdk/tools/confluence/api_wrapper.py +63 -14
  74. alita_sdk/tools/custom_open_api/__init__.py +11 -5
  75. alita_sdk/tools/elastic/__init__.py +10 -8
  76. alita_sdk/tools/elitea_base.py +387 -9
  77. alita_sdk/tools/figma/__init__.py +8 -7
  78. alita_sdk/tools/github/__init__.py +12 -14
  79. alita_sdk/tools/github/github_client.py +68 -2
  80. alita_sdk/tools/github/tool.py +5 -1
  81. alita_sdk/tools/gitlab/__init__.py +14 -11
  82. alita_sdk/tools/gitlab/api_wrapper.py +81 -1
  83. alita_sdk/tools/gitlab_org/__init__.py +9 -8
  84. alita_sdk/tools/google/bigquery/__init__.py +12 -12
  85. alita_sdk/tools/google/bigquery/tool.py +5 -1
  86. alita_sdk/tools/google_places/__init__.py +9 -8
  87. alita_sdk/tools/jira/__init__.py +15 -10
  88. alita_sdk/tools/keycloak/__init__.py +10 -8
  89. alita_sdk/tools/localgit/__init__.py +8 -3
  90. alita_sdk/tools/localgit/local_git.py +62 -54
  91. alita_sdk/tools/localgit/tool.py +5 -1
  92. alita_sdk/tools/memory/__init__.py +11 -3
  93. alita_sdk/tools/ocr/__init__.py +10 -8
  94. alita_sdk/tools/openapi/__init__.py +6 -2
  95. alita_sdk/tools/pandas/__init__.py +9 -7
  96. alita_sdk/tools/postman/__init__.py +10 -11
  97. alita_sdk/tools/pptx/__init__.py +9 -9
  98. alita_sdk/tools/qtest/__init__.py +9 -8
  99. alita_sdk/tools/rally/__init__.py +9 -8
  100. alita_sdk/tools/report_portal/__init__.py +11 -9
  101. alita_sdk/tools/salesforce/__init__.py +9 -9
  102. alita_sdk/tools/servicenow/__init__.py +10 -8
  103. alita_sdk/tools/sharepoint/__init__.py +9 -8
  104. alita_sdk/tools/sharepoint/api_wrapper.py +2 -2
  105. alita_sdk/tools/slack/__init__.py +8 -7
  106. alita_sdk/tools/sql/__init__.py +9 -8
  107. alita_sdk/tools/testio/__init__.py +9 -8
  108. alita_sdk/tools/testrail/__init__.py +10 -8
  109. alita_sdk/tools/utils/__init__.py +9 -4
  110. alita_sdk/tools/utils/text_operations.py +254 -0
  111. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +16 -18
  112. alita_sdk/tools/xray/__init__.py +10 -8
  113. alita_sdk/tools/yagmail/__init__.py +8 -3
  114. alita_sdk/tools/zephyr/__init__.py +8 -7
  115. alita_sdk/tools/zephyr_enterprise/__init__.py +10 -8
  116. alita_sdk/tools/zephyr_essential/__init__.py +9 -8
  117. alita_sdk/tools/zephyr_scale/__init__.py +9 -8
  118. alita_sdk/tools/zephyr_squad/__init__.py +9 -8
  119. {alita_sdk-0.3.486.dist-info → alita_sdk-0.3.515.dist-info}/METADATA +1 -1
  120. {alita_sdk-0.3.486.dist-info → alita_sdk-0.3.515.dist-info}/RECORD +124 -119
  121. {alita_sdk-0.3.486.dist-info → alita_sdk-0.3.515.dist-info}/WHEEL +0 -0
  122. {alita_sdk-0.3.486.dist-info → alita_sdk-0.3.515.dist-info}/entry_points.txt +0 -0
  123. {alita_sdk-0.3.486.dist-info → alita_sdk-0.3.515.dist-info}/licenses/LICENSE +0 -0
  124. {alita_sdk-0.3.486.dist-info → alita_sdk-0.3.515.dist-info}/top_level.txt +0 -0
@@ -15,9 +15,8 @@ from pydantic import BaseModel, ConfigDict, Field, SecretStr
15
15
  from ..tools.mcp_server_tool import McpServerTool
16
16
  from ..tools.mcp_remote_tool import McpRemoteTool
17
17
  from ..tools.mcp_inspect_tool import McpInspectTool
18
- from ...tools.utils import TOOLKIT_SPLITTER, clean_string
19
18
  from ..models.mcp_models import McpConnectionConfig
20
- from ..utils.mcp_sse_client import McpSseClient
19
+ from ..utils.mcp_client import McpClient
21
20
  from ..utils.mcp_oauth import (
22
21
  McpAuthorizationRequired,
23
22
  canonical_resource,
@@ -40,110 +39,6 @@ def safe_int(value, default):
40
39
  logger.warning(f"Invalid integer value '{value}', using default {default}")
41
40
  return default
42
41
 
43
- def optimize_tool_name(prefix: str, tool_name: str, max_total_length: int = 64) -> str:
44
- """
45
- Optimize tool name to fit within max_total_length while preserving meaning.
46
-
47
- Args:
48
- prefix: The toolkit prefix (already cleaned)
49
- tool_name: The original tool name
50
- max_total_length: Maximum total length for the full tool name (default: 64)
51
-
52
- Returns:
53
- Optimized full tool name in format: prefix___tool_name
54
- """
55
- splitter = TOOLKIT_SPLITTER
56
- splitter_len = len(splitter)
57
- prefix_len = len(prefix)
58
-
59
- # Calculate available space for tool name
60
- available_space = max_total_length - prefix_len - splitter_len
61
-
62
- if available_space <= 0:
63
- logger.error(f"Prefix '{prefix}' is too long ({prefix_len} chars), cannot create valid tool name")
64
- # Fallback: truncate prefix itself
65
- prefix = prefix[:max_total_length - splitter_len - 10] # Leave 10 chars for tool name
66
- available_space = max_total_length - len(prefix) - splitter_len
67
-
68
- # If tool name fits, use it as-is
69
- if len(tool_name) <= available_space:
70
- return f'{prefix}{splitter}{tool_name}'
71
-
72
- # Tool name is too long, need to optimize
73
- logger.debug(f"Tool name '{tool_name}' is too long ({len(tool_name)} chars), optimizing to fit {available_space} chars")
74
-
75
- # Split tool name into parts (handle camelCase, snake_case, and mixed)
76
- # First, split by underscores and hyphens
77
- parts = re.split(r'[_-]', tool_name)
78
-
79
- # Further split camelCase within each part
80
- all_parts = []
81
- for part in parts:
82
- # Insert underscore before uppercase letters (camelCase to snake_case)
83
- snake_case_part = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', part)
84
- all_parts.extend(snake_case_part.split('_'))
85
-
86
- # Filter out empty parts
87
- all_parts = [p for p in all_parts if p]
88
-
89
- # Remove redundant prefix words (case-insensitive comparison)
90
- # Only remove if prefix is meaningful (>= 3 chars) to avoid over-filtering
91
- prefix_lower = prefix.lower()
92
- filtered_parts = []
93
- for part in all_parts:
94
- part_lower = part.lower()
95
- # Skip if this part contains the prefix or the prefix contains this part
96
- # But only if both are meaningful (>= 3 chars)
97
- should_remove = False
98
- if len(prefix_lower) >= 3 and len(part_lower) >= 3:
99
- if part_lower in prefix_lower or prefix_lower in part_lower:
100
- should_remove = True
101
- logger.debug(f"Removing redundant part '{part}' (matches prefix '{prefix}')")
102
-
103
- if not should_remove:
104
- filtered_parts.append(part)
105
-
106
- # If we removed all parts, keep the original parts
107
- if not filtered_parts:
108
- filtered_parts = all_parts
109
-
110
- # Reconstruct tool name with filtered parts
111
- optimized_name = '_'.join(filtered_parts)
112
-
113
- # If still too long, truncate intelligently
114
- if len(optimized_name) > available_space:
115
- # Strategy: Keep beginning and end, as they often contain the most important info
116
- # For example: "projectalita_github_io_list_branches" -> "projectalita_list_branches"
117
-
118
- # Try removing middle parts first
119
- if len(filtered_parts) > 2:
120
- # Keep first and last parts, remove middle
121
- kept_parts = [filtered_parts[0], filtered_parts[-1]]
122
- optimized_name = '_'.join(kept_parts)
123
-
124
- # If still too long, add parts from the end until we run out of space
125
- if len(optimized_name) <= available_space and len(filtered_parts) > 2:
126
- for i in range(len(filtered_parts) - 2, 0, -1):
127
- candidate = '_'.join([filtered_parts[0]] + filtered_parts[i:])
128
- if len(candidate) <= available_space:
129
- optimized_name = candidate
130
- break
131
-
132
- # If still too long, just truncate
133
- if len(optimized_name) > available_space:
134
- # Try to truncate at word boundary
135
- truncated = optimized_name[:available_space]
136
- last_underscore = truncated.rfind('_')
137
- if last_underscore > available_space * 0.7: # Keep if we're not losing too much
138
- optimized_name = truncated[:last_underscore]
139
- else:
140
- optimized_name = truncated
141
-
142
- full_name = f'{prefix}{splitter}{optimized_name}'
143
- logger.info(f"Optimized tool name: '{tool_name}' ({len(tool_name)} chars) -> '{optimized_name}' ({len(optimized_name)} chars), full: '{full_name}' ({len(full_name)} chars)")
144
-
145
- return full_name
146
-
147
42
  class McpToolkit(BaseToolkit):
148
43
  """
149
44
  MCP Toolkit for connecting to a single remote MCP server and exposing its tools.
@@ -153,9 +48,6 @@ class McpToolkit(BaseToolkit):
153
48
  tools: List[BaseTool] = []
154
49
  toolkit_name: Optional[str] = None
155
50
 
156
- # Class variable (not Pydantic field) for tool name length limit
157
- toolkit_max_length: ClassVar[int] = 0 # No limit for MCP tool names
158
-
159
51
  def __getstate__(self):
160
52
  """Custom serialization for pickle compatibility."""
161
53
  state = self.__dict__.copy()
@@ -426,11 +318,6 @@ class McpToolkit(BaseToolkit):
426
318
  except Exception as e:
427
319
  logger.error(f"Direct discovery failed for MCP toolkit '{toolkit_name}': {e}", exc_info=True)
428
320
  logger.error(f"Discovery error details - URL: {connection_config.url}, Timeout: {timeout}s")
429
-
430
- # Check if the exception wraps McpAuthorizationRequired (can happen with asyncio)
431
- if hasattr(e, '__cause__') and isinstance(e.__cause__, McpAuthorizationRequired):
432
- logger.info(f"Found wrapped McpAuthorizationRequired, re-raising")
433
- raise e.__cause__
434
321
 
435
322
  # For new MCP toolkits (no client), don't silently return empty - surface the error
436
323
  # This helps users understand why tool discovery failed
@@ -464,37 +351,36 @@ class McpToolkit(BaseToolkit):
464
351
  toolkit_name: str,
465
352
  connection_config: McpConnectionConfig,
466
353
  timeout: int
467
- ) -> List[Dict[str, Any]]:
354
+ ) -> tuple[List[Dict[str, Any]], Optional[str]]:
468
355
  """
469
356
  Discover tools and prompts from MCP server using SSE client.
470
- Returns list of tool/prompt dictionaries with name, description, and inputSchema.
471
- Prompts are converted to tools that can be invoked.
357
+
358
+ Returns:
359
+ Tuple of (tool_list, server_session_id) - session_id may be server-provided
472
360
  """
473
- session_id = connection_config.session_id
361
+ initial_session_id = connection_config.session_id
474
362
 
475
- if not session_id:
476
- logger.warning(f"[MCP Session] No session_id provided for '{toolkit_name}' - server may require it")
477
- logger.warning(f"[MCP Session] Frontend should generate a UUID and include it with mcp_tokens")
363
+ if not initial_session_id:
364
+ logger.warning(f"[MCP Session] No session_id provided for '{toolkit_name}' - will generate one")
478
365
 
479
366
  # Run async discovery in sync context
480
367
  try:
481
- all_tools = asyncio.run(
368
+ all_tools, server_session_id = asyncio.run(
482
369
  cls._discover_tools_async(
483
370
  toolkit_name=toolkit_name,
484
371
  connection_config=connection_config,
485
372
  timeout=timeout
486
373
  )
487
374
  )
488
- return all_tools, session_id
375
+ # Return tools and the session_id (server-provided or generated)
376
+ logger.info(f"[MCP Session] Final session_id for '{toolkit_name}': {server_session_id}")
377
+ return all_tools, server_session_id
489
378
  except McpAuthorizationRequired:
490
379
  # Re-raise auth required exceptions directly
491
380
  logger.info(f"[MCP SSE] Authorization required for '{toolkit_name}'")
492
381
  raise
493
382
  except Exception as e:
494
383
  logger.error(f"[MCP SSE] Discovery failed for '{toolkit_name}': {e}")
495
- # Check if the exception wraps McpAuthorizationRequired
496
- if hasattr(e, '__cause__') and isinstance(e.__cause__, McpAuthorizationRequired):
497
- raise e.__cause__
498
384
  raise
499
385
 
500
386
  @classmethod
@@ -503,9 +389,12 @@ class McpToolkit(BaseToolkit):
503
389
  toolkit_name: str,
504
390
  connection_config: McpConnectionConfig,
505
391
  timeout: int
506
- ) -> List[Dict[str, Any]]:
392
+ ) -> tuple[List[Dict[str, Any]], Optional[str]]:
507
393
  """
508
394
  Async implementation of tool discovery using SSE client.
395
+
396
+ Returns:
397
+ Tuple of (tool_list, server_session_id)
509
398
  """
510
399
  all_tools = []
511
400
  session_id = connection_config.session_id
@@ -517,65 +406,74 @@ class McpToolkit(BaseToolkit):
517
406
  session_id = str(uuid.uuid4())
518
407
  logger.info(f"[MCP SSE] Generated temporary session_id for OAuth: {session_id}")
519
408
 
520
- logger.info(f"[MCP SSE] Discovering from {connection_config.url} with session {session_id}")
409
+ logger.info(f"[MCP] Discovering from {connection_config.url} with session {session_id}")
521
410
 
522
411
  # Prepare headers
523
412
  headers = {}
524
413
  if connection_config.headers:
525
414
  headers.update(connection_config.headers)
526
415
 
527
- # Create SSE client
528
- client = McpSseClient(
416
+ # Create unified MCP client (auto-detects SSE vs Streamable HTTP)
417
+ client = McpClient(
529
418
  url=connection_config.url,
530
419
  session_id=session_id,
531
420
  headers=headers,
532
421
  timeout=timeout
533
422
  )
534
423
 
535
- # Initialize MCP session
536
- await client.initialize()
537
- logger.info(f"[MCP SSE] Session initialized for '{toolkit_name}'")
538
-
539
- # Discover tools
540
- tools = await client.list_tools()
541
- all_tools.extend(tools)
542
- logger.info(f"[MCP SSE] Discovered {len(tools)} tools from '{toolkit_name}'")
543
-
544
- # Discover prompts
545
- try:
546
- prompts = await client.list_prompts()
547
- # Convert prompts to tool format
548
- for prompt in prompts:
549
- prompt_tool = {
550
- "name": f"prompt_{prompt.get('name', 'unnamed')}",
551
- "description": prompt.get('description', f"Execute prompt: {prompt.get('name')}"),
552
- "inputSchema": {
553
- "type": "object",
554
- "properties": {
555
- "arguments": {
556
- "type": "object",
557
- "description": "Arguments for the prompt template",
558
- "properties": {
559
- arg.get("name"): {
560
- "type": "string",
561
- "description": arg.get("description", ""),
562
- "required": arg.get("required", False)
424
+ server_session_id = None
425
+ async with client:
426
+ # Initialize MCP session
427
+ await client.initialize()
428
+ logger.info(f"[MCP] Session initialized for '{toolkit_name}' (transport={client.detected_transport})")
429
+
430
+ # Capture server-provided session_id (from mcp-session-id header)
431
+ server_session_id = client.server_session_id
432
+ if server_session_id:
433
+ logger.info(f"[MCP] Server provided session_id: {server_session_id}")
434
+
435
+ # Discover tools
436
+ tools = await client.list_tools()
437
+ all_tools.extend(tools)
438
+ logger.info(f"[MCP] Discovered {len(tools)} tools from '{toolkit_name}'")
439
+
440
+ # Discover prompts
441
+ try:
442
+ prompts = await client.list_prompts()
443
+ # Convert prompts to tool format
444
+ for prompt in prompts:
445
+ prompt_tool = {
446
+ "name": f"prompt_{prompt.get('name', 'unnamed')}",
447
+ "description": prompt.get('description', f"Execute prompt: {prompt.get('name')}"),
448
+ "inputSchema": {
449
+ "type": "object",
450
+ "properties": {
451
+ "arguments": {
452
+ "type": "object",
453
+ "description": "Arguments for the prompt template",
454
+ "properties": {
455
+ arg.get("name"): {
456
+ "type": "string",
457
+ "description": arg.get("description", ""),
458
+ "required": arg.get("required", False)
459
+ }
460
+ for arg in prompt.get("arguments", [])
563
461
  }
564
- for arg in prompt.get("arguments", [])
565
462
  }
566
463
  }
567
- }
568
- },
569
- "_mcp_type": "prompt",
570
- "_mcp_prompt_name": prompt.get('name')
571
- }
572
- all_tools.append(prompt_tool)
573
- logger.info(f"[MCP SSE] Discovered {len(prompts)} prompts from '{toolkit_name}'")
574
- except Exception as e:
575
- logger.warning(f"[MCP SSE] Failed to discover prompts: {e}")
464
+ },
465
+ "_mcp_type": "prompt",
466
+ "_mcp_prompt_name": prompt.get('name')
467
+ }
468
+ all_tools.append(prompt_tool)
469
+ logger.info(f"[MCP] Discovered {len(prompts)} prompts from '{toolkit_name}'")
470
+ except Exception as e:
471
+ logger.warning(f"[MCP] Failed to discover prompts: {e}")
576
472
 
577
- logger.info(f"[MCP SSE] Total discovered {len(all_tools)} items from '{toolkit_name}'")
578
- return all_tools
473
+ logger.info(f"[MCP] Total discovered {len(all_tools)} items from '{toolkit_name}'")
474
+ # Return tools and server-provided session_id (use server's if available, else the one we sent)
475
+ final_session_id = server_session_id or session_id
476
+ return all_tools, final_session_id
579
477
 
580
478
  @classmethod
581
479
  def _create_tool_from_dict(
@@ -589,28 +487,23 @@ class McpToolkit(BaseToolkit):
589
487
  ) -> Optional[BaseTool]:
590
488
  """Create a BaseTool from a tool/prompt dictionary (from direct HTTP discovery)."""
591
489
  try:
592
- # Store toolkit_max_length in local variable to avoid contextual access issues
593
- max_length_value = cls.toolkit_max_length
594
-
595
- # Clean toolkit name for prefixing
596
- clean_prefix = clean_string(toolkit_name, max_length_value)
597
-
598
- # Optimize tool name to fit within 64 character limit
599
- full_tool_name = optimize_tool_name(clean_prefix, tool_dict.get("name", "unknown"))
490
+ # Use original tool name directly
491
+ tool_name = tool_dict.get("name", "unknown")
600
492
 
601
493
  # Check if this is a prompt (converted to tool)
602
494
  is_prompt = tool_dict.get("_mcp_type") == "prompt"
603
495
  item_type = "prompt" if is_prompt else "tool"
604
496
 
605
- # Build description and ensure it doesn't exceed 1000 characters
606
- description = f"MCP {item_type} '{tool_dict.get('name')}' from toolkit '{toolkit_name}': {tool_dict.get('description', '')}"
497
+ # Build description with toolkit context and ensure it doesn't exceed 1000 characters
498
+ base_description = tool_dict.get('description', '')
499
+ description = f"{base_description}\nToolkit: {toolkit_name} ({connection_config.url})"
607
500
  if len(description) > 1000:
608
501
  description = description[:997] + "..."
609
- logger.debug(f"Trimmed description for tool '{tool_dict.get('name')}' from {len(description)} to 1000 chars")
502
+ logger.debug(f"Trimmed description for tool '{tool_name}' to 1000 chars")
610
503
 
611
504
  # Use McpRemoteTool for remote MCP servers (HTTP/SSE)
612
505
  return McpRemoteTool(
613
- name=full_tool_name,
506
+ name=tool_name,
614
507
  description=description,
615
508
  args_schema=McpServerTool.create_pydantic_model_from_schema(
616
509
  tool_dict.get("inputSchema", {})
@@ -622,11 +515,11 @@ class McpToolkit(BaseToolkit):
622
515
  tool_timeout_sec=timeout,
623
516
  is_prompt=is_prompt,
624
517
  prompt_name=tool_dict.get("_mcp_prompt_name") if is_prompt else None,
625
- original_tool_name=tool_dict.get('name'), # Store original name for MCP server invocation
518
+ original_tool_name=tool_name, # Store original name for MCP server invocation
626
519
  session_id=session_id # Pass session ID for stateful SSE servers
627
520
  )
628
521
  except Exception as e:
629
- logger.error(f"Failed to create MCP tool '{tool_dict.get('name')}' from toolkit '{toolkit_name}': {e}")
522
+ logger.error(f"Failed to create MCP tool '{tool_name}' from toolkit '{toolkit_name}': {e}")
630
523
  return None
631
524
 
632
525
  @classmethod
@@ -685,7 +578,7 @@ class McpToolkit(BaseToolkit):
685
578
  # We don't have full connection config in static mode, so create a basic one
686
579
  # The inspection tool will work as long as the server is accessible
687
580
  inspection_tool = McpInspectTool(
688
- name=f"{clean_string(toolkit_name, 50)}{TOOLKIT_SPLITTER}mcp_inspect",
581
+ name="mcp_inspect",
689
582
  server_name=toolkit_name,
690
583
  server_url="", # Will be populated by the client if available
691
584
  description=f"Inspect available tools, prompts, and resources from MCP toolkit '{toolkit_name}'"
@@ -707,22 +600,17 @@ class McpToolkit(BaseToolkit):
707
600
  ) -> Optional[BaseTool]:
708
601
  """Create a BaseTool from discovered metadata."""
709
602
  try:
710
- # Store toolkit_max_length in local variable to avoid contextual access issues
711
- max_length_value = cls.toolkit_max_length
603
+ # Use original tool name directly
604
+ tool_name = tool_metadata.name
712
605
 
713
- # Clean server name for prefixing (use tool_metadata.server instead of toolkit_name)
714
- clean_prefix = clean_string(tool_metadata.server, max_length_value)
715
- # Optimize tool name to fit within 64 character limit
716
- full_tool_name = optimize_tool_name(clean_prefix, tool_metadata.name)
717
-
718
- # Build description and ensure it doesn't exceed 1000 characters
719
- description = f"MCP tool '{tool_metadata.name}' from server '{tool_metadata.server}': {tool_metadata.description}"
606
+ # Build description with toolkit context and ensure it doesn't exceed 1000 characters
607
+ description = f"{tool_metadata.description}\nToolkit: {toolkit_name}"
720
608
  if len(description) > 1000:
721
609
  description = description[:997] + "..."
722
- logger.debug(f"Trimmed description for tool '{tool_metadata.name}' from {len(description)} to 1000 chars")
610
+ logger.debug(f"Trimmed description for tool '{tool_name}' to 1000 chars")
723
611
 
724
612
  return McpServerTool(
725
- name=full_tool_name,
613
+ name=tool_name,
726
614
  description=description,
727
615
  args_schema=McpServerTool.create_pydantic_model_from_schema(tool_metadata.input_schema),
728
616
  client=client,
@@ -730,7 +618,7 @@ class McpToolkit(BaseToolkit):
730
618
  tool_timeout_sec=timeout
731
619
  )
732
620
  except Exception as e:
733
- logger.error(f"Failed to create MCP tool '{tool_metadata.name}' from server '{tool_metadata.server}': {e}")
621
+ logger.error(f"Failed to create MCP tool '{tool_name}' from server '{tool_metadata.server}': {e}")
734
622
  return None
735
623
 
736
624
  @classmethod
@@ -743,23 +631,18 @@ class McpToolkit(BaseToolkit):
743
631
  ) -> Optional[BaseTool]:
744
632
  """Create a single MCP tool."""
745
633
  try:
746
- # Store toolkit_max_length in local variable to avoid contextual access issues
747
- max_length_value = cls.toolkit_max_length
748
-
749
- # Clean toolkit name for prefixing
750
- clean_prefix = clean_string(toolkit_name, max_length_value)
751
-
752
- # Optimize tool name to fit within 64 character limit
753
- full_tool_name = optimize_tool_name(clean_prefix, available_tool["name"])
634
+ # Use original tool name directly
635
+ tool_name = available_tool["name"]
754
636
 
755
- # Build description and ensure it doesn't exceed 1000 characters
756
- description = f"MCP tool '{available_tool['name']}' from toolkit '{toolkit_name}': {available_tool.get('description', '')}"
637
+ # Build description with toolkit context and ensure it doesn't exceed 1000 characters
638
+ base_description = available_tool.get('description', '')
639
+ description = f"{base_description}\nToolkit: {toolkit_name}"
757
640
  if len(description) > 1000:
758
641
  description = description[:997] + "..."
759
- logger.debug(f"Trimmed description for tool '{available_tool['name']}' from {len(description)} to 1000 chars")
642
+ logger.debug(f"Trimmed description for tool '{tool_name}' to 1000 chars")
760
643
 
761
644
  return McpServerTool(
762
- name=full_tool_name,
645
+ name=tool_name,
763
646
  description=description,
764
647
  args_schema=McpServerTool.create_pydantic_model_from_schema(
765
648
  available_tool.get("inputSchema", {})
@@ -769,7 +652,7 @@ class McpToolkit(BaseToolkit):
769
652
  tool_timeout_sec=timeout
770
653
  )
771
654
  except Exception as e:
772
- logger.error(f"Failed to create MCP tool '{available_tool.get('name')}' from toolkit '{toolkit_name}': {e}")
655
+ logger.error(f"Failed to create MCP tool '{tool_name}' from toolkit '{toolkit_name}': {e}")
773
656
  return None
774
657
 
775
658
  @classmethod
@@ -780,16 +663,8 @@ class McpToolkit(BaseToolkit):
780
663
  ) -> Optional[BaseTool]:
781
664
  """Create the inspection tool for the MCP toolkit."""
782
665
  try:
783
- # Store toolkit_max_length in local variable to avoid contextual access issues
784
- max_length_value = cls.toolkit_max_length
785
-
786
- # Clean toolkit name for prefixing
787
- clean_prefix = clean_string(toolkit_name, max_length_value)
788
-
789
- full_tool_name = f'{clean_prefix}{TOOLKIT_SPLITTER}mcp_inspect'
790
-
791
666
  return McpInspectTool(
792
- name=full_tool_name,
667
+ name="mcp_inspect",
793
668
  server_name=toolkit_name,
794
669
  server_url=connection_config.url,
795
670
  server_headers=connection_config.headers,
@@ -16,7 +16,7 @@ from pydantic.fields import FieldInfo
16
16
 
17
17
  from ..tools.planning import PlanningWrapper
18
18
  from ...tools.base.tool import BaseAction
19
- from ...tools.utils import clean_string, TOOLKIT_SPLITTER, get_max_toolkit_length
19
+ from ...tools.utils import clean_string, get_max_toolkit_length
20
20
 
21
21
 
22
22
  class PlanningToolkit(BaseToolkit):
@@ -94,7 +94,9 @@ class PlanningToolkit(BaseToolkit):
94
94
  "label": "Planning",
95
95
  "description": "Tools for managing multi-step execution plans with progress tracking. Uses PostgreSQL when configured, filesystem otherwise.",
96
96
  "icon_url": None,
97
- "max_length": PlanningToolkit._toolkit_max_length
97
+ "max_length": PlanningToolkit._toolkit_max_length,
98
+ "categories": ["planning", "internal_tool"],
99
+ "extra_categories": ["task management", "todo", "progress tracking"]
98
100
  }
99
101
  }
100
102
  )
@@ -148,8 +150,8 @@ class PlanningToolkit(BaseToolkit):
148
150
  plan_callback=plan_callback,
149
151
  )
150
152
 
151
- # Build tool name prefix
152
- prefix = clean_string(toolkit_name, cls._toolkit_max_length) + TOOLKIT_SPLITTER if toolkit_name else ''
153
+ # Use clean toolkit name for context (max 1000 chars in description)
154
+ toolkit_context = f" [Toolkit: {clean_string(toolkit_name, 0)}]" if toolkit_name else ''
153
155
 
154
156
  # Create tools from wrapper
155
157
  available_tools = wrapper.get_available_tools()
@@ -157,10 +159,15 @@ class PlanningToolkit(BaseToolkit):
157
159
  if tool["name"] not in selected_tools:
158
160
  continue
159
161
 
162
+ # Add toolkit context to description with character limit
163
+ description = tool["description"]
164
+ if toolkit_context and len(description + toolkit_context) <= 1000:
165
+ description = description + toolkit_context
166
+
160
167
  tools.append(BaseAction(
161
168
  api_wrapper=wrapper,
162
- name=prefix + tool["name"],
163
- description=tool["description"],
169
+ name=tool["name"],
170
+ description=description,
164
171
  args_schema=tool["args_schema"]
165
172
  ))
166
173