From 4b85e113f825d4236749cc235cbc0183ed6b8695 Mon Sep 17 00:00:00 2001 From: tangou Date: Thu, 7 Aug 2025 21:01:35 +0800 Subject: [PATCH 01/12] test(vefaas): update Unitest of CloudAgentEngine and CloudApp --- docs/docs/deploy.md | 46 +++++++++++++++- tests/test_cloud.py | 131 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 tests/test_cloud.py diff --git a/docs/docs/deploy.md b/docs/docs/deploy.md index 4f41ddfc..46a1661a 100644 --- a/docs/docs/deploy.md +++ b/docs/docs/deploy.md @@ -117,5 +117,47 @@ app = CloudApp(name="veadk-agent", endpoint=ENDPOINT) cloud_app.invoke(user_id=USER_ID, session_id=SESSION_ID, message=...) ``` -- 更新自身代码 -- 删除自身 +## 更新云应用 + +### 通过 CloudAgentEngine 更新 + +当您需要更新已部署的Agent代码时,可以使用`update_function_code`方法: + +```python +from veadk.cloud.cloud_agent_engine import CloudAgentEngine + +engine = CloudAgentEngine() + +# 更新现有应用的代码,保持相同的访问端点 +updated_cloud_app = engine.update_function_code( + application_name="my-agent-app", # 现有应用名称 + path="/my-agent-project" # 本地项目路径 +) + +print(f"应用已更新,访问地址:{updated_cloud_app.vefaas_endpoint}") +``` + +**注意事项:** +- 更新操作会保持相同的访问端点URL +- 确保项目路径包含`agent.py`文件 + + +## 删除云应用 + +### 通过 CloudAgentEngine 删除 + +```python +from veadk.cloud.cloud_agent_engine import CloudAgentEngine + +engine = CloudAgentEngine() + +# 删除指定的云应用 +engine.remove("my-agent-app") +``` + +执行时会提示确认: +``` +Confirm delete cloud app my-agent-app? (y/N): y +``` + +输入`y`确认删除,输入其他任何字符或直接回车则取消删除。 \ No newline at end of file diff --git a/tests/test_cloud.py b/tests/test_cloud.py new file mode 100644 index 00000000..25ff129f --- /dev/null +++ b/tests/test_cloud.py @@ -0,0 +1,131 @@ +# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import tempfile +import pytest +from unittest.mock import Mock, patch, AsyncMock + +from veadk.cloud.cloud_agent_engine import CloudAgentEngine + + +@pytest.mark.asyncio +async def test_cloud(): + app_name = "test-app" + key = "CloudTestIdentifier123" + test_endpoint = "https://test-endpoint.volcengine.com" + test_message = "Hello cloud agent" + + # Create temporary directory with required agent.py file for testing + with tempfile.TemporaryDirectory() as temp_dir: + with open(os.path.join(temp_dir, "agent.py"), "w") as f: + f.write(f"# Test agent implementation with {key}") + + with patch.dict( + os.environ, + { + "VOLCENGINE_ACCESS_KEY": "test_access_key", + "VOLCENGINE_SECRET_KEY": "test_secret_key", + }, + ): + # Mock shutil.copy to avoid template file copying issues + with patch("shutil.copy"): + with patch( + "veadk.cloud.cloud_agent_engine.VeFaaS" + ) as mock_vefaas_class: + # Setup mock VeFaaS service for all operations + mock_vefaas_service = Mock() + mock_vefaas_class.return_value = mock_vefaas_service + + # Mock deploy operation + mock_vefaas_service.deploy.return_value = ( + test_endpoint, + "app-123", + "func-456", + ) + + # Mock update operation + mock_vefaas_service._update_function_code.return_value = ( + test_endpoint, + "app-123", + "func-456", + ) + + # Mock remove operation + mock_vefaas_service.find_app_id_by_name.return_value = "app-123" + mock_vefaas_service.delete.return_value = None + + # Test CloudAgentEngine creation and deploy functionality + engine = CloudAgentEngine() + + # Test deploy operation + cloud_app = engine.deploy(application_name=app_name, path=temp_dir) + + # Verify deployment result contains expected values + assert cloud_app.vefaas_application_name == app_name + assert cloud_app.vefaas_endpoint == test_endpoint + assert cloud_app.vefaas_application_id == "app-123" + + # Test update_function_code operation + updated_app = engine.update_function_code( + application_name=app_name, path=temp_dir + ) + + # Verify update result maintains same endpoint + assert updated_app.vefaas_endpoint == test_endpoint + + # Test remove operation with mocked user input + with patch("builtins.input", return_value="y"): + engine.remove(app_name) + mock_vefaas_service.find_app_id_by_name.assert_called_with( + app_name + ) + mock_vefaas_service.delete.assert_called_with("app-123") + + # Test CloudApp message_send functionality + mock_response = Mock() + mock_message = Mock() + mock_response.root.result = mock_message + + with patch.object(cloud_app, "_get_a2a_client") as mock_get_client: + mock_client = AsyncMock() + mock_client.send_message = AsyncMock(return_value=mock_response) + mock_get_client.return_value = mock_client + + # Test message sending to cloud agent + result = await cloud_app.message_send( + message=test_message, + session_id="session-123", + user_id="user-456", + ) + + # Verify message sending result + assert result == mock_message + mock_client.send_message.assert_called_once() + + # Test CloudApp delete_self functionality + with patch("builtins.input", return_value="y"): + with patch( + "veadk.cli.services.vefaas.vefaas.VeFaaS" + ) as mock_vefaas_in_app: + mock_vefaas_client = Mock() + mock_vefaas_in_app.return_value = mock_vefaas_client + mock_vefaas_client.delete.return_value = None + + cloud_app.delete_self() + mock_vefaas_client.delete.assert_called_with("app-123") + + # Verify all mocks were called as expected + mock_vefaas_service.deploy.assert_called_once() + mock_vefaas_service._update_function_code.assert_called_once() From 23c332a2236b172c2815bdaa9acb063b3cae93d0 Mon Sep 17 00:00:00 2001 From: tangou Date: Thu, 7 Aug 2025 21:14:22 +0800 Subject: [PATCH 02/12] fix(test): test_cloud typer dependency --- tests/test_cloud.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_cloud.py b/tests/test_cloud.py index 25ff129f..5c17261a 100644 --- a/tests/test_cloud.py +++ b/tests/test_cloud.py @@ -15,9 +15,13 @@ import os import tempfile import pytest +import sys + from unittest.mock import Mock, patch, AsyncMock -from veadk.cloud.cloud_agent_engine import CloudAgentEngine +sys.modules["typer"] = Mock() + +from veadk.cloud.cloud_agent_engine import CloudAgentEngine # noqa: E402 @pytest.mark.asyncio From 7d3400f2d4074bddf4efbdb1b550532d54c09d87 Mon Sep 17 00:00:00 2001 From: tangou Date: Thu, 7 Aug 2025 22:00:35 +0800 Subject: [PATCH 03/12] test(vefaas): volcenginesdkcore dependency, uv sync --all-extras --- .github/workflows/unit-tests.yaml | 2 +- tests/test_cloud.py | 186 ++++++++++++++---------------- 2 files changed, 89 insertions(+), 99 deletions(-) diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index 5ccab19c..71e9f6c2 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -49,7 +49,7 @@ jobs: run: | uv venv .venv source .venv/bin/activate - uv sync + uv sync --all-extras uv pip install -e . - name: Run unit tests with pytest diff --git a/tests/test_cloud.py b/tests/test_cloud.py index 5c17261a..6fe17802 100644 --- a/tests/test_cloud.py +++ b/tests/test_cloud.py @@ -15,13 +15,13 @@ import os import tempfile import pytest -import sys from unittest.mock import Mock, patch, AsyncMock -sys.modules["typer"] = Mock() +os.environ["VOLCENGINE_ACCESS_KEY"] = "test_access_key" +os.environ["VOLCENGINE_SECRET_KEY"] = "test_secret_key" -from veadk.cloud.cloud_agent_engine import CloudAgentEngine # noqa: E402 +from veadk.cloud.cloud_agent_engine import CloudAgentEngine @pytest.mark.asyncio @@ -36,100 +36,90 @@ async def test_cloud(): with open(os.path.join(temp_dir, "agent.py"), "w") as f: f.write(f"# Test agent implementation with {key}") - with patch.dict( - os.environ, - { - "VOLCENGINE_ACCESS_KEY": "test_access_key", - "VOLCENGINE_SECRET_KEY": "test_secret_key", - }, - ): - # Mock shutil.copy to avoid template file copying issues - with patch("shutil.copy"): - with patch( - "veadk.cloud.cloud_agent_engine.VeFaaS" - ) as mock_vefaas_class: - # Setup mock VeFaaS service for all operations - mock_vefaas_service = Mock() - mock_vefaas_class.return_value = mock_vefaas_service - - # Mock deploy operation - mock_vefaas_service.deploy.return_value = ( - test_endpoint, - "app-123", - "func-456", + # 这里不再需要 patch.dict,因为环境变量已经设置 + # Mock shutil.copy to avoid template file copying issues + with patch("shutil.copy"): + with patch("veadk.cloud.cloud_agent_engine.VeFaaS") as mock_vefaas_class: + # Setup mock VeFaaS service for all operations + mock_vefaas_service = Mock() + mock_vefaas_class.return_value = mock_vefaas_service + + # Mock deploy operation + mock_vefaas_service.deploy.return_value = ( + test_endpoint, + "app-123", + "func-456", + ) + + # Mock update operation + mock_vefaas_service._update_function_code.return_value = ( + test_endpoint, + "app-123", + "func-456", + ) + + # Mock remove operation + mock_vefaas_service.find_app_id_by_name.return_value = "app-123" + mock_vefaas_service.delete.return_value = None + + # Test CloudAgentEngine creation and deploy functionality + engine = CloudAgentEngine() + + # Test deploy operation + cloud_app = engine.deploy(application_name=app_name, path=temp_dir) + + # Verify deployment result contains expected values + assert cloud_app.vefaas_application_name == app_name + assert cloud_app.vefaas_endpoint == test_endpoint + assert cloud_app.vefaas_application_id == "app-123" + + # Test update_function_code operation + updated_app = engine.update_function_code( + application_name=app_name, path=temp_dir + ) + + # Verify update result maintains same endpoint + assert updated_app.vefaas_endpoint == test_endpoint + + # Test remove operation with mocked user input + with patch("builtins.input", return_value="y"): + engine.remove(app_name) + mock_vefaas_service.find_app_id_by_name.assert_called_with(app_name) + mock_vefaas_service.delete.assert_called_with("app-123") + + # Test CloudApp message_send functionality + mock_response = Mock() + mock_message = Mock() + mock_response.root.result = mock_message + + with patch.object(cloud_app, "_get_a2a_client") as mock_get_client: + mock_client = AsyncMock() + mock_client.send_message = AsyncMock(return_value=mock_response) + mock_get_client.return_value = mock_client + + # Test message sending to cloud agent + result = await cloud_app.message_send( + message=test_message, + session_id="session-123", + user_id="user-456", ) - # Mock update operation - mock_vefaas_service._update_function_code.return_value = ( - test_endpoint, - "app-123", - "func-456", - ) - - # Mock remove operation - mock_vefaas_service.find_app_id_by_name.return_value = "app-123" - mock_vefaas_service.delete.return_value = None - - # Test CloudAgentEngine creation and deploy functionality - engine = CloudAgentEngine() - - # Test deploy operation - cloud_app = engine.deploy(application_name=app_name, path=temp_dir) - - # Verify deployment result contains expected values - assert cloud_app.vefaas_application_name == app_name - assert cloud_app.vefaas_endpoint == test_endpoint - assert cloud_app.vefaas_application_id == "app-123" - - # Test update_function_code operation - updated_app = engine.update_function_code( - application_name=app_name, path=temp_dir - ) - - # Verify update result maintains same endpoint - assert updated_app.vefaas_endpoint == test_endpoint - - # Test remove operation with mocked user input - with patch("builtins.input", return_value="y"): - engine.remove(app_name) - mock_vefaas_service.find_app_id_by_name.assert_called_with( - app_name - ) - mock_vefaas_service.delete.assert_called_with("app-123") - - # Test CloudApp message_send functionality - mock_response = Mock() - mock_message = Mock() - mock_response.root.result = mock_message - - with patch.object(cloud_app, "_get_a2a_client") as mock_get_client: - mock_client = AsyncMock() - mock_client.send_message = AsyncMock(return_value=mock_response) - mock_get_client.return_value = mock_client - - # Test message sending to cloud agent - result = await cloud_app.message_send( - message=test_message, - session_id="session-123", - user_id="user-456", - ) - - # Verify message sending result - assert result == mock_message - mock_client.send_message.assert_called_once() - - # Test CloudApp delete_self functionality - with patch("builtins.input", return_value="y"): - with patch( - "veadk.cli.services.vefaas.vefaas.VeFaaS" - ) as mock_vefaas_in_app: - mock_vefaas_client = Mock() - mock_vefaas_in_app.return_value = mock_vefaas_client - mock_vefaas_client.delete.return_value = None - - cloud_app.delete_self() - mock_vefaas_client.delete.assert_called_with("app-123") - - # Verify all mocks were called as expected - mock_vefaas_service.deploy.assert_called_once() - mock_vefaas_service._update_function_code.assert_called_once() + # Verify message sending result + assert result == mock_message + mock_client.send_message.assert_called_once() + + # Test CloudApp delete_self functionality + with patch("builtins.input", return_value="y"): + with patch( + "veadk.cli.services.vefaas.vefaas.VeFaaS" + ) as mock_vefaas_in_app: + mock_vefaas_client = Mock() + mock_vefaas_in_app.return_value = mock_vefaas_client + mock_vefaas_client.delete.return_value = None + + cloud_app.delete_self() + mock_vefaas_client.delete.assert_called_with("app-123") + + # Verify all mocks were called as expected + mock_vefaas_service.deploy.assert_called_once() + mock_vefaas_service._update_function_code.assert_called_once() From b63aeb8569eb9b239b49a100ffa05d4d479bb842 Mon Sep 17 00:00:00 2001 From: tangou Date: Fri, 8 Aug 2025 10:12:23 +0800 Subject: [PATCH 04/12] test(vefaas): modify chinese annotation --- tests/test_cloud.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_cloud.py b/tests/test_cloud.py index 6fe17802..0c7b0415 100644 --- a/tests/test_cloud.py +++ b/tests/test_cloud.py @@ -36,7 +36,6 @@ async def test_cloud(): with open(os.path.join(temp_dir, "agent.py"), "w") as f: f.write(f"# Test agent implementation with {key}") - # 这里不再需要 patch.dict,因为环境变量已经设置 # Mock shutil.copy to avoid template file copying issues with patch("shutil.copy"): with patch("veadk.cloud.cloud_agent_engine.VeFaaS") as mock_vefaas_class: From 97d4d0c2353d63b3a037887fd7dd290ceb29bbb0 Mon Sep 17 00:00:00 2001 From: tangou Date: Fri, 8 Aug 2025 10:54:23 +0800 Subject: [PATCH 05/12] test(vefaas): fix deploy docs --- docs/docs/deploy.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/deploy.md b/docs/docs/deploy.md index 46a1661a..79aa7b29 100644 --- a/docs/docs/deploy.md +++ b/docs/docs/deploy.md @@ -134,7 +134,7 @@ updated_cloud_app = engine.update_function_code( path="/my-agent-project" # 本地项目路径 ) -print(f"应用已更新,访问地址:{updated_cloud_app.vefaas_endpoint}") +# 可以使用updated_cloud_app.vefaas_endpoint访问您的项目 ``` **注意事项:** @@ -152,7 +152,7 @@ from veadk.cloud.cloud_agent_engine import CloudAgentEngine engine = CloudAgentEngine() # 删除指定的云应用 -engine.remove("my-agent-app") +engine.remove(app_name="my-agent-app") ``` 执行时会提示确认: From 9de67050465fd0e63d7a3db5d995456ca26a7cb6 Mon Sep 17 00:00:00 2001 From: tangou Date: Thu, 14 Aug 2025 12:40:28 +0800 Subject: [PATCH 06/12] feat(vefaas): add mcp server pre-commit --- pyproject.toml | 1 + tests/test_tracing.py | 10 +- veadk/cli/main.py | 27 +++- veadk/cli/services/vefaas/template/deploy.py | 47 ++++-- .../services/vefaas/template/src/app_mcp.py | 134 ++++++++++++++++++ .../vefaas/template/src/requirements.txt | 3 +- veadk/cli/services/vefaas/template/src/run.sh | 14 +- veadk/cloud/cloud_agent_engine.py | 10 ++ .../tracing/telemetry/opentelemetry_tracer.py | 3 +- 9 files changed, 227 insertions(+), 22 deletions(-) create mode 100644 veadk/cli/services/vefaas/template/src/app_mcp.py diff --git a/pyproject.toml b/pyproject.toml index 6d976580..42e0fd84 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,6 +21,7 @@ dependencies = [ "opentelemetry-exporter-otlp>=1.35.0", "opentelemetry-instrumentation-logging>=0.56b0", "wrapt>=1.17.2", # For patching built-in functions + "fastmcp>=2.11.3", ] [project.scripts] diff --git a/tests/test_tracing.py b/tests/test_tracing.py index 5cc855d7..0750c009 100644 --- a/tests/test_tracing.py +++ b/tests/test_tracing.py @@ -33,13 +33,14 @@ from opentelemetry.sdk import trace as trace_sdk from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import ( - OTLPSpanExporter, - ) + OTLPSpanExporter, +) APP_NAME = "app" USER_ID = "testuser" SESSION_ID = "testsession" + def init_exporters(): cozeloop_exporter = CozeloopExporter( config=CozeloopExporterConfig( @@ -68,6 +69,7 @@ def init_exporters(): ) return [cozeloop_exporter, apmplus_exporter, tls_exporter] + def gen_span_processor(endpoint: str): otlp_exporter = OTLPSpanExporter( endpoint=endpoint, @@ -75,6 +77,7 @@ def gen_span_processor(endpoint: str): span_processor = BatchSpanProcessor(otlp_exporter) return span_processor + @pytest.mark.asyncio async def test_tracing(): exporters = init_exporters() @@ -86,6 +89,7 @@ async def test_tracing(): # TODO: Ensure the tracing provider is set correctly after loading SDK # TODO: Ensure the tracing provider is set correctly after loading SDK + @pytest.mark.asyncio async def test_tracing_with_global_provider(): exporters = init_exporters() @@ -113,5 +117,3 @@ async def test_tracing_with_apmplus_global_provider(): # apmplus exporter won't init again assert len(tracer.exporters) == 4 # with extra 2 built-in exporters - - diff --git a/veadk/cli/main.py b/veadk/cli/main.py index 1c2372f0..8359ab70 100644 --- a/veadk/cli/main.py +++ b/veadk/cli/main.py @@ -98,14 +98,14 @@ def init(): ) deploy_mode_options = { - "1": "A2A Server", + "1": "A2A/MCP Server", "2": "VeADK Studio", "3": "VeADK Web / Google ADK Web", } deploy_mode = Prompt.ask( """Choose your deploy mode: -1. A2A Server +1. A2A/MCP Server 2. VeADK Studio 3. VeADK Web / Google ADK Web """, @@ -118,6 +118,28 @@ def init(): print("Invalid deploy mode, set default to A2A Server") deploy_mode = deploy_mode_options["1"] + # Sub-choice for A2A/MCP Server + server_type = None + if deploy_mode == deploy_mode_options["1"]: # A2A/MCP Server + server_type_options = { + "1": "A2A Server", + "2": "MCP Server", + } + + server_type_choice = Prompt.ask( + """Choose server type: + 1. A2A Server + 2. MCP Server + """, + default="1", + ) + + if server_type_choice in server_type_options: + server_type = server_type_options[server_type_choice] + else: + print("Invalid server type, set default to A2A Server") + server_type = server_type_options["1"] + setting_values = { "VEFAAS_APPLICATION_NAME": vefaas_application_name, "GATEWAY_NAME": gateway_name, @@ -125,6 +147,7 @@ def init(): "GATEWAY_UPSTREAM_NAME": gateway_upstream_name, "USE_STUDIO": deploy_mode == deploy_mode_options["2"], "USE_ADK_WEB": deploy_mode == deploy_mode_options["3"], + "USE_MCP": server_type == "MCP Server" if server_type else False, } shutil.copytree(template_dir, target_dir) diff --git a/veadk/cli/services/vefaas/template/deploy.py b/veadk/cli/services/vefaas/template/deploy.py index 72b6f341..83dae90d 100644 --- a/veadk/cli/services/vefaas/template/deploy.py +++ b/veadk/cli/services/vefaas/template/deploy.py @@ -16,6 +16,7 @@ from pathlib import Path from veadk.cloud.cloud_agent_engine import CloudAgentEngine +from fastmcp.client import Client SESSION_ID = "cloud_app_test_session" USER_ID = "cloud_app_test_user" @@ -26,11 +27,11 @@ GATEWAY_UPSTREAMNAME = "" USE_STUDIO = False USE_ADK_WEB = False +USE_MCP = False async def main(): engine = CloudAgentEngine() - cloud_app = engine.deploy( path=str(Path(__file__).parent / "src"), application_name=VEFAAS_APPLICATION_NAME, @@ -39,17 +40,43 @@ async def main(): gateway_upstream_name=GATEWAY_UPSTREAMNAME, use_studio=USE_STUDIO, use_adk_web=USE_ADK_WEB, + use_mcp=USE_MCP, ) - if not USE_STUDIO and not USE_ADK_WEB: - response_message = await cloud_app.message_send( - "How is the weather like in Beijing?", SESSION_ID, USER_ID - ) - print(f"VeFaaS application ID: {cloud_app.vefaas_application_id}") - print(f"Message ID: {response_message.messageId}") - print( - f"Response from {cloud_app.vefaas_endpoint}: {response_message.parts[0].root.text}" - ) + if not USE_STUDIO and (not USE_ADK_WEB): + if not USE_MCP: + response_message = await cloud_app.message_send( + "How is the weather like in Beijing?", SESSION_ID, USER_ID + ) + print(f"VeFaaS application ID: {cloud_app.vefaas_application_id}") + print(f"Message ID: {response_message.messageId}") + print( + f"Response from {cloud_app.vefaas_endpoint}: {response_message.parts[0].root.text}" + ) + else: + # cloud_app = CloudApp(vefaas_application_name=VEFAAS_APPLICATION_NAME) + endpoint = cloud_app._get_vefaas_endpoint() + print(f"endpoint:{endpoint}") + # Connect to MCP server + client = Client(f"{endpoint}/mcp") + + async with client: + # List available tools + tools = await client.list_tools() + print(f"tool_0: {tools[0].__dict__}\n") + + # Call run_agent tool, pass user input and session information + res = await client.call_tool( + "run_agent", + { + "user_input": "How is the weather like in Beijing?", + "session_id": SESSION_ID, + "user_id": USER_ID, + }, + ) + print(f"VeFaaS application ID: {cloud_app.vefaas_application_id}") + print(f"Response from {cloud_app.vefaas_endpoint}: {res}") + else: print(f"Web is running at: {cloud_app.vefaas_endpoint}") diff --git a/veadk/cli/services/vefaas/template/src/app_mcp.py b/veadk/cli/services/vefaas/template/src/app_mcp.py new file mode 100644 index 00000000..0388bf85 --- /dev/null +++ b/veadk/cli/services/vefaas/template/src/app_mcp.py @@ -0,0 +1,134 @@ +# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import argparse +from agent import agent, app_name, short_term_memory +from veadk.tracing.base_tracer import BaseTracer +from veadk.tracing.telemetry.opentelemetry_tracer import OpentelemetryTracer +from veadk import Agent +from veadk.memory.short_term_memory import ShortTermMemory +from veadk.runner import Runner +from fastmcp import FastMCP + + +# ============================================================================== +# Tracer Config ================================================================ + +TRACERS: list[BaseTracer] = [] + +exporters = [] +if os.getenv("VEADK_TRACER_APMPLUS", "").lower() == "true": + from veadk.tracing.telemetry.exporters.apmplus_exporter import APMPlusExporter + + exporters.append(APMPlusExporter()) + +if os.getenv("VEADK_TRACER_COZELOOP", "").lower() == "true": + from veadk.tracing.telemetry.exporters.cozeloop_exporter import CozeloopExporter + + exporters.append(CozeloopExporter()) + +if os.getenv("VEADK_TRACER_TLS", "").lower() == "true": + from veadk.tracing.telemetry.exporters.tls_exporter import TLSExporter + + exporters.append(TLSExporter()) + +TRACERS.append(OpentelemetryTracer(exporters=exporters)) + + +agent.tracers.extend(TRACERS) +if not getattr(agent, "before_model_callback", None): + agent.before_model_callback = [] +if not getattr(agent, "after_model_callback", None): + agent.after_model_callback = [] +if not getattr(agent, "after_tool_callback", None): + agent.after_tool_callback = [] +for tracer in TRACERS: + if tracer.tracer_hook_before_model not in agent.before_model_callback: + agent.before_model_callback.append(tracer.tracer_hook_before_model) + if tracer.tracer_hook_after_model not in agent.after_model_callback: + agent.after_model_callback.append(tracer.tracer_hook_after_model) + if tracer.tracer_hook_after_tool not in agent.after_tool_callback: + agent.after_tool_callback.append(tracer.tracer_hook_after_tool) + +# Tracer Config ================================================================ +# ============================================================================== + + +class VeMCPServer: + def __init__(self, agent: Agent, app_name: str, short_term_memory: ShortTermMemory): + self.agent = agent + self.app_name = app_name + self.short_term_memory = short_term_memory + + self.runner = Runner( + agent=self.agent, + short_term_memory=self.short_term_memory, + app_name=app_name, + user_id="", # waiting for tool call to provide user_id + ) + + def build(self) -> FastMCP: + # Create MCP server + mcp = FastMCP(name=self.app_name) + + @mcp.tool + async def run_agent( + user_input: str, + user_id: str = "unknown_user", + session_id: str = "unknown_session", + ) -> str: + """ + Execute agent with user input and return final output + Args: + user_input: str, user_id: str = "unknown_user", session_id: str = "unknown_session" + Returns: + final_output: str + """ + # Set user_id for runner + self.runner.user_id = user_id + + # Running agent and get final output + final_output = await self.runner.run( + messages=user_input, + session_id=session_id, + ) + + return final_output + + return mcp + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="MCP Server") + parser.add_argument( + "--transport", default="http", help="Transport type (default: http)" + ) + parser.add_argument( + "--host", default="0.0.0.0", help="Host address (default: 0.0.0.0)" + ) + parser.add_argument( + "--port", type=int, default=8000, help="Port number (default: 8000)" + ) + parser.add_argument("--log-level", default="INFO", help="Log level (default: INFO)") + + args = parser.parse_args() + + server = VeMCPServer( + agent=agent, + app_name=app_name, + short_term_memory=short_term_memory, + ) + mcp = server.build() + mcp.run(transport=args.transport, host=args.host, port=args.port) diff --git a/veadk/cli/services/vefaas/template/src/requirements.txt b/veadk/cli/services/vefaas/template/src/requirements.txt index dd4310cd..3297e021 100644 --- a/veadk/cli/services/vefaas/template/src/requirements.txt +++ b/veadk/cli/services/vefaas/template/src/requirements.txt @@ -3,4 +3,5 @@ opensearch-py==2.8.0 agent-pilot-sdk>=0.0.9 # extra dep for prompt optimization in veadk studio typer>=0.16.0 uvicorn[standard] -fastapi \ No newline at end of file +fastapi +fastmcp \ No newline at end of file diff --git a/veadk/cli/services/vefaas/template/src/run.sh b/veadk/cli/services/vefaas/template/src/run.sh index dc5b268d..9aad909d 100755 --- a/veadk/cli/services/vefaas/template/src/run.sh +++ b/veadk/cli/services/vefaas/template/src/run.sh @@ -38,8 +38,11 @@ python3 -m pip install uvicorn[standard] python3 -m pip install fastapi +python3 -m pip install fastmcp + USE_STUDIO=${USE_STUDIO:-False} USE_ADK_WEB=${USE_ADK_WEB:-False} +USE_MCP=${USE_MCP:-False} if [ "$USE_STUDIO" = "True" ]; then echo "USE_STUDIO is True, running veadk studio" @@ -54,10 +57,15 @@ elif [ "$USE_STUDIO" = "False" ]; then cd ../ exec python3 -m veadk.cli.main web --host "0.0.0.0" else - echo "USE_ADK_WEB is False, running a2a server" - exec python3 -m uvicorn app:app --host $HOST --port $PORT --timeout-graceful-shutdown $TIMEOUT --loop asyncio + if [ "$USE_MCP" = "True" ]; then + echo "USE_MCP is True, running MCP server" + exec python3 app_mcp.py --transport http --host $HOST --port $PORT --log-level "INFO" + else + echo "USE_MCP is False, running a2a server" + exec python3 -m uvicorn app:app --host $HOST --port $PORT --timeout-graceful-shutdown $TIMEOUT --loop asyncio + fi fi else - # running a2a server + # running a2a server (default) exec python3 -m uvicorn app:app --host $HOST --port $PORT --timeout-graceful-shutdown $TIMEOUT --loop asyncio fi \ No newline at end of file diff --git a/veadk/cloud/cloud_agent_engine.py b/veadk/cloud/cloud_agent_engine.py index 17c48f8d..5372dc9e 100644 --- a/veadk/cloud/cloud_agent_engine.py +++ b/veadk/cloud/cloud_agent_engine.py @@ -93,6 +93,7 @@ def deploy( gateway_upstream_name: str = "", use_studio: bool = False, use_adk_web: bool = False, + use_mcp: bool = False, ) -> CloudApp: """Deploy local agent project to Volcengine FaaS platform. @@ -128,6 +129,15 @@ def deploy( veadk.config.veadk_environments["USE_ADK_WEB"] = "False" + if use_mcp: + import veadk.config + + veadk.config.veadk_environments["USE_MCP"] = "True" + else: + import veadk.config + + veadk.config.veadk_environments["USE_MCP"] = "False" + # convert `path` to absolute path path = str(Path(path).resolve()) self._prepare(path, application_name) diff --git a/veadk/tracing/telemetry/opentelemetry_tracer.py b/veadk/tracing/telemetry/opentelemetry_tracer.py index f3a0e1e7..62f914ec 100644 --- a/veadk/tracing/telemetry/opentelemetry_tracer.py +++ b/veadk/tracing/telemetry/opentelemetry_tracer.py @@ -112,7 +112,7 @@ def _init_tracer_provider(self): if not isinstance(global_tracer_provider, TracerProvider): logger.info( - f"Global tracer provider has not been set. Create tracer provider and set it now." + "Global tracer provider has not been set. Create tracer provider and set it now." ) # 1.1 init tracer provider tracer_provider = trace_sdk.TracerProvider() @@ -144,7 +144,6 @@ def _init_tracer_provider(self): self._processors.append(processor) logger.debug(f"Init OpentelemetryTracer with {len(self.exporters)} exporters.") - @override def dump( self, From 306f68615a86e6e256e08f2a135655442ecb81c5 Mon Sep 17 00:00:00 2001 From: tangou Date: Thu, 14 Aug 2025 12:53:31 +0800 Subject: [PATCH 07/12] feat(vefaas): add mcp server pyproject cli --- pyproject.toml | 4 ++-- veadk/cli/services/vefaas/template/src/run.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 42e0fd84..43e89138 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,6 @@ dependencies = [ "opentelemetry-exporter-otlp>=1.35.0", "opentelemetry-instrumentation-logging>=0.56b0", "wrapt>=1.17.2", # For patching built-in functions - "fastmcp>=2.11.3", ] [project.scripts] @@ -44,7 +43,8 @@ cli = [ "volcengine-python-sdk==4.0.3", # For Volcengine API "typer>=0.16.0", # For command-line implementation "streamlit==1.46.1", # For running VeADK studio app - "agent-pilot-sdk>=0.0.9" # Prompt optimization by Volcengine AgentPilot/PromptPilot toolkits + "agent-pilot-sdk>=0.0.9", # Prompt optimization by Volcengine AgentPilot/PromptPilot toolkits + "fastmcp>=2.11.3", # For running MCP ] dev = [ "pre-commit>=4.2.0", # Format checking diff --git a/veadk/cli/services/vefaas/template/src/run.sh b/veadk/cli/services/vefaas/template/src/run.sh index 9aad909d..d8834159 100755 --- a/veadk/cli/services/vefaas/template/src/run.sh +++ b/veadk/cli/services/vefaas/template/src/run.sh @@ -66,6 +66,6 @@ elif [ "$USE_STUDIO" = "False" ]; then fi fi else - # running a2a server (default) + # running a2a server exec python3 -m uvicorn app:app --host $HOST --port $PORT --timeout-graceful-shutdown $TIMEOUT --loop asyncio fi \ No newline at end of file From 372b2e66ada47d854c8f700e232dc899db6d11a1 Mon Sep 17 00:00:00 2001 From: tangou Date: Thu, 14 Aug 2025 17:03:17 +0800 Subject: [PATCH 08/12] fix(vefaas): fix mcp mount a2a --- veadk/cli/main.py | 23 --- veadk/cli/services/vefaas/template/deploy.py | 66 ++++----- veadk/cli/services/vefaas/template/src/app.py | 87 +++++++++++- .../services/vefaas/template/src/app_mcp.py | 134 ------------------ veadk/cli/services/vefaas/template/src/run.sh | 9 +- veadk/cloud/cloud_agent_engine.py | 10 -- 6 files changed, 119 insertions(+), 210 deletions(-) delete mode 100644 veadk/cli/services/vefaas/template/src/app_mcp.py diff --git a/veadk/cli/main.py b/veadk/cli/main.py index 8359ab70..5f5a8c73 100644 --- a/veadk/cli/main.py +++ b/veadk/cli/main.py @@ -118,28 +118,6 @@ def init(): print("Invalid deploy mode, set default to A2A Server") deploy_mode = deploy_mode_options["1"] - # Sub-choice for A2A/MCP Server - server_type = None - if deploy_mode == deploy_mode_options["1"]: # A2A/MCP Server - server_type_options = { - "1": "A2A Server", - "2": "MCP Server", - } - - server_type_choice = Prompt.ask( - """Choose server type: - 1. A2A Server - 2. MCP Server - """, - default="1", - ) - - if server_type_choice in server_type_options: - server_type = server_type_options[server_type_choice] - else: - print("Invalid server type, set default to A2A Server") - server_type = server_type_options["1"] - setting_values = { "VEFAAS_APPLICATION_NAME": vefaas_application_name, "GATEWAY_NAME": gateway_name, @@ -147,7 +125,6 @@ def init(): "GATEWAY_UPSTREAM_NAME": gateway_upstream_name, "USE_STUDIO": deploy_mode == deploy_mode_options["2"], "USE_ADK_WEB": deploy_mode == deploy_mode_options["3"], - "USE_MCP": server_type == "MCP Server" if server_type else False, } shutil.copytree(template_dir, target_dir) diff --git a/veadk/cli/services/vefaas/template/deploy.py b/veadk/cli/services/vefaas/template/deploy.py index 83dae90d..2e12b223 100644 --- a/veadk/cli/services/vefaas/template/deploy.py +++ b/veadk/cli/services/vefaas/template/deploy.py @@ -15,6 +15,7 @@ import asyncio from pathlib import Path + from veadk.cloud.cloud_agent_engine import CloudAgentEngine from fastmcp.client import Client @@ -27,7 +28,6 @@ GATEWAY_UPSTREAMNAME = "" USE_STUDIO = False USE_ADK_WEB = False -USE_MCP = False async def main(): @@ -40,42 +40,42 @@ async def main(): gateway_upstream_name=GATEWAY_UPSTREAMNAME, use_studio=USE_STUDIO, use_adk_web=USE_ADK_WEB, - use_mcp=USE_MCP, ) - + query_example = "How is the weather like in Beijing?" if not USE_STUDIO and (not USE_ADK_WEB): - if not USE_MCP: - response_message = await cloud_app.message_send( - "How is the weather like in Beijing?", SESSION_ID, USER_ID - ) - print(f"VeFaaS application ID: {cloud_app.vefaas_application_id}") - print(f"Message ID: {response_message.messageId}") - print( - f"Response from {cloud_app.vefaas_endpoint}: {response_message.parts[0].root.text}" - ) - else: - # cloud_app = CloudApp(vefaas_application_name=VEFAAS_APPLICATION_NAME) - endpoint = cloud_app._get_vefaas_endpoint() - print(f"endpoint:{endpoint}") - # Connect to MCP server - client = Client(f"{endpoint}/mcp") + print("### A2A example ###") + response_message = await cloud_app.message_send( + query_example, SESSION_ID, USER_ID + ) + print(f"VeFaaS application ID: {cloud_app.vefaas_application_id}") + print(f"Message ID: {response_message.messageId}") + print( + f"Response from {cloud_app.vefaas_endpoint}: {response_message.parts[0].root.text}" + ) + + print("### MCP example ###") + # cloud_app = CloudApp(vefaas_application_name=VEFAAS_APPLICATION_NAME) + endpoint = cloud_app._get_vefaas_endpoint() + print(f"endpoint:{endpoint}") + # Connect to MCP server + client = Client(f"{endpoint}/mcp") - async with client: - # List available tools - tools = await client.list_tools() - print(f"tool_0: {tools[0].__dict__}\n") + async with client: + # List available tools + tools = await client.list_tools() + print(f"tool_0: {tools[0].__dict__}\n") - # Call run_agent tool, pass user input and session information - res = await client.call_tool( - "run_agent", - { - "user_input": "How is the weather like in Beijing?", - "session_id": SESSION_ID, - "user_id": USER_ID, - }, - ) - print(f"VeFaaS application ID: {cloud_app.vefaas_application_id}") - print(f"Response from {cloud_app.vefaas_endpoint}: {res}") + # Call run_agent tool, pass user input and session information + res = await client.call_tool( + "run_agent", + { + "user_input": query_example, + "session_id": SESSION_ID, + "user_id": USER_ID, + }, + ) + print(f"VeFaaS application ID: {cloud_app.vefaas_application_id}") + print(f"Response from {cloud_app.vefaas_endpoint}: {res}") else: print(f"Web is running at: {cloud_app.vefaas_endpoint}") diff --git a/veadk/cli/services/vefaas/template/src/app.py b/veadk/cli/services/vefaas/template/src/app.py index 23703a40..ff14588e 100644 --- a/veadk/cli/services/vefaas/template/src/app.py +++ b/veadk/cli/services/vefaas/template/src/app.py @@ -13,11 +13,16 @@ # limitations under the License. import os - from agent import agent, app_name, short_term_memory from veadk.a2a.ve_a2a_server import init_app from veadk.tracing.base_tracer import BaseTracer from veadk.tracing.telemetry.opentelemetry_tracer import OpentelemetryTracer +from veadk import Agent +from veadk.memory.short_term_memory import ShortTermMemory +from veadk.runner import Runner +from contextlib import asynccontextmanager +from fastmcp import FastMCP +from fastapi import FastAPI # ============================================================================== @@ -62,9 +67,85 @@ # Tracer Config ================================================================ # ============================================================================== -app = init_app( - server_url="0.0.0.0", # Automatic identification is not supported yet. + +# Create VeMCPServer class +class VeMCPServer: + def __init__(self, agent: Agent, app_name: str, short_term_memory: ShortTermMemory): + self.agent = agent + self.app_name = app_name + self.short_term_memory = short_term_memory + + self.runner = Runner( + agent=self.agent, + short_term_memory=self.short_term_memory, + app_name=app_name, + user_id="", # waiting for tool call to provide user_id + ) + + def build(self) -> FastMCP: + # Create MCP server + mcp = FastMCP(name=self.app_name) + + @mcp.tool + async def run_agent( + user_input: str, + user_id: str = "unknown_user", + session_id: str = "unknown_session", + ) -> str: + """ + Execute agent with user input and return final output + Args: + user_input: str, user_id: str = "unknown_user", session_id: str = "unknown_session" + Returns: + final_output: str + """ + # Set user_id for runner + self.runner.user_id = user_id + + # Running agent and get final output + final_output = await self.runner.run( + messages=user_input, + session_id=session_id, + ) + + return final_output + + return mcp + + +# Create A2A app +a2a_app = init_app( + server_url="0.0.0.0", app_name=app_name, agent=agent, short_term_memory=short_term_memory, ) + +# Create MCP server instance +mcp_server = VeMCPServer( + agent=agent, + app_name=app_name, + short_term_memory=short_term_memory, +) +mcp = mcp_server.build() + +# Create MCP ASGI app +mcp_app = mcp.http_app(path="/") + + +# Combined lifespan management +@asynccontextmanager +async def combined_lifespan(app: FastAPI): + async with mcp_app.lifespan(app): + yield + + +# Create main FastAPI app with combined lifespan +app = FastAPI(title=a2a_app.title, version=a2a_app.version, lifespan=combined_lifespan) + +# Mount A2A routes to main app +for route in a2a_app.routes: + app.routes.append(route) + +# Mount MCP server at /mcp endpoint +app.mount("/mcp", mcp_app) diff --git a/veadk/cli/services/vefaas/template/src/app_mcp.py b/veadk/cli/services/vefaas/template/src/app_mcp.py deleted file mode 100644 index 0388bf85..00000000 --- a/veadk/cli/services/vefaas/template/src/app_mcp.py +++ /dev/null @@ -1,134 +0,0 @@ -# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import os -import argparse -from agent import agent, app_name, short_term_memory -from veadk.tracing.base_tracer import BaseTracer -from veadk.tracing.telemetry.opentelemetry_tracer import OpentelemetryTracer -from veadk import Agent -from veadk.memory.short_term_memory import ShortTermMemory -from veadk.runner import Runner -from fastmcp import FastMCP - - -# ============================================================================== -# Tracer Config ================================================================ - -TRACERS: list[BaseTracer] = [] - -exporters = [] -if os.getenv("VEADK_TRACER_APMPLUS", "").lower() == "true": - from veadk.tracing.telemetry.exporters.apmplus_exporter import APMPlusExporter - - exporters.append(APMPlusExporter()) - -if os.getenv("VEADK_TRACER_COZELOOP", "").lower() == "true": - from veadk.tracing.telemetry.exporters.cozeloop_exporter import CozeloopExporter - - exporters.append(CozeloopExporter()) - -if os.getenv("VEADK_TRACER_TLS", "").lower() == "true": - from veadk.tracing.telemetry.exporters.tls_exporter import TLSExporter - - exporters.append(TLSExporter()) - -TRACERS.append(OpentelemetryTracer(exporters=exporters)) - - -agent.tracers.extend(TRACERS) -if not getattr(agent, "before_model_callback", None): - agent.before_model_callback = [] -if not getattr(agent, "after_model_callback", None): - agent.after_model_callback = [] -if not getattr(agent, "after_tool_callback", None): - agent.after_tool_callback = [] -for tracer in TRACERS: - if tracer.tracer_hook_before_model not in agent.before_model_callback: - agent.before_model_callback.append(tracer.tracer_hook_before_model) - if tracer.tracer_hook_after_model not in agent.after_model_callback: - agent.after_model_callback.append(tracer.tracer_hook_after_model) - if tracer.tracer_hook_after_tool not in agent.after_tool_callback: - agent.after_tool_callback.append(tracer.tracer_hook_after_tool) - -# Tracer Config ================================================================ -# ============================================================================== - - -class VeMCPServer: - def __init__(self, agent: Agent, app_name: str, short_term_memory: ShortTermMemory): - self.agent = agent - self.app_name = app_name - self.short_term_memory = short_term_memory - - self.runner = Runner( - agent=self.agent, - short_term_memory=self.short_term_memory, - app_name=app_name, - user_id="", # waiting for tool call to provide user_id - ) - - def build(self) -> FastMCP: - # Create MCP server - mcp = FastMCP(name=self.app_name) - - @mcp.tool - async def run_agent( - user_input: str, - user_id: str = "unknown_user", - session_id: str = "unknown_session", - ) -> str: - """ - Execute agent with user input and return final output - Args: - user_input: str, user_id: str = "unknown_user", session_id: str = "unknown_session" - Returns: - final_output: str - """ - # Set user_id for runner - self.runner.user_id = user_id - - # Running agent and get final output - final_output = await self.runner.run( - messages=user_input, - session_id=session_id, - ) - - return final_output - - return mcp - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="MCP Server") - parser.add_argument( - "--transport", default="http", help="Transport type (default: http)" - ) - parser.add_argument( - "--host", default="0.0.0.0", help="Host address (default: 0.0.0.0)" - ) - parser.add_argument( - "--port", type=int, default=8000, help="Port number (default: 8000)" - ) - parser.add_argument("--log-level", default="INFO", help="Log level (default: INFO)") - - args = parser.parse_args() - - server = VeMCPServer( - agent=agent, - app_name=app_name, - short_term_memory=short_term_memory, - ) - mcp = server.build() - mcp.run(transport=args.transport, host=args.host, port=args.port) diff --git a/veadk/cli/services/vefaas/template/src/run.sh b/veadk/cli/services/vefaas/template/src/run.sh index d8834159..2b471051 100755 --- a/veadk/cli/services/vefaas/template/src/run.sh +++ b/veadk/cli/services/vefaas/template/src/run.sh @@ -42,7 +42,6 @@ python3 -m pip install fastmcp USE_STUDIO=${USE_STUDIO:-False} USE_ADK_WEB=${USE_ADK_WEB:-False} -USE_MCP=${USE_MCP:-False} if [ "$USE_STUDIO" = "True" ]; then echo "USE_STUDIO is True, running veadk studio" @@ -57,12 +56,8 @@ elif [ "$USE_STUDIO" = "False" ]; then cd ../ exec python3 -m veadk.cli.main web --host "0.0.0.0" else - if [ "$USE_MCP" = "True" ]; then - echo "USE_MCP is True, running MCP server" - exec python3 app_mcp.py --transport http --host $HOST --port $PORT --log-level "INFO" - else - echo "USE_MCP is False, running a2a server" - exec python3 -m uvicorn app:app --host $HOST --port $PORT --timeout-graceful-shutdown $TIMEOUT --loop asyncio + echo "USE_ADK_WEB is False, running a2a server" + exec python3 -m uvicorn app:app --host $HOST --port $PORT --timeout-graceful-shutdown $TIMEOUT --loop asyncio fi fi else diff --git a/veadk/cloud/cloud_agent_engine.py b/veadk/cloud/cloud_agent_engine.py index 5372dc9e..17c48f8d 100644 --- a/veadk/cloud/cloud_agent_engine.py +++ b/veadk/cloud/cloud_agent_engine.py @@ -93,7 +93,6 @@ def deploy( gateway_upstream_name: str = "", use_studio: bool = False, use_adk_web: bool = False, - use_mcp: bool = False, ) -> CloudApp: """Deploy local agent project to Volcengine FaaS platform. @@ -129,15 +128,6 @@ def deploy( veadk.config.veadk_environments["USE_ADK_WEB"] = "False" - if use_mcp: - import veadk.config - - veadk.config.veadk_environments["USE_MCP"] = "True" - else: - import veadk.config - - veadk.config.veadk_environments["USE_MCP"] = "False" - # convert `path` to absolute path path = str(Path(path).resolve()) self._prepare(path, application_name) From 94ba042f0c641bf6a74aa883e8f78bff8264305b Mon Sep 17 00:00:00 2001 From: tangou Date: Thu, 14 Aug 2025 17:07:20 +0800 Subject: [PATCH 09/12] fix(vefaas): fix run.sh --- veadk/cli/services/vefaas/template/src/run.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/veadk/cli/services/vefaas/template/src/run.sh b/veadk/cli/services/vefaas/template/src/run.sh index 2b471051..34f2695f 100755 --- a/veadk/cli/services/vefaas/template/src/run.sh +++ b/veadk/cli/services/vefaas/template/src/run.sh @@ -58,7 +58,6 @@ elif [ "$USE_STUDIO" = "False" ]; then else echo "USE_ADK_WEB is False, running a2a server" exec python3 -m uvicorn app:app --host $HOST --port $PORT --timeout-graceful-shutdown $TIMEOUT --loop asyncio - fi fi else # running a2a server From 86eeee707d00f02b561e2f2f3adb35cd8c680231 Mon Sep 17 00:00:00 2001 From: tangou Date: Thu, 14 Aug 2025 17:18:40 +0800 Subject: [PATCH 10/12] fix(vefaas): fix app.py pre --- veadk/cli/services/vefaas/template/src/app.py | 57 +------------------ 1 file changed, 1 insertion(+), 56 deletions(-) diff --git a/veadk/cli/services/vefaas/template/src/app.py b/veadk/cli/services/vefaas/template/src/app.py index ff14588e..f390a1b4 100644 --- a/veadk/cli/services/vefaas/template/src/app.py +++ b/veadk/cli/services/vefaas/template/src/app.py @@ -17,9 +17,6 @@ from veadk.a2a.ve_a2a_server import init_app from veadk.tracing.base_tracer import BaseTracer from veadk.tracing.telemetry.opentelemetry_tracer import OpentelemetryTracer -from veadk import Agent -from veadk.memory.short_term_memory import ShortTermMemory -from veadk.runner import Runner from contextlib import asynccontextmanager from fastmcp import FastMCP from fastapi import FastAPI @@ -67,52 +64,6 @@ # Tracer Config ================================================================ # ============================================================================== - -# Create VeMCPServer class -class VeMCPServer: - def __init__(self, agent: Agent, app_name: str, short_term_memory: ShortTermMemory): - self.agent = agent - self.app_name = app_name - self.short_term_memory = short_term_memory - - self.runner = Runner( - agent=self.agent, - short_term_memory=self.short_term_memory, - app_name=app_name, - user_id="", # waiting for tool call to provide user_id - ) - - def build(self) -> FastMCP: - # Create MCP server - mcp = FastMCP(name=self.app_name) - - @mcp.tool - async def run_agent( - user_input: str, - user_id: str = "unknown_user", - session_id: str = "unknown_session", - ) -> str: - """ - Execute agent with user input and return final output - Args: - user_input: str, user_id: str = "unknown_user", session_id: str = "unknown_session" - Returns: - final_output: str - """ - # Set user_id for runner - self.runner.user_id = user_id - - # Running agent and get final output - final_output = await self.runner.run( - messages=user_input, - session_id=session_id, - ) - - return final_output - - return mcp - - # Create A2A app a2a_app = init_app( server_url="0.0.0.0", @@ -121,13 +72,7 @@ async def run_agent( short_term_memory=short_term_memory, ) -# Create MCP server instance -mcp_server = VeMCPServer( - agent=agent, - app_name=app_name, - short_term_memory=short_term_memory, -) -mcp = mcp_server.build() +mcp = FastMCP.from_fastapi(app=a2a_app, name=app_name) # Create MCP ASGI app mcp_app = mcp.http_app(path="/") From 8c409223a076f0579fb3b033effddf4cb9671708 Mon Sep 17 00:00:00 2001 From: tangou Date: Thu, 14 Aug 2025 18:32:45 +0800 Subject: [PATCH 11/12] fix(vefaas): VeA2AServer add post run_agent to mcp server --- veadk/a2a/ve_a2a_server.py | 36 +++++++++++++++++-- veadk/cli/services/vefaas/template/src/app.py | 2 +- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/veadk/a2a/ve_a2a_server.py b/veadk/a2a/ve_a2a_server.py index 4b3753b3..e3d7e1e5 100644 --- a/veadk/a2a/ve_a2a_server.py +++ b/veadk/a2a/ve_a2a_server.py @@ -21,6 +21,7 @@ from veadk.a2a.agent_card import get_agent_card from veadk.a2a.ve_agent_executor import VeAgentExecutor from veadk.memory.short_term_memory import ShortTermMemory +from veadk.runner import Runner class VeA2AServer: @@ -48,8 +49,39 @@ def build(self) -> FastAPI: ) app = app_application.build() # build routes - # import uvicorn - # uvicorn.run(app, host="127.0.0.1", port=8000) + runner = Runner( + agent=self.agent_executor.agent, + short_term_memory=self.agent_executor.short_term_memory, + app_name=self.agent_executor.app_name, + user_id="", + ) + + @app.post("/run_agent", operation_id="run_agent", tags=["mcp"]) + async def run_agent( + user_input: str, + user_id: str = "unknown_user", + session_id: str = "unknown_session", + ) -> str: + """ + Execute agent with user input and return final output + Args: + user_input: User's input message + user_id: User identifier + session_id: Session identifier + Returns: + Final agent response + """ + # Set user_id for runner + runner.user_id = user_id + + # Running agent and get final output + final_output = await runner.run( + messages=user_input, + session_id=session_id, + ) + + return final_output + return app diff --git a/veadk/cli/services/vefaas/template/src/app.py b/veadk/cli/services/vefaas/template/src/app.py index f390a1b4..66e9e2c3 100644 --- a/veadk/cli/services/vefaas/template/src/app.py +++ b/veadk/cli/services/vefaas/template/src/app.py @@ -72,7 +72,7 @@ short_term_memory=short_term_memory, ) -mcp = FastMCP.from_fastapi(app=a2a_app, name=app_name) +mcp = FastMCP.from_fastapi(app=a2a_app, name=app_name, include_tags={"mcp"}) # Create MCP ASGI app mcp_app = mcp.http_app(path="/") From 4d1d17ec61a99a073061c15e975d6fe5a64876e9 Mon Sep 17 00:00:00 2001 From: tangou Date: Thu, 14 Aug 2025 19:48:29 +0800 Subject: [PATCH 12/12] fix(vefaas): mcp server in app.py --- veadk/a2a/ve_a2a_server.py | 34 ----------------- veadk/cli/services/vefaas/template/deploy.py | 2 +- veadk/cli/services/vefaas/template/src/app.py | 37 +++++++++++++++++++ 3 files changed, 38 insertions(+), 35 deletions(-) diff --git a/veadk/a2a/ve_a2a_server.py b/veadk/a2a/ve_a2a_server.py index e3d7e1e5..ed93654c 100644 --- a/veadk/a2a/ve_a2a_server.py +++ b/veadk/a2a/ve_a2a_server.py @@ -21,7 +21,6 @@ from veadk.a2a.agent_card import get_agent_card from veadk.a2a.ve_agent_executor import VeAgentExecutor from veadk.memory.short_term_memory import ShortTermMemory -from veadk.runner import Runner class VeA2AServer: @@ -49,39 +48,6 @@ def build(self) -> FastAPI: ) app = app_application.build() # build routes - runner = Runner( - agent=self.agent_executor.agent, - short_term_memory=self.agent_executor.short_term_memory, - app_name=self.agent_executor.app_name, - user_id="", - ) - - @app.post("/run_agent", operation_id="run_agent", tags=["mcp"]) - async def run_agent( - user_input: str, - user_id: str = "unknown_user", - session_id: str = "unknown_session", - ) -> str: - """ - Execute agent with user input and return final output - Args: - user_input: User's input message - user_id: User identifier - session_id: Session identifier - Returns: - Final agent response - """ - # Set user_id for runner - runner.user_id = user_id - - # Running agent and get final output - final_output = await runner.run( - messages=user_input, - session_id=session_id, - ) - - return final_output - return app diff --git a/veadk/cli/services/vefaas/template/deploy.py b/veadk/cli/services/vefaas/template/deploy.py index 2e12b223..1d3dd4f1 100644 --- a/veadk/cli/services/vefaas/template/deploy.py +++ b/veadk/cli/services/vefaas/template/deploy.py @@ -63,7 +63,7 @@ async def main(): async with client: # List available tools tools = await client.list_tools() - print(f"tool_0: {tools[0].__dict__}\n") + print(f"tools: {tools}\n") # Call run_agent tool, pass user input and session information res = await client.call_tool( diff --git a/veadk/cli/services/vefaas/template/src/app.py b/veadk/cli/services/vefaas/template/src/app.py index 66e9e2c3..79e32019 100644 --- a/veadk/cli/services/vefaas/template/src/app.py +++ b/veadk/cli/services/vefaas/template/src/app.py @@ -17,6 +17,7 @@ from veadk.a2a.ve_a2a_server import init_app from veadk.tracing.base_tracer import BaseTracer from veadk.tracing.telemetry.opentelemetry_tracer import OpentelemetryTracer +from veadk.runner import Runner from contextlib import asynccontextmanager from fastmcp import FastMCP from fastapi import FastAPI @@ -72,6 +73,42 @@ short_term_memory=short_term_memory, ) +# Add a2a app to fastmcp +runner = Runner( + agent=agent, + short_term_memory=short_term_memory, + app_name=app_name, + user_id="", +) + + +# mcp server +@a2a_app.post("/run_agent", operation_id="run_agent", tags=["mcp"]) +async def run_agent( + user_input: str, + user_id: str = "unknown_user", + session_id: str = "unknown_session", +) -> str: + """ + Execute agent with user input and return final output + Args: + user_input: User's input message + user_id: User identifier + session_id: Session identifier + Returns: + Final agent response + """ + # Set user_id for runner + runner.user_id = user_id + + # Running agent and get final output + final_output = await runner.run( + messages=user_input, + session_id=session_id, + ) + return final_output + + mcp = FastMCP.from_fastapi(app=a2a_app, name=app_name, include_tags={"mcp"}) # Create MCP ASGI app