alita-sdk 0.3.443__py3-none-any.whl → 0.3.445__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/runtime/models/mcp_models.py +4 -0
- alita_sdk/runtime/toolkits/mcp.py +146 -11
- alita_sdk/runtime/toolkits/tools.py +32 -4
- alita_sdk/runtime/tools/mcp_remote_tool.py +92 -14
- alita_sdk/runtime/utils/mcp_oauth.py +25 -10
- {alita_sdk-0.3.443.dist-info → alita_sdk-0.3.445.dist-info}/METADATA +1 -1
- {alita_sdk-0.3.443.dist-info → alita_sdk-0.3.445.dist-info}/RECORD +10 -10
- {alita_sdk-0.3.443.dist-info → alita_sdk-0.3.445.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.443.dist-info → alita_sdk-0.3.445.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.443.dist-info → alita_sdk-0.3.445.dist-info}/top_level.txt +0 -0
|
@@ -19,6 +19,10 @@ class McpConnectionConfig(BaseModel):
|
|
|
19
19
|
default=None,
|
|
20
20
|
description="HTTP headers for the connection (JSON object)"
|
|
21
21
|
)
|
|
22
|
+
session_id: Optional[str] = Field(
|
|
23
|
+
default=None,
|
|
24
|
+
description="MCP session ID for stateful SSE servers (managed by client)"
|
|
25
|
+
)
|
|
22
26
|
|
|
23
27
|
@validator('url')
|
|
24
28
|
def validate_url(cls, v):
|
|
@@ -340,9 +340,14 @@ class McpToolkit(BaseToolkit):
|
|
|
340
340
|
logger.error(f"Headers must be a dictionary or JSON string, got: {type(headers)}")
|
|
341
341
|
raise ValueError(f"Headers must be a dictionary or JSON string, got: {type(headers)}")
|
|
342
342
|
|
|
343
|
+
# Extract session_id from kwargs if provided
|
|
344
|
+
session_id = kwargs.get('session_id')
|
|
345
|
+
if session_id:
|
|
346
|
+
logger.info(f"[MCP Session] Using provided session ID for toolkit '{toolkit_name}': {session_id}")
|
|
347
|
+
|
|
343
348
|
# Create MCP connection configuration
|
|
344
349
|
try:
|
|
345
|
-
connection_config = McpConnectionConfig(url=url, headers=parsed_headers)
|
|
350
|
+
connection_config = McpConnectionConfig(url=url, headers=parsed_headers, session_id=session_id)
|
|
346
351
|
except Exception as e:
|
|
347
352
|
logger.error(f"Invalid MCP connection configuration: {e}")
|
|
348
353
|
raise ValueError(f"Invalid MCP connection configuration: {e}")
|
|
@@ -382,7 +387,7 @@ class McpToolkit(BaseToolkit):
|
|
|
382
387
|
logger.info(f"Discovering tools from MCP toolkit '{toolkit_name}' at {connection_config.url}")
|
|
383
388
|
|
|
384
389
|
# Use synchronous HTTP discovery for toolkit initialization
|
|
385
|
-
tool_metadata_list = cls._discover_tools_sync(
|
|
390
|
+
tool_metadata_list, session_id = cls._discover_tools_sync(
|
|
386
391
|
toolkit_name=toolkit_name,
|
|
387
392
|
connection_config=connection_config,
|
|
388
393
|
timeout=timeout
|
|
@@ -397,13 +402,15 @@ class McpToolkit(BaseToolkit):
|
|
|
397
402
|
]
|
|
398
403
|
|
|
399
404
|
# Create BaseTool instances from discovered metadata
|
|
405
|
+
# Use session_id from connection_config (passed from UI) instead of discovery
|
|
400
406
|
for tool_metadata in tool_metadata_list:
|
|
401
407
|
server_tool = cls._create_tool_from_dict(
|
|
402
408
|
tool_dict=tool_metadata,
|
|
403
409
|
toolkit_name=toolkit_name,
|
|
404
410
|
connection_config=connection_config,
|
|
405
411
|
timeout=timeout,
|
|
406
|
-
client=client
|
|
412
|
+
client=client,
|
|
413
|
+
session_id=connection_config.session_id # Use session from connection config
|
|
407
414
|
)
|
|
408
415
|
|
|
409
416
|
if server_tool:
|
|
@@ -455,12 +462,19 @@ class McpToolkit(BaseToolkit):
|
|
|
455
462
|
"""
|
|
456
463
|
all_tools = []
|
|
457
464
|
|
|
465
|
+
# Note: We don't initialize MCP session here because:
|
|
466
|
+
# 1. Discovery happens before we have OAuth tokens
|
|
467
|
+
# 2. Sessions are initialized on-demand during first tool call (when we have auth)
|
|
468
|
+
# 3. Sessions are stored in UI sessionStorage and passed back via mcp_tokens
|
|
469
|
+
session_id = None
|
|
470
|
+
|
|
458
471
|
# Discover regular tools
|
|
459
472
|
tools_data = cls._discover_mcp_endpoint(
|
|
460
473
|
endpoint="tools/list",
|
|
461
474
|
toolkit_name=toolkit_name,
|
|
462
475
|
connection_config=connection_config,
|
|
463
|
-
timeout=timeout
|
|
476
|
+
timeout=timeout,
|
|
477
|
+
session_id=session_id
|
|
464
478
|
)
|
|
465
479
|
all_tools.extend(tools_data)
|
|
466
480
|
logger.info(f"Discovered {len(tools_data)} tools from MCP toolkit '{toolkit_name}'")
|
|
@@ -471,7 +485,8 @@ class McpToolkit(BaseToolkit):
|
|
|
471
485
|
endpoint="prompts/list",
|
|
472
486
|
toolkit_name=toolkit_name,
|
|
473
487
|
connection_config=connection_config,
|
|
474
|
-
timeout=timeout
|
|
488
|
+
timeout=timeout,
|
|
489
|
+
session_id=session_id
|
|
475
490
|
)
|
|
476
491
|
# Convert prompts to tool format
|
|
477
492
|
for prompt in prompts_data:
|
|
@@ -504,7 +519,107 @@ class McpToolkit(BaseToolkit):
|
|
|
504
519
|
logger.warning(f"Failed to discover prompts from MCP toolkit '{toolkit_name}': {e}")
|
|
505
520
|
|
|
506
521
|
logger.info(f"Total discovered {len(all_tools)} tools+prompts from MCP toolkit '{toolkit_name}'")
|
|
507
|
-
return all_tools
|
|
522
|
+
return all_tools, session_id
|
|
523
|
+
|
|
524
|
+
@classmethod
|
|
525
|
+
def _initialize_mcp_session(
|
|
526
|
+
cls,
|
|
527
|
+
toolkit_name: str,
|
|
528
|
+
connection_config: McpConnectionConfig,
|
|
529
|
+
timeout: int,
|
|
530
|
+
extra_headers: Optional[Dict[str, str]] = None
|
|
531
|
+
) -> Optional[str]:
|
|
532
|
+
"""
|
|
533
|
+
Initialize an MCP session for stateful SSE servers.
|
|
534
|
+
Returns sessionId if successful, None if server doesn't require sessions.
|
|
535
|
+
|
|
536
|
+
Note: This session should be stored by the caller (e.g., UI sessionStorage)
|
|
537
|
+
and passed back for subsequent requests. Sessions should NOT be cached
|
|
538
|
+
on the backend for security reasons (multi-tenant environment).
|
|
539
|
+
|
|
540
|
+
Args:
|
|
541
|
+
toolkit_name: Name of the toolkit
|
|
542
|
+
connection_config: MCP connection configuration
|
|
543
|
+
timeout: Request timeout in seconds
|
|
544
|
+
extra_headers: Additional headers (e.g., Authorization) to include
|
|
545
|
+
"""
|
|
546
|
+
import time
|
|
547
|
+
|
|
548
|
+
mcp_request = {
|
|
549
|
+
"jsonrpc": "2.0",
|
|
550
|
+
"id": f"initialize_{int(time.time())}",
|
|
551
|
+
"method": "initialize",
|
|
552
|
+
"params": {
|
|
553
|
+
"protocolVersion": "2024-11-05",
|
|
554
|
+
"capabilities": {
|
|
555
|
+
"roots": {"listChanged": True},
|
|
556
|
+
"sampling": {}
|
|
557
|
+
},
|
|
558
|
+
"clientInfo": {
|
|
559
|
+
"name": "Alita MCP Client",
|
|
560
|
+
"version": "1.0.0"
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
headers = {
|
|
566
|
+
"Content-Type": "application/json",
|
|
567
|
+
"Accept": "application/json, text/event-stream"
|
|
568
|
+
}
|
|
569
|
+
if connection_config.headers:
|
|
570
|
+
headers.update(connection_config.headers)
|
|
571
|
+
if extra_headers:
|
|
572
|
+
headers.update(extra_headers)
|
|
573
|
+
logger.debug(f"[MCP Session] Added extra headers: {list(extra_headers.keys())}")
|
|
574
|
+
|
|
575
|
+
try:
|
|
576
|
+
logger.info(f"[MCP Session] Attempting to initialize session for {connection_config.url}")
|
|
577
|
+
logger.debug(f"[MCP Session] Initialize request: {mcp_request}")
|
|
578
|
+
response = requests.post(
|
|
579
|
+
connection_config.url,
|
|
580
|
+
json=mcp_request,
|
|
581
|
+
headers=headers,
|
|
582
|
+
timeout=timeout
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
logger.info(f"[MCP Session] Initialize response status: {response.status_code}")
|
|
586
|
+
if response.status_code == 200:
|
|
587
|
+
# Parse the response to extract sessionId
|
|
588
|
+
content_type = response.headers.get('Content-Type', '')
|
|
589
|
+
logger.debug(f"[MCP Session] Response Content-Type: {content_type}")
|
|
590
|
+
|
|
591
|
+
if 'text/event-stream' in content_type:
|
|
592
|
+
data = cls._parse_sse_response(response.text)
|
|
593
|
+
elif 'application/json' in content_type:
|
|
594
|
+
data = response.json()
|
|
595
|
+
else:
|
|
596
|
+
logger.warning(f"[MCP Session] Unexpected Content-Type during initialize: {content_type}")
|
|
597
|
+
logger.debug(f"[MCP Session] Response text: {response.text[:500]}")
|
|
598
|
+
return None
|
|
599
|
+
|
|
600
|
+
logger.debug(f"[MCP Session] Parsed response: {data}")
|
|
601
|
+
|
|
602
|
+
# Extract sessionId from response
|
|
603
|
+
result = data.get("result", {})
|
|
604
|
+
session_id = result.get("sessionId")
|
|
605
|
+
if session_id:
|
|
606
|
+
logger.info(f"[MCP Session] ✓ Session initialized for '{toolkit_name}': {session_id}")
|
|
607
|
+
logger.info(f"[MCP Session] This sessionId should be stored by the caller and sent back for subsequent requests")
|
|
608
|
+
return session_id
|
|
609
|
+
else:
|
|
610
|
+
logger.info(f"[MCP Session] No sessionId in initialize response for '{toolkit_name}' - server may not require sessions")
|
|
611
|
+
logger.debug(f"[MCP Session] Full result: {result}")
|
|
612
|
+
return None
|
|
613
|
+
else:
|
|
614
|
+
logger.warning(f"[MCP Session] Initialize returned {response.status_code} for '{toolkit_name}' - server may not support sessions")
|
|
615
|
+
logger.debug(f"[MCP Session] Response: {response.text[:500]}")
|
|
616
|
+
return None
|
|
617
|
+
|
|
618
|
+
except Exception as e:
|
|
619
|
+
logger.warning(f"[MCP Session] Failed to initialize MCP session for '{toolkit_name}': {e} - proceeding without session")
|
|
620
|
+
import traceback
|
|
621
|
+
logger.debug(f"[MCP Session] Traceback: {traceback.format_exc()}")
|
|
622
|
+
return None
|
|
508
623
|
|
|
509
624
|
@classmethod
|
|
510
625
|
def _discover_mcp_endpoint(
|
|
@@ -512,7 +627,8 @@ class McpToolkit(BaseToolkit):
|
|
|
512
627
|
endpoint: str,
|
|
513
628
|
toolkit_name: str,
|
|
514
629
|
connection_config: McpConnectionConfig,
|
|
515
|
-
timeout: int
|
|
630
|
+
timeout: int,
|
|
631
|
+
session_id: Optional[str] = None
|
|
516
632
|
) -> List[Dict[str, Any]]:
|
|
517
633
|
"""
|
|
518
634
|
Discover items from a specific MCP endpoint (tools/list or prompts/list).
|
|
@@ -535,10 +651,19 @@ class McpToolkit(BaseToolkit):
|
|
|
535
651
|
if connection_config.headers:
|
|
536
652
|
headers.update(connection_config.headers)
|
|
537
653
|
|
|
654
|
+
# Add sessionId to URL if provided (for stateful SSE servers)
|
|
655
|
+
url = connection_config.url
|
|
656
|
+
if session_id:
|
|
657
|
+
separator = '&' if '?' in url else '?'
|
|
658
|
+
url = f"{url}{separator}sessionId={session_id}"
|
|
659
|
+
logger.info(f"[MCP Session] Using session {session_id} for {endpoint} request")
|
|
660
|
+
else:
|
|
661
|
+
logger.debug(f"[MCP Session] No session ID available for {endpoint} request - server may not require sessions")
|
|
662
|
+
|
|
538
663
|
try:
|
|
539
|
-
logger.debug(f"Sending MCP {endpoint} request to {
|
|
664
|
+
logger.debug(f"Sending MCP {endpoint} request to {url}")
|
|
540
665
|
response = requests.post(
|
|
541
|
-
|
|
666
|
+
url,
|
|
542
667
|
json=mcp_request,
|
|
543
668
|
headers=headers,
|
|
544
669
|
timeout=timeout
|
|
@@ -558,6 +683,14 @@ class McpToolkit(BaseToolkit):
|
|
|
558
683
|
metadata = {}
|
|
559
684
|
metadata['authorization_servers'] = inferred_servers
|
|
560
685
|
logger.info(f"Inferred authorization servers for {connection_config.url}: {inferred_servers}")
|
|
686
|
+
|
|
687
|
+
# Fetch OAuth authorization server metadata from the inferred server
|
|
688
|
+
# This avoids CORS issues in the frontend
|
|
689
|
+
from alita_sdk.runtime.utils.mcp_oauth import fetch_oauth_authorization_server_metadata
|
|
690
|
+
auth_server_metadata = fetch_oauth_authorization_server_metadata(inferred_servers[0], timeout=timeout)
|
|
691
|
+
if auth_server_metadata:
|
|
692
|
+
metadata['oauth_authorization_server'] = auth_server_metadata
|
|
693
|
+
logger.info(f"Fetched OAuth metadata for {inferred_servers[0]}")
|
|
561
694
|
|
|
562
695
|
raise McpAuthorizationRequired(
|
|
563
696
|
message=f"MCP server {connection_config.url} requires OAuth authorization",
|
|
@@ -635,7 +768,8 @@ class McpToolkit(BaseToolkit):
|
|
|
635
768
|
toolkit_name: str,
|
|
636
769
|
connection_config: McpConnectionConfig,
|
|
637
770
|
timeout: int,
|
|
638
|
-
client
|
|
771
|
+
client,
|
|
772
|
+
session_id: Optional[str] = None
|
|
639
773
|
) -> Optional[BaseTool]:
|
|
640
774
|
"""Create a BaseTool from a tool/prompt dictionary (from direct HTTP discovery)."""
|
|
641
775
|
try:
|
|
@@ -672,7 +806,8 @@ class McpToolkit(BaseToolkit):
|
|
|
672
806
|
tool_timeout_sec=timeout,
|
|
673
807
|
is_prompt=is_prompt,
|
|
674
808
|
prompt_name=tool_dict.get("_mcp_prompt_name") if is_prompt else None,
|
|
675
|
-
original_tool_name=tool_dict.get('name') # Store original name for MCP server invocation
|
|
809
|
+
original_tool_name=tool_dict.get('name'), # Store original name for MCP server invocation
|
|
810
|
+
session_id=session_id # Pass session ID for stateful SSE servers
|
|
676
811
|
)
|
|
677
812
|
except Exception as e:
|
|
678
813
|
logger.error(f"Failed to create MCP tool '{tool_dict.get('name')}' from toolkit '{toolkit_name}': {e}")
|
|
@@ -113,13 +113,41 @@ def get_tools(tools_list: list, alita_client, llm, memory_store: BaseStore = Non
|
|
|
113
113
|
settings = dict(tool['settings'])
|
|
114
114
|
url = settings.get('url')
|
|
115
115
|
headers = settings.get('headers')
|
|
116
|
-
|
|
116
|
+
token_data = None
|
|
117
|
+
session_id = None
|
|
117
118
|
if mcp_tokens and url:
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
canonical_url = canonical_resource(url)
|
|
120
|
+
logger.info(f"[MCP Auth] Looking for token for URL: {url}")
|
|
121
|
+
logger.info(f"[MCP Auth] Canonical URL: {canonical_url}")
|
|
122
|
+
logger.info(f"[MCP Auth] Available tokens: {list(mcp_tokens.keys())}")
|
|
123
|
+
token_data = mcp_tokens.get(canonical_url)
|
|
124
|
+
if token_data:
|
|
125
|
+
logger.info(f"[MCP Auth] Found token data for {canonical_url}")
|
|
126
|
+
# Handle both old format (string) and new format (dict with access_token and session_id)
|
|
127
|
+
if isinstance(token_data, dict):
|
|
128
|
+
access_token = token_data.get('access_token')
|
|
129
|
+
session_id = token_data.get('session_id')
|
|
130
|
+
logger.info(f"[MCP Auth] Token data: access_token={'present' if access_token else 'missing'}, session_id={session_id or 'none'}")
|
|
131
|
+
else:
|
|
132
|
+
# Backward compatibility: treat as plain token string
|
|
133
|
+
access_token = token_data
|
|
134
|
+
logger.info(f"[MCP Auth] Using legacy token format (string)")
|
|
135
|
+
else:
|
|
136
|
+
access_token = None
|
|
137
|
+
logger.warning(f"[MCP Auth] No token found for {canonical_url}")
|
|
138
|
+
else:
|
|
139
|
+
access_token = None
|
|
140
|
+
|
|
141
|
+
if access_token:
|
|
120
142
|
merged_headers = dict(headers) if headers else {}
|
|
121
|
-
merged_headers.setdefault('Authorization', f'Bearer {
|
|
143
|
+
merged_headers.setdefault('Authorization', f'Bearer {access_token}')
|
|
122
144
|
settings['headers'] = merged_headers
|
|
145
|
+
logger.info(f"[MCP Auth] Added Authorization header for {url}")
|
|
146
|
+
|
|
147
|
+
# Pass session_id to MCP toolkit if available
|
|
148
|
+
if session_id:
|
|
149
|
+
settings['session_id'] = session_id
|
|
150
|
+
logger.info(f"[MCP Auth] Passing session_id to toolkit: {session_id}")
|
|
123
151
|
tools.extend(McpToolkit.get_toolkit(
|
|
124
152
|
toolkit_name=tool.get('toolkit_name', ''),
|
|
125
153
|
client=alita_client,
|
|
@@ -12,7 +12,7 @@ from concurrent.futures import ThreadPoolExecutor
|
|
|
12
12
|
from typing import Any, Dict, Optional
|
|
13
13
|
|
|
14
14
|
from .mcp_server_tool import McpServerTool
|
|
15
|
-
from pydantic import Field
|
|
15
|
+
from pydantic import Field, computed_field
|
|
16
16
|
from ..utils.mcp_oauth import (
|
|
17
17
|
McpAuthorizationRequired,
|
|
18
18
|
canonical_resource,
|
|
@@ -36,6 +36,13 @@ class McpRemoteTool(McpServerTool):
|
|
|
36
36
|
original_tool_name: Optional[str] = Field(default=None, description="Original tool name from MCP server (before optimization)")
|
|
37
37
|
is_prompt: bool = False # Flag to indicate if this is a prompt tool
|
|
38
38
|
prompt_name: Optional[str] = None # Original prompt name if this is a prompt
|
|
39
|
+
session_id: Optional[str] = Field(default=None, description="MCP session ID for stateful SSE servers")
|
|
40
|
+
|
|
41
|
+
@computed_field
|
|
42
|
+
@property
|
|
43
|
+
def metadata(self) -> dict:
|
|
44
|
+
"""Return tool metadata including session information for UI."""
|
|
45
|
+
return self.get_session_metadata()
|
|
39
46
|
|
|
40
47
|
def __getstate__(self):
|
|
41
48
|
"""Custom serialization for pickle compatibility."""
|
|
@@ -112,14 +119,21 @@ class McpRemoteTool(McpServerTool):
|
|
|
112
119
|
if self.server_headers:
|
|
113
120
|
headers.update(self.server_headers)
|
|
114
121
|
|
|
122
|
+
# Add sessionId to URL if this is a stateful SSE server
|
|
123
|
+
url = self.server_url
|
|
124
|
+
if self.session_id:
|
|
125
|
+
separator = '&' if '?' in url else '?'
|
|
126
|
+
url = f"{url}{separator}sessionId={self.session_id}"
|
|
127
|
+
logger.debug(f"Using session URL: {url}")
|
|
128
|
+
|
|
115
129
|
# Execute the HTTP request
|
|
116
130
|
timeout = aiohttp.ClientTimeout(total=self.tool_timeout_sec)
|
|
117
131
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
118
132
|
try:
|
|
119
|
-
logger.debug(f"Calling remote MCP tool '{tool_name_for_server}' (optimized name: '{self.name}') at {
|
|
133
|
+
logger.debug(f"Calling remote MCP tool '{tool_name_for_server}' (optimized name: '{self.name}') at {url}")
|
|
120
134
|
logger.debug(f"Request: {json.dumps(mcp_request, indent=2)}")
|
|
121
135
|
|
|
122
|
-
async with session.post(
|
|
136
|
+
async with session.post(url, json=mcp_request, headers=headers) as response:
|
|
123
137
|
auth_header = response.headers.get('WWW-Authenticate') or response.headers.get('Www-Authenticate')
|
|
124
138
|
if response.status == 401:
|
|
125
139
|
resource_metadata_url = extract_resource_metadata_url(auth_header, self.server_url)
|
|
@@ -140,6 +154,14 @@ class McpRemoteTool(McpServerTool):
|
|
|
140
154
|
metadata = {}
|
|
141
155
|
metadata['authorization_servers'] = inferred_servers
|
|
142
156
|
logger.info(f"Inferred authorization servers for {self.server_url}: {inferred_servers}")
|
|
157
|
+
|
|
158
|
+
# Fetch OAuth authorization server metadata from the inferred server
|
|
159
|
+
# This avoids CORS issues in the frontend
|
|
160
|
+
from alita_sdk.runtime.utils.mcp_oauth import fetch_oauth_authorization_server_metadata
|
|
161
|
+
auth_server_metadata = fetch_oauth_authorization_server_metadata(inferred_servers[0], timeout=self.tool_timeout_sec)
|
|
162
|
+
if auth_server_metadata:
|
|
163
|
+
metadata['oauth_authorization_server'] = auth_server_metadata
|
|
164
|
+
logger.info(f"Fetched OAuth metadata for {inferred_servers[0]}")
|
|
143
165
|
|
|
144
166
|
raise McpAuthorizationRequired(
|
|
145
167
|
message=f"MCP server {self.server_url} requires OAuth authorization",
|
|
@@ -151,19 +173,66 @@ class McpRemoteTool(McpServerTool):
|
|
|
151
173
|
tool_name=self.name,
|
|
152
174
|
)
|
|
153
175
|
|
|
176
|
+
# Check for errors first
|
|
177
|
+
data = None
|
|
154
178
|
if response.status != 200:
|
|
155
179
|
error_text = await response.text()
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
180
|
+
|
|
181
|
+
# Check if this is a "Missing sessionId" error
|
|
182
|
+
if response.status == 404 and "sessionId" in error_text and not self.session_id:
|
|
183
|
+
logger.info(f"[MCP Session] Server requires session but none provided - attempting to initialize")
|
|
184
|
+
# Try to initialize session with current auth headers
|
|
185
|
+
from ..toolkits.mcp import McpToolkit
|
|
186
|
+
from ..models.mcp_models import McpConnectionConfig
|
|
187
|
+
|
|
188
|
+
connection_config = McpConnectionConfig(
|
|
189
|
+
url=self.server_url,
|
|
190
|
+
headers=self.server_headers
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# Initialize session with auth headers
|
|
194
|
+
session_id = McpToolkit._initialize_mcp_session(
|
|
195
|
+
toolkit_name=self.server,
|
|
196
|
+
connection_config=connection_config,
|
|
197
|
+
timeout=self.tool_timeout_sec,
|
|
198
|
+
extra_headers=headers # Include Authorization header
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
if session_id:
|
|
202
|
+
# Retry the request with session
|
|
203
|
+
self.session_id = session_id
|
|
204
|
+
separator = '&' if '?' in self.server_url else '?'
|
|
205
|
+
retry_url = f"{self.server_url}{separator}sessionId={session_id}"
|
|
206
|
+
logger.info(f"[MCP Session] Retrying tool call with new session: {session_id}")
|
|
207
|
+
logger.info(f"[MCP Session] Created session - server: {canonical_resource(self.server_url)}, session_id: {session_id}")
|
|
208
|
+
|
|
209
|
+
async with session.post(retry_url, json=mcp_request, headers=headers) as retry_response:
|
|
210
|
+
if retry_response.status != 200:
|
|
211
|
+
retry_error = await retry_response.text()
|
|
212
|
+
raise Exception(f"HTTP {retry_response.status} (after session init): {retry_error}")
|
|
213
|
+
|
|
214
|
+
# Process successful retry response
|
|
215
|
+
content_type = retry_response.headers.get('Content-Type', '')
|
|
216
|
+
if 'text/event-stream' in content_type:
|
|
217
|
+
text = await retry_response.text()
|
|
218
|
+
data = self._parse_sse(text)
|
|
219
|
+
else:
|
|
220
|
+
data = await retry_response.json()
|
|
221
|
+
else:
|
|
222
|
+
raise Exception(f"HTTP {response.status}: {error_text} (session initialization failed)")
|
|
223
|
+
else:
|
|
224
|
+
raise Exception(f"HTTP {response.status}: {error_text}")
|
|
225
|
+
|
|
226
|
+
# Parse response if not already done in retry logic
|
|
227
|
+
if data is None:
|
|
228
|
+
content_type = response.headers.get('Content-Type', '')
|
|
229
|
+
if 'text/event-stream' in content_type:
|
|
230
|
+
# Parse SSE format
|
|
231
|
+
text = await response.text()
|
|
232
|
+
data = self._parse_sse(text)
|
|
233
|
+
else:
|
|
234
|
+
# Parse regular JSON
|
|
235
|
+
data = await response.json()
|
|
167
236
|
|
|
168
237
|
logger.debug(f"Response: {json.dumps(data, indent=2)}")
|
|
169
238
|
|
|
@@ -216,3 +285,12 @@ class McpRemoteTool(McpServerTool):
|
|
|
216
285
|
json_str = line[5:].strip()
|
|
217
286
|
return json.loads(json_str)
|
|
218
287
|
raise ValueError("No data found in SSE response")
|
|
288
|
+
|
|
289
|
+
def get_session_metadata(self) -> dict:
|
|
290
|
+
"""Return session metadata to be included in tool responses."""
|
|
291
|
+
if self.session_id:
|
|
292
|
+
return {
|
|
293
|
+
'mcp_session_id': self.session_id,
|
|
294
|
+
'mcp_server_url': canonical_resource(self.server_url)
|
|
295
|
+
}
|
|
296
|
+
return {}
|
|
@@ -63,6 +63,28 @@ def extract_resource_metadata_url(www_authenticate: Optional[str], server_url: O
|
|
|
63
63
|
return None
|
|
64
64
|
|
|
65
65
|
|
|
66
|
+
def fetch_oauth_authorization_server_metadata(base_url: str, timeout: int = 10) -> Optional[Dict[str, Any]]:
|
|
67
|
+
"""
|
|
68
|
+
Fetch OAuth authorization server metadata from well-known endpoints.
|
|
69
|
+
Tries both oauth-authorization-server and openid-configuration discovery endpoints.
|
|
70
|
+
"""
|
|
71
|
+
discovery_endpoints = [
|
|
72
|
+
f"{base_url}/.well-known/oauth-authorization-server",
|
|
73
|
+
f"{base_url}/.well-known/openid-configuration",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
for endpoint in discovery_endpoints:
|
|
77
|
+
try:
|
|
78
|
+
resp = requests.get(endpoint, timeout=timeout)
|
|
79
|
+
if resp.status_code == 200:
|
|
80
|
+
return resp.json()
|
|
81
|
+
except Exception as exc:
|
|
82
|
+
logger.debug(f"Failed to fetch OAuth metadata from {endpoint}: {exc}")
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
|
|
66
88
|
def infer_authorization_servers_from_realm(www_authenticate: Optional[str], server_url: str) -> Optional[list]:
|
|
67
89
|
"""
|
|
68
90
|
Infer authorization server URLs from WWW-Authenticate realm or server URL.
|
|
@@ -84,16 +106,9 @@ def infer_authorization_servers_from_realm(www_authenticate: Optional[str], serv
|
|
|
84
106
|
parsed = urlparse(server_url)
|
|
85
107
|
base_url = f"{parsed.scheme}://{parsed.netloc}"
|
|
86
108
|
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
# Standard OAuth 2.0 / 2.1 discovery endpoints
|
|
91
|
-
authorization_servers.append(f"{base_url}/.well-known/oauth-authorization-server")
|
|
92
|
-
authorization_servers.append(f"{base_url}/.well-known/openid-configuration")
|
|
93
|
-
else:
|
|
94
|
-
# If no realm or different realm, still try standard endpoints
|
|
95
|
-
authorization_servers.append(f"{base_url}/.well-known/oauth-authorization-server")
|
|
96
|
-
authorization_servers.append(f"{base_url}/.well-known/openid-configuration")
|
|
109
|
+
# Return the base authorization server URL (not the discovery endpoint)
|
|
110
|
+
# The client will append .well-known paths when fetching metadata
|
|
111
|
+
authorization_servers.append(base_url)
|
|
97
112
|
|
|
98
113
|
return authorization_servers if authorization_servers else None
|
|
99
114
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alita_sdk
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.445
|
|
4
4
|
Summary: SDK for building langchain agents using resources from Alita
|
|
5
5
|
Author-email: Artem Rozumenko <artyom.rozumenko@gmail.com>, Mikalai Biazruchka <mikalai_biazruchka@epam.com>, Roman Mitusov <roman_mitusov@epam.com>, Ivan Krakhmaliuk <lifedj27@gmail.com>, Artem Dubrovskiy <ad13box@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -97,16 +97,16 @@ alita_sdk/runtime/langchain/tools/bdd_parser/feature_types.py,sha256=l3AdjSQnNv1
|
|
|
97
97
|
alita_sdk/runtime/langchain/tools/bdd_parser/parser.py,sha256=1H1Nd_OH5Wx8A5YV1zUghBxo613yPptZ7fqNo8Eg48M,17289
|
|
98
98
|
alita_sdk/runtime/llms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
99
99
|
alita_sdk/runtime/llms/preloaded.py,sha256=3AaUbZK3d8fvxAQMjR3ftOoYa0SnkCOL1EvdvDCXIHE,11321
|
|
100
|
-
alita_sdk/runtime/models/mcp_models.py,sha256=
|
|
100
|
+
alita_sdk/runtime/models/mcp_models.py,sha256=rbWCAtF8Jjb7uNgQHhVWyDttXaqPNbRLL087Lf0AjNU,2301
|
|
101
101
|
alita_sdk/runtime/toolkits/__init__.py,sha256=IenSyI2SrXSFuiWT7c8YO_mRFLVE_zNge61U4bpoyPw,631
|
|
102
102
|
alita_sdk/runtime/toolkits/application.py,sha256=HHAKgwKOckxc7EQG-AV7rz4POOzQJKFRr7AGEjmLudE,2688
|
|
103
103
|
alita_sdk/runtime/toolkits/artifact.py,sha256=YChNCX4QhVpaQG7Jk4TS-Wl0Aruc4slQ2K21zh9nNO0,3176
|
|
104
104
|
alita_sdk/runtime/toolkits/configurations.py,sha256=kIDAlnryPQfbZyFxV-9SzN2-Vefzx06TX1BBdIIpN90,141
|
|
105
105
|
alita_sdk/runtime/toolkits/datasource.py,sha256=qk78OdPoReYPCWwahfkKLbKc4pfsu-061oXRryFLP6I,2498
|
|
106
|
-
alita_sdk/runtime/toolkits/mcp.py,sha256=
|
|
106
|
+
alita_sdk/runtime/toolkits/mcp.py,sha256=bBfV-iOJ481SzMt14vMgs8BqZzzXwpa3111cGSB3810,46391
|
|
107
107
|
alita_sdk/runtime/toolkits/prompt.py,sha256=WIpTkkVYWqIqOWR_LlSWz3ug8uO9tm5jJ7aZYdiGRn0,1192
|
|
108
108
|
alita_sdk/runtime/toolkits/subgraph.py,sha256=wwUK8JjPXkGzyVZ3tAukmvST6eGbqx_U11rpnmbrvtg,2105
|
|
109
|
-
alita_sdk/runtime/toolkits/tools.py,sha256=
|
|
109
|
+
alita_sdk/runtime/toolkits/tools.py,sha256=74R0SadMiUJFUe3U5_PJcxzw5m1KMMBiVfVeiowZK6I,13434
|
|
110
110
|
alita_sdk/runtime/toolkits/vectorstore.py,sha256=BGppQADa1ZiLO17fC0uCACTTEvPHlodEDYEzUcBRbAA,2901
|
|
111
111
|
alita_sdk/runtime/tools/__init__.py,sha256=Fx7iHqkzA90-KfjdcUUzMUI_7kDarjuTsSpSzOW2pN0,568
|
|
112
112
|
alita_sdk/runtime/tools/agent.py,sha256=m98QxOHwnCRTT9j18Olbb5UPS8-ZGeQaGiUyZJSyFck,3162
|
|
@@ -122,7 +122,7 @@ alita_sdk/runtime/tools/llm.py,sha256=iRG_wU4T01LRsjEMPZe5Uah7LiMqDc-vspwkMuQtlt
|
|
|
122
122
|
alita_sdk/runtime/tools/loop.py,sha256=uds0WhZvwMxDVFI6MZHrcmMle637cQfBNg682iLxoJA,8335
|
|
123
123
|
alita_sdk/runtime/tools/loop_output.py,sha256=U4hO9PCQgWlXwOq6jdmCGbegtAxGAPXObSxZQ3z38uk,8069
|
|
124
124
|
alita_sdk/runtime/tools/mcp_inspect_tool.py,sha256=38X8euaxDbEGjcfp6ElvExZalpZun6QEr6ZEW4nU5pQ,11496
|
|
125
|
-
alita_sdk/runtime/tools/mcp_remote_tool.py,sha256=
|
|
125
|
+
alita_sdk/runtime/tools/mcp_remote_tool.py,sha256=12-EaNqvU0qbJ0ur-L34Ssi4J0n0QyNrozI4gSyHaSU,15294
|
|
126
126
|
alita_sdk/runtime/tools/mcp_server_tool.py,sha256=-xO3H6BM63KaIV1CdcQKPVE0WPigiqOgFZDX7m2_yGs,4419
|
|
127
127
|
alita_sdk/runtime/tools/pgvector_search.py,sha256=NN2BGAnq4SsDHIhUcFZ8d_dbEOM8QwB0UwpsWCYruXU,11692
|
|
128
128
|
alita_sdk/runtime/tools/prompt.py,sha256=nJafb_e5aOM1Rr3qGFCR-SKziU9uCsiP2okIMs9PppM,741
|
|
@@ -136,7 +136,7 @@ alita_sdk/runtime/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
|
|
|
136
136
|
alita_sdk/runtime/utils/constants.py,sha256=Xntx1b_uxUzT4clwqHA_U6K8y5bBqf_4lSQwXdcWrp4,13586
|
|
137
137
|
alita_sdk/runtime/utils/evaluate.py,sha256=iM1P8gzBLHTuSCe85_Ng_h30m52hFuGuhNXJ7kB1tgI,1872
|
|
138
138
|
alita_sdk/runtime/utils/logging.py,sha256=svPyiW8ztDfhqHFITv5FBCj8UhLxz6hWcqGIY6wpJJE,3331
|
|
139
|
-
alita_sdk/runtime/utils/mcp_oauth.py,sha256=
|
|
139
|
+
alita_sdk/runtime/utils/mcp_oauth.py,sha256=Ynoa_C_G5WXL_tlcdol2wBLgQauyvIPX0isCJKsvWMs,6151
|
|
140
140
|
alita_sdk/runtime/utils/save_dataframe.py,sha256=i-E1wp-t4wb17Zq3nA3xYwgSILjoXNizaQAA9opWvxY,1576
|
|
141
141
|
alita_sdk/runtime/utils/streamlit.py,sha256=yIb4YIGH8HRAXZtZlywjxI07Xdcb5eUt7rMA-elFTdc,107261
|
|
142
142
|
alita_sdk/runtime/utils/toolkit_runtime.py,sha256=MU63Fpxj0b5_r1IUUc0Q3-PN9VwL7rUxp2MRR4tmYR8,5136
|
|
@@ -360,8 +360,8 @@ alita_sdk/tools/zephyr_scale/api_wrapper.py,sha256=kT0TbmMvuKhDUZc0i7KO18O38JM9S
|
|
|
360
360
|
alita_sdk/tools/zephyr_squad/__init__.py,sha256=0ne8XLJEQSLOWfzd2HdnqOYmQlUliKHbBED5kW_Vias,2895
|
|
361
361
|
alita_sdk/tools/zephyr_squad/api_wrapper.py,sha256=kmw_xol8YIYFplBLWTqP_VKPRhL_1ItDD0_vXTe_UuI,14906
|
|
362
362
|
alita_sdk/tools/zephyr_squad/zephyr_squad_cloud_client.py,sha256=R371waHsms4sllHCbijKYs90C-9Yu0sSR3N4SUfQOgU,5066
|
|
363
|
-
alita_sdk-0.3.
|
|
364
|
-
alita_sdk-0.3.
|
|
365
|
-
alita_sdk-0.3.
|
|
366
|
-
alita_sdk-0.3.
|
|
367
|
-
alita_sdk-0.3.
|
|
363
|
+
alita_sdk-0.3.445.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
364
|
+
alita_sdk-0.3.445.dist-info/METADATA,sha256=eTg9o5vUnq9FRBtfxGhWQEvcFmx1G5UvbJNpwLU1V3Y,19071
|
|
365
|
+
alita_sdk-0.3.445.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
366
|
+
alita_sdk-0.3.445.dist-info/top_level.txt,sha256=0vJYy5p_jK6AwVb1aqXr7Kgqgk3WDtQ6t5C-XI9zkmg,10
|
|
367
|
+
alita_sdk-0.3.445.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|