Skip to content

Commit 72ce248

Browse files
committed
feat: add support to TrustedMCP
1 parent ecad812 commit 72ce248

File tree

9 files changed

+601
-6
lines changed

9 files changed

+601
-6
lines changed

docs/content/1.introduction/1.overview.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ seo:
3131

3232
- 支持 Identity 管理
3333
- 支持 API Key 服务鉴权与 OAuth2 用户鉴权能力
34+
- 支持 AICC 可信部署与 Agent 到 MCP 再到 LLM 服务的端到端加密通信
3435

3536
### 更完备的可观测性和评估能力
3637

docs/content/3.agent/1.agent.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,3 +265,49 @@ print(response)
265265
## 多智能体协作
266266

267267
使用 VeADK 可以构建多 Agent 协作, 主 Agent 通过 `sub_agents` 机制协调多个子 Agent 完成复杂任务。
268+
269+
## 可信 Agent
270+
271+
依托机密计算技术(如Jeddak AICC),可以将 Agent 以及大模型服务(如机密豆包)运行在可信环境中,并通过 TrustedMCP 以及端到端加密通信,构建全链路可信的 Agent。其中,TrustedMCP是火山引擎推出的可信MCP,在标准MCP协议的基础上扩展核心组件之间的身份证明及验证能力,提供组件之间端到端的通信安全保障,可以解决MCP应用中服务身份不可信、数据被篡改、流量劫持、数据隐私泄露等安全威胁。
272+
273+
使用 VeADK 可以构建可信 Agent, 并通过 `tools` 机制调用 TrustedMCP 服务。
274+
275+
### 使用示例
276+
277+
```python [agent.py]
278+
import asyncio
279+
280+
from veadk import Agent
281+
from veadk.utils.mcp_utils import get_mcp_params
282+
from veadk.tools.mcp_tool.trusted_mcp_toolset import TrustedMcpToolset
283+
284+
mcp_url = "<TrustedMCP server address>"
285+
286+
# 1. 开启 TrustedMCP 功能以及相关配置
287+
connection_params = get_mcp_params(mcp_url)
288+
connection_params.headers = {"x-trusted-mcp": "true"}
289+
290+
# 2. 初始化 TrustedMcpToolset 工具集
291+
toolset = TrustedMcpToolset(connection_params=connection_params)
292+
293+
# 3. 初始化 Agent
294+
agent = Agent(tools=[toolset])
295+
296+
# 4. 运行 Agent
297+
response = asyncio.run(agent.run("北京天气怎么样?"))
298+
299+
print(response) # 北京天气晴朗,气温25°C。
300+
```
301+
302+
### 选项
303+
304+
TrustedMcpToolset 配置参数:工作流 Agent 采用统一的参数:
305+
306+
::field-group
307+
::field{name="x-trusted-mcp" type="string"}
308+
默认为 `true` - 是否开启 TrustedMCP 功能
309+
::
310+
311+
::field{name="aicc-config" type="string"}
312+
默认为 `./aicc_config.json` - AICC 配置文件路径。配置文件内容请参考[TrustedMCP 配置文件](https://github.com/volcengine/AICC-Trusted-MCP/blob/main/README.md)
313+
::

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ dependencies = [
2626
"volcengine>=1.0.193", # For Volcengine sign
2727
"agent-pilot-sdk>=0.1.2", # Prompt optimization by Volcengine AgentPilot/PromptPilot toolkits
2828
"fastmcp>=2.11.3", # For running MCP
29+
"trustedmcp>=0.0.4", # For running TrustedMCP
2930
"cookiecutter>=2.6.0", # For cloud deploy
3031
"omegaconf>=2.3.0", # For agent builder
3132
"llama-index>=0.14.0",
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
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+
import sys
16+
import unittest
17+
from unittest import mock
18+
import asyncio
19+
20+
from veadk.tools.mcp_tool.trusted_mcp_toolset import TrustedMcpToolset
21+
from veadk.tools.mcp_tool.trusted_mcp_session_manager import (
22+
TrustedMcpSessionManager,
23+
)
24+
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
25+
26+
27+
class TestTrustedMcpComponents(unittest.TestCase):
28+
def setUp(self):
29+
# Create simple mock objects
30+
self.mock_stdio_params = mock.MagicMock()
31+
# Add serializable headers to stdio_params
32+
mock_headers = mock.MagicMock()
33+
mock_headers.copy.return_value = {"Content-Type": "application/json"}
34+
self.mock_stdio_params.headers = mock_headers
35+
# Add getattr method that throws AttributeError by default to simulate non-HTTP connection
36+
self.mock_stdio_params.getattr = mock.MagicMock(side_effect=AttributeError)
37+
38+
self.mock_http_params = mock.MagicMock(spec=StreamableHTTPConnectionParams)
39+
self.mock_http_params.url = "http://mock-url.com"
40+
self.mock_http_params.timeout = 30
41+
self.mock_http_params.sse_read_timeout = 60
42+
self.mock_http_params.terminate_on_close = False
43+
# Add serializable headers to http_params
44+
mock_http_headers = mock.MagicMock()
45+
mock_http_headers.copy.return_value = {
46+
"Content-Type": "application/json",
47+
"x-trusted-mcp": "true",
48+
}
49+
self.mock_http_params.headers = mock_http_headers
50+
51+
def test_trusted_mcp_toolset_init(self):
52+
"""Test the initialization of TrustedMcpToolset"""
53+
# Mock TrustedMcpSessionManager and logger
54+
with (
55+
mock.patch(
56+
"veadk.tools.mcp_tool.trusted_mcp_toolset.TrustedMcpSessionManager"
57+
) as mock_session_manager,
58+
mock.patch("veadk.tools.mcp_tool.trusted_mcp_toolset.logger"),
59+
):
60+
# Create instance directly without mocking parent initialization to automatically set necessary attributes
61+
toolset = TrustedMcpToolset(
62+
connection_params=self.mock_stdio_params, tool_filter=["read_file"]
63+
)
64+
65+
# Verify TrustedMcpSessionManager was created
66+
mock_session_manager.assert_called_once()
67+
68+
# Verify _trusted_mcp_session_manager attribute is set
69+
self.assertIsNotNone(toolset._mcp_session_manager)
70+
71+
def test_trusted_mcp_session_manager_create_client_trusted(self):
72+
"""Test TrustedMcpSessionManager._create_client method - TrustedMCP mode"""
73+
# Mock trusted_mcp_client_context
74+
with mock.patch(
75+
"veadk.tools.mcp_tool.trusted_mcp_session_manager.trusted_mcp_client_context"
76+
) as mock_trusted_client:
77+
# Create manager instance directly
78+
manager = TrustedMcpSessionManager(
79+
connection_params=self.mock_http_params, errlog=sys.stderr
80+
)
81+
82+
# Call _create_client with trusted_mcp header
83+
merged_headers = {"x-trusted-mcp": "true"}
84+
result = manager._create_client(merged_headers)
85+
86+
# Verify trusted_mcp_client_context was called
87+
mock_trusted_client.assert_called_once()
88+
89+
# Verify result
90+
self.assertEqual(result, mock_trusted_client.return_value)
91+
92+
def test_trusted_mcp_session_manager_create_client_standard(self):
93+
"""Test TrustedMcpSessionManager._create_client method - Standard mode"""
94+
# Mock parent class's _create_client method
95+
with mock.patch(
96+
"veadk.tools.mcp_tool.trusted_mcp_session_manager.MCPSessionManager._create_client"
97+
) as mock_super_create:
98+
expected_client = mock.MagicMock()
99+
mock_super_create.return_value = expected_client
100+
101+
# Create manager instance directly without mocking parent initialization to automatically set necessary attributes
102+
manager = TrustedMcpSessionManager(
103+
connection_params=self.mock_stdio_params, errlog=sys.stderr
104+
)
105+
106+
# Call _create_client with normal headers
107+
merged_headers = {"content-type": "application/json"}
108+
result = manager._create_client(merged_headers)
109+
110+
# Verify parent's _create_client was called
111+
mock_super_create.assert_called_once_with(merged_headers)
112+
113+
# Verify result
114+
self.assertEqual(result, expected_client)
115+
116+
def test_trusted_mcp_session_manager_trusted_create_session(self):
117+
"""Test TrustedMcpSessionManager.create_session method - TrustedMCP mode"""
118+
119+
# Use coroutine test helper to run async test
120+
async def run_test():
121+
# Create a mock async context manager
122+
class MockAsyncContext:
123+
def __init__(self):
124+
self.session = mock.MagicMock()
125+
126+
async def __aenter__(self):
127+
return self.session
128+
129+
async def __aexit__(self, exc_type, exc_val, exc_tb):
130+
return False
131+
132+
# Create a mock AsyncExitStack that supports async methods
133+
class MockAsyncExitStack:
134+
def __init__(self):
135+
self.entered_contexts = []
136+
137+
async def enter_async_context(self, context):
138+
self.entered_contexts.append(context)
139+
return await context.__aenter__()
140+
141+
async def aclose(self):
142+
pass
143+
144+
# Mock required functions and classes
145+
mock_trusted_context = MockAsyncContext()
146+
with (
147+
mock.patch(
148+
"veadk.tools.mcp_tool.trusted_mcp_session_manager.trusted_mcp_client",
149+
return_value=mock_trusted_context,
150+
),
151+
mock.patch(
152+
"veadk.tools.mcp_tool.trusted_mcp_session_manager.MCPSessionManager._merge_headers",
153+
return_value={"x-trusted-mcp": "true"},
154+
),
155+
mock.patch(
156+
"veadk.tools.mcp_tool.trusted_mcp_session_manager.MCPSessionManager._generate_session_key",
157+
return_value="session-key",
158+
),
159+
mock.patch(
160+
"veadk.tools.mcp_tool.trusted_mcp_session_manager.MCPSessionManager._is_session_disconnected",
161+
return_value=False,
162+
),
163+
mock.patch(
164+
"veadk.tools.mcp_tool.trusted_mcp_session_manager.AsyncExitStack",
165+
return_value=MockAsyncExitStack(),
166+
),
167+
):
168+
# Create manager instance and set necessary attributes
169+
manager = TrustedMcpSessionManager(
170+
connection_params=self.mock_http_params, errlog=sys.stderr
171+
)
172+
manager._sessions = {}
173+
manager._session_lock = asyncio.Lock()
174+
175+
# Call create_session
176+
headers = {"x-trusted-mcp": "true"}
177+
result = await manager.create_session(headers)
178+
179+
# Verify result is the mock session
180+
self.assertEqual(result, mock_trusted_context.session)
181+
182+
# Run async test
183+
asyncio.run(run_test())
184+
185+
def test_trusted_mcp_session_manager_session_reuse(self):
186+
"""Test TrustedMcpSessionManager.create_session method - Session reuse"""
187+
188+
# Use coroutine test helper to run async test
189+
async def run_test():
190+
# Mock necessary methods
191+
with (
192+
mock.patch(
193+
"veadk.tools.mcp_tool.trusted_mcp_session_manager.MCPSessionManager._merge_headers",
194+
return_value={"header": "value"},
195+
),
196+
mock.patch(
197+
"veadk.tools.mcp_tool.trusted_mcp_session_manager.MCPSessionManager._generate_session_key",
198+
return_value="session-key",
199+
),
200+
mock.patch(
201+
"veadk.tools.mcp_tool.trusted_mcp_session_manager.MCPSessionManager._is_session_disconnected",
202+
return_value=False,
203+
),
204+
):
205+
# Create manager instance
206+
manager = TrustedMcpSessionManager(
207+
connection_params=self.mock_http_params, errlog=sys.stderr
208+
)
209+
manager._sessions = {}
210+
manager._session_lock = asyncio.Lock()
211+
212+
# Set up an existing session
213+
existing_session = mock.MagicMock()
214+
existing_exit_stack = mock.MagicMock()
215+
manager._sessions = {
216+
"session-key": (existing_session, existing_exit_stack)
217+
}
218+
219+
# Call create_session
220+
result = await manager.create_session({"header": "value"})
221+
222+
# Verify existing session was returned
223+
self.assertEqual(result, existing_session)
224+
225+
# Run async test
226+
asyncio.run(run_test())
227+
228+
229+
if __name__ == "__main__":
230+
unittest.main()

veadk/tools/builtin_tools/web_search.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
logger = get_logger(__name__)
2828

2929

30-
def web_search(query: str, tool_context: ToolContext) -> list[str]:
30+
def web_search(query: str, tool_context: ToolContext | None = None) -> list[str]:
3131
"""Search a query in websites.
3232
3333
Args:
@@ -36,8 +36,11 @@ def web_search(query: str, tool_context: ToolContext) -> list[str]:
3636
Returns:
3737
A list of result documents.
3838
"""
39-
ak = tool_context.state.get("VOLCENGINE_ACCESS_KEY")
40-
sk = tool_context.state.get("VOLCENGINE_SECRET_KEY")
39+
ak = None
40+
sk = None
41+
if tool_context:
42+
ak = tool_context.state.get("VOLCENGINE_ACCESS_KEY")
43+
sk = tool_context.state.get("VOLCENGINE_SECRET_KEY")
4144
session_token = ""
4245

4346
if not (ak and sk):

veadk/tools/mcp_tool/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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.

0 commit comments

Comments
 (0)