alita-sdk 0.3.462__py3-none-any.whl → 0.3.627__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +258 -0
- alita_sdk/cli/agent_executor.py +15 -3
- alita_sdk/cli/agent_loader.py +56 -8
- alita_sdk/cli/agent_ui.py +93 -31
- alita_sdk/cli/agents.py +2274 -230
- alita_sdk/cli/callbacks.py +96 -25
- alita_sdk/cli/cli.py +10 -1
- alita_sdk/cli/config.py +162 -9
- alita_sdk/cli/context/__init__.py +30 -0
- alita_sdk/cli/context/cleanup.py +198 -0
- alita_sdk/cli/context/manager.py +731 -0
- alita_sdk/cli/context/message.py +285 -0
- alita_sdk/cli/context/strategies.py +289 -0
- alita_sdk/cli/context/token_estimation.py +127 -0
- alita_sdk/cli/input_handler.py +419 -0
- alita_sdk/cli/inventory.py +1073 -0
- alita_sdk/cli/testcases/__init__.py +94 -0
- alita_sdk/cli/testcases/data_generation.py +119 -0
- alita_sdk/cli/testcases/discovery.py +96 -0
- alita_sdk/cli/testcases/executor.py +84 -0
- alita_sdk/cli/testcases/logger.py +85 -0
- alita_sdk/cli/testcases/parser.py +172 -0
- alita_sdk/cli/testcases/prompts.py +91 -0
- alita_sdk/cli/testcases/reporting.py +125 -0
- alita_sdk/cli/testcases/setup.py +108 -0
- alita_sdk/cli/testcases/test_runner.py +282 -0
- alita_sdk/cli/testcases/utils.py +39 -0
- alita_sdk/cli/testcases/validation.py +90 -0
- alita_sdk/cli/testcases/workflow.py +196 -0
- alita_sdk/cli/toolkit.py +14 -17
- alita_sdk/cli/toolkit_loader.py +35 -5
- alita_sdk/cli/tools/__init__.py +36 -2
- alita_sdk/cli/tools/approval.py +224 -0
- alita_sdk/cli/tools/filesystem.py +910 -64
- alita_sdk/cli/tools/planning.py +389 -0
- alita_sdk/cli/tools/terminal.py +414 -0
- alita_sdk/community/__init__.py +72 -12
- alita_sdk/community/inventory/__init__.py +236 -0
- alita_sdk/community/inventory/config.py +257 -0
- alita_sdk/community/inventory/enrichment.py +2137 -0
- alita_sdk/community/inventory/extractors.py +1469 -0
- alita_sdk/community/inventory/ingestion.py +3172 -0
- alita_sdk/community/inventory/knowledge_graph.py +1457 -0
- alita_sdk/community/inventory/parsers/__init__.py +218 -0
- alita_sdk/community/inventory/parsers/base.py +295 -0
- alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
- alita_sdk/community/inventory/parsers/go_parser.py +851 -0
- alita_sdk/community/inventory/parsers/html_parser.py +389 -0
- alita_sdk/community/inventory/parsers/java_parser.py +593 -0
- alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
- alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
- alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
- alita_sdk/community/inventory/parsers/python_parser.py +604 -0
- alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
- alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
- alita_sdk/community/inventory/parsers/text_parser.py +322 -0
- alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
- alita_sdk/community/inventory/patterns/__init__.py +61 -0
- alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
- alita_sdk/community/inventory/patterns/loader.py +348 -0
- alita_sdk/community/inventory/patterns/registry.py +198 -0
- alita_sdk/community/inventory/presets.py +535 -0
- alita_sdk/community/inventory/retrieval.py +1403 -0
- alita_sdk/community/inventory/toolkit.py +173 -0
- alita_sdk/community/inventory/toolkit_utils.py +176 -0
- alita_sdk/community/inventory/visualize.py +1370 -0
- alita_sdk/configurations/__init__.py +1 -1
- alita_sdk/configurations/ado.py +141 -20
- alita_sdk/configurations/bitbucket.py +0 -3
- alita_sdk/configurations/confluence.py +76 -42
- alita_sdk/configurations/figma.py +76 -0
- alita_sdk/configurations/gitlab.py +17 -5
- alita_sdk/configurations/openapi.py +329 -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 +3 -3
- alita_sdk/runtime/clients/client.py +353 -48
- alita_sdk/runtime/clients/sandbox_client.py +0 -21
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +123 -26
- alita_sdk/runtime/langchain/constants.py +642 -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 +6 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
- alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
- alita_sdk/runtime/langchain/document_loaders/constants.py +12 -7
- alita_sdk/runtime/langchain/langraph_agent.py +279 -73
- alita_sdk/runtime/langchain/utils.py +82 -15
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/skills/__init__.py +91 -0
- alita_sdk/runtime/skills/callbacks.py +498 -0
- alita_sdk/runtime/skills/discovery.py +540 -0
- alita_sdk/runtime/skills/executor.py +610 -0
- alita_sdk/runtime/skills/input_builder.py +371 -0
- alita_sdk/runtime/skills/models.py +330 -0
- alita_sdk/runtime/skills/registry.py +355 -0
- alita_sdk/runtime/skills/skill_runner.py +330 -0
- alita_sdk/runtime/toolkits/__init__.py +7 -0
- alita_sdk/runtime/toolkits/application.py +21 -9
- alita_sdk/runtime/toolkits/artifact.py +15 -5
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +139 -251
- alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
- alita_sdk/runtime/toolkits/planning.py +178 -0
- alita_sdk/runtime/toolkits/skill_router.py +238 -0
- alita_sdk/runtime/toolkits/subgraph.py +251 -6
- alita_sdk/runtime/toolkits/tools.py +238 -32
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/__init__.py +3 -1
- alita_sdk/runtime/tools/application.py +20 -6
- alita_sdk/runtime/tools/artifact.py +511 -28
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/function.py +43 -15
- alita_sdk/runtime/tools/image_generation.py +50 -44
- alita_sdk/runtime/tools/llm.py +852 -67
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/mcp_remote_tool.py +25 -10
- alita_sdk/runtime/tools/mcp_server_tool.py +7 -6
- alita_sdk/runtime/tools/planning/__init__.py +36 -0
- alita_sdk/runtime/tools/planning/models.py +246 -0
- alita_sdk/runtime/tools/planning/wrapper.py +607 -0
- alita_sdk/runtime/tools/router.py +2 -4
- alita_sdk/runtime/tools/sandbox.py +9 -6
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +7 -2
- alita_sdk/runtime/tools/vectorstore_base.py +51 -11
- alita_sdk/runtime/utils/AlitaCallback.py +137 -21
- alita_sdk/runtime/utils/constants.py +5 -1
- alita_sdk/runtime/utils/mcp_client.py +492 -0
- alita_sdk/runtime/utils/mcp_oauth.py +202 -5
- alita_sdk/runtime/utils/mcp_sse_client.py +36 -7
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/serialization.py +155 -0
- alita_sdk/runtime/utils/streamlit.py +6 -10
- alita_sdk/runtime/utils/toolkit_utils.py +16 -5
- alita_sdk/runtime/utils/utils.py +36 -0
- alita_sdk/tools/__init__.py +113 -29
- alita_sdk/tools/ado/repos/__init__.py +51 -33
- alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
- alita_sdk/tools/ado/test_plan/__init__.py +25 -9
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
- alita_sdk/tools/ado/utils.py +1 -18
- alita_sdk/tools/ado/wiki/__init__.py +25 -8
- alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
- alita_sdk/tools/ado/work_item/__init__.py +26 -9
- alita_sdk/tools/ado/work_item/ado_wrapper.py +56 -3
- alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
- alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
- alita_sdk/tools/aws/delta_lake/tool.py +5 -1
- alita_sdk/tools/azure_ai/search/__init__.py +11 -8
- alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
- alita_sdk/tools/base/tool.py +5 -1
- alita_sdk/tools/base_indexer_toolkit.py +170 -45
- alita_sdk/tools/bitbucket/__init__.py +17 -12
- alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
- alita_sdk/tools/browser/__init__.py +5 -4
- alita_sdk/tools/carrier/__init__.py +5 -6
- alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
- alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
- alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
- alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
- alita_sdk/tools/chunkers/universal_chunker.py +270 -0
- alita_sdk/tools/cloud/aws/__init__.py +10 -7
- alita_sdk/tools/cloud/azure/__init__.py +10 -7
- alita_sdk/tools/cloud/gcp/__init__.py +10 -7
- alita_sdk/tools/cloud/k8s/__init__.py +10 -7
- alita_sdk/tools/code/linter/__init__.py +10 -8
- alita_sdk/tools/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code/sonar/__init__.py +10 -7
- alita_sdk/tools/code_indexer_toolkit.py +73 -23
- alita_sdk/tools/confluence/__init__.py +21 -15
- alita_sdk/tools/confluence/api_wrapper.py +78 -23
- alita_sdk/tools/confluence/loader.py +4 -2
- alita_sdk/tools/custom_open_api/__init__.py +12 -5
- alita_sdk/tools/elastic/__init__.py +11 -8
- alita_sdk/tools/elitea_base.py +493 -30
- alita_sdk/tools/figma/__init__.py +58 -11
- alita_sdk/tools/figma/api_wrapper.py +1235 -143
- alita_sdk/tools/figma/figma_client.py +73 -0
- alita_sdk/tools/figma/toon_tools.py +2748 -0
- alita_sdk/tools/github/__init__.py +13 -14
- alita_sdk/tools/github/github_client.py +224 -100
- alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
- alita_sdk/tools/github/schemas.py +14 -5
- alita_sdk/tools/github/tool.py +5 -1
- alita_sdk/tools/github/tool_prompts.py +9 -22
- alita_sdk/tools/gitlab/__init__.py +15 -11
- alita_sdk/tools/gitlab/api_wrapper.py +207 -41
- alita_sdk/tools/gitlab_org/__init__.py +10 -8
- alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
- alita_sdk/tools/google/bigquery/__init__.py +13 -12
- alita_sdk/tools/google/bigquery/tool.py +5 -1
- alita_sdk/tools/google_places/__init__.py +10 -8
- alita_sdk/tools/google_places/api_wrapper.py +1 -1
- alita_sdk/tools/jira/__init__.py +17 -11
- alita_sdk/tools/jira/api_wrapper.py +91 -40
- alita_sdk/tools/keycloak/__init__.py +11 -8
- alita_sdk/tools/localgit/__init__.py +9 -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/non_code_indexer_toolkit.py +1 -0
- alita_sdk/tools/ocr/__init__.py +11 -8
- alita_sdk/tools/openapi/__init__.py +490 -114
- alita_sdk/tools/openapi/api_wrapper.py +1368 -0
- alita_sdk/tools/openapi/tool.py +20 -0
- alita_sdk/tools/pandas/__init__.py +20 -12
- alita_sdk/tools/pandas/api_wrapper.py +38 -25
- alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
- alita_sdk/tools/postman/__init__.py +11 -11
- alita_sdk/tools/pptx/__init__.py +10 -9
- alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
- alita_sdk/tools/qtest/__init__.py +30 -10
- alita_sdk/tools/qtest/api_wrapper.py +430 -13
- alita_sdk/tools/rally/__init__.py +10 -8
- alita_sdk/tools/rally/api_wrapper.py +1 -1
- alita_sdk/tools/report_portal/__init__.py +12 -9
- alita_sdk/tools/salesforce/__init__.py +10 -9
- alita_sdk/tools/servicenow/__init__.py +17 -14
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +10 -8
- alita_sdk/tools/sharepoint/api_wrapper.py +4 -4
- alita_sdk/tools/slack/__init__.py +10 -8
- alita_sdk/tools/slack/api_wrapper.py +2 -2
- alita_sdk/tools/sql/__init__.py +11 -9
- alita_sdk/tools/testio/__init__.py +10 -8
- alita_sdk/tools/testrail/__init__.py +11 -8
- alita_sdk/tools/testrail/api_wrapper.py +1 -1
- alita_sdk/tools/utils/__init__.py +9 -4
- alita_sdk/tools/utils/content_parser.py +77 -3
- alita_sdk/tools/utils/text_operations.py +410 -0
- alita_sdk/tools/utils/tool_prompts.py +79 -0
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +17 -13
- alita_sdk/tools/xray/__init__.py +12 -9
- alita_sdk/tools/yagmail/__init__.py +9 -3
- alita_sdk/tools/zephyr/__init__.py +9 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +11 -8
- alita_sdk/tools/zephyr_essential/__init__.py +10 -8
- alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
- alita_sdk/tools/zephyr_essential/client.py +2 -2
- alita_sdk/tools/zephyr_scale/__init__.py +11 -9
- alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
- alita_sdk/tools/zephyr_squad/__init__.py +10 -8
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +147 -7
- alita_sdk-0.3.627.dist-info/RECORD +468 -0
- alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
- alita_sdk-0.3.462.dist-info/RECORD +0 -384
- alita_sdk-0.3.462.dist-info/entry_points.txt +0 -2
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
|
@@ -43,6 +43,23 @@ class McpAuthorizationRequired(ToolException):
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
def extract_authorization_uri(www_authenticate: Optional[str]) -> Optional[str]:
|
|
47
|
+
"""
|
|
48
|
+
Extract authorization_uri from WWW-Authenticate header.
|
|
49
|
+
This points directly to the OAuth authorization server metadata URL.
|
|
50
|
+
Should be used before falling back to resource_metadata.
|
|
51
|
+
"""
|
|
52
|
+
if not www_authenticate:
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
# Look for authorization_uri="<url>" in the header
|
|
56
|
+
match = re.search(r'authorization_uri\s*=\s*\"?([^\", ]+)\"?', www_authenticate)
|
|
57
|
+
if match:
|
|
58
|
+
return match.group(1)
|
|
59
|
+
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
|
|
46
63
|
def extract_resource_metadata_url(www_authenticate: Optional[str], server_url: Optional[str] = None) -> Optional[str]:
|
|
47
64
|
"""
|
|
48
65
|
Pull the resource_metadata URL from a WWW-Authenticate header if present.
|
|
@@ -62,15 +79,33 @@ def extract_resource_metadata_url(www_authenticate: Optional[str], server_url: O
|
|
|
62
79
|
# or using well-known OAuth discovery endpoints directly
|
|
63
80
|
return None
|
|
64
81
|
|
|
65
|
-
|
|
66
|
-
def fetch_oauth_authorization_server_metadata(base_url: str, timeout: int = 10) -> Optional[Dict[str, Any]]:
|
|
82
|
+
def fetch_oauth_authorization_server_metadata(url: str, timeout: int = 10) -> Optional[Dict[str, Any]]:
|
|
67
83
|
"""
|
|
68
84
|
Fetch OAuth authorization server metadata from well-known endpoints.
|
|
69
|
-
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
url: Either a full well-known URL (e.g., https://api.figma.com/.well-known/oauth-authorization-server)
|
|
88
|
+
or a base URL (e.g., https://api.figma.com) where we'll try discovery endpoints.
|
|
89
|
+
timeout: Request timeout in seconds.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
OAuth authorization server metadata dict, or None if not found.
|
|
70
93
|
"""
|
|
94
|
+
# If the URL is already a .well-known endpoint, try it directly first
|
|
95
|
+
if '/.well-known/' in url:
|
|
96
|
+
try:
|
|
97
|
+
resp = requests.get(url, timeout=timeout)
|
|
98
|
+
if resp.status_code == 200:
|
|
99
|
+
return resp.json()
|
|
100
|
+
except Exception as exc:
|
|
101
|
+
logger.debug(f"Failed to fetch OAuth metadata from {url}: {exc}")
|
|
102
|
+
# If direct fetch failed, don't try other endpoints
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
# Otherwise, try standard discovery endpoints
|
|
71
106
|
discovery_endpoints = [
|
|
72
|
-
f"{
|
|
73
|
-
f"{
|
|
107
|
+
f"{url}/.well-known/oauth-authorization-server",
|
|
108
|
+
f"{url}/.well-known/openid-configuration",
|
|
74
109
|
]
|
|
75
110
|
|
|
76
111
|
for endpoint in discovery_endpoints:
|
|
@@ -162,3 +197,165 @@ def canonical_resource(server_url: str) -> str:
|
|
|
162
197
|
if resource.endswith("/") and parsed.path in ("", "/"):
|
|
163
198
|
resource = resource[:-1]
|
|
164
199
|
return resource
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def exchange_oauth_token(
|
|
203
|
+
token_endpoint: str,
|
|
204
|
+
code: str,
|
|
205
|
+
redirect_uri: str,
|
|
206
|
+
client_id: Optional[str] = None,
|
|
207
|
+
client_secret: Optional[str] = None,
|
|
208
|
+
code_verifier: Optional[str] = None,
|
|
209
|
+
scope: Optional[str] = None,
|
|
210
|
+
timeout: int = 30,
|
|
211
|
+
) -> Dict[str, Any]:
|
|
212
|
+
"""
|
|
213
|
+
Exchange an OAuth authorization code for access tokens.
|
|
214
|
+
|
|
215
|
+
This function performs the OAuth token exchange on the server side,
|
|
216
|
+
avoiding CORS issues that would occur if done from a browser.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
token_endpoint: OAuth token endpoint URL
|
|
220
|
+
code: Authorization code from OAuth provider
|
|
221
|
+
redirect_uri: Redirect URI used in authorization request
|
|
222
|
+
client_id: OAuth client ID (optional for DCR/public clients)
|
|
223
|
+
client_secret: OAuth client secret (optional for public clients)
|
|
224
|
+
code_verifier: PKCE code verifier (optional)
|
|
225
|
+
scope: OAuth scope (optional)
|
|
226
|
+
timeout: Request timeout in seconds
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Token response from OAuth provider containing access_token, etc.
|
|
230
|
+
|
|
231
|
+
Raises:
|
|
232
|
+
requests.RequestException: If the HTTP request fails
|
|
233
|
+
ValueError: If the token exchange fails
|
|
234
|
+
|
|
235
|
+
Note:
|
|
236
|
+
client_id may be optional for:
|
|
237
|
+
- Dynamic Client Registration (DCR): client_id may be in the code
|
|
238
|
+
- OIDC public clients: some providers don't require it
|
|
239
|
+
- Some MCP servers handle auth differently
|
|
240
|
+
"""
|
|
241
|
+
# Build the token request body
|
|
242
|
+
token_body = {
|
|
243
|
+
"grant_type": "authorization_code",
|
|
244
|
+
"code": code,
|
|
245
|
+
"redirect_uri": redirect_uri,
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if client_id:
|
|
249
|
+
token_body["client_id"] = client_id
|
|
250
|
+
if client_secret:
|
|
251
|
+
token_body["client_secret"] = client_secret
|
|
252
|
+
if code_verifier:
|
|
253
|
+
token_body["code_verifier"] = code_verifier
|
|
254
|
+
if scope:
|
|
255
|
+
token_body["scope"] = scope
|
|
256
|
+
|
|
257
|
+
logger.info(f"MCP OAuth: exchanging code at {token_endpoint}")
|
|
258
|
+
|
|
259
|
+
# Make the token exchange request
|
|
260
|
+
response = requests.post(
|
|
261
|
+
token_endpoint,
|
|
262
|
+
data=token_body,
|
|
263
|
+
headers={
|
|
264
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
265
|
+
"Accept": "application/json",
|
|
266
|
+
},
|
|
267
|
+
timeout=timeout
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
# Try to parse as JSON
|
|
271
|
+
try:
|
|
272
|
+
token_data = response.json()
|
|
273
|
+
except Exception:
|
|
274
|
+
# Some providers return URL-encoded response
|
|
275
|
+
from urllib.parse import parse_qs
|
|
276
|
+
token_data = {k: v[0] if len(v) == 1 else v
|
|
277
|
+
for k, v in parse_qs(response.text).items()}
|
|
278
|
+
|
|
279
|
+
if response.ok:
|
|
280
|
+
logger.info("MCP OAuth: token exchange successful")
|
|
281
|
+
return token_data
|
|
282
|
+
else:
|
|
283
|
+
error_msg = token_data.get("error_description") or token_data.get("error") or response.text
|
|
284
|
+
logger.error(f"MCP OAuth: token exchange failed - {response.status_code}: {error_msg}")
|
|
285
|
+
raise ValueError(f"Token exchange failed: {error_msg}")
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def refresh_oauth_token(
|
|
289
|
+
token_endpoint: str,
|
|
290
|
+
refresh_token: str,
|
|
291
|
+
client_id: Optional[str] = None,
|
|
292
|
+
client_secret: Optional[str] = None,
|
|
293
|
+
scope: Optional[str] = None,
|
|
294
|
+
timeout: int = 30,
|
|
295
|
+
) -> Dict[str, Any]:
|
|
296
|
+
"""
|
|
297
|
+
Refresh an OAuth access token using a refresh token.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
token_endpoint: OAuth token endpoint URL
|
|
301
|
+
refresh_token: Refresh token from previous authorization
|
|
302
|
+
client_id: OAuth client ID (optional for DCR/public clients)
|
|
303
|
+
client_secret: OAuth client secret (optional for public clients)
|
|
304
|
+
scope: OAuth scope (optional)
|
|
305
|
+
timeout: Request timeout in seconds
|
|
306
|
+
|
|
307
|
+
Returns:
|
|
308
|
+
Token response from OAuth provider containing access_token, etc.
|
|
309
|
+
May also include a new refresh_token depending on the provider.
|
|
310
|
+
|
|
311
|
+
Raises:
|
|
312
|
+
requests.RequestException: If the HTTP request fails
|
|
313
|
+
ValueError: If the token refresh fails
|
|
314
|
+
|
|
315
|
+
Note:
|
|
316
|
+
client_id may be optional for:
|
|
317
|
+
- Dynamic Client Registration (DCR): client_id embedded in refresh_token
|
|
318
|
+
- OIDC public clients: some providers don't require it
|
|
319
|
+
- Some MCP servers handle auth differently
|
|
320
|
+
"""
|
|
321
|
+
token_body = {
|
|
322
|
+
"grant_type": "refresh_token",
|
|
323
|
+
"refresh_token": refresh_token,
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if client_id:
|
|
327
|
+
token_body["client_id"] = client_id
|
|
328
|
+
if client_secret:
|
|
329
|
+
token_body["client_secret"] = client_secret
|
|
330
|
+
if scope:
|
|
331
|
+
token_body["scope"] = scope
|
|
332
|
+
|
|
333
|
+
logger.info(f"MCP OAuth: refreshing token at {token_endpoint}")
|
|
334
|
+
|
|
335
|
+
response = requests.post(
|
|
336
|
+
token_endpoint,
|
|
337
|
+
data=token_body,
|
|
338
|
+
headers={
|
|
339
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
340
|
+
"Accept": "application/json",
|
|
341
|
+
},
|
|
342
|
+
timeout=timeout
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
# Try to parse as JSON
|
|
346
|
+
try:
|
|
347
|
+
token_data = response.json()
|
|
348
|
+
except Exception:
|
|
349
|
+
# Some providers return URL-encoded response
|
|
350
|
+
from urllib.parse import parse_qs
|
|
351
|
+
token_data = {k: v[0] if len(v) == 1 else v
|
|
352
|
+
for k, v in parse_qs(response.text).items()}
|
|
353
|
+
|
|
354
|
+
if response.ok:
|
|
355
|
+
logger.info("MCP OAuth: token refresh successful")
|
|
356
|
+
return token_data
|
|
357
|
+
else:
|
|
358
|
+
error_msg = token_data.get("error_description") or token_data.get("error") or response.text
|
|
359
|
+
logger.error(f"MCP OAuth: token refresh failed - {response.status_code}: {error_msg}")
|
|
360
|
+
raise ValueError(f"Token refresh failed: {error_msg}")
|
|
361
|
+
|
|
@@ -71,6 +71,7 @@ class McpSseClient:
|
|
|
71
71
|
McpAuthorizationRequired,
|
|
72
72
|
canonical_resource,
|
|
73
73
|
extract_resource_metadata_url,
|
|
74
|
+
extract_authorization_uri,
|
|
74
75
|
fetch_resource_metadata_async,
|
|
75
76
|
infer_authorization_servers_from_realm,
|
|
76
77
|
fetch_oauth_authorization_server_metadata
|
|
@@ -79,13 +80,41 @@ class McpSseClient:
|
|
|
79
80
|
auth_header = self._stream_response.headers.get('WWW-Authenticate', '')
|
|
80
81
|
resource_metadata_url = extract_resource_metadata_url(auth_header, self.url)
|
|
81
82
|
|
|
83
|
+
# First, try authorization_uri from WWW-Authenticate header (preferred)
|
|
84
|
+
authorization_uri = extract_authorization_uri(auth_header)
|
|
85
|
+
|
|
82
86
|
metadata = None
|
|
83
|
-
if
|
|
84
|
-
metadata
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
if authorization_uri:
|
|
88
|
+
# Fetch OAuth metadata directly from authorization_uri
|
|
89
|
+
auth_server_metadata = fetch_oauth_authorization_server_metadata(authorization_uri, timeout=30)
|
|
90
|
+
if auth_server_metadata:
|
|
91
|
+
# Extract base authorization server URL from the issuer or the well-known URL
|
|
92
|
+
base_auth_server = auth_server_metadata.get('issuer')
|
|
93
|
+
if not base_auth_server and '/.well-known/' in authorization_uri:
|
|
94
|
+
base_auth_server = authorization_uri.split('/.well-known/')[0]
|
|
95
|
+
|
|
96
|
+
metadata = {
|
|
97
|
+
'authorization_servers': [base_auth_server] if base_auth_server else [authorization_uri],
|
|
98
|
+
'oauth_authorization_server': auth_server_metadata
|
|
99
|
+
}
|
|
100
|
+
logger.info(f"[MCP SSE Client] Using authorization_uri: {authorization_uri}, base: {base_auth_server}")
|
|
101
|
+
|
|
102
|
+
# Fall back to resource_metadata if authorization_uri didn't work
|
|
103
|
+
if not metadata:
|
|
104
|
+
if resource_metadata_url:
|
|
105
|
+
metadata = await fetch_resource_metadata_async(
|
|
106
|
+
resource_metadata_url,
|
|
107
|
+
session=self._stream_session,
|
|
108
|
+
timeout=30
|
|
109
|
+
)
|
|
110
|
+
# If we got resource_metadata, also fetch oauth_authorization_server
|
|
111
|
+
if metadata and metadata.get('authorization_servers'):
|
|
112
|
+
auth_server_metadata = fetch_oauth_authorization_server_metadata(
|
|
113
|
+
metadata['authorization_servers'][0], timeout=30
|
|
114
|
+
)
|
|
115
|
+
if auth_server_metadata:
|
|
116
|
+
metadata['oauth_authorization_server'] = auth_server_metadata
|
|
117
|
+
logger.info(f"[MCP SSE Client] Fetched OAuth metadata from resource_metadata")
|
|
89
118
|
|
|
90
119
|
# Infer authorization servers if not in metadata
|
|
91
120
|
if not metadata or not metadata.get('authorization_servers'):
|
|
@@ -345,7 +374,7 @@ class McpSseClient:
|
|
|
345
374
|
"sampling": {}
|
|
346
375
|
},
|
|
347
376
|
"clientInfo": {
|
|
348
|
-
"name": "
|
|
377
|
+
"name": "ELITEA MCP Client",
|
|
349
378
|
"version": "1.0.0"
|
|
350
379
|
}
|
|
351
380
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Tools Discovery Utility.
|
|
3
|
+
Provides a standalone function to discover tools from remote MCP servers.
|
|
4
|
+
Supports both SSE (Server-Sent Events) and Streamable HTTP transports with auto-detection.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
10
|
+
|
|
11
|
+
from .mcp_oauth import McpAuthorizationRequired
|
|
12
|
+
from .mcp_client import McpClient
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def discover_mcp_tools(
|
|
18
|
+
url: str,
|
|
19
|
+
headers: Optional[Dict[str, str]] = None,
|
|
20
|
+
timeout: int = 60,
|
|
21
|
+
session_id: Optional[str] = None,
|
|
22
|
+
) -> List[Dict[str, Any]]:
|
|
23
|
+
"""
|
|
24
|
+
Discover available tools from a remote MCP server.
|
|
25
|
+
|
|
26
|
+
This function connects to a remote MCP server and retrieves the list of
|
|
27
|
+
available tools using the MCP protocol. Automatically detects and uses
|
|
28
|
+
the appropriate transport (SSE or Streamable HTTP).
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
url: MCP server HTTP URL (http:// or https://)
|
|
32
|
+
headers: Optional HTTP headers for authentication
|
|
33
|
+
timeout: Request timeout in seconds (default: 60)
|
|
34
|
+
session_id: Optional session ID for stateful connections
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
List of tool definitions, each containing:
|
|
38
|
+
- name: Tool name
|
|
39
|
+
- description: Tool description
|
|
40
|
+
- inputSchema: JSON schema for tool input parameters
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
McpAuthorizationRequired: If the server requires OAuth authorization (401)
|
|
44
|
+
Exception: For other connection or protocol errors
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
>>> tools = discover_mcp_tools(
|
|
48
|
+
... url="https://mcp.example.com/sse",
|
|
49
|
+
... headers={"Authorization": "Bearer token123"}
|
|
50
|
+
... )
|
|
51
|
+
>>> print(f"Found {len(tools)} tools")
|
|
52
|
+
"""
|
|
53
|
+
logger.info(f"[MCP Discovery] Starting tool discovery from {url}")
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
# Run the async discovery in a new event loop
|
|
57
|
+
tools_list = asyncio.run(
|
|
58
|
+
_discover_tools_async(url, headers, timeout, session_id)
|
|
59
|
+
)
|
|
60
|
+
logger.info(f"[MCP Discovery] Successfully discovered {len(tools_list)} tools from {url}")
|
|
61
|
+
return tools_list
|
|
62
|
+
|
|
63
|
+
except McpAuthorizationRequired:
|
|
64
|
+
# Re-raise auth exceptions directly
|
|
65
|
+
logger.info(f"[MCP Discovery] Authorization required for {url}")
|
|
66
|
+
raise
|
|
67
|
+
|
|
68
|
+
except Exception as e:
|
|
69
|
+
logger.error(f"[MCP Discovery] Failed to discover tools from {url}: {e}")
|
|
70
|
+
raise
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
async def _discover_tools_async(
|
|
74
|
+
url: str,
|
|
75
|
+
headers: Optional[Dict[str, str]],
|
|
76
|
+
timeout: int,
|
|
77
|
+
session_id: Optional[str],
|
|
78
|
+
) -> List[Dict[str, Any]]:
|
|
79
|
+
"""
|
|
80
|
+
Async implementation of tool discovery using unified MCP client.
|
|
81
|
+
"""
|
|
82
|
+
all_tools = []
|
|
83
|
+
|
|
84
|
+
# Create unified MCP client (auto-detects transport)
|
|
85
|
+
client = McpClient(
|
|
86
|
+
url=url,
|
|
87
|
+
session_id=session_id,
|
|
88
|
+
headers=headers,
|
|
89
|
+
timeout=timeout
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
async with client:
|
|
93
|
+
# Initialize MCP session
|
|
94
|
+
await client.initialize()
|
|
95
|
+
logger.debug(f"[MCP Discovery] Session initialized (transport={client.detected_transport})")
|
|
96
|
+
|
|
97
|
+
# Get tools list
|
|
98
|
+
tools = await client.list_tools()
|
|
99
|
+
logger.debug(f"[MCP Discovery] Received {len(tools)} tools")
|
|
100
|
+
|
|
101
|
+
# Convert tools to standard format
|
|
102
|
+
for tool in tools:
|
|
103
|
+
tool_def = {
|
|
104
|
+
'name': tool.get('name'),
|
|
105
|
+
'description': tool.get('description', ''),
|
|
106
|
+
'inputSchema': tool.get('inputSchema', {}),
|
|
107
|
+
}
|
|
108
|
+
all_tools.append(tool_def)
|
|
109
|
+
|
|
110
|
+
return all_tools
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
async def discover_mcp_tools_async(
|
|
114
|
+
url: str,
|
|
115
|
+
headers: Optional[Dict[str, str]] = None,
|
|
116
|
+
timeout: int = 60,
|
|
117
|
+
session_id: Optional[str] = None,
|
|
118
|
+
) -> List[Dict[str, Any]]:
|
|
119
|
+
"""
|
|
120
|
+
Async version of discover_mcp_tools.
|
|
121
|
+
|
|
122
|
+
See discover_mcp_tools for full documentation.
|
|
123
|
+
"""
|
|
124
|
+
return await _discover_tools_async(url, headers, timeout, session_id)
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Serialization utilities for safe JSON encoding of complex objects.
|
|
3
|
+
|
|
4
|
+
Handles Pydantic models, LangChain messages, datetime objects, and other
|
|
5
|
+
non-standard types that may appear in state variables.
|
|
6
|
+
"""
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
from datetime import datetime, date
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _convert_to_serializable(obj: Any, _seen: set = None) -> Any:
|
|
16
|
+
"""
|
|
17
|
+
Recursively convert an object to JSON-serializable primitives.
|
|
18
|
+
|
|
19
|
+
Handles nested dicts and lists that may contain non-serializable objects.
|
|
20
|
+
Uses a seen set to prevent infinite recursion with circular references.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
obj: Any object to convert
|
|
24
|
+
_seen: Internal set to track seen object ids (for circular reference detection)
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
JSON-serializable representation of the object
|
|
28
|
+
"""
|
|
29
|
+
# Initialize seen set for circular reference detection
|
|
30
|
+
if _seen is None:
|
|
31
|
+
_seen = set()
|
|
32
|
+
|
|
33
|
+
# Check for circular references (only for mutable objects)
|
|
34
|
+
obj_id = id(obj)
|
|
35
|
+
if isinstance(obj, (dict, list, set)) and obj_id in _seen:
|
|
36
|
+
return f"<circular reference: {type(obj).__name__}>"
|
|
37
|
+
|
|
38
|
+
# Primitives - return as-is
|
|
39
|
+
if obj is None or isinstance(obj, (str, int, float, bool)):
|
|
40
|
+
return obj
|
|
41
|
+
|
|
42
|
+
# Add to seen set for mutable containers
|
|
43
|
+
if isinstance(obj, (dict, list, set)):
|
|
44
|
+
_seen = _seen | {obj_id} # Create new set to avoid mutation issues
|
|
45
|
+
|
|
46
|
+
# Dict - recursively process all values
|
|
47
|
+
if isinstance(obj, dict):
|
|
48
|
+
return {
|
|
49
|
+
_convert_to_serializable(k, _seen): _convert_to_serializable(v, _seen)
|
|
50
|
+
for k, v in obj.items()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# List/tuple - recursively process all items
|
|
54
|
+
if isinstance(obj, (list, tuple)):
|
|
55
|
+
return [_convert_to_serializable(item, _seen) for item in obj]
|
|
56
|
+
|
|
57
|
+
# Set - convert to list and process
|
|
58
|
+
if isinstance(obj, set):
|
|
59
|
+
return [_convert_to_serializable(item, _seen) for item in obj]
|
|
60
|
+
|
|
61
|
+
# Bytes - decode to string
|
|
62
|
+
if isinstance(obj, bytes):
|
|
63
|
+
try:
|
|
64
|
+
return obj.decode('utf-8')
|
|
65
|
+
except UnicodeDecodeError:
|
|
66
|
+
return obj.decode('utf-8', errors='replace')
|
|
67
|
+
|
|
68
|
+
# Datetime objects
|
|
69
|
+
if isinstance(obj, datetime):
|
|
70
|
+
return obj.isoformat()
|
|
71
|
+
if isinstance(obj, date):
|
|
72
|
+
return obj.isoformat()
|
|
73
|
+
|
|
74
|
+
# Pydantic BaseModel (v2) - check for model_dump method
|
|
75
|
+
if hasattr(obj, 'model_dump') and callable(getattr(obj, 'model_dump')):
|
|
76
|
+
try:
|
|
77
|
+
return _convert_to_serializable(obj.model_dump(), _seen)
|
|
78
|
+
except Exception as e:
|
|
79
|
+
logger.debug(f"Failed to call model_dump on {type(obj).__name__}: {e}")
|
|
80
|
+
|
|
81
|
+
# Pydantic BaseModel (v1) - check for dict method
|
|
82
|
+
if hasattr(obj, 'dict') and callable(getattr(obj, 'dict')) and hasattr(obj, '__fields__'):
|
|
83
|
+
try:
|
|
84
|
+
return _convert_to_serializable(obj.dict(), _seen)
|
|
85
|
+
except Exception as e:
|
|
86
|
+
logger.debug(f"Failed to call dict on {type(obj).__name__}: {e}")
|
|
87
|
+
|
|
88
|
+
# LangChain BaseMessage - extract key fields
|
|
89
|
+
if hasattr(obj, 'type') and hasattr(obj, 'content'):
|
|
90
|
+
try:
|
|
91
|
+
result = {
|
|
92
|
+
"type": obj.type,
|
|
93
|
+
"content": _convert_to_serializable(obj.content, _seen),
|
|
94
|
+
}
|
|
95
|
+
if hasattr(obj, 'additional_kwargs') and obj.additional_kwargs:
|
|
96
|
+
result["additional_kwargs"] = _convert_to_serializable(obj.additional_kwargs, _seen)
|
|
97
|
+
if hasattr(obj, 'name') and obj.name:
|
|
98
|
+
result["name"] = obj.name
|
|
99
|
+
return result
|
|
100
|
+
except Exception as e:
|
|
101
|
+
logger.debug(f"Failed to extract message fields from {type(obj).__name__}: {e}")
|
|
102
|
+
|
|
103
|
+
# Objects with __dict__ attribute (custom classes)
|
|
104
|
+
if hasattr(obj, '__dict__'):
|
|
105
|
+
try:
|
|
106
|
+
return _convert_to_serializable(obj.__dict__, _seen)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logger.debug(f"Failed to serialize __dict__ of {type(obj).__name__}: {e}")
|
|
109
|
+
|
|
110
|
+
# UUID objects
|
|
111
|
+
if hasattr(obj, 'hex') and hasattr(obj, 'int'):
|
|
112
|
+
return str(obj)
|
|
113
|
+
|
|
114
|
+
# Enum objects
|
|
115
|
+
if hasattr(obj, 'value') and hasattr(obj, 'name') and hasattr(obj.__class__, '__members__'):
|
|
116
|
+
return obj.value
|
|
117
|
+
|
|
118
|
+
# Last resort - convert to string
|
|
119
|
+
try:
|
|
120
|
+
return str(obj)
|
|
121
|
+
except Exception:
|
|
122
|
+
return f"<non-serializable: {type(obj).__name__}>"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def safe_serialize(obj: Any, **kwargs) -> str:
|
|
126
|
+
"""
|
|
127
|
+
Safely serialize any object to a JSON string.
|
|
128
|
+
|
|
129
|
+
Pre-processes the entire object tree to convert non-serializable
|
|
130
|
+
objects before passing to json.dumps. This ensures nested dicts
|
|
131
|
+
and lists with non-standard objects are handled correctly.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
obj: Any object to serialize
|
|
135
|
+
**kwargs: Additional arguments passed to json.dumps
|
|
136
|
+
(e.g., indent, sort_keys)
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
JSON string representation of the object
|
|
140
|
+
|
|
141
|
+
Example:
|
|
142
|
+
>>> from pydantic import BaseModel
|
|
143
|
+
>>> class User(BaseModel):
|
|
144
|
+
... name: str
|
|
145
|
+
>>> state = {"user": User(name="Alice"), "count": 5}
|
|
146
|
+
>>> safe_serialize(state)
|
|
147
|
+
'{"user": {"name": "Alice"}, "count": 5}'
|
|
148
|
+
"""
|
|
149
|
+
# Pre-process the entire object tree
|
|
150
|
+
serializable = _convert_to_serializable(obj)
|
|
151
|
+
|
|
152
|
+
# Set defaults
|
|
153
|
+
kwargs.setdefault('ensure_ascii', False)
|
|
154
|
+
|
|
155
|
+
return json.dumps(serializable, **kwargs)
|
|
@@ -287,7 +287,6 @@ def run_streamlit(st, ai_icon=None, user_icon=None):
|
|
|
287
287
|
model_config={
|
|
288
288
|
"temperature": 0.1,
|
|
289
289
|
"max_tokens": 1000,
|
|
290
|
-
"top_p": 1.0
|
|
291
290
|
}
|
|
292
291
|
)
|
|
293
292
|
except Exception as e:
|
|
@@ -1256,7 +1255,6 @@ def run_streamlit(st, ai_icon=None, user_icon=None):
|
|
|
1256
1255
|
model_config={
|
|
1257
1256
|
"temperature": 0.1,
|
|
1258
1257
|
"max_tokens": 1000,
|
|
1259
|
-
"top_p": 1.0
|
|
1260
1258
|
}
|
|
1261
1259
|
)
|
|
1262
1260
|
except Exception as e:
|
|
@@ -1387,20 +1385,18 @@ def run_streamlit(st, ai_icon=None, user_icon=None):
|
|
|
1387
1385
|
help="Maximum number of tokens in the AI response"
|
|
1388
1386
|
)
|
|
1389
1387
|
|
|
1390
|
-
|
|
1391
|
-
"
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
step=0.1,
|
|
1396
|
-
help="Controls diversity via nucleus sampling"
|
|
1388
|
+
reasoning_effort = st.selectbox(
|
|
1389
|
+
"Reasoning effort:",
|
|
1390
|
+
options=['null', 'low', 'medium', 'high'],
|
|
1391
|
+
index=0,
|
|
1392
|
+
help="Higher effort better reasoning, slower response"
|
|
1397
1393
|
)
|
|
1398
1394
|
|
|
1399
1395
|
# Create LLM config
|
|
1400
1396
|
llm_config = {
|
|
1401
1397
|
'max_tokens': max_tokens,
|
|
1402
1398
|
'temperature': temperature,
|
|
1403
|
-
'
|
|
1399
|
+
'reasoning_effort': reasoning_effort
|
|
1404
1400
|
}
|
|
1405
1401
|
|
|
1406
1402
|
col1, col2 = st.columns([3, 1])
|
|
@@ -12,7 +12,9 @@ logger = logging.getLogger(__name__)
|
|
|
12
12
|
|
|
13
13
|
def instantiate_toolkit_with_client(toolkit_config: Dict[str, Any],
|
|
14
14
|
llm_client: Any,
|
|
15
|
-
alita_client: Optional[Any] = None
|
|
15
|
+
alita_client: Optional[Any] = None,
|
|
16
|
+
mcp_tokens: Optional[Dict[str, Any]] = None,
|
|
17
|
+
use_prefix: bool = False) -> List[Any]:
|
|
16
18
|
"""
|
|
17
19
|
Instantiate a toolkit with LLM client support.
|
|
18
20
|
|
|
@@ -22,7 +24,11 @@ def instantiate_toolkit_with_client(toolkit_config: Dict[str, Any],
|
|
|
22
24
|
Args:
|
|
23
25
|
toolkit_config: Configuration dictionary for the toolkit
|
|
24
26
|
llm_client: LLM client instance for tools that need LLM capabilities
|
|
25
|
-
|
|
27
|
+
alita_client: Optional additional client instance
|
|
28
|
+
mcp_tokens: Optional dictionary of MCP OAuth tokens by server URL
|
|
29
|
+
use_prefix: If True, tools get prefixed with toolkit_name to prevent collisions
|
|
30
|
+
(for agent use). If False, tools use base names only (for testing interface).
|
|
31
|
+
Default False for backward compatibility with testing.
|
|
26
32
|
|
|
27
33
|
Returns:
|
|
28
34
|
List of instantiated tools from the toolkit
|
|
@@ -52,16 +58,19 @@ def instantiate_toolkit_with_client(toolkit_config: Dict[str, Any],
|
|
|
52
58
|
toolkit_type = toolkit_config.get('type', toolkit_name.lower())
|
|
53
59
|
|
|
54
60
|
# Create a tool configuration dict with required fields
|
|
61
|
+
# Note: MCP toolkit always requires toolkit_name, other toolkits respect use_prefix flag
|
|
62
|
+
# Note: 'name' is always set for provider-based toolkits (used by provider_worker.utils.tools)
|
|
55
63
|
tool_config = {
|
|
56
64
|
'id': toolkit_config.get('id', random.randint(1, 1000000)),
|
|
57
65
|
'type': toolkit_config.get('type', toolkit_type),
|
|
58
66
|
'settings': settings,
|
|
59
|
-
'
|
|
67
|
+
'name': toolkit_name, # Always pass name for provider toolkits
|
|
68
|
+
'toolkit_name': toolkit_name if (use_prefix or toolkit_type == 'mcp') else None
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
# Get tools using the toolkit configuration with clients
|
|
63
|
-
# Parameter order: get_tools(tools_list, alita_client, llm, memory_store)
|
|
64
|
-
tools = get_tools([tool_config], alita_client, llm_client)
|
|
72
|
+
# Parameter order: get_tools(tools_list, alita_client, llm, memory_store, debug_mode, mcp_tokens)
|
|
73
|
+
tools = get_tools([tool_config], alita_client, llm_client, mcp_tokens=mcp_tokens)
|
|
65
74
|
|
|
66
75
|
if not tools:
|
|
67
76
|
logger.warning(f"No tools returned for toolkit {toolkit_name}")
|
|
@@ -73,9 +82,11 @@ def instantiate_toolkit_with_client(toolkit_config: Dict[str, Any],
|
|
|
73
82
|
except Exception as e:
|
|
74
83
|
# Re-raise McpAuthorizationRequired without logging as error
|
|
75
84
|
from ..utils.mcp_oauth import McpAuthorizationRequired
|
|
85
|
+
|
|
76
86
|
if isinstance(e, McpAuthorizationRequired):
|
|
77
87
|
logger.info(f"Toolkit {toolkit_name} requires MCP OAuth authorization")
|
|
78
88
|
raise
|
|
89
|
+
|
|
79
90
|
# Log and re-raise other errors
|
|
80
91
|
logger.error(f"Error instantiating toolkit {toolkit_name} with client: {str(e)}")
|
|
81
92
|
raise
|