diff --git a/01-tutorials/02-AgentKit-Gateway/01_create_mcp_service.ipynb b/01-tutorials/02-AgentKit-Gateway/01_create_mcp_service.ipynb new file mode 100644 index 0000000..2ddb05b --- /dev/null +++ b/01-tutorials/02-AgentKit-Gateway/01_create_mcp_service.ipynb @@ -0,0 +1,440 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6eb109cf", + "metadata": {}, + "source": [ + "# 创建 MCP 服务\n", + "\n", + "AgentKit 网关支持自有和公共 MCP 服务的快速部署,并且支持将 Local MCP 转换为 Remote MCP 并提供远程访问的能力。部署自有 MCP 服务时,AgentKit 网关可以访问你的函数服务中的 MCP 服务,也可以通过公共包或你的镜像仓库中的私有镜像再函数服务中创建 MCP 服务。部署公共 MCP 服务时,AgentKit 网关可以通过固定域名的形式访问外部的 MCP 服务,支持 API Key 形式的出站认证。无论是部署自有 MCP 服务还是公共 MCP 服务,AgentKit 网关都支持 API Key 和 OAuth2 JWT 形式的入站认证,为你的服务安全保驾护航。\n", + "\n", + "在本教程中,我们将演示如何通过公共包和固定域名部署自有 MCP 服务并为服务增加 API Key 入站认证和 API Key 出站认证,你将学会,\n", + "\n", + "1. 认识 AgentKit 网关,\n", + "2. 如何在 AgentKit 网关中部署 MCP 服务,\n", + "3. 如何在 AgentKit 网关中为 MCP 服务配置入站认证。\n", + "4. 如何在 AgentKit 网关中为 MCP 服务配置出站认证。\n", + "\n", + "在开始本教程之前,请确保你已经**正确配置了凭证**。AgentKit 需要两类凭证:\n", + "\n", + "1. SDK 与 CLI 的云服务访问凭证\n", + "\n", + " 云服务访问凭证用于创建和管理云资源,你可以通过 `agentkit` 命令行工具全局配置。\n", + "\n", + " ```sh\n", + " agentkit config --global --set volcengine.access_key=YOUR_ACCESS_KEY\n", + " agentkit config --global --set volcengine.secret_key=YOUR_SECRET_KEY\n", + " agentkit config --global --set volcengine.region=cn-beijing\n", + " ```\n", + "\n", + "2. Agent 运行时的环境变量\n", + "\n", + " 环境变量用于 Agent 访问大模型等服务。你可以在 `agentkit.yaml` 的 `runtime_envs` 中配置。\n", + "\n", + " ```python\n", + " config.add_runtime_env(\"MODEL_AGENT_NAME\", \"your-model-name\")\n", + " config.add_runtime_env(\"MODEL_AGENT_API_KEY\", \"your-model-api-key\")\n", + " ```\n", + "\n", + "> **注意**:如果你还没有配置凭证,请先学习 [`01-tutorials/01-runtime/00_configuration_and_credentials.ipynb`](../01-runtime/00_configuration_and_credentials.ipynb) 教程完成配置。" + ] + }, + { + "cell_type": "markdown", + "id": "625f2784", + "metadata": {}, + "source": [ + "## 1. 为公共包创建 MCP 服务\n", + "\n", + "AgentKit 网关支持通过公共包或镜像仓库中的私有镜像创建 MCP 服务,也支持通过函数服务或固定域名创建内部或外部的 MCP 服务。在创建 MCP 服务时,AgentKit 网关将要求您选择通过 API Key 或 OAuth2 JWT进行入站认证。入站认证可以保证只有您信任的对象才能访问您的 MCP 服务。当请求抵达 AgentKit 网关时,对于 API Key 类型的入站认证,AgentKit 网关将校验 Authorization 请求头中的 API Key 是否和预先创建的 API Key 一致,对于 OAuth2 JWT 类型的入站认证,AgentKit 网关将校验 Authorization 请求头中的 JWT Token 的颁发者(issuer)、客户端 ID、权限范围(scopes)和受众(audiences)是否和你预先设置的认证策略一致。更多关于入站认证的细节将在后续的教程中介绍。\n", + "\n", + "首先,我们将为公共包创建 MCP 服务。公共包是单个 MCP 服务器配置信息。例如\n", + "\n", + "```json\n", + "{\n", + " \"mcpServers\": {\n", + " \"sequential-thinking\": {\n", + " \"command\": \"npx\",\n", + " \"args\": [\n", + " \"-y\",\n", + " \"@modelcontextprotocol/server-sequential-thinking\"\n", + " ]\n", + " }\n", + " }\n", + "}\n", + "```\n", + "\n", + "是 `@modelcontextprotocol/server-sequential-thinking` MCP 服务器的公共包。通过公共包创建 MCP 服务时,AgentKit 网关将在函数服务中按照 Local MCP 的方式部署 MCP 服务器,然后将它转换为 Remote MCP 并通过 AgentKit 网关提供远程访问的能力。\n", + "\n", + "接下来,我们将为 `@modelcontextprotocol/server-sequential-thinking` 这一公共包部署 MCP 服务。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6afc899b", + "metadata": {}, + "outputs": [], + "source": [ + "from agentkit.sdk.mcp.client import AgentkitMCPClient\n", + "from agentkit.sdk.mcp.types import (\n", + " CreateMCPServiceRequest,\n", + " NetworkForCreateMCPService,\n", + " InboundAuthorizerForCreateMCPService,\n", + " InboundAuthorizerAuthorizerForCreateMCPService,\n", + " InboundAuthorizerAuthorizerKeyAuthForCreateMCPService,\n", + " InboundAuthorizerAuthorizerKeyAuthApiKeysItemForCreateMCPService,\n", + " BackendForCreateMCPService,\n", + " BackendCustomMcpForCreateMCPService,\n", + " BackendCustomMcpPublicPackageForCreateMCPService,\n", + ")\n", + "import json\n", + "import uuid\n", + "\n", + "id = str(uuid.uuid4())[-8:]\n", + "mcp_config = {\n", + " \"mcpServers\": {\n", + " \"sequential-thinking\": {\n", + " \"command\": \"npx\",\n", + " \"args\": [\"-y\", \"@modelcontextprotocol/server-sequential-thinking\"],\n", + " },\n", + " },\n", + "}\n", + "\n", + "client = AgentkitMCPClient()\n", + "request = CreateMCPServiceRequest(\n", + " Name=f\"mcp-service-{id}\",\n", + " ProtocolType=\"MCP\",\n", + " # 通过 `/mcp` 路径,访问 AgentKit 网关 MCP 服务。\n", + " Path=\"/mcp\",\n", + " # 通过公网访问 MCP 服务。\n", + " NetworkConfiguration=NetworkForCreateMCPService(\n", + " EnablePublicNetwork=True,\n", + " EnablePrivateNetwork=False,\n", + " ),\n", + " # 使用 API Key 入站认证保证服务安全。\n", + " InboundAuthorizerConfiguration=InboundAuthorizerForCreateMCPService(\n", + " AuthorizerType=\"ApiKey\",\n", + " Authorizer=InboundAuthorizerAuthorizerForCreateMCPService(\n", + " KeyAuth=InboundAuthorizerAuthorizerKeyAuthForCreateMCPService(\n", + " ApiKeys=[\n", + " InboundAuthorizerAuthorizerKeyAuthApiKeysItemForCreateMCPService(\n", + " Name=f\"mcp-service-api-key-{id}\",\n", + " ),\n", + " ],\n", + " ),\n", + " ),\n", + " ),\n", + " # AgentKit 网关 MCP 服务指向公共包。\n", + " BackendType=\"CustomPublic\",\n", + " BackendConfiguration=BackendForCreateMCPService(\n", + " CustomMcpConfiguration=BackendCustomMcpForCreateMCPService(\n", + " PublicPackage=BackendCustomMcpPublicPackageForCreateMCPService(\n", + " McpType=\"Remote\",\n", + " PackageManagerType=\"NPX\",\n", + " RawConfig=json.dumps(mcp_config),\n", + " )\n", + " ),\n", + " ),\n", + " ProjectName=\"default\",\n", + ")\n", + "\n", + "mcp_service_id = \"\"\n", + "try:\n", + " response = client.create_mcp_service(request)\n", + " mcp_service_id = response.mcp_service_id\n", + " print(f\"MCP service created successfully! MCP service ID: {mcp_service_id}\")\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e" + ] + }, + { + "cell_type": "markdown", + "id": "edf7a523", + "metadata": {}, + "source": [ + "AgentKit Gateway 部署 MCP 服务需要一段时间。在等待服务部署的时间里,你可以泡一杯咖啡歇息一会儿。等到 MCP 服务部署完成后,我们测试一下刚刚部署的 `@modelcontextprotocol/server-sequential-thinking` MCP 服务。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "91252e59", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "from agentkit.sdk.mcp.types import (\n", + " GetMCPServiceRequest,\n", + ")\n", + "\n", + "request = GetMCPServiceRequest(\n", + " MCPServiceId=mcp_service_id,\n", + ")\n", + "\n", + "endpoint = \"\"\n", + "api_key = \"\"\n", + "try:\n", + " while True:\n", + " response = client.get_mcp_service(request)\n", + " if response.mcp_service.status == \"Ready\":\n", + " endpoint = response.mcp_service.network_configurations[0].endpoint\n", + " api_key = response.mcp_service.inbound_authorizer_configuration.authorizer.key_auth.api_keys[0].key\n", + " break\n", + " if response.mcp_service.status != \"Creating\":\n", + " print(f\"The MCP service is in {response.mcp_service.status}\")\n", + " raise RuntimeError()\n", + " time.sleep(2)\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e\n", + "\n", + "url = f\"{endpoint}/mcp\"\n", + "print(f\"MCP service URL: {url}\")\n", + "print(\n", + " f'MCP service API key: {api_key}\\n (Hint: Add a \"Authorization: Bearer {api_key}\" request header to access the MCP service)'\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "accf4a44", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install fastmcp --quiet" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f36748e7", + "metadata": {}, + "outputs": [], + "source": [ + "from fastmcp import Client\n", + "from fastmcp.client.transports import StreamableHttpTransport\n", + "\n", + "transport = StreamableHttpTransport(\n", + " url=url,\n", + " headers={\n", + " \"Authorization\": f\"Bearer {api_key}\",\n", + " }\n", + ")\n", + "mcp_client = Client(transport)\n", + "\n", + "async with mcp_client:\n", + " tools = await mcp_client.list_tools()\n", + " print(f\"The MCP server has a \\\"{tools[0].name}\\\" MCP tool.\")" + ] + }, + { + "cell_type": "markdown", + "id": "b663108f", + "metadata": {}, + "source": [ + "恭喜!你已经使用 AgentKit 网关部署了第 1 个 MCP 服务,这个 MCP 服务包含 1 个名为 `sequentialthinking` 的 MCP 工具。接下来,我们将尝试通过固定域名部署 MCP 服务。" + ] + }, + { + "cell_type": "markdown", + "id": "afc0713a", + "metadata": {}, + "source": [ + "## 2. 为固定域名创建 MCP 服务\n", + "\n", + "公共 MCP 服务大多通过固定域名的形式访问,例如高德地图 MCP 服务域名是 `mcp.amap.com`,GitHub MCP 服务域名是 `api.githubcopilot.com/mcp`。大多数公共 MCP 服务都需要携带 API Key 访问。如果你希望通过固定域名访问内部的 MCP 服务,或是二次分发公共 MCP 服务,你可以使用 AgentKit 网关为固定域名创建 MCP 服务。AgentKit 网关 MCP 服务支持 API Key 类型的出站认证,也就是说,AgentKit 网关将为出网请求自动带上公共 MCP 服务所需的 API Key。更多关于出站认证的细节将在后续的教程中介绍。\n", + "\n", + "我们刚刚通过公共包部署了 `@modelcontextprotocol/server-sequential-thinking` 的 MCP 服务。并且获得了 MCP 服务的访问地址和 API Key。接下来,我们将为这一个 URL,通过固定域名的形式,再次创建 MCP 服务。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "655be933", + "metadata": {}, + "outputs": [], + "source": [ + "from urllib.parse import urlparse\n", + "from agentkit.sdk.mcp.types import (\n", + " BackendCustomForCreateMCPService,\n", + " OutboundAuthorizerForCreateMCPService,\n", + " OutboundAuthorizerAuthorizerForCreateMCPService,\n", + " OutboundAuthorizerAuthorizerKeyAuthForCreateMCPService,\n", + " OutboundAuthorizerAuthorizerKeyAuthApiKeysItemForCreateMCPService,\n", + ")\n", + "\n", + "parsed_url = urlparse(url)\n", + "domain = parsed_url.netloc\n", + "\n", + "id_2 = str(uuid.uuid4())[-8:]\n", + "\n", + "client = AgentkitMCPClient()\n", + "request = CreateMCPServiceRequest(\n", + " Name=f\"mcp-service-{id_2}\",\n", + " ProtocolType=\"MCP\",\n", + " # 通过 `/mcp` 路径,访问 AgentKit 网关 MCP 服务。\n", + " Path=\"/mcp\",\n", + " # 通过公网访问 MCP 服务。\n", + " NetworkConfiguration=NetworkForCreateMCPService(\n", + " EnablePublicNetwork=True,\n", + " EnablePrivateNetwork=False,\n", + " ),\n", + " # 使用 API Key 入站认证保证服务安全。\n", + " InboundAuthorizerConfiguration=InboundAuthorizerForCreateMCPService(\n", + " AuthorizerType=\"ApiKey\",\n", + " Authorizer=InboundAuthorizerAuthorizerForCreateMCPService(\n", + " KeyAuth=InboundAuthorizerAuthorizerKeyAuthForCreateMCPService(\n", + " ApiKeys=[\n", + " InboundAuthorizerAuthorizerKeyAuthApiKeysItemForCreateMCPService(\n", + " Name=f\"mcp-service-api-key-{id_2}\",\n", + " ),\n", + " ],\n", + " ),\n", + " ),\n", + " ),\n", + " # AgentKit 网关 MCP 服务指向固定域名。\n", + " BackendType=\"Domain\",\n", + " BackendConfiguration=BackendForCreateMCPService(\n", + " CustomConfiguration=BackendCustomForCreateMCPService(\n", + " Domain=domain,\n", + " ProtocolType=\"HTTP\",\n", + " Port=80,\n", + " )\n", + " ),\n", + " # 使用 API Key 出站认证为出站请求添加凭据。\n", + " OutboundAuthorizerConfiguration=OutboundAuthorizerForCreateMCPService(\n", + " AuthorizerType=\"ApiKey\",\n", + " Authorizer=OutboundAuthorizerAuthorizerForCreateMCPService(\n", + " KeyAuth=OutboundAuthorizerAuthorizerKeyAuthForCreateMCPService(\n", + " ApiKeyLocation=\"Header\",\n", + " Parameter=\"Authorization\",\n", + " ApiKeys=[\n", + " OutboundAuthorizerAuthorizerKeyAuthApiKeysItemForCreateMCPService(\n", + " Name=\"\",\n", + " Key=f\"Bearer {api_key}\",\n", + " ),\n", + " ],\n", + " ),\n", + " ),\n", + " ),\n", + " ProjectName=\"default\",\n", + ")\n", + "\n", + "mcp_service_id_2 = \"\"\n", + "try:\n", + " response = client.create_mcp_service(request)\n", + " mcp_service_id_2 = response.mcp_service_id\n", + " print(f\"MCP service created successfully! MCP service ID: {mcp_service_id_2}\")\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd0d8694", + "metadata": {}, + "outputs": [], + "source": [ + "request = GetMCPServiceRequest(\n", + " MCPServiceId=mcp_service_id_2,\n", + ")\n", + "\n", + "endpoint_2 = \"\"\n", + "api_key_2 = \"\"\n", + "try:\n", + " while True:\n", + " response = client.get_mcp_service(request)\n", + " if response.mcp_service.status == \"Ready\":\n", + " endpoint_2 = response.mcp_service.network_configurations[0].endpoint\n", + " api_key_2 = response.mcp_service.inbound_authorizer_configuration.authorizer.key_auth.api_keys[0].key\n", + " break\n", + " if response.mcp_service.status != \"Creating\":\n", + " print(f\"The MCP service is in {response.mcp_service.status}\")\n", + " raise RuntimeError()\n", + " time.sleep(2)\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e\n", + "\n", + "url_2 = f\"{endpoint_2}/mcp\"\n", + "print(f\"MCP service URL: {url_2}\")\n", + "print(\n", + " f'MCP service API key: {api_key_2}\\n (Hint: Add a \"Authorization: Bearer {api_key_2}\" request header to access the MCP service)'\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bcf9dd51", + "metadata": {}, + "outputs": [], + "source": [ + "from fastmcp import Client\n", + "from fastmcp.client.transports import StreamableHttpTransport\n", + "\n", + "transport = StreamableHttpTransport(\n", + " url=url_2,\n", + " headers={\n", + " \"Authorization\": f\"Bearer {api_key_2}\",\n", + " }\n", + ")\n", + "mcp_client = Client(transport)\n", + "\n", + "async with mcp_client:\n", + " tools = await mcp_client.list_tools()\n", + " print(f\"The MCP server has a \\\"{tools[0].name}\\\" MCP tool.\")" + ] + }, + { + "cell_type": "markdown", + "id": "e6241a30", + "metadata": {}, + "source": [ + "恭喜!你已经使用 AgentKit 网关部署了第 2 个 MCP 服务,这个 MCP 服务通过固定域名访问了 `@modelcontextprotocol/server-sequential-thinking` MCP 服务。" + ] + }, + { + "cell_type": "markdown", + "id": "e92ae044", + "metadata": {}, + "source": [ + "## 总结\n", + "\n", + "恭喜你!现在你已经掌握了创建 AgentKit 网关 MCP 服务的方法,\n", + "\n", + "1. 通过公共包创建 MCP 服务。\n", + "2. 通过固定域名创建 MCP 服务。\n", + "\n", + "现在,请继续学习其他教程。" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.12.12)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/01-tutorials/02-AgentKit-Gateway/02_convert_api_service_to_mcp_service.ipynb b/01-tutorials/02-AgentKit-Gateway/02_convert_api_service_to_mcp_service.ipynb new file mode 100644 index 0000000..e014d2c --- /dev/null +++ b/01-tutorials/02-AgentKit-Gateway/02_convert_api_service_to_mcp_service.ipynb @@ -0,0 +1,358 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c6fbd39f", + "metadata": {}, + "source": [ + "# 转换 API 服务到 MCP 服务\n", + "\n", + "AgentKit 网关支持将现有 API 服务一键转换为 MCP 服务。通过上传 Swagger 或 OpenAPI 3 格式的 API 文档,AgentKit 网关支持批量将存量系统的 API 服务转换为 MCP 服务。在这一过程中,你无需改动任何代码,AgentKit 可以帮助你快速提升大模型与存量系统的交互能力。部署 MCP 服务时,AgentKit 网关可以访问你的函数服务中的 API 服务,也可以通过固定域名的形式访问外部的 API 服务。AgentKit 网关支持入站认证和出站认证,在认证入站请求的同时,为出站请求添加身份凭据,兼容多种系统的认证策略。\n", + "\n", + "在本教程中,我们将演示如何将 API 服务转换为 MCP 服务,你将学会\n", + "\n", + "1. 如何在 AgentKit 网关中将 API 服务转换为 MCP 服务。\n", + "\n", + "在开始本教程之前,请确保你已经**正确配置了凭证**。\n", + "\n", + "> **注意**:如果你还没有配置凭证,请先学习 [`01-tutorials/01-runtime/00_configuration_and_credentials.ipynb`](../01-runtime/00_configuration_and_credentials.ipynb) 教程完成配置。" + ] + }, + { + "cell_type": "markdown", + "id": "203f636e", + "metadata": {}, + "source": [ + "## 1. 转换 API 服务到 MCP 服务\n", + "\n", + "AgentKit 网关支持通过 Swagger 或 OpenAPI 3 格式的 API 文档,将存量系统的 API 服务转换为 MCP服务。你可以选择函数服务或者固定域名类型的 API 服务,通过 AgentKit 网关转换为 MCP 服务。当 MCP 请求抵达 AgentKit 网关时,AgentKit 网关将判断 MCP 请求的类型主动回复响应或是转换请求交由后端处理。对于 MCP `tools/list` 请求,AgentKit 网关将直接返回 API 定义中的各种操作。对于 MCP `tools/call` 请求,AgentKit 网关将 MCP 请求中的报文转换为 API 请求并发送到后端服务器中。\n", + "\n", + "AgentKit 网关支持从 MCP 请求中将请求内容转换为 API 请求的 URL 参数(Query)、请求头、Cookie,以及表单(Form)或 JSON 类型的请求体。AgentKit 网关支持从 JSON 类型的 API 响应中响应体转换为 MCP 响应的响应内容。你可以阅读[如何上传正确的 MCP 配置文件](https://www.volcengine.com/docs/6569/1816086#OA4azxgS)获得更多关于服务转换的条件和细节。\n", + "\n", + "[Postman Echo](https://postman-echo.com/) 是 Postman 提供的样例应用,它提供了一组用于测试 HTTP 客户端的调试 API。例如,当你通过 `curl` 调用 GET Request 接口时,你将会收到对应的返回。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "66251c9e", + "metadata": {}, + "outputs": [], + "source": [ + "!curl \"https://postman-echo.com/get\"" + ] + }, + { + "cell_type": "markdown", + "id": "c2b6eec9", + "metadata": {}, + "source": [ + "我们还需要为 Postman Echo 服务准备 Swagger 或 OpenAPI 3 格式的 API 文档。\n", + "\n", + "如果你有 Postman Collection,通过 [Postman to OpenAPI](https://www.postman.com/postman/postman-public-workspace/collection/1blkypo/postman-to-openapi) 可以方便的从 Postman Collection 转换为 OpenAPI 3 文档。如果你的应用通过 `Spring Boot`、`NestJS` 和 `FastAPI` 等框架构建,你也可以通过相关工具快速生成 Swagger 或 OpenAPI 3 文档。\n", + "\n", + "最终,我们得到了 Postman Echo 服务的 OpenAPI 3 文档(为了方便阅读本教程,我们对一部分内容进行了裁剪)。AgentKit 网关支持 JSON 和 YAML 格式的 API 文档。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd06f07a", + "metadata": {}, + "outputs": [], + "source": [ + "api_config = \"\"\"\n", + "openapi: 3.0.3\n", + "info:\n", + " title: Postman Echo\n", + " description: >-\n", + " Postman Echo is a service you can use to test your REST clients and make\n", + " sample API calls. It provides endpoints for `GET`, `POST`, `PUT`, various\n", + " auth mechanisms and other utility endpoints.\n", + "\n", + "\n", + " The documentation for the endpoints as well as example responses can be\n", + " found at\n", + " [https://postman-echo.com](https://postman-echo.com/?source=echo-collection-app-onboarding)\n", + " version: 1.0.0\n", + "servers:\n", + " - url: https://postman-echo.com\n", + " - url: postman-echo.com\n", + "paths:\n", + " /get:\n", + " get:\n", + " summary: GET Request\n", + " description: >-\n", + " The HTTP `GET` request method is meant to retrieve data from a server.\n", + " The data\n", + "\n", + " is identified by a unique URI (Uniform Resource Identifier).\n", + "\n", + "\n", + " A `GET` request can pass parameters to the server using \"Query String\n", + "\n", + " Parameters\". For example, in the following request,\n", + "\n", + "\n", + " >\n", + " [http://example.com/hi/there?hand=wave](http://example.com/hi/there?hand=wave)\n", + "\n", + "\n", + " The parameter \"hand\" has the value \"wave\".\n", + "\n", + "\n", + " This endpoint echoes the HTTP headers, request parameters and the\n", + " complete\n", + "\n", + " URI requested.\n", + " operationId: getRequest\n", + " parameters:\n", + " - name: foo1\n", + " in: query\n", + " schema:\n", + " type: string\n", + " example: bar1\n", + " - name: foo2\n", + " in: query\n", + " schema:\n", + " type: string\n", + " example: bar2\n", + " responses:\n", + " '200':\n", + " description: GET Request Woops\n", + " content:\n", + " application/json:\n", + " schema:\n", + " type: object\n", + " properties:\n", + " args:\n", + " type: object\n", + " properties:\n", + " foo1:\n", + " type: string\n", + " example: bar1\n", + " foo2:\n", + " type: string\n", + " example: bar2\n", + " headers:\n", + " type: object\n", + " url:\n", + " type: string\n", + "\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "025db000", + "metadata": {}, + "source": [ + "接下来,我们将为 Swagger Petstore 服务创建 MCP 服务。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0197ced2", + "metadata": {}, + "outputs": [], + "source": [ + "from agentkit.sdk.mcp.client import AgentkitMCPClient\n", + "from agentkit.sdk.mcp.types import (\n", + " CreateMCPServiceRequest,\n", + " ProtocolForCreateMCPService,\n", + " ProtocolHttpApiForCreateMCPService,\n", + " NetworkForCreateMCPService,\n", + " InboundAuthorizerForCreateMCPService,\n", + " InboundAuthorizerAuthorizerForCreateMCPService,\n", + " InboundAuthorizerAuthorizerKeyAuthForCreateMCPService,\n", + " InboundAuthorizerAuthorizerKeyAuthApiKeysItemForCreateMCPService,\n", + " BackendForCreateMCPService,\n", + " BackendCustomForCreateMCPService,\n", + ")\n", + "import base64\n", + "import uuid\n", + "\n", + "id = str(uuid.uuid4())[-8:]\n", + "\n", + "client = AgentkitMCPClient()\n", + "request = CreateMCPServiceRequest(\n", + " Name=f\"mcp-service-{id}\",\n", + " # 转换 API 服务到 MCP 服务时,`ProtocolType` 为 `HTTP`。\n", + " ProtocolType=\"HTTP\",\n", + " ProtocolConfiguration=ProtocolForCreateMCPService(\n", + " HttpApiConfiguration=ProtocolHttpApiForCreateMCPService(\n", + " Configuration=f\"{base64.b64encode(api_config.encode()).decode()}\",\n", + " )\n", + " ),\n", + " Path=\"/mcp\",\n", + " NetworkConfiguration=NetworkForCreateMCPService(\n", + " EnablePublicNetwork=True,\n", + " EnablePrivateNetwork=False,\n", + " ),\n", + " InboundAuthorizerConfiguration=InboundAuthorizerForCreateMCPService(\n", + " AuthorizerType=\"ApiKey\",\n", + " Authorizer=InboundAuthorizerAuthorizerForCreateMCPService(\n", + " KeyAuth=InboundAuthorizerAuthorizerKeyAuthForCreateMCPService(\n", + " ApiKeys=[\n", + " InboundAuthorizerAuthorizerKeyAuthApiKeysItemForCreateMCPService(\n", + " Name=f\"mcp-service-api-key-{id}\",\n", + " ),\n", + " ],\n", + " ),\n", + " ),\n", + " ),\n", + " BackendType=\"Domain\",\n", + " BackendConfiguration=BackendForCreateMCPService(\n", + " CustomConfiguration=BackendCustomForCreateMCPService(\n", + " Domain=\"postman-echo.com\",\n", + " ProtocolType=\"HTTP\",\n", + " Port=80,\n", + " )\n", + " ),\n", + " ProjectName=\"default\",\n", + ")\n", + "\n", + "mcp_service_id = \"\"\n", + "try:\n", + " response = client.create_mcp_service(request)\n", + " mcp_service_id = response.mcp_service_id\n", + " print(f\"MCP service created successfully! MCP service ID: {mcp_service_id}\")\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e" + ] + }, + { + "cell_type": "markdown", + "id": "8c7962f0", + "metadata": {}, + "source": [ + "AgentKit Gateway 部署 MCP 服务需要一段时间。在部署完成后,我们可以检查一下 API 服务 转换的 MCP 服务是否可用。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6083fc95", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "from agentkit.sdk.mcp.types import (\n", + " GetMCPServiceRequest,\n", + ")\n", + "\n", + "request = GetMCPServiceRequest(\n", + " MCPServiceId=mcp_service_id,\n", + ")\n", + "\n", + "endpoint = \"\"\n", + "api_key = \"\"\n", + "try:\n", + " while True:\n", + " response = client.get_mcp_service(request)\n", + " if response.mcp_service.status == \"Ready\":\n", + " endpoint = response.mcp_service.network_configurations[0].endpoint\n", + " api_key = response.mcp_service.inbound_authorizer_configuration.authorizer.key_auth.api_keys[0].key\n", + " break\n", + " if response.mcp_service.status != \"Creating\":\n", + " print(f\"The MCP service is in {response.mcp_service.status}\")\n", + " raise RuntimeError()\n", + " time.sleep(2)\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e\n", + "\n", + "url = f\"{endpoint}/mcp\"\n", + "print(f\"MCP service URL: {url}\")\n", + "print(\n", + " f'MCP service API key: {api_key}\\n (Hint: Add a \"Authorization: Bearer {api_key}\" request header to access the MCP service)'\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9e31b756", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install fastmcp --quiet" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75cecd8a", + "metadata": {}, + "outputs": [], + "source": [ + "from fastmcp import Client\n", + "from fastmcp.client.transports import StreamableHttpTransport\n", + "import json\n", + "from mcp.types import TextContent\n", + "\n", + "transport = StreamableHttpTransport(\n", + " url=url,\n", + " headers={\n", + " \"Authorization\": f\"Bearer {api_key}\",\n", + " }\n", + ")\n", + "mcp_client = Client(transport)\n", + "\n", + "async with mcp_client:\n", + " result = await mcp_client.call_tool(\"getRequest\", {\n", + " \"foo1\": \"bar1\",\n", + " \"foo2\": \"bar2\"\n", + " })\n", + " text: TextContent = result.content[0]\n", + " print(f\"The MCP server responds with {json.dumps(json.loads(text.text), indent=2)}.\")" + ] + }, + { + "cell_type": "markdown", + "id": "a698a8d6", + "metadata": {}, + "source": [ + "恭喜!你已经使用 AgentKit 网关转换 API 服务到 MCP 服务,这个 MCP 服务包含一个名为 `getRequest` 的工具。" + ] + }, + { + "cell_type": "markdown", + "id": "369967bf", + "metadata": {}, + "source": [ + "## 总结\n", + "\n", + "恭喜你!现在你已经掌握了转换 API 服务到 MCP 服务的方法,\n", + "\n", + "1. 通过 Swagger 或 OpenAPI 3 文档转换 API 服务到 MCP 服务。\n", + "\n", + "你可以继续尝试为 [Swagger Petstore](https://petstore3.swagger.io/)(OpenAPI 3) 和 [HTTPBin](https://httpbin.org/)(Swagger)等样例项目生成 MCP 服务。\n", + "\n", + "现在,请继续学习其他教程。" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.12.12)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/01-tutorials/02-AgentKit-Gateway/03_create_mcp_toolset.ipynb b/01-tutorials/02-AgentKit-Gateway/03_create_mcp_toolset.ipynb new file mode 100644 index 0000000..7b9a248 --- /dev/null +++ b/01-tutorials/02-AgentKit-Gateway/03_create_mcp_toolset.ipynb @@ -0,0 +1,404 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cd99c961", + "metadata": {}, + "source": [ + "# 创建 MCP 工具集\n", + "\n", + "AgentKit 网关支持创建 MCP 工具集。MCP 工具集是 MCP 工具的集合,当你在 MCP 工具集中添加 MCP 服务时,MCP 工具集将自动索引 MCP 服务中的 MCP 工具。MCP 工具集也是 1 个 MCP 服务,在调用 MCP 工具集时,MCP 工具集会使用语义搜索,通过对调用意图和 MCP 工具描述进行语义匹配,并返回匹配程度最高的工具。使用 MCP 工具集有助于提高工具匹配准确度和准确性,并且降低 Token 的消耗。\n", + "\n", + "在本教程中,我们将演示如何创建 MCP 工具集,你将学会,\n", + "\n", + "1. 如何在 AgentKit 网关中创建 MCP 工具集,\n", + "2. 如何使用 MCP 工具集。\n", + "\n", + "在开始本教程之前,请确保你已经**正确配置了凭证**。\n", + "\n", + "> **注意**:如果你还没有配置凭证,请先学习 [`01-tutorials/01-runtime/00_configuration_and_credentials.ipynb`](../01-runtime/00_configuration_and_credentials.ipynb) 教程完成配置。" + ] + }, + { + "cell_type": "markdown", + "id": "c87521b4", + "metadata": {}, + "source": [ + "## 1. 创建 MCP 工具集\n", + "\n", + "AgentKit 网关支持创建 MCP 工具集。MCP 工具集是 1 个 MCP 服务,通过 MCP 协议提供服务,你可以将 MCP 工具集的服务地址配置在智能体中使用 MCP 工具集。对于配置在 MCP 工具集内的 MCP 服务,MCP 工具集会在 MCP 工具发生变更时索引,并且将 MCP 工具的元数据保存在 VikingDB 向量数据库中。在智能体调用 MCP 工具集时,智能体首先会生成任务描述并调用 `mcp_search_tool` 工具,MCP 工具集将进行语义匹配并返回匹配程度最高的 10 个工具。然后,智能体根据返回的 MCP 工具元数据调用 `mcp_use_tool` 工具,MCP 工具集将完成最终的调用并返回调用结果。\n", + "\n", + "MCP 工具集有助于提高工具匹配准备度和准确性,并且降低 Token 的消耗。智能体在启动时会列出所有的 MCP 工具,并且在和大语言模型的第 1 轮会话前注入。一个典型的 MCP 服务器包含数十个 MCP 工具,GitHub 的 MCP 服务器包含将近 100 个工具,而飞书的 MCP 服务器更是包含了 700 余个工具。当你接入 50 个 MCP 工具时,大语言模型的上下文将被 MCP 工具的元数据占用,这不仅会影响大语言模型对 MCP 工具调用的决策,还会大幅度增加 Token 的消耗。使用 MCP 工具集可以提升 MCP 工具调用的准确性,减少大语言模型的响应时间,并且将 Token 的消耗量降低到十分之一及以下。\n", + "\n", + "在创建 MCP 工具集之前,我们首先创建 1 个 MCP 服务。我们使用 `@modelcontextprotocol/server-everything` 公共包创建 MCP 服务。`@modelcontextprotocol/server-everything` 包含了大量的 MCP 工具,是我们测试 MCP 工具集的不二之选。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47c1a0da", + "metadata": {}, + "outputs": [], + "source": [ + "from agentkit.sdk.mcp.client import AgentkitMCPClient\n", + "from agentkit.sdk.mcp.types import (\n", + " CreateMCPServiceRequest,\n", + " NetworkForCreateMCPService,\n", + " InboundAuthorizerForCreateMCPService,\n", + " InboundAuthorizerAuthorizerForCreateMCPService,\n", + " InboundAuthorizerAuthorizerKeyAuthForCreateMCPService,\n", + " InboundAuthorizerAuthorizerKeyAuthApiKeysItemForCreateMCPService,\n", + " BackendForCreateMCPService,\n", + " BackendCustomMcpForCreateMCPService,\n", + " BackendCustomMcpPublicPackageForCreateMCPService,\n", + ")\n", + "import json\n", + "import uuid\n", + "\n", + "id = str(uuid.uuid4())[-8:]\n", + "mcp_config = {\n", + " \"mcpServers\": {\n", + " \"sequential-thinking\": {\n", + " \"command\": \"npx\",\n", + " \"args\": [\"-y\", \"@modelcontextprotocol/server-everything\"],\n", + " },\n", + " },\n", + "}\n", + "\n", + "client = AgentkitMCPClient()\n", + "request = CreateMCPServiceRequest(\n", + " Name=f\"mcp-service-{id}\",\n", + " ProtocolType=\"MCP\",\n", + " Path=\"/mcp\",\n", + " NetworkConfiguration=NetworkForCreateMCPService(\n", + " EnablePublicNetwork=True,\n", + " EnablePrivateNetwork=False,\n", + " ),\n", + " InboundAuthorizerConfiguration=InboundAuthorizerForCreateMCPService(\n", + " AuthorizerType=\"ApiKey\",\n", + " Authorizer=InboundAuthorizerAuthorizerForCreateMCPService(\n", + " KeyAuth=InboundAuthorizerAuthorizerKeyAuthForCreateMCPService(\n", + " ApiKeys=[\n", + " InboundAuthorizerAuthorizerKeyAuthApiKeysItemForCreateMCPService(\n", + " Name=f\"mcp-service-api-key-{id}\",\n", + " ),\n", + " ],\n", + " ),\n", + " ),\n", + " ),\n", + " BackendType=\"CustomPublic\",\n", + " BackendConfiguration=BackendForCreateMCPService(\n", + " CustomMcpConfiguration=BackendCustomMcpForCreateMCPService(\n", + " PublicPackage=BackendCustomMcpPublicPackageForCreateMCPService(\n", + " McpType=\"Remote\",\n", + " PackageManagerType=\"NPX\",\n", + " RawConfig=json.dumps(mcp_config),\n", + " )\n", + " ),\n", + " ),\n", + " ProjectName=\"default\",\n", + ")\n", + "\n", + "mcp_service_id = \"\"\n", + "try:\n", + " response = client.create_mcp_service(request)\n", + " mcp_service_id = response.mcp_service_id\n", + " print(f\"MCP service created successfully! MCP service ID: {mcp_service_id}\")\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37d91ad7", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "from agentkit.sdk.mcp.types import (\n", + " GetMCPServiceRequest,\n", + ")\n", + "\n", + "request = GetMCPServiceRequest(\n", + " MCPServiceId=mcp_service_id,\n", + ")\n", + "\n", + "endpoint = \"\"\n", + "api_key = \"\"\n", + "try:\n", + " while True:\n", + " response = client.get_mcp_service(request)\n", + " if response.mcp_service.status == \"Ready\":\n", + " endpoint = response.mcp_service.network_configurations[0].endpoint\n", + " api_key = response.mcp_service.inbound_authorizer_configuration.authorizer.key_auth.api_keys[0].key\n", + " break\n", + " if response.mcp_service.status != \"Creating\":\n", + " print(f\"The MCP service is in {response.mcp_service.status}\")\n", + " raise RuntimeError()\n", + " time.sleep(2)\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e\n", + "\n", + "url = f\"{endpoint}/mcp\"\n", + "print(f\"MCP service URL: {url}\")\n", + "print(\n", + " f'MCP service API key: {api_key}\\n (Hint: Add a \"Authorization: Bearer {api_key}\" request header to access the MCP service)'\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "c45504a7", + "metadata": {}, + "source": [ + "很好,我们已经创建了 `@modelcontextprotocol/server-everything` MCP 服务,让我们来看一下这个 MCP 服务中包含了多少工具。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67b1b8a7", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install fastmcp --quiet" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "246e869f", + "metadata": {}, + "outputs": [], + "source": [ + "from fastmcp import Client\n", + "from fastmcp.client.transports import StreamableHttpTransport\n", + "\n", + "transport = StreamableHttpTransport(\n", + " url=url,\n", + " headers={\n", + " \"Authorization\": f\"Bearer {api_key}\",\n", + " }\n", + ")\n", + "mcp_client = Client(transport)\n", + "\n", + "async with mcp_client:\n", + " tools = await mcp_client.list_tools()\n", + " print(f\"The MCP server has {len(tools)} MCP tools.\")\n", + " for tool in tools:\n", + " print(f\"- {tool.name}\")" + ] + }, + { + "cell_type": "markdown", + "id": "f38c0af9", + "metadata": {}, + "source": [ + "`@modelcontextprotocol/server-everything` MCP 服务包含了各种各样的样例工具。这些工具种类繁多,从加法计算的 `add` 到压缩文件的 `zip`。而有的工具名称之间又存在一定的混淆,例如 `getResourceReference` 和 `getResourceLinks`。在这种多个 MCP 工具的复合场景下,使用 MCP 工具集不妨为一个好主意。\n", + "\n", + "接下来,我们将为刚刚创建的 MCP 服务创建 MCP 工具集。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21453ae1", + "metadata": {}, + "outputs": [], + "source": [ + "from agentkit.sdk.mcp.types import (\n", + " CreateMCPToolsetRequest,\n", + " NetworkForCreateMCPToolset,\n", + " AuthorizerForCreateMCPToolset,\n", + " AuthorizerAuthorizerForCreateMCPToolset,\n", + " AuthorizerAuthorizerKeyAuthForCreateMCPToolset,\n", + " AuthorizerAuthorizerKeyAuthApiKeysItemForCreateMCPToolset,\n", + ")\n", + "\n", + "id_2 = str(uuid.uuid4())[-8:]\n", + "\n", + "request = CreateMCPToolsetRequest(\n", + " Name=f\"mcp-toolset-{id_2}\",\n", + " Path=\"/mcp\",\n", + " NetworkConfiguration=NetworkForCreateMCPToolset(\n", + " EnablePublicNetwork=True,\n", + " EnablePrivateNetwork=False,\n", + " ),\n", + " AuthorizerConfiguration=AuthorizerForCreateMCPToolset(\n", + " AuthorizerType=\"ApiKey\",\n", + " Authorizer=AuthorizerAuthorizerForCreateMCPToolset(\n", + " KeyAuth=AuthorizerAuthorizerKeyAuthForCreateMCPToolset(\n", + " ApiKeys=[\n", + " AuthorizerAuthorizerKeyAuthApiKeysItemForCreateMCPToolset(\n", + " Name=f\"mcp-toolset-api-key-{id_2}\",\n", + " ),\n", + " ],\n", + " ),\n", + " ),\n", + " ),\n", + " # AgentKit 网关 MCP 服务 ID,可以选择多个 MCP 服务。\n", + " MCPServiceIds=[\n", + " mcp_service_id,\n", + " ],\n", + " ProjectName=\"default\",\n", + ")\n", + "\n", + "mcp_toolset_id = \"\"\n", + "try:\n", + " response = client.create_mcp_toolset(request)\n", + " mcp_toolset_id = response.mcp_toolset_id\n", + " print(f\"MCP toolset created successfully! MCP toolset ID: {mcp_toolset_id}\")\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e" + ] + }, + { + "cell_type": "markdown", + "id": "cf03f12c", + "metadata": {}, + "source": [ + "AgentKit Gateway 部署 MCP 工具集需要一段时间,MCP 工具集索引 MCP 工具也需要一定的时间。等到 MCP 工具集部署完成后,我们尝试调用 `mcp_search_tool` 和 `mcp_use_tool` 来看看效果如何。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87e2c425", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "from agentkit.sdk.mcp.types import (\n", + " GetMCPToolsetRequest,\n", + ")\n", + "\n", + "request = GetMCPToolsetRequest(\n", + " MCPToolsetId=mcp_toolset_id,\n", + ")\n", + "\n", + "endpoint_2 = \"\"\n", + "api_key_2 = \"\"\n", + "try:\n", + " while True:\n", + " response = client.get_mcp_toolset(request)\n", + " if response.mcp_toolset.status == \"Ready\":\n", + " endpoint_2 = response.mcp_toolset.network_configurations[0].endpoint\n", + " api_key_2 = response.mcp_toolset.authorizer_configuration.authorizer.key_auth.api_keys[0].key\n", + " break\n", + " if response.mcp_toolset.status != \"Creating\":\n", + " print(f\"The MCP toolset is in {response.mcp_toolset.status}\")\n", + " raise RuntimeError()\n", + " time.sleep(2)\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e\n", + "\n", + "url_2 = f\"{endpoint_2}/mcp\"\n", + "print(f\"MCP toolset URL: {url_2}\")\n", + "print(\n", + " f'MCP toolset API key: {api_key_2}\\n (Hint: Add a \"Authorization: Bearer {api_key_2}\" request header to access the MCP toolset)'\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "019c2444", + "metadata": {}, + "outputs": [], + "source": [ + "from mcp.types import TextContent\n", + "\n", + "transport = StreamableHttpTransport(\n", + " url=url_2,\n", + " headers={\n", + " \"Authorization\": f\"Bearer {api_key_2}\",\n", + " },\n", + ")\n", + "mcp_client = Client(transport)\n", + "\n", + "async with mcp_client:\n", + " result = await mcp_client.call_tool(\n", + " \"mcp_search_tool\",\n", + " {\n", + " \"task_description\": [\n", + " \"Add 2 + 3\",\n", + " ],\n", + " },\n", + " )\n", + " text: TextContent = result.content[0]\n", + " metadata = text.text.splitlines()[1]\n", + " tool = json.loads(metadata)[0]\n", + " print(f'The MCP toolset finds the most specific tool is \"{tool[\"tool_name\"]}\".')\n", + "\n", + " result = await mcp_client.call_tool(\n", + " \"mcp_use_tool\",\n", + " {\n", + " \"use_tool_info\": [\n", + " {\n", + " \"server_id\": tool[\"server_id\"],\n", + " \"server_name\": tool[\"server_name\"],\n", + " \"tool_id\": tool[\"tool_id\"],\n", + " \"tool_name\": tool[\"tool_name\"],\n", + " \"argument\": {\n", + " \"a\": 2,\n", + " \"b\": 3,\n", + " },\n", + " },\n", + " ],\n", + " },\n", + " )\n", + " text: TextContent = result.content[0]\n", + " tool_text = json.loads(text.text)[0][\"result\"][0][\"text\"]\n", + " print(f\"The MCP toolset call tool result is \\\"{tool_text}\\\".\")" + ] + }, + { + "cell_type": "markdown", + "id": "82d7576e", + "metadata": {}, + "source": [ + "恭喜!你已经使用 AgentKit 网关部署了 MCP 工具集,这个 MCP 工具集指向 `@modelcontextprotocol/server-everything` MCP 服务,并且可以通过语义搜索选出最适合当前任务的 MCP 工具集。" + ] + }, + { + "cell_type": "markdown", + "id": "761ad936", + "metadata": {}, + "source": [ + "## 总结\n", + "\n", + "恭喜你!现在你已经掌握了创建 AgentKit 网关 MCP 工具集的方法,\n", + "\n", + "1. 创建 MCP 工具集。\n", + "\n", + "现在,请继续学习其他教程。" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.12.12)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/01-tutorials/02-AgentKit-Gateway/04_inbound_auth.ipynb b/01-tutorials/02-AgentKit-Gateway/04_inbound_auth.ipynb new file mode 100644 index 0000000..d823581 --- /dev/null +++ b/01-tutorials/02-AgentKit-Gateway/04_inbound_auth.ipynb @@ -0,0 +1,521 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7ae24154", + "metadata": {}, + "source": [ + "# 入站认证\n", + "\n", + "AgentKit 网关支持 MCP 服务和 MCP 工具集,MCP 服务和 MCP 工具集都通过远程 MCP 服务的形式通过网络进行调用。如果这些服务不设置入站认证,那么知道服务地址的任何人都可以随意使用,这会造成服务不可用,数据泄漏以及财产损失。入站认证可以为 MCP 服务和 MCP 工具集提供防护,通过 API Key 或 OAuth2 JWT 防止未授权的用户非法访问 MCP 服务和 MCP 工具集。\n", + "\n", + "在本教程中,我们将演示如何为 MCP 服务和 MCP 工具集增加 API Key 入站认证和 OAuth2 JWT 入站认证,你将学会,\n", + "\n", + "1. 如何在 AgentKit 网关中为 MCP 服务配置 API Key 入站认证。\n", + "2. 如何在 AgentKit 网关中为 MCP 服务配置 OAuth2 JWT 入站认证。\n", + "3. 如何在 Agent Identity 中创建用户池和客户端。\n", + "\n", + "在开始本教程之前,请确保你已经**正确配置了凭证**。\n", + "\n", + "> **注意**:如果你还没有配置凭证,请先学习 [`01-tutorials/01-runtime/00_configuration_and_credentials.ipynb`](../01-runtime/00_configuration_and_credentials.ipynb) 教程完成配置。" + ] + }, + { + "cell_type": "markdown", + "id": "8f3e76f2", + "metadata": {}, + "source": [ + "## 1. 为 MCP 服务配置 API Key 入站认证\n", + "\n", + "在创建 MCP 服务或 MCP 工具集时,AgentKit 网关要求选择 API Key 或 OAuth2 JWT 形式的入站认证。API Key 是一种用于身份验证和授权的密钥。当 MCP 客户端携带唯一的 API Key 发起请求时,AgentKit 网关将校验 API Key 的合法性以确认调用者身份,并判断是否允许访问的简易且高效的认证方式。AgentKit 网关约定所有的 API Key 均通过 Authorization 请求头携带。每个 MCP 服务或 MCP 工具集支持最多 25 个 API Key,你可以为不同的用户分配不同的 API Key。API Key 长期有效,但我们建议你定时轮转 API Key,防止 API Key 泄漏造成的损失。\n", + "\n", + "在 AgentKit 网关的控制台中创建 MCP 服务或 MCP 工具集时,AgentKit 网关默认使用 API Key 作为入站认证方式。在使用 AgentKit SDK 创建 MCP 服务或 MCP 工具集时,你也可以简单的配置 API Key。所有的 API Key 都是由 AgentKit 平台生成的,你不能指定 API Key 的内容。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a96c34d3", + "metadata": {}, + "outputs": [], + "source": [ + "from agentkit.sdk.mcp.client import AgentkitMCPClient\n", + "from agentkit.sdk.mcp.types import (\n", + " CreateMCPServiceRequest,\n", + " NetworkForCreateMCPService,\n", + " InboundAuthorizerForCreateMCPService,\n", + " InboundAuthorizerAuthorizerForCreateMCPService,\n", + " InboundAuthorizerAuthorizerKeyAuthForCreateMCPService,\n", + " InboundAuthorizerAuthorizerKeyAuthApiKeysItemForCreateMCPService,\n", + " BackendForCreateMCPService,\n", + " BackendCustomMcpForCreateMCPService,\n", + " BackendCustomMcpPublicPackageForCreateMCPService,\n", + ")\n", + "import json\n", + "import uuid\n", + "\n", + "id = str(uuid.uuid4())[-8:]\n", + "mcp_config = {\n", + " \"mcpServers\": {\n", + " \"sequential-thinking\": {\n", + " \"command\": \"npx\",\n", + " \"args\": [\"-y\", \"@modelcontextprotocol/server-sequential-thinking\"],\n", + " },\n", + " },\n", + "}\n", + "\n", + "client = AgentkitMCPClient()\n", + "request = CreateMCPServiceRequest(\n", + " Name=f\"mcp-service-{id}\",\n", + " ProtocolType=\"MCP\",\n", + " Path=\"/mcp\",\n", + " NetworkConfiguration=NetworkForCreateMCPService(\n", + " EnablePublicNetwork=True,\n", + " EnablePrivateNetwork=False,\n", + " ),\n", + " # 使用 API Key 入站认证保证服务安全。你可以创建多个 API Key 用于区分用户身份。\n", + " InboundAuthorizerConfiguration=InboundAuthorizerForCreateMCPService(\n", + " AuthorizerType=\"ApiKey\",\n", + " Authorizer=InboundAuthorizerAuthorizerForCreateMCPService(\n", + " KeyAuth=InboundAuthorizerAuthorizerKeyAuthForCreateMCPService(\n", + " ApiKeys=[\n", + " InboundAuthorizerAuthorizerKeyAuthApiKeysItemForCreateMCPService(\n", + " Name=f\"mcp-service-api-key-{id}-1\",\n", + " ),\n", + " InboundAuthorizerAuthorizerKeyAuthApiKeysItemForCreateMCPService(\n", + " Name=f\"mcp-service-api-key-{id}-2\",\n", + " ),\n", + " InboundAuthorizerAuthorizerKeyAuthApiKeysItemForCreateMCPService(\n", + " Name=f\"mcp-service-api-key-{id}-3\",\n", + " ),\n", + " ],\n", + " ),\n", + " ),\n", + " ),\n", + " BackendType=\"CustomPublic\",\n", + " BackendConfiguration=BackendForCreateMCPService(\n", + " CustomMcpConfiguration=BackendCustomMcpForCreateMCPService(\n", + " PublicPackage=BackendCustomMcpPublicPackageForCreateMCPService(\n", + " McpType=\"Remote\",\n", + " PackageManagerType=\"NPX\",\n", + " RawConfig=json.dumps(mcp_config),\n", + " )\n", + " ),\n", + " ),\n", + " ProjectName=\"default\",\n", + ")\n", + "\n", + "mcp_service_id = \"\"\n", + "try:\n", + " response = client.create_mcp_service(request)\n", + " mcp_service_id = response.mcp_service_id\n", + " print(f\"MCP service created successfully! MCP service ID: {mcp_service_id}\")\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5f8bd081", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "from agentkit.sdk.mcp.types import (\n", + " GetMCPServiceRequest,\n", + ")\n", + "\n", + "request = GetMCPServiceRequest(\n", + " MCPServiceId=mcp_service_id,\n", + ")\n", + "\n", + "endpoint = \"\"\n", + "api_keys = []\n", + "try:\n", + " while True:\n", + " response = client.get_mcp_service(request)\n", + " if response.mcp_service.status == \"Ready\":\n", + " endpoint = response.mcp_service.network_configurations[0].endpoint\n", + " for (\n", + " api_key\n", + " ) in (\n", + " response.mcp_service.inbound_authorizer_configuration.authorizer.key_auth.api_keys\n", + " ):\n", + " api_keys.append(api_key.key)\n", + " break\n", + " if response.mcp_service.status != \"Creating\":\n", + " print(f\"The MCP service is in {response.mcp_service.status}\")\n", + " raise RuntimeError()\n", + " time.sleep(2)\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e\n", + "\n", + "url = f\"{endpoint}/mcp\"\n", + "print(f\"MCP service URL: {url}\")\n", + "print(f\"MCP service API key:\")\n", + "for api_key in api_keys:\n", + " print(f\"- {api_key}\")" + ] + }, + { + "cell_type": "markdown", + "id": "58899659", + "metadata": {}, + "source": [ + "恭喜!你已经使用 AgentKit 网关部署了具有 API Key 认证的 MCP 服务,这个 MCP 服务可以使用 3 个 API Key 进行访问。接下来,我们将尝试为 MCP 服务配置 OAuth2 JWT 入站认证。" + ] + }, + { + "cell_type": "markdown", + "id": "09c18fa9", + "metadata": {}, + "source": [ + "## 2. 为 MCP 服务配置 OAuth2 JWT 入站认证\n", + "\n", + "OAuth2 是一种授权协议,允许你授权第三方应用访问你在另一个服务上的受保护信息,而无需提供登录凭据。AgentKit Gateway 支持使用 Agent Identity 作为身份提供商(IdP),用户的账号、密码等敏感信息存储在 Agent Identity 中,MCP 客户端携带 Agent Identity 颁发的 JWT 短期访问令牌访问 AgentKit Gateway。AgentKit Gateway 将检查 Authorization 请求头中的访问令牌是否合法且在有效期内,以及颁发者(issuer)、客户端 ID、权限范围(scopes)和受众(audiences)是否和你预先设置的认证策略一致,并判断是否允许访问。相比于 API Key,OAuth2 JWT 有着权限控制等更高级的特性,也可以减少凭据泄漏的风险。\n", + "\n", + "Agent Identity 是用户与智能体的身份认证与权限控制平台。我们需要首先创建身份池用于隔离不同系统间的身份管理,然后再在身份池内创建客户端用于标记不同的智能体。\n", + "\n", + "首先,我们在 Agent Identity 中创建用户池和客户端。\n", + "\n", + "> **注意**:创建 Agent Identity 用户池和客户端需要使用火山引擎 SDK。因此,你需要将 `VOLCENGINE_ACCESS_KEY`、`VOLCENGINE_SECRET_KEY` 和 `VOLCENGINE_REGION` 配置在 `.env` 文件中。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "759e44e3", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install volcengine-python-sdk --quiet" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78057dc8", + "metadata": {}, + "outputs": [], + "source": [ + "import dotenv\n", + "import os\n", + "import uuid\n", + "import volcenginesdkcore\n", + "from volcenginesdkid import (\n", + " IDApi,\n", + " CreateUserPoolRequest,\n", + " CreateUserPoolResponse,\n", + " GetUserPoolRequest,\n", + " GetUserPoolResponse,\n", + " CreateUserPoolClientRequest,\n", + " CreateUserPoolClientResponse,\n", + ")\n", + "\n", + "dotenv.load_dotenv()\n", + "\n", + "id = str(uuid.uuid4())[-8:]\n", + "\n", + "configuration = volcenginesdkcore.Configuration()\n", + "configuration.ak = os.getenv(\"VOLCENGINE_ACCESS_KEY\")\n", + "configuration.sk = os.getenv(\"VOLCENGINE_SECRET_KEY\")\n", + "configuration.region = os.getenv(\"VOLCENGINE_REGION\")\n", + "volcenginesdkcore.Configuration.set_default(configuration)\n", + "\n", + "api_instance = IDApi()\n", + "request = CreateUserPoolRequest(\n", + " name=f\"user-pool-{id}\",\n", + " sign_in_attributes=[\"preferred_username\"],\n", + " required_sign_up_attributes=[\"preferred_username\"],\n", + " project_name=\"default\",\n", + ")\n", + "\n", + "user_pool_id = \"\"\n", + "try:\n", + " response: CreateUserPoolResponse = api_instance.create_user_pool(request)\n", + " user_pool_id = response.uid\n", + " print(f\"User pool created successfully!\\n User pool ID: {user_pool_id}\")\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e\n", + "\n", + "request = GetUserPoolRequest(\n", + " user_pool_uid=user_pool_id,\n", + ")\n", + "\n", + "discovery_url = \"\"\n", + "try:\n", + " response: GetUserPoolResponse = api_instance.get_user_pool(request)\n", + " discovery_url = response.discovery_url\n", + " print(f\" Discovery URL: {discovery_url}\")\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e\n", + "\n", + "\n", + "request = CreateUserPoolClientRequest(\n", + " user_pool_uid=user_pool_id,\n", + " name=f\"client-{id}\",\n", + " client_type=\"MACHINE_TO_MACHINE\",\n", + ")\n", + "\n", + "client_id = \"\"\n", + "client_secret = \"\"\n", + "try:\n", + " response: CreateUserPoolClientResponse = api_instance.create_user_pool_client(\n", + " request\n", + " )\n", + " client_id = response.uid\n", + " client_secret = response.client_secret\n", + " print(\n", + " f\"Client created successfully!\\n Client ID: {client_id}\\n Client secret: {client_secret}\"\n", + " )\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e" + ] + }, + { + "cell_type": "markdown", + "id": "c0a734f2", + "metadata": {}, + "source": [ + "AgentKit Gateway OAuth2 JWT 入站认证支持允许用户池中所有的客户端访问,也支持仅允许一部分客户端访问。如果需要将认证粒度控制在客户端,AgentKit Gateway 支持配置最多 5 个不同的客户端。\n", + "\n", + "接下来,我们使用刚刚创建好的用户池,创建 MCP 服务并配置 OAuth2 JWT 入站认证。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "623d8267", + "metadata": {}, + "outputs": [], + "source": [ + "from agentkit.sdk.mcp.client import AgentkitMCPClient\n", + "from agentkit.sdk.mcp.types import (\n", + " CreateMCPServiceRequest,\n", + " NetworkForCreateMCPService,\n", + " InboundAuthorizerForCreateMCPService,\n", + " InboundAuthorizerAuthorizerForCreateMCPService,\n", + " InboundAuthorizerAuthorizerCustomJwtAuthorizerForCreateMCPService,\n", + " BackendForCreateMCPService,\n", + " BackendCustomMcpForCreateMCPService,\n", + " BackendCustomMcpPublicPackageForCreateMCPService,\n", + ")\n", + "import json\n", + "import uuid\n", + "\n", + "id = str(uuid.uuid4())[-8:]\n", + "mcp_config = {\n", + " \"mcpServers\": {\n", + " \"sequential-thinking\": {\n", + " \"command\": \"npx\",\n", + " \"args\": [\"-y\", \"@modelcontextprotocol/server-sequential-thinking\"],\n", + " },\n", + " },\n", + "}\n", + "\n", + "client = AgentkitMCPClient()\n", + "request = CreateMCPServiceRequest(\n", + " Name=f\"mcp-service-{id}\",\n", + " ProtocolType=\"MCP\",\n", + " Path=\"/mcp\",\n", + " NetworkConfiguration=NetworkForCreateMCPService(\n", + " EnablePublicNetwork=True,\n", + " EnablePrivateNetwork=False,\n", + " ),\n", + " # 使用 OAuth2 JWT 入站认证保证服务安全。通过不配置 `AllowedClients`,放行用户池中所有的客户端。\n", + " InboundAuthorizerConfiguration=InboundAuthorizerForCreateMCPService(\n", + " AuthorizerType=\"CustomJWT\",\n", + " Authorizer=InboundAuthorizerAuthorizerForCreateMCPService(\n", + " CustomJwtAuthorizer=InboundAuthorizerAuthorizerCustomJwtAuthorizerForCreateMCPService(\n", + " DiscoveryUrl=discovery_url,\n", + " AllowedClients=[],\n", + " )\n", + " ),\n", + " ),\n", + " BackendType=\"CustomPublic\",\n", + " BackendConfiguration=BackendForCreateMCPService(\n", + " CustomMcpConfiguration=BackendCustomMcpForCreateMCPService(\n", + " PublicPackage=BackendCustomMcpPublicPackageForCreateMCPService(\n", + " McpType=\"Remote\",\n", + " PackageManagerType=\"NPX\",\n", + " RawConfig=json.dumps(mcp_config),\n", + " )\n", + " ),\n", + " ),\n", + " ProjectName=\"default\",\n", + ")\n", + "\n", + "mcp_service_id = \"\"\n", + "try:\n", + " response = client.create_mcp_service(request)\n", + " mcp_service_id = response.mcp_service_id\n", + " print(f\"MCP service created successfully! MCP service ID: {mcp_service_id}\")\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e" + ] + }, + { + "cell_type": "markdown", + "id": "74017499", + "metadata": {}, + "source": [ + "AgentKit Gatway 部署 MCP 服务需要一段时间。等到 MCP 服务部署完成后,我们通过 OAuth2 流程测试一下 MCP 服务。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87fff24a", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "from agentkit.sdk.mcp.types import (\n", + " GetMCPServiceRequest,\n", + ")\n", + "\n", + "request = GetMCPServiceRequest(\n", + " MCPServiceId=mcp_service_id,\n", + ")\n", + "\n", + "endpoint = \"\"\n", + "try:\n", + " while True:\n", + " response = client.get_mcp_service(request)\n", + " if response.mcp_service.status == \"Ready\":\n", + " endpoint = response.mcp_service.network_configurations[0].endpoint\n", + " break\n", + " if response.mcp_service.status != \"Creating\":\n", + " print(f\"The MCP service is in {response.mcp_service.status}\")\n", + " raise RuntimeError()\n", + " time.sleep(2)\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e\n", + "\n", + "url = f\"{endpoint}/mcp\"\n", + "print(f\"MCP service URL: {url}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "147dba15", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install fastmcp --quiet" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f745b990", + "metadata": {}, + "outputs": [], + "source": [ + "import base64\n", + "import requests\n", + "\n", + "response = requests.request(\"GET\", discovery_url)\n", + "token_endpoint = response.json()[\"token_endpoint\"]\n", + "\n", + "authorization = base64.b64encode(f\"{client_id}:{client_secret}\".encode()).decode()\n", + "\n", + "headers = {\n", + " \"Authorization\": f\"Basic {authorization}\",\n", + " \"Content-Type\": \"application/x-www-form-urlencoded\",\n", + "}\n", + "payload = \"grant_type=client_credentials\"\n", + "\n", + "access_token = \"\"\n", + "try:\n", + " response = requests.request(\n", + " \"POST\",\n", + " token_endpoint,\n", + " headers=headers,\n", + " data=payload,\n", + " )\n", + " access_token = response.json()[\"access_token\"]\n", + " print(f\"Access token: {access_token}\")\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1cdfa786", + "metadata": {}, + "outputs": [], + "source": [ + "from fastmcp import Client\n", + "from fastmcp.client.transports import StreamableHttpTransport\n", + "\n", + "transport = StreamableHttpTransport(\n", + " url=url,\n", + " headers={\n", + " \"Authorization\": f\"Bearer {access_token}\",\n", + " }\n", + ")\n", + "mcp_client = Client(transport)\n", + "\n", + "async with mcp_client:\n", + " tools = await mcp_client.list_tools()\n", + " print(f\"The MCP server has a \\\"{tools[0].name}\\\" MCP tool.\")" + ] + }, + { + "cell_type": "markdown", + "id": "655b97d8", + "metadata": {}, + "source": [ + "恭喜!你已经使用 AgentKit 网关部署了具有 OAuth2 JWT 认证的 MCP 服务,这个 MCP 服务可以使用 OAuth2 流程进行访问。" + ] + }, + { + "cell_type": "markdown", + "id": "30396e21", + "metadata": {}, + "source": [ + "## 总结\n", + "\n", + "恭喜你!现在你已经掌握了在 AgentKit 网关中为 MCP 服务配置入站认证的方法,\n", + "\n", + "1. 为 MCP 服务配置 API Key 入站认证。\n", + "2. 为 MCP 服务配置 OAuth2 JWT 入站认证。\n", + "\n", + "现在,请继续学习其他教程。" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.12.12)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/01-tutorials/02-AgentKit-Gateway/05_integration.ipynb b/01-tutorials/02-AgentKit-Gateway/05_integration.ipynb new file mode 100644 index 0000000..defcac0 --- /dev/null +++ b/01-tutorials/02-AgentKit-Gateway/05_integration.ipynb @@ -0,0 +1,505 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "950eaba1", + "metadata": {}, + "source": [ + "# 在智能体运行时中使用 MCP 服务\n", + "\n", + "AgentKit 智能体运行时可以访问 AgentKit 网关部署的 MCP 服务和 MCP 工具集。\n", + "\n", + "在本教程中,我们将演示如何在 AgentKit 智能体运行时中部署智能体,同时访问 AgentKit 网关部署的 MCP 服务和 MCP 工具集,你将学会,\n", + "\n", + "1. 如何使用 AgentKit 智能体运行时中的智能体访问 AgentKit 网关中的 MCP 服务。\n", + "\n", + "本教程将部署一个端到端应用,在开始本教程之前,请确保你已经学习了 AgentKit SDK Gateway 教程的前几节内容。\n", + "\n", + "> **注意**:本教程需要使用火山引擎 SDK。因此,你需要将 `VOLCENGINE_ACCESS_KEY`、`VOLCENGINE_SECRET_KEY`、`VOLCENGINE_REGION` 和 `MODEL_API_KEY` 配置在 `.env` 文件中。" + ] + }, + { + "cell_type": "markdown", + "id": "721aa45a", + "metadata": {}, + "source": [ + "## 1. 在函数应用中部署 API 服务\n", + "\n", + "AgentKit 网关支持将存量 API 服务转换为 MCP 服务。将存量 API 服务转换为 MCP 服务,是一种利旧,是实现经济效益的高效路径,目的是使自身的服务能被外部的智能体调用,从而提升存量服务的价值。以高德地图为例,高德将存量的 API 服务,例如 IP 定位、地理编码转换为 MCP 服务,就可以让外部智能体调用高德地图,从而提升服务的活跃度。\n", + "\n", + "MCP 大幅降低了智能体访问和调用外部资源、数据和服务的复杂度,但如果您将复用存量基础设施作为 MCP 服务的开发策略,那么将面对一个新的问题,从存量 API 服务到 MCP 服务的转换。存量 API 服务转换为 MCP 服务是一个重复的体力活,日常还需要进行各类维护,例如接口更新和稳定性保障等。将存量 API 服务对外通过 MCP 协议暴露成 MCP 服务,这个过程包括:\n", + "\n", + "1. 读取并解析 API 文档,获得访问路径、请求方法、请求参数和响应等。\n", + "2. 按照 MCP 协议,将 API 转换为 MCP 工具。\n", + "3. 对于 `tools/list` MCP 请求,返回由 API 转换后的 MCP 工具列表。\n", + "4. 对于 `tools/call` MCP 请求,解析 MCP 请求中的 JSON-RPC 内容,生成 API 请求发送到后端。当后端响应时,将 API 响应包装成 MCP 响应返回给 MCP 客户端。\n", + "\n", + "接下来,我们先在函数服务中部署天气 Mock 服务。这个天气 Mock 服务总是会告诉我们今天在下雨,气温是 25 度。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2fed6fee", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install volcengine-python-sdk --quiet" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27d6094b", + "metadata": {}, + "outputs": [], + "source": [ + "import base64\n", + "import dotenv\n", + "import os\n", + "import time\n", + "import uuid\n", + "import volcenginesdkcore\n", + "from volcenginesdkvefaas import (\n", + " VEFAASApi,\n", + " CreateFunctionRequest,\n", + " CreateFunctionResponse,\n", + " ReleaseRequest,\n", + " ReleaseResponse,\n", + " GetReleaseStatusRequest,\n", + " GetReleaseStatusResponse,\n", + ")\n", + "\n", + "dotenv.load_dotenv()\n", + "\n", + "id = str(uuid.uuid4())[-8:]\n", + "\n", + "configuration = volcenginesdkcore.Configuration()\n", + "configuration.ak = os.getenv(\"VOLCENGINE_ACCESS_KEY\")\n", + "configuration.sk = os.getenv(\"VOLCENGINE_SECRET_KEY\")\n", + "configuration.region = os.getenv(\"VOLCENGINE_REGION\")\n", + "volcenginesdkcore.Configuration.set_default(configuration)\n", + "\n", + "source = b\"\"\n", + "with open(\"resources/volc-weather.zip\", \"rb\") as f:\n", + " content = f.read()\n", + " source = base64.b64encode(content).decode()\n", + "\n", + "api_instance = VEFAASApi()\n", + "request = CreateFunctionRequest(\n", + " name=f\"function-{id}\",\n", + " runtime=\"native-node20/v1\",\n", + " source_type=\"zip\",\n", + " source=source,\n", + " command=\"./run.sh\",\n", + " project_name=\"default\",\n", + ")\n", + "\n", + "function_id = \"\"\n", + "try:\n", + " response: CreateFunctionResponse = api_instance.create_function(request)\n", + " function_id = response.id\n", + " print(f\"Function created successfully! Function ID: {function_id}\")\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e\n", + "\n", + "request = ReleaseRequest(\n", + " function_id=function_id,\n", + " revision_number=0,\n", + ")\n", + "\n", + "try:\n", + " response: ReleaseResponse = api_instance.release(request)\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e\n", + "\n", + "\n", + "request = GetReleaseStatusRequest(\n", + " function_id=function_id,\n", + ")\n", + "\n", + "try:\n", + " while True:\n", + " response: GetReleaseStatusResponse = api_instance.get_release_status(request)\n", + " if response.status == \"done\":\n", + " break\n", + " if response.status != \"inprogress\":\n", + " print(f\"The function is in {response.status}\")\n", + " raise RuntimeError()\n", + " time.sleep(2)\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e" + ] + }, + { + "cell_type": "markdown", + "id": "cf226d3f", + "metadata": {}, + "source": [ + "## 2. 通过 MCP 服务访问函数应用中的 API 服务\n", + "\n", + "接下来,我们使用 AgentKit 网关将 API 服务转换为 MCP 服务。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6265da2b", + "metadata": {}, + "outputs": [], + "source": [ + "from agentkit.sdk.mcp.client import AgentkitMCPClient\n", + "from agentkit.sdk.mcp.types import (\n", + " CreateMCPServiceRequest,\n", + " ProtocolForCreateMCPService,\n", + " ProtocolHttpApiForCreateMCPService,\n", + " NetworkForCreateMCPService,\n", + " InboundAuthorizerForCreateMCPService,\n", + " InboundAuthorizerAuthorizerForCreateMCPService,\n", + " InboundAuthorizerAuthorizerKeyAuthForCreateMCPService,\n", + " InboundAuthorizerAuthorizerKeyAuthApiKeysItemForCreateMCPService,\n", + " BackendForCreateMCPService,\n", + " BackendFunctionForCreateMCPService,\n", + ")\n", + "\n", + "id = str(uuid.uuid4())[-8:]\n", + "\n", + "api_config = \"\"\n", + "with open(\"resources/volc-weather.json\", \"r\") as f:\n", + " api_config = f.read()\n", + "\n", + "client = AgentkitMCPClient()\n", + "request = CreateMCPServiceRequest(\n", + " Name=f\"mcp-service-{id}\",\n", + " # 转换 API 服务到 MCP 服务时,`ProtocolType` 为 `HTTP`。\n", + " ProtocolType=\"HTTP\",\n", + " ProtocolConfiguration=ProtocolForCreateMCPService(\n", + " HttpApiConfiguration=ProtocolHttpApiForCreateMCPService(\n", + " Configuration=f\"{base64.b64encode(api_config.encode()).decode()}\",\n", + " )\n", + " ),\n", + " Path=\"/mcp\",\n", + " NetworkConfiguration=NetworkForCreateMCPService(\n", + " EnablePublicNetwork=True,\n", + " EnablePrivateNetwork=False,\n", + " ),\n", + " InboundAuthorizerConfiguration=InboundAuthorizerForCreateMCPService(\n", + " AuthorizerType=\"ApiKey\",\n", + " Authorizer=InboundAuthorizerAuthorizerForCreateMCPService(\n", + " KeyAuth=InboundAuthorizerAuthorizerKeyAuthForCreateMCPService(\n", + " ApiKeys=[\n", + " InboundAuthorizerAuthorizerKeyAuthApiKeysItemForCreateMCPService(\n", + " Name=f\"mcp-service-api-key-{id}\",\n", + " ),\n", + " ],\n", + " ),\n", + " ),\n", + " ),\n", + " BackendType=\"Function\",\n", + " BackendConfiguration=BackendForCreateMCPService(\n", + " FunctionConfiguration=BackendFunctionForCreateMCPService(\n", + " FunctionId=function_id,\n", + " ),\n", + " ),\n", + " ProjectName=\"default\",\n", + ")\n", + "\n", + "mcp_service_id = \"\"\n", + "try:\n", + " response = client.create_mcp_service(request)\n", + " mcp_service_id = response.mcp_service_id\n", + " print(f\"MCP service created successfully! MCP service ID: {mcp_service_id}\")\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e" + ] + }, + { + "cell_type": "markdown", + "id": "4026b89a", + "metadata": {}, + "source": [ + "AgentKit Gateway 部署 MCP 服务需要一段时间。在部署完成后,我们可以检查一下 API 服务 转换的 MCP 服务是否可用。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "07c1b3c9", + "metadata": {}, + "outputs": [], + "source": [ + "from agentkit.sdk.mcp.types import (\n", + " GetMCPServiceRequest,\n", + ")\n", + "\n", + "request = GetMCPServiceRequest(\n", + " MCPServiceId=mcp_service_id,\n", + ")\n", + "\n", + "endpoint = \"\"\n", + "api_key = \"\"\n", + "try:\n", + " while True:\n", + " response = client.get_mcp_service(request)\n", + " if response.mcp_service.status == \"Ready\":\n", + " endpoint = response.mcp_service.network_configurations[0].endpoint\n", + " api_key = response.mcp_service.inbound_authorizer_configuration.authorizer.key_auth.api_keys[0].key\n", + " break\n", + " if response.mcp_service.status != \"Creating\":\n", + " print(f\"The MCP service is in {response.mcp_service.status}\")\n", + " raise RuntimeError()\n", + " time.sleep(2)\n", + "except Exception as e:\n", + " print(f\"An error occurred: {e}\")\n", + " raise e\n", + "\n", + "url = f\"{endpoint}/mcp\"\n", + "print(f\"MCP service URL: {url}\")\n", + "print(\n", + " f'MCP service API key: {api_key}\\n (Hint: Add a \"Authorization: Bearer {api_key}\" request header to access the MCP service)'\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dedabfb7", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install fastmcp --quiet" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a2d8620c", + "metadata": {}, + "outputs": [], + "source": [ + "from fastmcp import Client\n", + "from fastmcp.client.transports import StreamableHttpTransport\n", + "\n", + "transport = StreamableHttpTransport(\n", + " url=url,\n", + " headers={\n", + " \"Authorization\": f\"Bearer {api_key}\",\n", + " }\n", + ")\n", + "mcp_client = Client(transport)\n", + "\n", + "async with mcp_client:\n", + " tools = await mcp_client.list_tools()\n", + " print(f\"The MCP server has {len(tools)} tools.\")\n", + " for tool in tools:\n", + " print(f\"- {tool.name}\")" + ] + }, + { + "cell_type": "markdown", + "id": "08b9e39b", + "metadata": {}, + "source": [ + "## 3. 在智能体运行时中使用 MCP 服务\n", + "\n", + "我们的天气 Mock MCP 服务已经部署完成了,可以看到,这个 MCP 服务包含了 3 个工具:查询天气情况、查询降水情况以及查询气象预警,看着不错!\n", + "\n", + "我们准备一下部署到 AgentKit 智能体运行时的智能体。关于 AgentKit 智能体运行时,你可以阅读 AgentKit SDK Runtime 教程。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "99303f22", + "metadata": {}, + "outputs": [], + "source": [ + "%%writefile my_agent.py\n", + "from agentkit.apps import AgentkitSimpleApp\n", + "from google.adk.tools.mcp_tool.mcp_toolset import (\n", + " MCPToolset,\n", + " StreamableHTTPConnectionParams,\n", + ")\n", + "import os\n", + "from veadk import Runner, Agent\n", + "\n", + "url = os.getenv(\"MCP_SERVICE_URL\")\n", + "api_key = os.getenv(\"MCP_SERVICE_API_KEY\")\n", + "get_city_weather = MCPToolset(\n", + " connection_params=StreamableHTTPConnectionParams(\n", + " url=url,\n", + " headers={\"Authorization\": f\"Bearer {api_key}\"},\n", + " ),\n", + ")\n", + "\n", + "app = AgentkitSimpleApp()\n", + "agent = Agent(model_name=\"doubao-seed-1-6-250615\", tools=[get_city_weather])\n", + "runner = Runner(agent=agent)\n", + "\n", + "\n", + "@app.entrypoint\n", + "async def run(payload: dict, headers: dict) -> str:\n", + " prompt = payload[\"prompt\"]\n", + " if prompt is None:\n", + " return \"prompt is None\"\n", + "\n", + " response = await runner.run(\n", + " messages=prompt,\n", + " )\n", + " return response\n", + "\n", + "\n", + "@app.ping\n", + "def ping() -> str:\n", + " return \"pong!\"\n", + "\n", + "\n", + "if __name__ == \"__main__\":\n", + " app.run(host=\"0.0.0.0\", port=8000)" + ] + }, + { + "cell_type": "markdown", + "id": "22717412", + "metadata": {}, + "source": [ + "AgentKit 智能体运行时的 `CreateRuntime` API 支持全面的配置选项。您可以指定容器镜像、环境变量和加密设置,还可以配置协议类型(HTTP、MCP)和入站认证,以控制客户端与智能体的通信方式。\n", + "\n", + "> **注意**:我们建议你将代码打包成容器,结合火山引擎镜像仓库产品,通过 CI/CD 流水线和基础设施即代码(IaC)将镜像推送到镜像仓库中。\n", + "\n", + "在本教程中,我们将使用 AgentKit Python SDK 轻松打包您的代码并将其部署到 AgentKit 智能体运行时中。\n", + "\n", + "在配置步骤中,我们将根据您的应用程序代码自动生成 `agentkit.yaml` 配置文件,它用于在本地保存 AgentKit 智能体运行时的配置。\n", + "\n", + "> **说明**:本教程使用 `cloud` 部署模式,即在远程构建镜像并自动部署到 AgentKit 智能体运行时中。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f0b3baeb", + "metadata": {}, + "outputs": [], + "source": [ + "from agentkit.toolkit.sdk.config import AgentConfig\n", + "\n", + "MODEL_API_KEY = os.getenv(\"MODEL_API_KEY\")\n", + "\n", + "config = (\n", + " AgentConfig(\"agentkit.yaml\")\n", + " .set(\"common.agent_name\", \"http_to_mcp_agent\")\n", + " .set(\"common.entry_point\", \"my_agent.py\")\n", + " .set(\"common.language\", \"Python\")\n", + " .set(\"common.language_version\", \"3.12\")\n", + " .set(\"common.launch_type\", \"cloud\")\n", + " .set(\"launch_types.cloud.region\", \"cn-beijing\")\n", + " .set(\"launch_types.cloud.runtime_id\", \"\")\n", + " .set(\"launch_types.cloud.runtime_name\", \"\")\n", + " .set(\"launch_types.cloud.runtime_role_name\", \"\")\n", + " .set(\"launch_types.cloud.runtime_envs.MODEL_AGENT_API_KEY\", MODEL_API_KEY)\n", + " .set(\"launch_types.cloud.runtime_envs.MCP_SERVICE_URL\", url)\n", + " .set(\"launch_types.cloud.runtime_envs.MCP_SERVICE_API_KEY\", api_key)\n", + " .save()\n", + ")\n", + "\n", + "print(\"Configuration created and saved!\")" + ] + }, + { + "cell_type": "markdown", + "id": "83af0f6b", + "metadata": {}, + "source": [ + "我们已经准备好了关于智能体的一切,现在,我们就将它部署到 AgentKit 智能体运行时中。在部署过程中,AgentKit 将,\n", + "\n", + "1. 构建镜像。\n", + "\n", + " 1. 生成 Dockerfile,根据项目配置自动生成容器化配置,\n", + " 2. 构建 Docker 镜像,将你的智能体代码打包成容器镜像,\n", + " 3. 准备镜像仓库,\n", + " 4. 将构建好的镜像推送到云端镜像仓库中。\n", + "\n", + "2. 在 AgentKit 智能体运行时中部署智能体。\n", + "\n", + " 1. 创建 IAM 角色,为 AgentKit 智能体运行时生成必要的权限角色,\n", + " 2. 创建 AgentKit 智能体运行时。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "91aede76", + "metadata": {}, + "outputs": [], + "source": [ + "from agentkit.toolkit import sdk\n", + "\n", + "result = sdk.launch()\n", + "print(f\"Agent deployed successfully. Agent runtime ID: {result.deploy_result.service_id}\")" + ] + }, + { + "cell_type": "markdown", + "id": "1693bd3a", + "metadata": {}, + "source": [ + "最后,让我们试着调用 AgentKit 智能体运行时中的智能体。" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f29ab8a5", + "metadata": {}, + "outputs": [], + "source": [ + "invoke_response = sdk.invoke(\n", + " payload={\n", + " \"prompt\": \"今天上海的天气怎么样?\",\n", + " }\n", + ")\n", + "print(f\"The agent responds with \\\"{invoke_response.response}\\\".\")" + ] + }, + { + "cell_type": "markdown", + "id": "3a2eae8e", + "metadata": {}, + "source": [ + "## 总结\n", + "\n", + "恭喜你!现在你已经掌握了在智能体运行时中访问 MCP 服务的方法,\n", + "\n", + "1. 通过 Swagger 或 OpenAPI 3 文档转换函数服务中的 API 服务到 MCP 服务。\n", + "2. 创建智能体运行时访问 MCP 服务。\n", + "\n", + "现在,请继续学习其他教程。" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv (3.12.12)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/01-tutorials/02-AgentKit-Gateway/resources/volc-weather.json b/01-tutorials/02-AgentKit-Gateway/resources/volc-weather.json new file mode 100644 index 0000000..79fedec --- /dev/null +++ b/01-tutorials/02-AgentKit-Gateway/resources/volc-weather.json @@ -0,0 +1,127 @@ +{ + "swagger": "2.0", + "info": { + "title": "volc-weather", + "version": "0.0.1" + }, + "paths": { + "/weather": { + "get": { + "operationId": "weather", + "summary": "查看天气情况。", + "parameters": [ + { + "name": "city", + "description": "城市。", + "type": "string", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "天气情况。", + "schema": { + "type": "object", + "properties": { + "city": { + "description": "城市。", + "type": "string" + }, + "weather": { + "description": "天气。", + "type": "string" + }, + "temperature": { + "description": "温度,单位摄氏度。", + "type": "number" + }, + "humidity": { + "description": "湿度,单位百分比。", + "type": "number" + } + } + } + } + } + } + }, + "/precipitation": { + "post": { + "operationId": "precipitation", + "summary": "查看降水情况。", + "consumes": ["application/json"], + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "type": "object", + "properties": { + "city": { + "description": "城市。", + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "降水情况。", + "schema": { + "type": "object", + "properties": { + "city": { + "description": "城市。", + "type": "string" + }, + "humidity": { + "description": "湿度,单位百分比。", + "type": "number" + }, + "probability": { + "description": "降水概率,单位百分比。", + "type": "number" + } + } + } + } + } + } + }, + "/warnings": { + "get": { + "operationId": "warnings", + "summary": "查看气象预警。", + "responses": { + "200": { + "description": "气象预警。", + "schema": { + "type": "object", + "properties": { + "warnings": { + "description": "预警。", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "description": "预警名称。", + "type": "string" + }, + "city": { + "description": "预警城市。", + "type": "string" + } + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/01-tutorials/02-AgentKit-Gateway/resources/volc-weather.zip b/01-tutorials/02-AgentKit-Gateway/resources/volc-weather.zip new file mode 100644 index 0000000..b98022c Binary files /dev/null and b/01-tutorials/02-AgentKit-Gateway/resources/volc-weather.zip differ diff --git a/01-tutorials/tutorial_template.ipynb b/01-tutorials/tutorial_template.ipynb new file mode 100644 index 0000000..8e84fd7 --- /dev/null +++ b/01-tutorials/tutorial_template.ipynb @@ -0,0 +1,48 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "ba20dedc", + "metadata": {}, + "source": [ + "# Agentkit Tutorial 样例模板\n", + "\n", + "## 概述\n", + "\n", + "本教程面向 **第一次接触 AgentKit SDK 的新手用户**,在一个 Jupyter Notebook 中,带你完成:\n", + " - 内容1\n", + " - 内容2\n", + " - 内容3\n", + "\n", + "你可以直接从上到下依次运行每个代码单元。建议在 **仓库根目录** 或已安装好 `agentkit` 的 Python 环境中运行。\n", + "\n", + "### Tutorial 说明\n", + "|分类| 详情|\n", + "|:--------------------|:---------------------------------------------------------------------------------|\n", + "| Agent 类型 | 单Agent |\n", + "| Agent 开发框架 | Volcengine Agent Development Kit(VeADK) |\n", + "| Tutorial 核心模块 | AgentKit Runtime, ApmPlus, Volcengine Identity, AgentKit Tool |\n", + "| 教程复杂度 | 中等 |\n", + "| 使用到的SDK | AgentKit Python SDK, VeADK, volcengine-python-sdk |\n", + "\n", + "## 教程\n", + "\n", + "在这里step-by-step 介绍:如何使用SDK 创建、集成 AgentKit模块能力,保证每段代码可执行。\n", + "\n", + "1. 安装...\n", + "2. 创建agent...\n", + "3. 集成模块...\n", + "4. 结果展示...\n", + "\n", + "## 总结" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}