这是一个快速入门模板,用于轻松构建并部署一个自定义的远程 MCP 服务器到云端,使用 Azure Functions 和 Python。您可以在本地机器上进行克隆/恢复/运行调试,并通过 azd up 在几分钟内将其部署到云端。MCP 服务器通过设计使用密钥和 HTTPS 进行安全防护,并允许使用内置身份验证和/或 API 管理 提供更多的 OAuth 选项,以及通过 VNET 实现网络隔离。
如果您需要更多语言的示例,请查看 .NET/C# 和 Node.js/TypeScript 版本。
以下是使用 Azure Functions 的远程 MCP 服务器架构图:

4.0.7030此特定示例需要 Azure 存储模拟器,因为我们将在 blob 存储中保存和获取片段。
启动 Azurite
docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 \
mcr.microsoft.com/azure-storage/azurite
注意 如果您使用来自 VS Code 扩展的 Azurite,现在需要运行
Azurite: Start,否则您会看到错误。
切换到新终端窗口中的 src 文件夹:
cd src
安装 Python 依赖项:
pip install -r requirements.txt
注意 在执行
pip install之前创建虚拟环境是一个最佳实践,以避免依赖项问题/冲突,或者如果您正在 CodeSpaces 中运行。有关更多信息,请参阅 VS Code 中的 Python 环境。
本地启动函数主机:
func start
注意 默认情况下,这将使用 webhook 路由:
/runtime/webhooks/mcp/sse。稍后我们将在 Azure 中设置客户端/主机调用的密钥:/runtime/webhooks/mcp/sse?code=<system_key>。
添加 MCP 服务器 从命令面板中添加 URL 到正在运行的函数应用的 SSE 终结点:
http://0.0.0.0:7071/runtime/webhooks/mcp/sse
列出 MCP 服务器 从命令面板中开始服务器
在 Copilot 聊天代理模式下输入提示以触发工具,例如选择一些代码并输入以下提示
说你好
将此片段保存为 snippet1
检索 snippet1 并应用于 newFile.py
当提示运行工具时,点击 继续 同意
完成后,在终端窗口中按 Ctrl+C 停止函数主机进程。
在 新的终端窗口 中安装并运行 MCP Inspector
npx @modelcontextprotocol/inspector
CTRL 点击以从应用程序显示的 URL 加载 MCP Inspector Web 应用程序(例如 http://0.0.0.0:5173/#resources)
设置传输类型为 SSE
设置 URL 为正在运行的函数应用的 SSE 终结点并 连接:
http://0.0.0.0:7071/runtime/webhooks/mcp/sse
注意 此步骤在 CodeSpaces 中不起作用。请继续部署到远程 MCP。
在本地测试片段保存功能之后,您可以验证 blob 是否正确存储在本地 Azurite 存储模拟器中。
如果您更喜欢使用命令行,也可以使用 Azure CLI 和存储模拟器来验证 blob:
# 列出 snippets 容器中的 blob
az storage blob list --container-name snippets --connection-string "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
# 下载特定 blob 以查看其内容
az storage blob download --container-name snippets --name <blob-name> --file <local-file-path> --connection-string "DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;"
此验证步骤确保您的 MCP 服务器正确与本地存储模拟器交互,并且在部署到 Azure 之前 blob 存储功能按预期工作。
运行此 azd 命令以提供函数应用及其所需的所有 Azure 资源,并部署您的代码:
azd up
您可以选择使用 VNet。要在 azd up 之前启用 VNet,请执行以下操作:
azd env set VNET_ENABLED true
此外,可以使用 API 管理 来提高安全性并为您的 MCP 服务器制定策略,还可以使用 App Service 内置身份验证来设置您最喜欢的 OAuth 提供商,包括 Entra。
您的客户端需要一个密钥才能调用新的托管 SSE 终结点,该终结点的形式为 https://<funcappname>.azurewebsites.net/runtime/webhooks/mcp/sse。托管函数默认需要系统密钥,可以从 门户 或 CLI (az functionapp keys list --resource-group <resource_group> --name <function_app_name>) 获取。获取名为 mcp_extension 的系统密钥。
对于 MCP Inspector,您可以在 URL 中包含密钥:
https://<funcappname>.azurewebsites.net/runtime/webhooks/mcp/sse?code=<your-mcp-extension-system-key>
对于 GitHub Copilot 内部的 VS Code,您应将密钥设置为 mcp.json 中的 x-functions-key 头,并使用 https://<funcappname>.azurewebsites.net/runtime/webhooks/mcp/sse 作为 URL。以下示例使用输入,并在您从 VS Code 启动服务器时提示您提供密钥。请注意,mcp.json 已经包含在此仓库中,并将被 VS Code 捕获。点击服务器上的“开始”以提供包括 functionapp-name(在您的 /.azure/*/.env 文件中)和 functions-mcp-extension-system-key 的值,后者可以通过上面的 CLI 命令或门户中的 API 密钥获得。
{
"inputs": [
{
"type": "promptString",
"id": "functions-mcp-extension-system-key",
"description": "Azure Functions MCP 扩展系统密钥",
"password": true
},
{
"type": "promptString",
"id": "functionapp-name",
"description": "Azure Functions 应用名称"
}
],
"servers": {
"remote-mcp-function": {
"type": "sse",
"url": "https://${input:functionapp-name}.azurewebsites.net/runtime/webhooks/mcp/sse",
"headers": {
"x-functions-key": "${input:functions-mcp-extension-system-key}"
}
},
"local-mcp-function": {
"type": "sse",
"url": "http://0.0.0.0:7071/runtime/webhooks/mcp/sse"
}
}
}
对于 MCP Inspector,您可以在 URL 中包含密钥:https://<funcappname>.azurewebsites.net/runtime/webhooks/mcp/sse?code=<your-mcp-extension-system-key>。
对于 GitHub Copilot 内部的 VS Code,您应将密钥设置为 mcp.json 中的 x-functions-key 头,并使用 https://<funcappname>.azurewebsites.net/runtime/webhooks/mcp/sse 作为 URL。以下示例使用输入,并在您从 VS Code 启动服务器时提示您提供密钥:
{
"inputs": [
{
"type": "promptString",
"id": "functions-mcp-extension-system-key",
"description": "Azure Functions MCP 扩展系统密钥",
"password": true
}
],
"servers": {
"my-mcp-server": {
"type": "sse",
"url": "<funcappname>.azurewebsites.net/runtime/webhooks/mcp/sse",
"headers": {
"x-functions-key": "${input:functions-mcp-extension-system-key}"
}
}
}
}
您可以多次运行 azd up 命令以提供 Azure 资源并部署代码更新到您的函数应用。
[!NOTE] 已部署的代码文件始终会被最新的部署包覆盖。
当您完成对函数应用及相关资源的工作后,可以使用此命令从 Azure 删除函数应用及其相关资源,以避免产生进一步的成本:
azd down
一旦您的应用程序部署完毕,您可以使用这些命令来管理和监控您的应用程序:
# 从环境文件获取您的函数应用名称
FUNCTION_APP_NAME=$(cat .azure/$(cat .azure/config.json | jq -r '.defaultEnvironment')/env.json | jq -r '.FUNCTION_APP_NAME')
echo $FUNCTION_APP_NAME
# 获取资源组
RESOURCE_GROUP=$(cat .azure/$(cat .azure/config.json | jq -r '.defaultEnvironment')/env.json | jq -r '.AZURE_RESOURCE_GROUP')
echo $RESOURCE_GROUP
# 查看函数应用日志
az webapp log tail --name $FUNCTION_APP_NAME --resource-group $RESOURCE_GROUP
# 重新部署应用程序而不提供新的资源
azd deploy
get_snippet 和 save_snippet 终结点的函数代码定义在 src 目录中的 Python 文件中。MCP 函数注解将这些函数暴露为 MCP 服务器工具。
这是来自 function_app.py 文件的实际代码:
@app.generic_trigger(arg_name="context", type="mcpToolTrigger", toolName="hello",
description="Hello world.",
toolProperties="[]")
def hello_mcp(context) -> None:
"""
一个简单的函数,返回问候消息。
参数:
context: 触发上下文(在此函数中未使用)。
返回:
str: 问候消息。
"""
return "Hello I am MCPTool!"
@app.generic_trigger(
arg_name="context",
type="mcpToolTrigger",
toolName="getsnippet",
description="通过名称检索片段。",
toolProperties=tool_properties_get_snippets_json
)
@app.generic_input_binding(
arg_name="file",
type="blob",
connection="AzureWebJobsStorage",
path=_BLOB_PATH
)
def get_snippet(file: func.InputStream, context) -> str:
"""
从 Azure Blob 存储中通过名称检索片段。
参数:
file (func.InputStream): 读取 Azure Blob 存储中片段的输入绑定。
context: 包含输入参数的触发上下文。
返回:
str: 片段的内容或错误消息。
"""
snippet_content = file.read().decode("utf-8")
logging.info(f"检索片段: {snippet_content}")
return snippet_content
@app.generic_trigger(
arg_name="context",
type="mcpToolTrigger",
toolName="savesnippet",
description="通过名称保存片段。",
toolProperties=tool_properties_save_snippets_json
)
@app.generic_output_binding(
arg_name="file",
type="blob",
connection="AzureWebJobsStorage",
path=_BLOB_PATH
)
def save_snippet(file: func.Out[str], context) -> str:
content = json.loads(context)
snippet_name_from_args = content["arguments"][_SNIPPET_NAME_PROPERTY_NAME]
snippet_content_from_args = content["arguments"][_SNIPPET_PROPERTY_NAME]
if not snippet_name_from_args:
return "未提供片段名称"
if not snippet_content_from_args:
return "未提供片段内容"
file.set(snippet_content_from_args)
logging.info(f"保存片段: {snippet_content_from_args}")
return f"片段 '{snippet_content_from_args}' 保存成功"
请注意,host.json 文件还包括对实验性捆绑包的引用,这是使用此功能的应用程序所必需的:
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle.Experimental",
"version": "[4.*, 5.0.0)"
}