示例:使用Azure Functions进行自托管MCP服务器授权
此示例展示了如何使用应用程序服务身份验证和授权来为模型上下文协议(MCP)服务器进行授权。MCP服务器是使用TypeScript/Node.js实现的Azure Functions MCP扩展来构建的。该示例还展示了如何代表已登录用户调用Microsoft Graph。
该示例使用了一个TypeScript项目。有以下两种开始方式:
- 您可以使用 Azure 开发者命令行界面 (azd) 将应用程序部署到 Azure - 这是开始使用示例的推荐方法。
- 你可以在本地运行这个应用程序 - 此选项不使用任何MCP服务器授权。它不使用已授权用户的身份,而是使用您的开发者凭据来向Microsoft Graph发出出站调用。提供此选项是为了完整性,并展示如何构建可在本地运行的应用程序,但它不是本示例的核心重点。
您可以本地运行它,或者使用 Azure 开发者 CLI (azd) 快速将其部署到 Azure。
先决条件
- 一个Azure订阅 - 创建一个Azure免费账户
- Azure CLI -(Azure 命令行接口) 安装 Azure CLI
- Azure 开发者命令行界面 (azd) - 安装 Azure 开发者 CLI
- Azure Functions 核心工具 - 安装 Azure Functions 核心工具
- Node.js 20 LTS 或更高版本 -
- Visual Studio Code - (可翻译为)“Visual Studio 代码”或“Visual Studio Code(简称VS Code)” 安装 Visual Studio Code
- 蓝铜矿 - 安装Azurite
您还必须拥有一个Entra客户端ID,以便您的MCP客户端可以使用。对于基本测试,您可以使用Visual Studio Code,其客户端ID已在下面的设置说明中给出。
开始使用(将应用部署到 Azure azd)
- 克隆此存储库。
- 登录到 Azure 并初始化 azd:
az login
azd auth login- 创建一个新的AZD项目环境,并指定位置(
westus2(建议),您想要使用的订阅ID,以及环境的名称:
azd env new --location westus2 --subscription - 此示例使用Visual Studio Code作为主要客户端。将其配置为允许的客户端应用程序:
azd env set PRE_AUTHORIZED_CLIENT_IDS aebc6443-996d-45c2-90f0-388ff96faa56如果您有其他会调用您服务器的MCP客户端,请提供所有客户端ID,并用逗号分隔
azd env set PRE_AUTHORIZED_CLIENT_IDS - 如您的组织有要求,请指定一个服务管理参考。 如果你不是微软员工,且不知道需要设置此选项,可以跳过此步骤。 然而,如果在配置过程中因缺少服务管理引用而出现错误,您可能需要重新执行此步骤。使用 Microsoft 租户的 Microsoft 员工必须提供服务管理引用(即您的服务树 ID)。如果没有此引用,您将无法创建 Entra 应用注册,配置也会失败。
azd env set SERVICE_MANAGEMENT_REFERENCE 如果您不知道在此处应设置什么,请咨询您组织的租户管理员。
- (可选)如果您正在将应用部署到主权云,请设置用于该云的令牌交换受众。
对于Entra ID美国政府版:
azd env set TOKEN_EXCHANGE_AUDIENCE api://AzureADTokenExchangeUSGov对于由21Vianet运营的Entra ID中国服务:
azd env set TOKEN_EXCHANGE_AUDIENCE api://AzureADTokenExchangeChina- 创建Azure资源并部署应用程序:
azd up在继续之前,请确保所有资源均显示为“成功”状态。
- 同意该应用程序的申请,以便您的MCP客户端能够成功登录。为了测试,您只需通过浏览器登录应用程序来授予自己的授权。请参阅 同意书撰写 关于你如何在生产环境中处理这个问题。
1. 导航至 /.auth/login/aad 您已部署的函数应用的端点。例如,如果您的函数应用位于 https://my-mcp-function-app.azurewebsites.net导航至 https://my-mcp-function-app.azurewebsites.net/.auth/login/aad.
您的函数应用基础URL应该会显示在输出中 azd up 命令。如果你在部署输出中错过了它,可以使用以下命令来获取:
azd env get-value SERVICE_MCP_DEFAULT_HOSTNAME或者,在 Azure 门户中找到它,位于您的 Function App 资源下。
1. 您应该会被重定向到一个登录提示页面。请使用您将在MCP客户端中用于测试的帐户进行登录。
1. 登录后,系统会提示您授权该应用程序。请查看所请求的权限,并点击“接受”以授予授权。
1. 您应该会被重定向回函数应用URL托管的一个页面,该页面会显示“您已成功登录”。此时,您可以关闭该页面。
- 获取您的Function App的MCP扩展系统密钥以连接MCP客户端。除了使用Entra ID进行服务器授权外,您的客户端还需要此密钥来调用MCP服务器。
你可以获取名为(此处应接具体系统名称,但原文未给出)的系统密钥 mcp_extension 使用 Azure CLI。首先,从您的 azd 部署中获取资源组和函数应用的名称:
# Get resource group name
azd env get-value AZURE_RESOURCE_GROUP_NAME
# Get function app name
azd env get-value SERVICE_MCP_NAME然后使用这些值来检索系统密钥:
az functionapp keys list --resource-group --name --query "systemKeys.mcp_extension" -o tsv或者,您可以从……中检索它 Azure 门户:
1. 在 Azure 门户中导航到您的函数应用 1. 首选 功能 → 应用密钥 1. 复制该值用于 mcp_extension 系统密钥
- 按照说明测试您的MCP服务器 测试您的MCP服务器 以下部分。您将需要您的函数应用URL和
mcp_extension上一步中的系统密钥。
开始使用(本地运行)
\[!NOTE\](注:此符号通常用于标记注意点,以下为翻译示例) 当本地运行时,MCP 服务器将使用您的开发者凭据(来自 Azure CLI 或 Visual Studio Code)来向 Microsoft Graph 发出出站调用,而不是使用已授权用户的身份。
- 克隆这个仓库。
- (可选)更新
local.settings.json文件在(某个地方/某个文件夹中)src/mcp-server文件夹中包含您的 Microsoft Entra 租户 ID。这有助于应用程序在您能够登录多个租户时,仍然能够正确访问您的开发者身份。
1. 从 Azure CLI 获取租户 ID:
az account show --query tenantId -o tsv1. 更新 local.settings.json 包含租户ID:
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "node",
"AZURE_TENANT_ID": ""
}
}- 启动Azurite。如果你使用命令行界面(CLI)来启动它,使用
azurite或者一个docker run例如,可以在一个单独的后台终端中执行该命令。
- 将您的主要终端移动到
src/mcp-server文件夹。
- 安装依赖项:
npm install- 启动项目:
npm start- 按照说明测试您的MCP服务器 测试您的MCP服务器 以下部分。
测试你的MCP服务器
一旦您完成了上述“入门”选项中的一个,就可以通过使用MCP客户端连接到MCP服务器来测试生成的MCP服务器。
以下提供的说明是关于在Visual Studio Code中使用GitHub Copilot进行测试的,该环境同时支持MCP协议和Entra ID认证。此设置还提供了日志,便于查看客户端和服务器之间如何进行服务器授权的交互。
- 在VS Code中创建一个新的工作区并安装 MCP扩展.
- 创建或更新您的MCP配置 在VS Code中。将这个添加到你的
mcp.json文件(或创建一个):
{
"inputs": [
{
"type": "promptString",
"id": "functions-mcp-extension-system-key",
"description": "Azure Functions MCP Extension System Key",
"password": true
},
{
"type": "promptString",
"id": "functionapp-host",
"description": "The host domain of the function app."
}
],
"servers": {
"remote-mcp-function": {
"type": "http",
"url": "https://${input:functionapp-host}/runtime/webhooks/mcp",
"headers": {
"x-functions-key": "${input:functions-mcp-extension-system-key}"
}
},
"local-mcp-function": {
"type": "http",
"url": "http://localhost:7071/runtime/webhooks/mcp"
}
}
}这将创建两个服务器配置,分别对应上面每个“开始使用”选项。在后续步骤中,请选择与您设置的MCP服务器方式(本地或远程)相匹配的服务器。
- (可选) 显示输出日志:
- 在VS Code中,打开命令面板: - 在Windows/Linux上:按 Ctrl+Shift+P - 在Mac上:按 Cmd+Shift+P - 输入“MCP: 列出服务器”并按回车键 - 选择你想要启动的服务器(无论是 remote-mcp-function 或者 local-mcp-function) - 选择“显示输出”——这将打开输出面板。 - 在输出面板中选择齿轮图标,然后选择“调试”或更高级别。“跟踪”提供了最详细的信息,但可能会包含额外的干扰信息。
- 启动MCP服务器:
- 在 VS Code 中,打开命令面板: - 在Windows/Linux上:按下 Ctrl+Shift+P - 在Mac上:按下 Cmd+Shift+P - 输入“MCP: 列出服务器”并按回车键 - 选择你想要启动的服务器(任选其一 remote-mcp-function 或者 local-mcp-function) - 选择“启动服务器”。 - 如果你选择了 remote-mcp-function,系统会提示您输入: - 您的函数应用URL - 你的 mcp_extension 系统密钥
- 如果你正在连接到由应用服务认证和授权后端托管的应用程序,VS Code 会提示你允许 GitHub Copilot 访问你的 Microsoft 帐户。请按照提示进行登录。
如果你配置了调试输出日志,你应该能看到MCP客户端和服务器在登录过程中是如何交互的。请参阅 服务器授权协议 了解更多详情。
- 打开 GitHub Copilot 聊天面板(
Ctrl+Alt+I) 并输入提示以使用该工具。确保它调用该工具的最简单方法是发送消息#HelloTool。
- GitHub Copilot 应该会提示您允许它调用该工具。请确认它正在连接到正确的服务器,然后允许其继续操作。
- 你应该在聊天窗口中看到工具的响应。它应该会以你的名字打招呼,并显示你的电子邮件。这些信息来自MCP服务器调用的Microsoft Graph。
概念概述
代码结构
MCP服务器的代码在 src/mcp-server 项目文件夹。这是一个使用TypeScript和Node.js构建的Azure Functions项目,它采用了Azure Functions的MCP扩展以及一个自定义处理程序。MCP处理程序在(此处原文未给出具体定义位置,可翻译为): mcp-handler/function.json 并实现了一个工具在 index.ts这个函数的目标是调用 Microsoft Graph 并返回一个简单的问候语。当在 Azure 上托管时,它会代表已登录的用户为请求调用 Graph。当本地运行时,它会使用您的开发者凭据。
为了满足这些要求,处理者使用了 @azure/identity 打包以获取正确用户的令牌。该 index.ts 文件配置了一个适当的(设置/参数/选项等,具体根据上下文确定) TokenCredential 基于上下文的实例。在本地上下文中,这是一个简单的 ChainedTokenCredential然而,当在Azure中使用应用程序服务身份验证和授权托管时,处理程序会使用MCP工具触发器的 ToolInvocationContext 要从底层HTTP传输中获取请求头。它使用这些请求头来构建一个自定义的令牌凭证实现。这个实现使用了 OnBehalfOfCredential在配置过程中,使用托管身份作为联合身份凭据,以应用程序注册的身份进行身份验证。
使用@azure/identity包中的自定义处理程序的方法,使工具实现保持简洁并专注于其核心目的,避免引入不必要的复杂性。此外,如果需要,这种方法还允许在多个工具中使用相同的设置。
同意书撰写
在这个示例中所描述的步骤中,您通过在浏览器中登录应用表示了同意。这使得应用能够请求对Microsoft Graph的委托权限。处理同意主要有两种方式:
- 用户同意 - 这是上述示例中采用的方法。每个用户登录应用程序并同意所请求的权限。他们只能为自己这样做,除非他们是租户管理员,并有权代表他人同意。在本示例中,用户同意是合适的,因为它允许您快速进行测试,而不会影响其他用户。然而,本示例中用户同意的编写方式并不反映您在生产环境中通常会采用的方法。下文将对此进行更详细的说明。
- 管理员同意 - 租户管理员可以在所有用户登录并查看权限时,代表所有用户同意应用程序的使用。一旦完成此操作,单个用户即可登录,而无需自行同意。这种方法更具可扩展性,并确保所有用户都能访问应用程序,而不会遇到同意问题。在示例中,管理员同意并不合适,但对于生产环境来说,这是一个很好的选择。
此示例的用户同意方法是独立登录,因为该示例使用Visual Studio Code作为客户端。尽管Visual Studio Code已预先授权访问我们的应用程序,但这仅创建了用户调用MCP服务器的同意。它并未创建MCP服务器代表用户调用Microsoft Graph的同意。当我们直接登录应用程序时,我们会作为联合同意体验的一部分请求Microsoft Graph的权限。
主要区别在于,由于Visual Studio Code使用的是单点登录流程,因此它仅请求MCP服务器的令牌。它不会提供机会让用户交互式地同意MCP服务器所需的任何权限。如果你构建了一个客户端,该客户端使用某种形式的交互式登录,那么所有操作都可以完全由该客户端处理。无需单独的浏览器登录。
见 Microsoft身份平台中的权限和同意概述 如需了解Entra ID如何处理同意的相关信息,请参阅。
服务器授权协议
在VS Code的调试输出中,你会看到MCP客户端和服务器交互时的一系列请求和响应。当使用MCP服务器授权时,你应该会看到以下事件序列:
- 编辑器向MCP服务器发送初始化请求。
- MCP服务器返回一个错误,指示需要授权。响应中包含指向应用程序受保护资源元数据(PRM)的指针。应用程序服务的认证和授权功能会为使用此示例构建的应用程序生成PRM。
- 编辑器获取PRM(可能是指某种凭证或令牌管理信息)并使用它来识别授权服务器。
- 编辑尝试从授权服务器上的一个已知端点获取授权服务器元数据(ASM)。
- Microsoft Entra ID 不支持在知名终结点上使用 ASM(应用程序服务管理或类似服务,具体含义需根据上下文确定),因此编辑器回退到使用 OpenID Connect 元数据终结点来获取 ASM。它尝试通过在任何其他路径信息之前插入知名终结点来发现该元数据。
- OpenID Connect规范实际上将众所周知的终结点定义为位于路径信息之后,而这就是Microsoft Entra ID所托管的位置。因此,编辑器再次尝试使用该格式。
- 编辑器成功检索到ASM(应用程序安全模块)。然后,它可以结合自身的客户端ID使用这些信息进行登录。此时,编辑器会提示您登录并同意该应用程序的使用。
- 假设您成功登录并同意,编辑器将完成登录过程。它会再次向MCP服务器发送初始化请求,但这次的请求中会包含一个授权令牌。此重试过程在调试输出级别下不可见,但在跟踪输出级别下可以看到。
- MCP服务器验证令牌,并对初始化请求返回成功响应。从这一点开始,标准的MCP流程继续进行,最终导致发现本示例中定义的MCP工具。
您可以了解更多关于完整协议的详细信息在 MCP规范。
