快速入门:使用 Azure Functions 生成自定义远程 MCP 服务器

在本快速入门中,你将使用 Azure 开发人员 CLI 从模板项目创建自定义远程模型上下文协议 (azdMCP) 服务器。 MCP 服务器使用 Azure Functions MCP 服务器扩展为 AI 模型、代理和助手提供工具。 在本地运行项目并使用 GitHub Copilot 验证代码后,将其部署到 Azure Functions 中的新无服务器函数应用,该应用遵循当前安全且可缩放的部署最佳做法。

小窍门

Functions 还使你能够将现有的 MCP 服务器代码项目部署到 Flex Consumption 计划应用,而无需对代码项目进行更改。 有关详细信息,请参阅 快速入门:在 Azure Functions 上托管现有的 MCP 服务器

由于新应用在 Flex Consumption 计划中运行,因此遵循 即用即付 计费模型,因此完成本快速入门会在 Azure 帐户中花费几美分或更少。

重要

本文目前仅在 C#、Java、Python 和 TypeScript 中受支持。 若要完成快速入门,请在文章顶部选择其中一种受支持的语言。

本文支持适用于 Azure Functions 的 Node.js 编程模型版本 4。

本文支持适用于 Azure Functions 的 Python 编程模型版本 2。

先决条件

初始化项目

azd init使用命令从模板创建本地 Azure Functions 代码项目。

  1. 在 Visual Studio Code 中,打开要在其中创建项目的文件夹或工作区。
  1. 在终端中,运行以下命令 azd init

    azd init --template remote-mcp-functions-dotnet -e mcpserver-dotnet
    

    此命令从模板存储库中拉取项目文件,并在当前文件夹中初始化项目。 -e 标志设置当前环境的名称。 在 azd,环境维护您应用程序的独特部署上下文,并且您可以定义多个。 它也在 Azure 中你创建的资源组名称中使用。

  1. 在本地终端或命令提示符下运行以下命令 azd init

    azd init --template remote-mcp-functions-java -e mcpserver-java 
    

    此命令从模板存储库中拉取项目文件,并在当前文件夹中初始化项目。 -e 标志设置当前环境的名称。 在 azd,环境维护您应用程序的独特部署上下文,并且您可以定义多个。 它还用于在 Azure 中创建的资源的名称。

  1. 在本地终端或命令提示符下运行以下命令 azd init

    azd init --template remote-mcp-functions-typescript -e mcpserver-ts
    

    此命令从模板存储库中拉取项目文件,并在当前文件夹中初始化项目。 -e 标志设置当前环境的名称。 在 azd,环境维护您应用程序的独特部署上下文,并且您可以定义多个。 它还用于在 Azure 中创建的资源的名称。

  1. 在本地终端或命令提示符下运行以下命令 azd init

    azd init --template remote-mcp-functions-python -e mcpserver-python
    

    此命令从模板存储库中拉取项目文件,并在当前文件夹中初始化项目。 -e 标志设置当前环境的名称。 在 azd,环境维护您应用程序的独特部署上下文,并且您可以定义多个。 它还用于在 Azure 中创建的资源的名称。

启动存储模拟器

在本地运行代码项目时,使用 Azurite 模拟器模拟 Azure 存储帐户连接。

  1. 如果尚未 安装,请安装 Azurite

  2. F1。 在命令面板中,搜索并运行命令 Azurite: Start 以启动本地存储模拟器。

在本地运行 MCP 服务器

Visual Studio Code 与 Azure Functions Core 工具 集成,使你能够使用 Azurite 模拟器在本地开发计算机上运行此项目。

  1. 若要在本地启动函数,请按 F5 或左侧活动栏中的 “运行和调试 ”图标。 “终端”面板将显示 Core Tools 的输出。 应用在 终端 面板中启动,可以看到在本地运行的函数的名称。

  2. 记下用于在 Visual Studio Code 中配置 GitHub Copilot 的本地 MCP 服务器终结点(例如 http://localhost:7071/runtime/webhooks/mcp)。

查看代码(可选)

可以查看定义 MCP 服务器工具的代码:

MCP 服务器工具的函数代码在 src 文件夹中定义。 该 McpToolTrigger 属性将函数公开为 MCP 服务器工具:

    [Function(nameof(SayHello))]
    public string SayHello(
        [McpToolTrigger(HelloToolName, HelloToolDescription)] ToolInvocationContext context
    )
    {
        logger.LogInformation("Saying hello");
        return "Hello I am MCP Tool!";
    }
    [Function(nameof(GetSnippet))]
    public object GetSnippet(
        [McpToolTrigger(GetSnippetToolName, GetSnippetToolDescription)]
            ToolInvocationContext context,
        [BlobInput(BlobPath)] string snippetContent
    )
    {
        return snippetContent;
    }

    [Function(nameof(SaveSnippet))]
    [BlobOutput(BlobPath)]
    public string SaveSnippet(
        [McpToolTrigger(SaveSnippetToolName, SaveSnippetToolDescription)]
            ToolInvocationContext context,
        [McpToolProperty(SnippetNamePropertyName, SnippetNamePropertyDescription, true)]
            string name,
        [McpToolProperty(SnippetPropertyName, SnippetPropertyDescription, true)]
            string snippet
    )
    {
        return snippet;
    }
}

