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.
- alita_sdk/cli/agent_loader.py +27 -6
- alita_sdk/cli/agents.py +10 -1
- alita_sdk/cli/inventory.py +12 -195
- alita_sdk/cli/tools/filesystem.py +95 -9
- alita_sdk/community/inventory/__init__.py +12 -0
- alita_sdk/community/inventory/toolkit.py +9 -5
- alita_sdk/community/inventory/toolkit_utils.py +176 -0
- alita_sdk/configurations/ado.py +144 -0
- alita_sdk/configurations/confluence.py +76 -42
- alita_sdk/configurations/figma.py +76 -0
- alita_sdk/configurations/gitlab.py +2 -0
- alita_sdk/configurations/qtest.py +72 -1
- alita_sdk/configurations/report_portal.py +96 -0
- alita_sdk/configurations/sharepoint.py +148 -0
- alita_sdk/configurations/testio.py +83 -0
- alita_sdk/runtime/clients/artifact.py +2 -2
- alita_sdk/runtime/clients/client.py +64 -40
- alita_sdk/runtime/clients/sandbox_client.py +14 -0
- alita_sdk/runtime/langchain/assistant.py +48 -2
- alita_sdk/runtime/langchain/constants.py +3 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +2 -1
- alita_sdk/runtime/langchain/document_loaders/constants.py +12 -7
- alita_sdk/runtime/langchain/langraph_agent.py +10 -10
- alita_sdk/runtime/langchain/utils.py +6 -1
- alita_sdk/runtime/toolkits/artifact.py +14 -5
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +94 -219
- alita_sdk/runtime/toolkits/planning.py +13 -6
- alita_sdk/runtime/toolkits/tools.py +60 -25
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/artifact.py +185 -23
- alita_sdk/runtime/tools/function.py +2 -1
- alita_sdk/runtime/tools/llm.py +155 -34
- alita_sdk/runtime/tools/mcp_remote_tool.py +25 -10
- alita_sdk/runtime/tools/mcp_server_tool.py +2 -4
- alita_sdk/runtime/tools/vectorstore_base.py +3 -3
- alita_sdk/runtime/utils/AlitaCallback.py +136 -21
- alita_sdk/runtime/utils/mcp_client.py +492 -0
- alita_sdk/runtime/utils/mcp_oauth.py +125 -8
- alita_sdk/runtime/utils/mcp_sse_client.py +35 -6
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/toolkit_utils.py +7 -13
- alita_sdk/runtime/utils/utils.py +2 -0
- alita_sdk/tools/__init__.py +15 -0
- alita_sdk/tools/ado/repos/__init__.py +10 -12
- alita_sdk/tools/ado/test_plan/__init__.py +23 -8
- alita_sdk/tools/ado/wiki/__init__.py +24 -8
- alita_sdk/tools/ado/wiki/ado_wrapper.py +21 -7
- alita_sdk/tools/ado/work_item/__init__.py +24 -8
- alita_sdk/tools/advanced_jira_mining/__init__.py +10 -8
- alita_sdk/tools/aws/delta_lake/__init__.py +12 -9
- alita_sdk/tools/aws/delta_lake/tool.py +5 -1
- alita_sdk/tools/azure_ai/search/__init__.py +9 -7
- alita_sdk/tools/base/tool.py +5 -1
- alita_sdk/tools/base_indexer_toolkit.py +26 -1
- alita_sdk/tools/bitbucket/__init__.py +14 -10
- alita_sdk/tools/bitbucket/api_wrapper.py +50 -2
- alita_sdk/tools/browser/__init__.py +5 -4
- alita_sdk/tools/carrier/__init__.py +5 -6
- alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +2 -0
- alita_sdk/tools/chunkers/universal_chunker.py +1 -0
- alita_sdk/tools/cloud/aws/__init__.py +9 -7
- alita_sdk/tools/cloud/azure/__init__.py +9 -7
- alita_sdk/tools/cloud/gcp/__init__.py +9 -7
- alita_sdk/tools/cloud/k8s/__init__.py +9 -7
- alita_sdk/tools/code/linter/__init__.py +9 -8
- alita_sdk/tools/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code/sonar/__init__.py +9 -7
- alita_sdk/tools/confluence/__init__.py +15 -10
- alita_sdk/tools/confluence/api_wrapper.py +63 -14
- alita_sdk/tools/custom_open_api/__init__.py +11 -5
- alita_sdk/tools/elastic/__init__.py +10 -8
- alita_sdk/tools/elitea_base.py +387 -9
- alita_sdk/tools/figma/__init__.py +8 -7
- alita_sdk/tools/github/__init__.py +12 -14
- alita_sdk/tools/github/github_client.py +68 -2
- alita_sdk/tools/github/tool.py +5 -1
- alita_sdk/tools/gitlab/__init__.py +14 -11
- alita_sdk/tools/gitlab/api_wrapper.py +81 -1
- alita_sdk/tools/gitlab_org/__init__.py +9 -8
- alita_sdk/tools/google/bigquery/__init__.py +12 -12
- alita_sdk/tools/google/bigquery/tool.py +5 -1
- alita_sdk/tools/google_places/__init__.py +9 -8
- alita_sdk/tools/jira/__init__.py +15 -10
- alita_sdk/tools/keycloak/__init__.py +10 -8
- alita_sdk/tools/localgit/__init__.py +8 -3
- alita_sdk/tools/localgit/local_git.py +62 -54
- alita_sdk/tools/localgit/tool.py +5 -1
- alita_sdk/tools/memory/__init__.py +11 -3
- alita_sdk/tools/ocr/__init__.py +10 -8
- alita_sdk/tools/openapi/__init__.py +6 -2
- alita_sdk/tools/pandas/__init__.py +9 -7
- alita_sdk/tools/postman/__init__.py +10 -11
- alita_sdk/tools/pptx/__init__.py +9 -9
- alita_sdk/tools/qtest/__init__.py +9 -8
- alita_sdk/tools/rally/__init__.py +9 -8
- alita_sdk/tools/report_portal/__init__.py +11 -9
- alita_sdk/tools/salesforce/__init__.py +9 -9
- alita_sdk/tools/servicenow/__init__.py +10 -8
- alita_sdk/tools/sharepoint/__init__.py +9 -8
- alita_sdk/tools/sharepoint/api_wrapper.py +2 -2
- alita_sdk/tools/slack/__init__.py +8 -7
- alita_sdk/tools/sql/__init__.py +9 -8
- alita_sdk/tools/testio/__init__.py +9 -8
- alita_sdk/tools/testrail/__init__.py +10 -8
- alita_sdk/tools/utils/__init__.py +9 -4
- alita_sdk/tools/utils/text_operations.py +254 -0
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +16 -18
- alita_sdk/tools/xray/__init__.py +10 -8
- alita_sdk/tools/yagmail/__init__.py +8 -3
- alita_sdk/tools/zephyr/__init__.py +8 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +10 -8
- alita_sdk/tools/zephyr_essential/__init__.py +9 -8
- alita_sdk/tools/zephyr_scale/__init__.py +9 -8
- alita_sdk/tools/zephyr_squad/__init__.py +9 -8
- {alita_sdk-0.3.486.dist-info → alita_sdk-0.3.515.dist-info}/METADATA +1 -1
- {alita_sdk-0.3.486.dist-info → alita_sdk-0.3.515.dist-info}/RECORD +124 -119
- {alita_sdk-0.3.486.dist-info → alita_sdk-0.3.515.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.486.dist-info → alita_sdk-0.3.515.dist-info}/entry_points.txt +0 -0
- {alita_sdk-0.3.486.dist-info → alita_sdk-0.3.515.dist-info}/licenses/LICENSE +0 -0
- {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.
|
|
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
|
-
|
|
471
|
-
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
Tuple of (tool_list, server_session_id) - session_id may be server-provided
|
|
472
360
|
"""
|
|
473
|
-
|
|
361
|
+
initial_session_id = connection_config.session_id
|
|
474
362
|
|
|
475
|
-
if not
|
|
476
|
-
logger.warning(f"[MCP Session] No session_id provided for '{toolkit_name}' -
|
|
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
|
-
|
|
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
|
|
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
|
|
528
|
-
client =
|
|
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
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
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
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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
|
|
578
|
-
|
|
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
|
-
#
|
|
593
|
-
|
|
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
|
-
|
|
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 '{
|
|
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=
|
|
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=
|
|
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 '{
|
|
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=
|
|
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
|
-
#
|
|
711
|
-
|
|
603
|
+
# Use original tool name directly
|
|
604
|
+
tool_name = tool_metadata.name
|
|
712
605
|
|
|
713
|
-
#
|
|
714
|
-
|
|
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 '{
|
|
610
|
+
logger.debug(f"Trimmed description for tool '{tool_name}' to 1000 chars")
|
|
723
611
|
|
|
724
612
|
return McpServerTool(
|
|
725
|
-
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 '{
|
|
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
|
-
#
|
|
747
|
-
|
|
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
|
-
|
|
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 '{
|
|
642
|
+
logger.debug(f"Trimmed description for tool '{tool_name}' to 1000 chars")
|
|
760
643
|
|
|
761
644
|
return McpServerTool(
|
|
762
|
-
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 '{
|
|
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=
|
|
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,
|
|
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
|
-
#
|
|
152
|
-
|
|
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=
|
|
163
|
-
description=
|
|
169
|
+
name=tool["name"],
|
|
170
|
+
description=description,
|
|
164
171
|
args_schema=tool["args_schema"]
|
|
165
172
|
))
|
|
166
173
|
|