veadk-python 0.2.27__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.
- veadk/__init__.py +37 -0
- veadk/a2a/__init__.py +13 -0
- veadk/a2a/agent_card.py +45 -0
- veadk/a2a/remote_ve_agent.py +390 -0
- veadk/a2a/utils/__init__.py +13 -0
- veadk/a2a/utils/agent_to_a2a.py +170 -0
- veadk/a2a/ve_a2a_server.py +93 -0
- veadk/a2a/ve_agent_executor.py +78 -0
- veadk/a2a/ve_middlewares.py +313 -0
- veadk/a2a/ve_task_store.py +37 -0
- veadk/agent.py +402 -0
- veadk/agent_builder.py +93 -0
- veadk/agents/loop_agent.py +68 -0
- veadk/agents/parallel_agent.py +72 -0
- veadk/agents/sequential_agent.py +64 -0
- veadk/auth/__init__.py +13 -0
- veadk/auth/base_auth.py +22 -0
- veadk/auth/ve_credential_service.py +203 -0
- veadk/auth/veauth/__init__.py +13 -0
- veadk/auth/veauth/apmplus_veauth.py +58 -0
- veadk/auth/veauth/ark_veauth.py +75 -0
- veadk/auth/veauth/base_veauth.py +50 -0
- veadk/auth/veauth/cozeloop_veauth.py +13 -0
- veadk/auth/veauth/opensearch_veauth.py +75 -0
- veadk/auth/veauth/postgresql_veauth.py +75 -0
- veadk/auth/veauth/prompt_pilot_veauth.py +60 -0
- veadk/auth/veauth/speech_veauth.py +54 -0
- veadk/auth/veauth/utils.py +69 -0
- veadk/auth/veauth/vesearch_veauth.py +62 -0
- veadk/auth/veauth/viking_mem0_veauth.py +91 -0
- veadk/cli/__init__.py +13 -0
- veadk/cli/cli.py +58 -0
- veadk/cli/cli_clean.py +87 -0
- veadk/cli/cli_create.py +163 -0
- veadk/cli/cli_deploy.py +233 -0
- veadk/cli/cli_eval.py +215 -0
- veadk/cli/cli_init.py +214 -0
- veadk/cli/cli_kb.py +110 -0
- veadk/cli/cli_pipeline.py +285 -0
- veadk/cli/cli_prompt.py +86 -0
- veadk/cli/cli_update.py +106 -0
- veadk/cli/cli_uploadevalset.py +139 -0
- veadk/cli/cli_web.py +143 -0
- veadk/cloud/__init__.py +13 -0
- veadk/cloud/cloud_agent_engine.py +485 -0
- veadk/cloud/cloud_app.py +475 -0
- veadk/config.py +115 -0
- veadk/configs/__init__.py +13 -0
- veadk/configs/auth_configs.py +133 -0
- veadk/configs/database_configs.py +132 -0
- veadk/configs/model_configs.py +78 -0
- veadk/configs/tool_configs.py +54 -0
- veadk/configs/tracing_configs.py +110 -0
- veadk/consts.py +74 -0
- veadk/evaluation/__init__.py +17 -0
- veadk/evaluation/adk_evaluator/__init__.py +17 -0
- veadk/evaluation/adk_evaluator/adk_evaluator.py +302 -0
- veadk/evaluation/base_evaluator.py +642 -0
- veadk/evaluation/deepeval_evaluator/__init__.py +17 -0
- veadk/evaluation/deepeval_evaluator/deepeval_evaluator.py +339 -0
- veadk/evaluation/eval_set_file_loader.py +48 -0
- veadk/evaluation/eval_set_recorder.py +146 -0
- veadk/evaluation/types.py +65 -0
- veadk/evaluation/utils/prometheus.py +196 -0
- veadk/integrations/__init__.py +13 -0
- veadk/integrations/ve_apig/__init__.py +13 -0
- veadk/integrations/ve_apig/ve_apig.py +349 -0
- veadk/integrations/ve_apig/ve_apig_utils.py +332 -0
- veadk/integrations/ve_code_pipeline/__init__.py +13 -0
- veadk/integrations/ve_code_pipeline/ve_code_pipeline.py +431 -0
- veadk/integrations/ve_cozeloop/__init__.py +13 -0
- veadk/integrations/ve_cozeloop/ve_cozeloop.py +96 -0
- veadk/integrations/ve_cr/__init__.py +13 -0
- veadk/integrations/ve_cr/ve_cr.py +220 -0
- veadk/integrations/ve_faas/__init__.py +13 -0
- veadk/integrations/ve_faas/template/cookiecutter.json +15 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/__init__.py +13 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/clean.py +23 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/config.yaml.example +6 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/deploy.py +106 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/__init__.py +13 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/agent.py +25 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/app.py +202 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/requirements.txt +3 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/run.sh +49 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/{{ cookiecutter.app_name }}/__init__.py +14 -0
- veadk/integrations/ve_faas/template/{{cookiecutter.local_dir_name}}/src/{{ cookiecutter.app_name }}/agent.py +27 -0
- veadk/integrations/ve_faas/ve_faas.py +754 -0
- veadk/integrations/ve_faas/ve_faas_utils.py +408 -0
- veadk/integrations/ve_faas/web_template/cookiecutter.json +20 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/__init__.py +13 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/clean.py +23 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/config.yaml.example +2 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/deploy.py +44 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/Dockerfile +23 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/app.py +123 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/init_db.py +46 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/models.py +36 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/requirements.txt +4 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/run.sh +21 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/css/style.css +368 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/static/js/admin.js +0 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/dashboard.html +21 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/edit_post.html +24 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/login.html +21 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/admin/posts.html +53 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/base.html +45 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/index.html +29 -0
- veadk/integrations/ve_faas/web_template/{{cookiecutter.local_dir_name}}/src/templates/post.html +14 -0
- veadk/integrations/ve_identity/__init__.py +110 -0
- veadk/integrations/ve_identity/auth_config.py +261 -0
- veadk/integrations/ve_identity/auth_mixins.py +650 -0
- veadk/integrations/ve_identity/auth_processor.py +385 -0
- veadk/integrations/ve_identity/function_tool.py +158 -0
- veadk/integrations/ve_identity/identity_client.py +864 -0
- veadk/integrations/ve_identity/mcp_tool.py +181 -0
- veadk/integrations/ve_identity/mcp_toolset.py +431 -0
- veadk/integrations/ve_identity/models.py +228 -0
- veadk/integrations/ve_identity/token_manager.py +188 -0
- veadk/integrations/ve_identity/utils.py +151 -0
- veadk/integrations/ve_prompt_pilot/__init__.py +13 -0
- veadk/integrations/ve_prompt_pilot/ve_prompt_pilot.py +85 -0
- veadk/integrations/ve_tls/__init__.py +13 -0
- veadk/integrations/ve_tls/utils.py +116 -0
- veadk/integrations/ve_tls/ve_tls.py +212 -0
- veadk/integrations/ve_tos/ve_tos.py +710 -0
- veadk/integrations/ve_viking_db_memory/__init__.py +13 -0
- veadk/integrations/ve_viking_db_memory/ve_viking_db_memory.py +308 -0
- veadk/knowledgebase/__init__.py +17 -0
- veadk/knowledgebase/backends/__init__.py +13 -0
- veadk/knowledgebase/backends/base_backend.py +72 -0
- veadk/knowledgebase/backends/in_memory_backend.py +91 -0
- veadk/knowledgebase/backends/opensearch_backend.py +162 -0
- veadk/knowledgebase/backends/redis_backend.py +172 -0
- veadk/knowledgebase/backends/utils.py +92 -0
- veadk/knowledgebase/backends/vikingdb_knowledge_backend.py +608 -0
- veadk/knowledgebase/entry.py +25 -0
- veadk/knowledgebase/knowledgebase.py +307 -0
- veadk/memory/__init__.py +35 -0
- veadk/memory/long_term_memory.py +365 -0
- veadk/memory/long_term_memory_backends/__init__.py +13 -0
- veadk/memory/long_term_memory_backends/base_backend.py +35 -0
- veadk/memory/long_term_memory_backends/in_memory_backend.py +67 -0
- veadk/memory/long_term_memory_backends/mem0_backend.py +155 -0
- veadk/memory/long_term_memory_backends/opensearch_backend.py +124 -0
- veadk/memory/long_term_memory_backends/redis_backend.py +140 -0
- veadk/memory/long_term_memory_backends/vikingdb_memory_backend.py +189 -0
- veadk/memory/short_term_memory.py +252 -0
- veadk/memory/short_term_memory_backends/__init__.py +13 -0
- veadk/memory/short_term_memory_backends/base_backend.py +31 -0
- veadk/memory/short_term_memory_backends/mysql_backend.py +49 -0
- veadk/memory/short_term_memory_backends/postgresql_backend.py +49 -0
- veadk/memory/short_term_memory_backends/sqlite_backend.py +55 -0
- veadk/memory/short_term_memory_processor.py +100 -0
- veadk/processors/__init__.py +26 -0
- veadk/processors/base_run_processor.py +120 -0
- veadk/prompts/__init__.py +13 -0
- veadk/prompts/agent_default_prompt.py +30 -0
- veadk/prompts/prompt_evaluator.py +20 -0
- veadk/prompts/prompt_memory_processor.py +55 -0
- veadk/prompts/prompt_optimization.py +150 -0
- veadk/runner.py +732 -0
- veadk/tools/__init__.py +13 -0
- veadk/tools/builtin_tools/__init__.py +13 -0
- veadk/tools/builtin_tools/agent_authorization.py +94 -0
- veadk/tools/builtin_tools/generate_image.py +23 -0
- veadk/tools/builtin_tools/image_edit.py +300 -0
- veadk/tools/builtin_tools/image_generate.py +446 -0
- veadk/tools/builtin_tools/lark.py +67 -0
- veadk/tools/builtin_tools/las.py +24 -0
- veadk/tools/builtin_tools/link_reader.py +66 -0
- veadk/tools/builtin_tools/llm_shield.py +381 -0
- veadk/tools/builtin_tools/load_knowledgebase.py +97 -0
- veadk/tools/builtin_tools/mcp_router.py +29 -0
- veadk/tools/builtin_tools/run_code.py +113 -0
- veadk/tools/builtin_tools/tts.py +253 -0
- veadk/tools/builtin_tools/vesearch.py +49 -0
- veadk/tools/builtin_tools/video_generate.py +363 -0
- veadk/tools/builtin_tools/web_scraper.py +76 -0
- veadk/tools/builtin_tools/web_search.py +83 -0
- veadk/tools/demo_tools.py +58 -0
- veadk/tools/load_knowledgebase_tool.py +149 -0
- veadk/tools/sandbox/__init__.py +13 -0
- veadk/tools/sandbox/browser_sandbox.py +37 -0
- veadk/tools/sandbox/code_sandbox.py +40 -0
- veadk/tools/sandbox/computer_sandbox.py +34 -0
- veadk/tracing/__init__.py +13 -0
- veadk/tracing/base_tracer.py +58 -0
- veadk/tracing/telemetry/__init__.py +13 -0
- veadk/tracing/telemetry/attributes/attributes.py +29 -0
- veadk/tracing/telemetry/attributes/extractors/common_attributes_extractors.py +180 -0
- veadk/tracing/telemetry/attributes/extractors/llm_attributes_extractors.py +858 -0
- veadk/tracing/telemetry/attributes/extractors/tool_attributes_extractors.py +152 -0
- veadk/tracing/telemetry/attributes/extractors/types.py +164 -0
- veadk/tracing/telemetry/exporters/__init__.py +13 -0
- veadk/tracing/telemetry/exporters/apmplus_exporter.py +558 -0
- veadk/tracing/telemetry/exporters/base_exporter.py +39 -0
- veadk/tracing/telemetry/exporters/cozeloop_exporter.py +129 -0
- veadk/tracing/telemetry/exporters/inmemory_exporter.py +248 -0
- veadk/tracing/telemetry/exporters/tls_exporter.py +139 -0
- veadk/tracing/telemetry/opentelemetry_tracer.py +320 -0
- veadk/tracing/telemetry/telemetry.py +411 -0
- veadk/types.py +47 -0
- veadk/utils/__init__.py +13 -0
- veadk/utils/audio_manager.py +95 -0
- veadk/utils/auth.py +294 -0
- veadk/utils/logger.py +59 -0
- veadk/utils/mcp_utils.py +44 -0
- veadk/utils/misc.py +184 -0
- veadk/utils/patches.py +101 -0
- veadk/utils/volcengine_sign.py +205 -0
- veadk/version.py +15 -0
- veadk_python-0.2.27.dist-info/METADATA +373 -0
- veadk_python-0.2.27.dist-info/RECORD +218 -0
- veadk_python-0.2.27.dist-info/WHEEL +5 -0
- veadk_python-0.2.27.dist-info/entry_points.txt +2 -0
- veadk_python-0.2.27.dist-info/licenses/LICENSE +201 -0
- veadk_python-0.2.27.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Any
|
|
18
|
+
from typing_extensions import override
|
|
19
|
+
|
|
20
|
+
from mcp.types import Tool as McpBaseTool
|
|
21
|
+
from google.genai.types import FunctionDeclaration
|
|
22
|
+
from google.adk.auth.auth_credential import AuthCredential
|
|
23
|
+
from google.adk.tools.base_tool import BaseTool
|
|
24
|
+
from google.adk.tools.tool_context import ToolContext
|
|
25
|
+
from google.adk.tools._gemini_schema_util import _to_gemini_schema
|
|
26
|
+
from google.adk.tools.mcp_tool.mcp_session_manager import MCPSessionManager
|
|
27
|
+
from google.adk.tools.mcp_tool.mcp_session_manager import retry_on_closed_resource
|
|
28
|
+
|
|
29
|
+
from veadk.integrations.ve_identity.auth_config import VeIdentityAuthConfig
|
|
30
|
+
from veadk.integrations.ve_identity.auth_mixins import (
|
|
31
|
+
VeIdentityAuthMixin,
|
|
32
|
+
AuthRequiredException,
|
|
33
|
+
)
|
|
34
|
+
from veadk.integrations.ve_identity.utils import generate_headers
|
|
35
|
+
from veadk.utils.logger import get_logger
|
|
36
|
+
|
|
37
|
+
logger = get_logger(__name__)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class VeIdentityMcpTool(VeIdentityAuthMixin, BaseTool):
|
|
41
|
+
"""Unified MCP tool with automatic VeIdentity authentication.
|
|
42
|
+
|
|
43
|
+
This tool wraps an MCP Tool interface and automatically handles authentication
|
|
44
|
+
based on the provided auth configuration. It supports both API Key and OAuth2 authentication.
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
# API Key authentication
|
|
48
|
+
api_key_tool = VeIdentityMcpTool(
|
|
49
|
+
mcp_tool=mcp_tool,
|
|
50
|
+
mcp_session_manager=session_manager,
|
|
51
|
+
auth_config=api_key_auth("my-provider")
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# OAuth2 authentication
|
|
55
|
+
oauth2_tool = VeIdentityMcpTool(
|
|
56
|
+
mcp_tool=mcp_tool,
|
|
57
|
+
mcp_session_manager=session_manager,
|
|
58
|
+
auth_config=oauth2_auth(
|
|
59
|
+
provider_name="my-provider",
|
|
60
|
+
scopes=["read", "write"],
|
|
61
|
+
auth_flow="M2M"
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
Note: For API key authentication, only header-based API keys are supported.
|
|
66
|
+
Query and cookie-based API keys will result in authentication errors.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __init__(
|
|
70
|
+
self,
|
|
71
|
+
*,
|
|
72
|
+
mcp_tool: McpBaseTool,
|
|
73
|
+
mcp_session_manager: MCPSessionManager,
|
|
74
|
+
auth_config: VeIdentityAuthConfig,
|
|
75
|
+
):
|
|
76
|
+
"""Initialize the unified Identity MCP tool.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
mcp_tool: The MCP tool to wrap.
|
|
80
|
+
mcp_session_manager: The MCP session manager to use for communication.
|
|
81
|
+
auth_config: Authentication configuration (ApiKeyAuthConfig, WorkloadAuthConfig, or OAuth2AuthConfig).
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
ValueError: If mcp_tool or mcp_session_manager is None.
|
|
85
|
+
"""
|
|
86
|
+
if mcp_tool is None:
|
|
87
|
+
raise ValueError("mcp_tool cannot be None")
|
|
88
|
+
if mcp_session_manager is None:
|
|
89
|
+
raise ValueError("mcp_session_manager cannot be None")
|
|
90
|
+
|
|
91
|
+
# Initialize mixins first
|
|
92
|
+
super().__init__(
|
|
93
|
+
name=mcp_tool.name,
|
|
94
|
+
description=mcp_tool.description if mcp_tool.description else "",
|
|
95
|
+
auth_config=auth_config,
|
|
96
|
+
)
|
|
97
|
+
self._mcp_tool = mcp_tool
|
|
98
|
+
self._mcp_session_manager = mcp_session_manager
|
|
99
|
+
|
|
100
|
+
@override
|
|
101
|
+
def _get_declaration(self) -> FunctionDeclaration:
|
|
102
|
+
"""Gets the function declaration for the tool.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
FunctionDeclaration: The Gemini function declaration for the tool.
|
|
106
|
+
"""
|
|
107
|
+
schema_dict = self._mcp_tool.inputSchema
|
|
108
|
+
schema = _to_gemini_schema(schema_dict)
|
|
109
|
+
|
|
110
|
+
return FunctionDeclaration(
|
|
111
|
+
name=self.name,
|
|
112
|
+
description=self.description,
|
|
113
|
+
parameters=schema,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
@override
|
|
117
|
+
async def run_async(
|
|
118
|
+
self, *, args: dict[str, Any], tool_context: ToolContext
|
|
119
|
+
) -> Any:
|
|
120
|
+
"""Execute the wrapped MCP tool with Identity authentication.
|
|
121
|
+
|
|
122
|
+
This method handles authentication based on the configured auth type.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
args: Arguments to pass to the wrapped tool.
|
|
126
|
+
tool_context: The tool context for accessing session state and auth.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
The result from the wrapped tool, or an auth pending message for OAuth2.
|
|
130
|
+
"""
|
|
131
|
+
try:
|
|
132
|
+
return await self.run_with_identity_auth(
|
|
133
|
+
args=args, tool_context=tool_context
|
|
134
|
+
)
|
|
135
|
+
except AuthRequiredException as e:
|
|
136
|
+
# Only OAuth2 can raise this exception
|
|
137
|
+
return e.message
|
|
138
|
+
|
|
139
|
+
async def _execute_with_credential(
|
|
140
|
+
self,
|
|
141
|
+
*,
|
|
142
|
+
args: dict[str, Any],
|
|
143
|
+
tool_context: ToolContext,
|
|
144
|
+
credential: AuthCredential,
|
|
145
|
+
) -> Any:
|
|
146
|
+
"""Execute the MCP tool with the provided credential.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
args: Arguments to pass to the tool.
|
|
150
|
+
tool_context: The tool context.
|
|
151
|
+
credential: The authentication credential (API key or OAuth2).
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
The result from the tool execution.
|
|
155
|
+
"""
|
|
156
|
+
return await self._run_async_impl(
|
|
157
|
+
args=args, tool_context=tool_context, credential=credential
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
@retry_on_closed_resource
|
|
161
|
+
async def _run_async_impl(
|
|
162
|
+
self, *, args, tool_context: ToolContext, credential: AuthCredential
|
|
163
|
+
):
|
|
164
|
+
"""Runs the tool asynchronously.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
args: The arguments as a dict to pass to the tool.
|
|
168
|
+
tool_context: The tool context of the current invocation.
|
|
169
|
+
credential: The authentication credential.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Any: The response from the tool.
|
|
173
|
+
"""
|
|
174
|
+
# Extract headers from credential for session pooling
|
|
175
|
+
headers = generate_headers(credential)
|
|
176
|
+
|
|
177
|
+
# Get the session from the session manager
|
|
178
|
+
session = await self._mcp_session_manager.create_session(headers=headers)
|
|
179
|
+
|
|
180
|
+
response = await session.call_tool(self._mcp_tool.name, arguments=args)
|
|
181
|
+
return response
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
# Copyright 2025 Google LLC
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
import sys
|
|
19
|
+
from typing import List, Dict, Any
|
|
20
|
+
from typing import Optional
|
|
21
|
+
from typing import TextIO
|
|
22
|
+
from typing import Union
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
from pydantic import model_validator, field_validator
|
|
26
|
+
from typing_extensions import override
|
|
27
|
+
|
|
28
|
+
from mcp import StdioServerParameters, ClientSession
|
|
29
|
+
from mcp.types import ListToolsResult
|
|
30
|
+
|
|
31
|
+
from google.adk.tools.mcp_tool.mcp_session_manager import (
|
|
32
|
+
retry_on_closed_resource,
|
|
33
|
+
SseConnectionParams,
|
|
34
|
+
StdioConnectionParams,
|
|
35
|
+
StreamableHTTPConnectionParams,
|
|
36
|
+
MCPSessionManager,
|
|
37
|
+
)
|
|
38
|
+
from google.adk.tools.base_tool import BaseTool
|
|
39
|
+
from google.adk.tools.base_toolset import BaseToolset, ToolPredicate
|
|
40
|
+
from google.adk.tools.tool_configs import ToolArgsConfig, BaseToolConfig
|
|
41
|
+
from google.adk.agents.readonly_context import ReadonlyContext
|
|
42
|
+
|
|
43
|
+
from veadk.integrations.ve_identity.auth_config import VeIdentityAuthConfig
|
|
44
|
+
from veadk.integrations.ve_identity.auth_mixins import VeIdentityAuthMixin
|
|
45
|
+
from veadk.integrations.ve_identity.mcp_tool import VeIdentityMcpTool
|
|
46
|
+
from veadk.integrations.ve_identity.utils import generate_headers
|
|
47
|
+
|
|
48
|
+
logger = logging.getLogger(__name__)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class VeIdentityMcpToolset(VeIdentityAuthMixin, BaseToolset):
|
|
52
|
+
"""Connects to a MCP Server, and retrieves MCP Tools into ADK Tools.
|
|
53
|
+
|
|
54
|
+
Unified MCP toolset with automatic Identity authentication.
|
|
55
|
+
|
|
56
|
+
This toolset manages the connection to an MCP server and provides tools
|
|
57
|
+
that can be used by an agent. It properly implements the BaseToolset
|
|
58
|
+
interface for easy integration with the agent framework.
|
|
59
|
+
|
|
60
|
+
Examples:
|
|
61
|
+
With API Key authentication:
|
|
62
|
+
|
|
63
|
+
from veadk.integrations.ve_identity import VeIdentityMcpToolset, api_key_auth
|
|
64
|
+
from mcp import StdioServerParameters
|
|
65
|
+
|
|
66
|
+
toolset = VeIdentityMcpToolset(
|
|
67
|
+
auth_config=api_key_auth("my-provider"),
|
|
68
|
+
connection_params=StdioServerParameters(
|
|
69
|
+
command='npx',
|
|
70
|
+
args=["-y", "@modelcontextprotocol/server-filesystem"],
|
|
71
|
+
),
|
|
72
|
+
tool_filter=['read_file', 'list_directory']
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
With OAuth2 authentication:
|
|
76
|
+
|
|
77
|
+
from veadk.integrations.ve_identity import VeIdentityMcpToolset, oauth2_auth
|
|
78
|
+
from mcp import StdioServerParameters
|
|
79
|
+
|
|
80
|
+
toolset = VeIdentityMcpToolset(
|
|
81
|
+
auth_config=oauth2_auth(
|
|
82
|
+
provider_name="github",
|
|
83
|
+
scopes=["repo", "user"],
|
|
84
|
+
auth_flow="M2M"
|
|
85
|
+
),
|
|
86
|
+
connection_params=StdioServerParameters(
|
|
87
|
+
command='npx',
|
|
88
|
+
args=["-y", "@modelcontextprotocol/server-filesystem"],
|
|
89
|
+
),
|
|
90
|
+
tool_filter=['read_file', 'list_directory']
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
Using in an agent:
|
|
94
|
+
|
|
95
|
+
agent = LlmAgent(
|
|
96
|
+
model='gemini-2.0-flash',
|
|
97
|
+
name='enterprise_assistant',
|
|
98
|
+
instruction='Help user accessing their file systems',
|
|
99
|
+
tools=[toolset],
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Cleanup is handled automatically by the agent framework
|
|
103
|
+
# But you can also manually close if needed:
|
|
104
|
+
# await toolset.close()
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
*,
|
|
110
|
+
auth_config: VeIdentityAuthConfig,
|
|
111
|
+
connection_params: Union[
|
|
112
|
+
StdioServerParameters,
|
|
113
|
+
StdioConnectionParams,
|
|
114
|
+
SseConnectionParams,
|
|
115
|
+
StreamableHTTPConnectionParams,
|
|
116
|
+
],
|
|
117
|
+
tool_filter: Optional[Union[ToolPredicate, List[str]]] = None,
|
|
118
|
+
tool_name_prefix: Optional[str] = None,
|
|
119
|
+
errlog: TextIO = sys.stderr,
|
|
120
|
+
):
|
|
121
|
+
"""Initializes the MCPToolset.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
auth_config: Authentication configuration (ApiKeyAuthConfig, WorkloadAuthConfig, or OAuth2AuthConfig).
|
|
125
|
+
connection_params: The connection parameters to the MCP server. Can be:
|
|
126
|
+
``StdioConnectionParams`` for using local mcp server (e.g. using ``npx`` or
|
|
127
|
+
``python3``); or ``SseConnectionParams`` for a local/remote SSE server; or
|
|
128
|
+
``StreamableHTTPConnectionParams`` for local/remote Streamable http
|
|
129
|
+
server. Note, ``StdioServerParameters`` is also supported for using local
|
|
130
|
+
mcp server (e.g. using ``npx`` or ``python3`` ), but it does not support
|
|
131
|
+
timeout, and we recommend to use ``StdioConnectionParams`` instead when
|
|
132
|
+
timeout is needed.
|
|
133
|
+
tool_filter: Optional filter to select specific tools. Can be either: - A
|
|
134
|
+
list of tool names to include - A ToolPredicate function for custom
|
|
135
|
+
filtering logic
|
|
136
|
+
tool_name_prefix: A prefix to be added to the name of each tool in this
|
|
137
|
+
toolset.
|
|
138
|
+
errlog: TextIO stream for error logging.
|
|
139
|
+
"""
|
|
140
|
+
if not connection_params:
|
|
141
|
+
raise ValueError("Missing connection params in VeIdentityMcpToolset.")
|
|
142
|
+
|
|
143
|
+
# Initialize mixins first
|
|
144
|
+
super().__init__(
|
|
145
|
+
auth_config=auth_config,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
# Store Identity specific configuration
|
|
149
|
+
self._auth_config = auth_config
|
|
150
|
+
self._connection_params = connection_params
|
|
151
|
+
self._tool_filter = tool_filter
|
|
152
|
+
self._tool_name_prefix = tool_name_prefix
|
|
153
|
+
self._errlog = errlog
|
|
154
|
+
|
|
155
|
+
# Create MCP session manager
|
|
156
|
+
self._mcp_session_manager = MCPSessionManager(
|
|
157
|
+
connection_params=connection_params,
|
|
158
|
+
errlog=errlog,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
@retry_on_closed_resource
|
|
162
|
+
@override
|
|
163
|
+
async def get_tools(
|
|
164
|
+
self,
|
|
165
|
+
readonly_context: Optional[ReadonlyContext] = None,
|
|
166
|
+
) -> List[BaseTool]:
|
|
167
|
+
"""Return all tools in the toolset based on the provided context.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
readonly_context: Context used to filter tools available to the agent.
|
|
171
|
+
If None, all tools in the toolset are returned.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
List[BaseTool]: A list of tools available under the specified context.
|
|
175
|
+
"""
|
|
176
|
+
if readonly_context is None:
|
|
177
|
+
raise ValueError("Readonly context is required for VeIdentityMcpToolset.")
|
|
178
|
+
|
|
179
|
+
# Get credential for authentication
|
|
180
|
+
credential = await self._get_credential(tool_context=readonly_context)
|
|
181
|
+
|
|
182
|
+
headers = generate_headers(credential)
|
|
183
|
+
# Get session from session manager
|
|
184
|
+
session: ClientSession = await self._mcp_session_manager.create_session(
|
|
185
|
+
headers=headers
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# Fetch available tools from the MCP server
|
|
189
|
+
tools_response: ListToolsResult = await session.list_tools()
|
|
190
|
+
|
|
191
|
+
# Apply filtering based on context and tool_filter
|
|
192
|
+
tools = []
|
|
193
|
+
for tool in tools_response.tools:
|
|
194
|
+
mcp_tool = VeIdentityMcpTool(
|
|
195
|
+
mcp_tool=tool,
|
|
196
|
+
mcp_session_manager=self._mcp_session_manager,
|
|
197
|
+
auth_config=self._auth_config,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
# Apply tool name prefix if specified
|
|
201
|
+
if self._tool_name_prefix:
|
|
202
|
+
mcp_tool._name = f"{self._tool_name_prefix}{mcp_tool.name}"
|
|
203
|
+
|
|
204
|
+
if self._is_tool_selected(mcp_tool, readonly_context):
|
|
205
|
+
tools.append(mcp_tool)
|
|
206
|
+
return tools
|
|
207
|
+
|
|
208
|
+
def _is_tool_selected(
|
|
209
|
+
self, tool: BaseTool, readonly_context: Optional[ReadonlyContext]
|
|
210
|
+
) -> bool:
|
|
211
|
+
"""Check if a tool should be included based on filters and context."""
|
|
212
|
+
# Apply tool filter if specified
|
|
213
|
+
if self._tool_filter is not None:
|
|
214
|
+
if callable(self._tool_filter):
|
|
215
|
+
# ToolPredicate function
|
|
216
|
+
if not self._tool_filter(tool, readonly_context):
|
|
217
|
+
return False
|
|
218
|
+
elif isinstance(self._tool_filter, list):
|
|
219
|
+
# List of tool names
|
|
220
|
+
if tool.name not in self._tool_filter:
|
|
221
|
+
return False
|
|
222
|
+
|
|
223
|
+
return True
|
|
224
|
+
|
|
225
|
+
async def close(self) -> None:
|
|
226
|
+
"""Performs cleanup and releases resources held by the toolset.
|
|
227
|
+
|
|
228
|
+
This method closes the MCP session and cleans up all associated resources.
|
|
229
|
+
It's designed to be safe to call multiple times and handles cleanup errors
|
|
230
|
+
gracefully to avoid blocking application shutdown.
|
|
231
|
+
"""
|
|
232
|
+
try:
|
|
233
|
+
await self._mcp_session_manager.close()
|
|
234
|
+
except Exception as e:
|
|
235
|
+
# Log the error but don't re-raise to avoid blocking shutdown
|
|
236
|
+
print(f"Warning: Error during MCPToolset cleanup: {e}", file=self._errlog)
|
|
237
|
+
|
|
238
|
+
@override
|
|
239
|
+
@classmethod
|
|
240
|
+
def from_config(
|
|
241
|
+
cls, config: ToolArgsConfig, config_abs_path: str
|
|
242
|
+
) -> "VeIdentityMcpToolset":
|
|
243
|
+
"""Create VeIdentityMcpToolset from configuration.
|
|
244
|
+
|
|
245
|
+
Priority order:
|
|
246
|
+
1. If config_abs_path points to an existing file, load configuration from that file
|
|
247
|
+
2. Otherwise, use the provided config object directly
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
config: Configuration object (used as fallback).
|
|
251
|
+
config_abs_path: Absolute path to the config file. If this file exists,
|
|
252
|
+
it will be loaded as the primary configuration source.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
VeIdentityMcpToolset instance.
|
|
256
|
+
"""
|
|
257
|
+
import os
|
|
258
|
+
|
|
259
|
+
# Priority 1: Try to load from config_abs_path if it exists
|
|
260
|
+
if config_abs_path and os.path.exists(config_abs_path):
|
|
261
|
+
try:
|
|
262
|
+
file_config_dict = cls._load_config_from_file(config_abs_path)
|
|
263
|
+
# Let Pydantic handle the validation and conversion
|
|
264
|
+
ve_identity_config = VeIdentityMcpToolsetConfig.model_validate(
|
|
265
|
+
file_config_dict
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
except Exception as e:
|
|
269
|
+
# If file loading fails, fall back to config parameter
|
|
270
|
+
print(f"Warning: Failed to load config from {config_abs_path}: {e}")
|
|
271
|
+
print("Falling back to provided config parameter")
|
|
272
|
+
ve_identity_config = VeIdentityMcpToolsetConfig.model_validate(
|
|
273
|
+
config.model_dump()
|
|
274
|
+
)
|
|
275
|
+
else:
|
|
276
|
+
# Priority 2: Use provided config object
|
|
277
|
+
ve_identity_config = VeIdentityMcpToolsetConfig.model_validate(
|
|
278
|
+
config.model_dump()
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Determine which connection params to use
|
|
282
|
+
if ve_identity_config.stdio_server_params:
|
|
283
|
+
connection_params = ve_identity_config.stdio_server_params
|
|
284
|
+
elif ve_identity_config.stdio_connection_params:
|
|
285
|
+
connection_params = ve_identity_config.stdio_connection_params
|
|
286
|
+
elif ve_identity_config.sse_connection_params:
|
|
287
|
+
connection_params = ve_identity_config.sse_connection_params
|
|
288
|
+
elif ve_identity_config.streamable_http_connection_params:
|
|
289
|
+
connection_params = ve_identity_config.streamable_http_connection_params
|
|
290
|
+
else:
|
|
291
|
+
raise ValueError(
|
|
292
|
+
"No connection params found in VeIdentityMcpToolsetConfig."
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
return cls(
|
|
296
|
+
auth_config=ve_identity_config.auth_config,
|
|
297
|
+
connection_params=connection_params,
|
|
298
|
+
tool_filter=ve_identity_config.tool_filter,
|
|
299
|
+
tool_name_prefix=ve_identity_config.tool_name_prefix,
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
@classmethod
|
|
303
|
+
def _load_config_from_file(cls, file_path: str) -> dict:
|
|
304
|
+
"""Load configuration from JSON or YAML file.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
file_path: Path to the configuration file.
|
|
308
|
+
|
|
309
|
+
Returns:
|
|
310
|
+
Configuration dictionary.
|
|
311
|
+
|
|
312
|
+
Raises:
|
|
313
|
+
FileNotFoundError: If the config file doesn't exist.
|
|
314
|
+
ValueError: If the file format is not supported or invalid.
|
|
315
|
+
"""
|
|
316
|
+
import os
|
|
317
|
+
import json
|
|
318
|
+
import yaml
|
|
319
|
+
from pathlib import Path
|
|
320
|
+
|
|
321
|
+
if not os.path.exists(file_path):
|
|
322
|
+
raise FileNotFoundError(f"Configuration file not found: {file_path}")
|
|
323
|
+
|
|
324
|
+
file_ext = Path(file_path).suffix.lower()
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
328
|
+
if file_ext in [".json"]:
|
|
329
|
+
return json.load(f)
|
|
330
|
+
elif file_ext in [".yaml", ".yml"]:
|
|
331
|
+
return yaml.safe_load(f)
|
|
332
|
+
else:
|
|
333
|
+
# Try to detect format by content
|
|
334
|
+
content = f.read()
|
|
335
|
+
f.seek(0)
|
|
336
|
+
|
|
337
|
+
# Try JSON first
|
|
338
|
+
try:
|
|
339
|
+
return json.loads(content)
|
|
340
|
+
except json.JSONDecodeError:
|
|
341
|
+
# Try YAML
|
|
342
|
+
try:
|
|
343
|
+
return yaml.safe_load(content)
|
|
344
|
+
except yaml.YAMLError:
|
|
345
|
+
raise ValueError(
|
|
346
|
+
f"Unsupported configuration file format: {file_ext}"
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
except Exception as e:
|
|
350
|
+
if isinstance(e, (FileNotFoundError, ValueError)):
|
|
351
|
+
raise
|
|
352
|
+
raise ValueError(f"Failed to load configuration from {file_path}: {e}")
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class VeIdentityMcpToolsetConfig(BaseToolConfig):
|
|
356
|
+
"""Configuration for VeIdentityMcpToolset."""
|
|
357
|
+
|
|
358
|
+
model_config = {"arbitrary_types_allowed": True}
|
|
359
|
+
|
|
360
|
+
# Authentication configuration
|
|
361
|
+
auth_config: Union[VeIdentityAuthConfig, Dict[str, Any]]
|
|
362
|
+
|
|
363
|
+
# Connection parameters (exactly one must be set)
|
|
364
|
+
stdio_server_params: Optional[StdioServerParameters] = None
|
|
365
|
+
stdio_connection_params: Optional[StdioConnectionParams] = None
|
|
366
|
+
sse_connection_params: Optional[SseConnectionParams] = None
|
|
367
|
+
streamable_http_connection_params: Optional[StreamableHTTPConnectionParams] = None
|
|
368
|
+
|
|
369
|
+
# Optional toolset configuration
|
|
370
|
+
tool_filter: Optional[List[str]] = None
|
|
371
|
+
tool_name_prefix: Optional[str] = None
|
|
372
|
+
|
|
373
|
+
@field_validator("auth_config", mode="before")
|
|
374
|
+
@classmethod
|
|
375
|
+
def _validate_auth_config(cls, v):
|
|
376
|
+
"""Convert dict to proper auth config object."""
|
|
377
|
+
if isinstance(v, dict):
|
|
378
|
+
from veadk.integrations.ve_identity.auth_config import (
|
|
379
|
+
api_key_auth,
|
|
380
|
+
oauth2_auth,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
provider_name = v.get("provider_name")
|
|
384
|
+
if not provider_name:
|
|
385
|
+
raise ValueError("provider_name is required in auth_config")
|
|
386
|
+
|
|
387
|
+
region = v.get("region", "cn-beijing")
|
|
388
|
+
|
|
389
|
+
# Determine auth type based on presence of OAuth2-specific fields
|
|
390
|
+
has_scopes = "scopes" in v
|
|
391
|
+
has_auth_flow = "auth_flow" in v
|
|
392
|
+
|
|
393
|
+
if has_scopes or has_auth_flow:
|
|
394
|
+
# OAuth2 configuration
|
|
395
|
+
scopes = v.get("scopes")
|
|
396
|
+
auth_flow = v.get("auth_flow")
|
|
397
|
+
callback_url = v.get("callback_url")
|
|
398
|
+
force_authentication = v.get("force_authentication", False)
|
|
399
|
+
response_for_auth_required = v.get("response_for_auth_required")
|
|
400
|
+
|
|
401
|
+
return oauth2_auth(
|
|
402
|
+
provider_name=provider_name,
|
|
403
|
+
scopes=scopes,
|
|
404
|
+
auth_flow=auth_flow,
|
|
405
|
+
callback_url=callback_url,
|
|
406
|
+
force_authentication=force_authentication,
|
|
407
|
+
response_for_auth_required=response_for_auth_required,
|
|
408
|
+
region=region,
|
|
409
|
+
)
|
|
410
|
+
else:
|
|
411
|
+
# API Key configuration
|
|
412
|
+
return api_key_auth(provider_name=provider_name, region=region)
|
|
413
|
+
return v
|
|
414
|
+
|
|
415
|
+
@model_validator(mode="after")
|
|
416
|
+
def _check_only_one_connection_param(self):
|
|
417
|
+
"""Ensure exactly one connection parameter is set."""
|
|
418
|
+
connection_fields = [
|
|
419
|
+
self.stdio_server_params,
|
|
420
|
+
self.stdio_connection_params,
|
|
421
|
+
self.sse_connection_params,
|
|
422
|
+
self.streamable_http_connection_params,
|
|
423
|
+
]
|
|
424
|
+
populated_fields = [f for f in connection_fields if f is not None]
|
|
425
|
+
|
|
426
|
+
if len(populated_fields) != 1:
|
|
427
|
+
raise ValueError(
|
|
428
|
+
"Exactly one of stdio_server_params, stdio_connection_params, "
|
|
429
|
+
"sse_connection_params, streamable_http_connection_params must be set."
|
|
430
|
+
)
|
|
431
|
+
return self
|