可以在 Azure Functions .NET MCP 服务器 GitHub 存储库中查看完整的项目模板。

MCP 服务器工具的函数代码在 src/main/java/com/function/ 文件夹中定义。 批 @McpToolTrigger 注将函数公开为 MCP 服务器工具:

                description = "The messages to be logged.",
                isRequired = true,
                isArray = true)
            String messages,
            final ExecutionContext functionExecutionContext
    ) {
        functionExecutionContext.getLogger().info("Hello, World!");
        functionExecutionContext.getLogger().info("Tool Name: " + mcpToolInvocationContext.getName());
        functionExecutionContext.getLogger().info("Transport Type: " + mcpToolInvocationContext.getTransportType());

        // Handle different transport types
        if (mcpToolInvocationContext.isHttpStreamable()) {
            functionExecutionContext.getLogger().info("Session ID: " + mcpToolInvocationContext.getSessionid());
        } else if (mcpToolInvocationContext.isHttpSse()) {
            if (mcpToolInvocationContext.getClientinfo() != null) {
                functionExecutionContext.getLogger().info("Client: " + 
                    mcpToolInvocationContext.getClientinfo().get("name").getAsString() + " v" +
        // Write the snippet content to the output blob
        outputBlob.setValue(snippet);

        return "Successfully saved snippet '" + snippetName + "' with " + snippet.length() + " characters.";
    }

    /**
     * Azure Function that handles retrieving a text snippet from Azure Blob Storage.
     * <p>
     * The function is triggered by an MCP Tool Trigger. The snippet name is provided
     * as an MCP tool property, and the snippet content is read from the blob at the 
     * path derived from the snippet name.
     *
     * @param mcpToolInvocationContext The JSON input from the MCP tool trigger.
     * @param snippetName   The name of the snippet to retrieve, provided as an MCP tool property.
     * @param inputBlob     The Azure Blob input binding that fetches the snippet content.
     * @param functionExecutionContext       The execution context for logging.
     */
    @FunctionName("GetSnippets")
    @StorageAccount("AzureWebJobsStorage")
    public String getSnippet(
            @McpToolTrigger(
                name = "getSnippets",
                description = "Gets a text snippet from your snippets collection.")
            String mcpToolInvocationContext,
            @McpToolProperty(
                name = SNIPPET_NAME_PROPERTY_NAME,
                propertyType = "string",
                description = "The name of the snippet.",
                isRequired = true)
            String snippetName,
            @BlobInput(name = "inputBlob", path = BLOB_PATH)
            String inputBlob,
            final ExecutionContext functionExecutionContext
    ) {
        // Log the entire incoming JSON for debugging
        functionExecutionContext.getLogger().info(mcpToolInvocationContext);

        // Log the snippet name and the fetched snippet content from the blob

可以在 Azure Functions Java MCP 服务器 GitHub 存储库中查看完整的项目模板。

MCP 服务器工具的函数代码在 src/function_app.py 文件中定义。 MCP 函数注释将这些函数公开为 MCP 服务器工具:

tool_properties_save_snippets_json = json.dumps([prop.to_dict() for prop in tool_properties_save_snippets_object])
tool_properties_get_snippets_json = json.dumps([prop.to_dict() for prop in tool_properties_get_snippets_object])


@app.generic_trigger(
    arg_name="context",
    type="mcpToolTrigger",
    toolName="hello_mcp",
    description="Hello world.",
    toolProperties="[]",
)
def hello_mcp(context) -> None:
    """

@app.generic_trigger(
    arg_name="context",
    type="mcpToolTrigger",
    toolName="save_snippet",
    description="Save a snippet with a name.",
    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 "No snippet name provided"

    if not snippet_content_from_args:
        return "No snippet content provided"

    file.set(snippet_content_from_args)
    logging.info(f"Saved snippet: {snippet_content_from_args}")
    return f"Snippet '{snippet_content_from_args}' saved successfully"

可以在 Azure Functions Python MCP 服务器 GitHub 存储库中查看完整的项目模板。

MCP 服务器工具的函数代码在 src 文件夹中定义。 MCP 函数注册将这些函数公开为 MCP 服务器工具:

export async function mcpToolHello(_toolArguments:unknown, context: InvocationContext): Promise<string> {
    console.log(_toolArguments);
    // Get name from the tool arguments
    const mcptoolargs = context.triggerMetadata.mcptoolargs as {
        name?: string;
    };
    const name = mcptoolargs?.name;

    console.info(`Hello ${name}, I am MCP Tool!`);

    return `Hello ${name || 'World'}, I am MCP Tool!`;
}

// Register the hello tool
app.mcpTool('hello', {
    toolName: 'hello',
    description: 'Simple hello world MCP Tool that responses with a hello message.',
    toolProperties:{
        name: arg.string().describe('Required property to identify the caller.').optional()
    },
    handler: mcpToolHello
});
// SaveSnippet function - saves a snippet with a name
export async function saveSnippet(
  _toolArguments: unknown,
  context: InvocationContext
): Promise<string> {
  console.info("Saving snippet");

  // Get snippet name and content from the tool arguments
  const mcptoolargs = context.triggerMetadata.mcptoolargs as {
    snippetname?: string;
    snippet?: string;
  };

  const snippetName = mcptoolargs?.snippetname;
  const snippet = mcptoolargs?.snippet;

  if (!snippetName) {
    return "No snippet name provided";
  }

  if (!snippet) {
    return "No snippet content provided";
  }

  // Save the snippet to blob storage using the output binding
  context.extraOutputs.set(blobOutputBinding, snippet);

  console.info(`Saved snippet: ${snippetName}`);
  return snippet;
}

可以在 Azure Functions TypeScript MCP 服务器 GitHub 存储库中查看完整的项目模板。

在本地验证 MCP 服务器工具后,可以将项目发布到 Azure。

部署到 Azure 云

此项目配置为使用 azd up 命令将此项目部署到 Azure 中 Flex 消耗计划中的新函数应用。 该项目包括一组 Bicep 文件, azd 这些文件用于创建安全部署到遵循最佳做法的 Flex 消耗计划。

  1. 在 Visual Studio Code 中,按 F1 打开命令面板。 搜索并运行命令 Azure Developer CLI (azd): Package, Provison and Deploy (up)。 然后,使用 Azure 帐户登录。

  2. 如果尚未登录,请使用 Azure 帐户进行身份验证。

  3. 出现提示时,请提供以下所需的部署参数:

    参数 Description
    Azure 订阅 要在其中创建资源的订阅。
    Azure 位置 要在其中创建包含新 Azure 资源的资源组的 Azure 区域。 仅显示当前支持 Flex 消耗计划的区域。

    命令成功完成后,你会看到指向所创建资源的链接。

连接到远程 MCP 服务器

MCP 服务器现在在 Azure 中运行。 访问这些工具时,需要在请求中包含系统密钥。 此密钥为访问远程 MCP 服务器的客户端提供一定程度的访问控制。 获取此密钥后,可以将 GitHub Copilot 连接到远程服务器。

  1. 运行此脚本,该脚本使用 azd Azure CLI 输出访问工具所需的 MCP 服务器 URL 和系统密钥(mcp_extension):

    eval $(azd env get-values --output dotenv)
    MCP_EXTENSION_KEY=$(az functionapp keys list --resource-group $AZURE_RESOURCE_GROUP \
        --name $AZURE_FUNCTION_NAME --query "systemKeys.mcp_extension" -o tsv)
    printf "MCP Server URL: %s\n" "https://$SERVICE_API_NAME.chinacloudsites.cn/runtime/webhooks/mcp"
    printf "MCP Server key: %s\n" "$MCP_EXTENSION_KEY"
    
  2. 在 Visual Studio Code 中,按 F1 打开命令面板,搜索并运行打开配置文件的MCP: Open Workspace Folder MCP Configuraton命令mcp.json

  3. mcp.json 配置中,找到前面添加的命名 MCP 服务器,将 url 值更改为远程 MCP 服务器 URL,并添加包含 headers.x-functions-key 复制的 MCP 服务器访问密钥的元素,如以下示例所示:

    {
        "servers": {
            "remote-mcp-function": {
                "type": "http",
                "url": "https://contoso.chinacloudsites.cn/runtime/webhooks/mcp",
                "headers": {
                    "x-functions-key": "A1bC2dE3fH4iJ5kL6mN7oP8qR9sT0u..."
                }
            }
        }
    }
    
  4. 选择打开的服务器名称上方的mcp.json”按钮以重启远程 MCP 服务器,这次使用已部署的应用。

验证部署

现在可以让 GitHub Copilot 像在本地一样使用远程 MCP 工具,但现在代码在 Azure 中安全运行。 为确保一切正常工作,请重复执行您之前使用的相同命令。

清理资源

完成 MCP 服务器和相关资源的操作后,使用此命令从 Azure 中删除函数应用及其相关资源,以避免产生额外费用。

azd down --no-prompt

注释

--no-prompt选项指示azd在无需您确认的情况下删除您的资源组。 此命令不会影响本地代码项目。

后续步骤