这是一个快速入门模板,用于轻松地在云中构建和部署自定义远程MCP服务器。你可以克隆/恢复/运行在本地机器上进行调试,并通过azd up在几分钟内将其部署到云端。MCP服务器设计时使用密钥和HTTPS进行安全保护,并允许通过EasyAuth和/或API管理以及VNET网络隔离来提供更多OAuth选项。
观看视频概述
<a href="https://www.youtube.com/watch?v=U9DsLcP5vEk"> <img src="./images/video-overview.png" alt="观看视频" width="500" /> </a>如果你正在寻找更多语言版本的示例,请查看 .NET/C# 和 Python 版本。
以下是使用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,否则你会看到错误。
安装依赖项
npm install
构建项目
npm run build
在本地启动Functions主机:
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服务器类型为HTTP (Server-Sent-Events)。
输入正在运行的功能应用的SSE端点的URL。
输入服务器ID。(这可以是任何你想的名字)
选择是否要在用户设置(对你可用的所有应用程序)或工作区设置(仅对此应用程序可用)中运行此操作。
列出MCP服务器 从命令面板中开始服务器。上一步可能已经启动了你的本地服务器。如果是这样,你可以跳过这一步。
在Copilot聊天代理模式下输入提示以触发工具,例如,选择一些代码并输入以下提示
说你好
将此片段保存为snippet1
检索snippet1并应用于newFile.ts
当提示运行工具时,点击继续以同意。
完成后,在终端窗口中按Ctrl+C停止func.exe主机进程,并从命令面板中列出MCP服务器停止本地服务器。
在新的终端窗口中,安装并运行MCP Inspector
npx @modelcontextprotocol/inspector node build/index.js
如果你之前停止了功能应用,现在在本地启动Functions主机:
func start
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
列出工具。点击一个工具并运行工具。
完成后,在终端窗口中按Ctrl+C停止func.exe主机进程,并在终端窗口中按Ctrl+C停止@modelcontextprotocol/inspector主机进程。
在本地测试片段保存功能后,你可以验证片段是否正确存储在本地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存储功能按预期工作。
可选地,你可以选择在示例中使用VNet。(如果选择此选项,请在azd up之前执行)
azd env set VNET_ENABLED true
运行此azd命令以提供功能应用及其所需的Azure资源,并部署你的代码:
azd up
注意 API管理 可用于提高MCP服务器的安全性和策略,而应用服务内置身份验证可用于设置你喜欢的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。以下示例来自此存储库中的mcp.json文件,并使用输入提示你在从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-name",
"description": "Azure Functions App Name"
}
],
"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.json文件中点击开始服务器remote-mcp-function:
当VS Code提示时,输入你在Azure门户中创建的功能应用名称。
输入Azure Functions MCP Extension System Key到提示中。你可以在Azure门户中复制此密钥,进入功能菜单项,然后选择应用密钥,复制mcp_extension键。
在Copilot聊天代理模式下输入提示以触发工具,例如,选择一些代码并输入以下提示
说你好
将此片段保存为snippet1
检索snippet1并应用于newFile.ts
你可以多次运行azd up命令以提供Azure资源并部署代码更新到你的功能应用。
[!NOTE] 已部署的代码文件始终会被最新的部署包覆盖。
当你完成对功能应用及相关资源的工作后,可以使用此命令删除功能应用及其相关资源,避免产生进一步的成本:
azd down
getSnippet和saveSnippet端点的功能代码定义在src目录下的TypeScript文件中。MCP函数注解暴露这些函数作为MCP服务器工具。
这展示了几个MCP服务器示例的代码(获取字符串、获取对象、保存对象):
// Hello函数 - 响应带有问候消息
export async function mcpToolHello(context: InvocationContext): Promise<string> {
return "你好,我是MCP工具!";
}
// 注册hello工具
app.mcpTool('hello', {
toolName: 'hello',
description: '简单的Hello World MCP工具,响应带有问候消息。',
handler: mcpToolHello
});
// GetSnippet函数 - 根据名称检索片段
export async function getSnippet(_message: unknown, context: InvocationContext): Promise<string> {
console.info('获取片段');
// 从工具参数中获取片段名称
const mcptoolargs = context.triggerMetadata.mcptoolargs as { snippetname?: string };
const snippetName = mcptoolargs?.snippetname;
console.info(`片段名称: ${snippetName}`);
if (!snippetName) {
return "未提供片段名称";
}
// 从额外输入中正确检索片段内容
const snippetContent = context.extraInputs.get(blobInputBinding);
if (!snippetContent) {
return `片段 '${snippetName}' 未找到`;
}
console.info(`检索到片段: ${snippetName}`);
return snippetContent as string;
}
// 注册GetSnippet工具
app.mcpTool('getsnippet', {
toolName: GET_SNIPPET_TOOL_NAME,
description: GET_SNIPPET_TOOL_DESCRIPTION,
toolProperties: [
{
propertyName: SNIPPET_NAME_PROPERTY_NAME,
propertyValue: PROPERTY_TYPE,
description: SNIPPET_NAME_PROPERTY_DESCRIPTION,
}
],
extraInputs: [blobInputBinding],
handler: getSnippet
});
// SaveSnippet函数 - 保存具有名称的片段
export async function saveSnippet(_message: unknown, context: InvocationContext): Promise<string> {
console.info('保存片段');
// 从工具参数中获取片段名称和内容
const mcptoolargs = context.triggerMetadata.mcptoolargs as {
snippetname?: string;
snippet?: string;
};
const snippetName = mcptoolargs?.snippetname;
const snippet = mcptoolargs?.snippet;
if (!snippetName) {
return "未提供片段名称";
}
if (!snippet) {
return "未提供片段内容";
}
// 使用输出绑定将片段保存到Blob存储
context.extraOutputs.set(blobOutputBinding, snippet);
console.info(`保存片段: ${snippetName}`);
return snippet;
}
// 注册SaveSnippet工具
app.mcpTool('savesnippet', {
toolName: SAVE_SNIPPET_TOOL_NAME,
description: SAVE_SNIPPET_TOOL_DESCRIPTION,
toolProperties: [
{
propertyName: SNIPPET_NAME_PROPERTY_NAME,
propertyValue: PROPERTY_TYPE,
description: SNIPPET_NAME_PROPERTY_DESCRIPTION,
},
{
propertyName: SNIPPET_PROPERTY_NAME,
propertyValue: PROPERTY_TYPE,
description: SNIPPET_PROPERTY_DESCRIPTION,
}
],
extraOutputs: [blobOutputBinding],
handler: saveSnippet
});
请注意,host.json文件还包括对实验性捆绑包的引用,这是使用此功能的应用程序所必需的:
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle.Experimental",
"version": "[4.*, 5.0.0)"
}
VNET_ENABLED=true标志启用VNET