返回市场
远程-mcp功能-类型脚本

远程-mcp功能-类型脚本

作者:Azure-Samples44 星标更新:2025-11-20

项目介绍

<!-- --- name: 远程MCP与Azure Functions (Node.js/TypeScript/JavaScript) description: 在Azure Functions上运行远程MCP服务器。 languages: - typescript - javascript - nodejs - bicep - azdeveloper products: - azure-functions - azure page_type: sample urlFragment: remote-mcp-functions-typescript --- -->

使用Azure Functions (Node.js/TypeScript) 开始构建远程MCP服务器

这是一个快速入门模板,用于轻松地在云中构建和部署自定义远程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 版本。

在GitHub Codespaces中打开

以下是使用Azure Functions的远程MCP服务器架构图:

架构图

先决条件

准备本地环境

此特定示例需要一个Azure存储模拟器,因为我们将从Blob存储中保存和获取片段。

  1. 启动Azurite

    docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 \
        mcr.microsoft.com/azure-storage/azurite
    

注意 如果你使用来自VS Code扩展的Azurite,你需要现在运行 Azurite: Start,否则你会看到错误。

从终端本地运行你的MCP服务器

  1. 安装依赖项

    npm install
    
  2. 构建项目

    npm run build
    
  3. 在本地启动Functions主机:

    func start
    

注意 默认情况下,这将使用webhook路由:/runtime/webhooks/mcp/sse。稍后我们将在Azure中设置客户端/主机调用的密钥:/runtime/webhooks/mcp/sse?code=<system_key>

从客户端/主机内部使用本地MCP服务器

VS Code - Copilot编辑

  1. 添加MCP服务器 从命令面板中添加URL到正在运行的功能应用的SSE端点:

    http://0.0.0.0:7071/runtime/webhooks/mcp/sse
    
  2. 选择要添加的MCP服务器类型为HTTP (Server-Sent-Events)

  3. 输入正在运行的功能应用的SSE端点的URL。

  4. 输入服务器ID。(这可以是任何你想的名字)

  5. 选择是否要在用户设置(对你可用的所有应用程序)或工作区设置(仅对此应用程序可用)中运行此操作。

  6. 列出MCP服务器 从命令面板中开始服务器。上一步可能已经启动了你的本地服务器。如果是这样,你可以跳过这一步。

  7. 在Copilot聊天代理模式下输入提示以触发工具,例如,选择一些代码并输入以下提示

    说你好
    
    将此片段保存为snippet1
    
    检索snippet1并应用于newFile.ts
    
  8. 当提示运行工具时,点击继续以同意。

  9. 完成后,在终端窗口中按Ctrl+C停止func.exe主机进程,并从命令面板中列出MCP服务器停止本地服务器。

MCP Inspector

  1. 新的终端窗口中,安装并运行MCP Inspector

    npx @modelcontextprotocol/inspector node build/index.js
    
  2. 如果你之前停止了功能应用,现在在本地启动Functions主机:

    func start
    
  3. CTRL点击以从应用显示的URL加载MCP Inspector Web应用(例如 http://0.0.0.0:5173/#resources)

  4. 设置传输类型为SSE

  5. 设置URL为你正在运行的功能应用的SSE端点并连接

    http://0.0.0.0:7071/runtime/webhooks/mcp/sse
    
  6. 列出工具。点击一个工具并运行工具

  7. 完成后,在终端窗口中按Ctrl+C停止func.exe主机进程,并在终端窗口中按Ctrl+C停止@modelcontextprotocol/inspector主机进程。

验证Azurite中的本地Blob存储

在本地测试片段保存功能后,你可以验证片段是否正确存储在本地Azurite存储模拟器中。

使用Azure存储资源管理器

  1. 打开Azure存储资源管理器
  2. 在左侧面板中,展开模拟器&附加存储账户(模拟器 - 默认端口) (密钥)
  3. 导航到Blob容器snippets
  4. 你应该在此容器中看到任何已保存的片段作为Blob文件
  5. 双击任何Blob以查看其内容并验证片段数据是否正确保存

使用Azure CLI(替代方法)

如果你更喜欢使用命令行,也可以使用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存储功能按预期工作。

部署到Azure以实现远程MCP

可选地,你可以选择在示例中使用VNet。(如果选择此选项,请在azd up之前执行)

azd env set VNET_ENABLED true

运行此azd命令以提供功能应用及其所需的Azure资源,并部署你的代码:

azd up

注意 API管理 可用于提高MCP服务器的安全性和策略,而应用服务内置身份验证可用于设置你喜欢的OAuth提供商,包括Entra。

从客户端连接到你的远程MCP服务器功能应用

客户端需要一个密钥才能调用新的托管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中连接到远程MCP服务器

对于MCP Inspector,你可以在URL中包含密钥:

https://<funcappname>.azurewebsites.net/runtime/webhooks/mcp/sse?code=<your-mcp-extension-system-key>

在VS Code - GitHub Copilot中连接到远程MCP服务器

对于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"
        }
    }
}
  1. mcp.json文件中点击开始服务器remote-mcp-function

  2. 当VS Code提示时,输入你在Azure门户中创建的功能应用名称。

  3. 输入Azure Functions MCP Extension System Key到提示中。你可以在Azure门户中复制此密钥,进入功能菜单项,然后选择应用密钥,复制mcp_extension键。

  4. 在Copilot聊天代理模式下输入提示以触发工具,例如,选择一些代码并输入以下提示

    说你好
    
    将此片段保存为snippet1
    
    检索snippet1并应用于newFile.ts
    

重新部署你的代码

你可以多次运行azd up命令以提供Azure资源并部署代码更新到你的功能应用。

[!NOTE] 已部署的代码文件始终会被最新的部署包覆盖。

清理资源

当你完成对功能应用及相关资源的工作后,可以使用此命令删除功能应用及其相关资源,避免产生进一步的成本:

azd down

源代码

getSnippetsaveSnippet端点的功能代码定义在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)"
}

下一步