共用方式為


快速入門:在 Azure Functions 上使用 MCP SDK 建置的主機伺服器

本快速入門會介紹如何在託管以官方 MCP SDK 建立的 Azure Functions 模型內容通訊協定 (MCP) 伺服器。 Flex Consumption 方案主機讓你能利用 Azure Functions 的無伺服器規模、按使用量付費的計費模式,以及內建的安全功能。 它非常適合使用 streamable-http 傳輸的 MCP 伺服器。

本文使用了一個使用 MCP SDK 建立的範例 MCP 伺服器專案。

小提示

Functions 也提供一個 MCP 擴充功能,讓你能透過 Azure Functions 程式設計模型建立 MCP 伺服器。 欲了解更多資訊,請參閱 快速入門:使用 Azure 函式建立自訂遠端 MCP 伺服器

由於新伺服器採用彈性消費方案,採用 按使用量付費 的計費模式,完成此快速啟動會在你的 Azure 帳戶中產生少量費用,甚至更少。

這很重要

雖然所有語言都支援 使用 Custom Handlers 來架設 MCP 伺服器 ,但這個快速入門情境目前僅有 C#、Python 和 TypeScript 的範例。 要完成此快速入門,請在文章頂端選擇其中一種支援語言。

先決條件

  • Python 3.11 或以上版本
  • uv 用於 Python 套件管理

備註

這個範例需要你有權限在你使用的 Azure 訂閱中建立 Microsoft Entra 應用程式

從一個範例專案開始

最簡單的入門方式是複製一個使用 MCP SDK 建置的 MCP 伺服器範例專案:

  1. 在 Visual Studio Code 中,開啟您要建立專案的資料夾或工作區。
  1. 在終端機中執行以下指令以初始化 .NET 範例:

    azd init --template mcp-sdk-functions-hosting-dotnet -e mcpsdkserver-dotnet
    

    此命令會從範本存放庫提取專案檔,並初始化目前資料夾中的專案。 -e 旗標會設定目前環境的名稱。 在 azd 中,該環境會為您的應用程式維護一個唯一的部署內容,而您可以定義多個部署內容。 它也會用在你在 Azure 中建立的資源名稱中。

  1. 在終端機中執行此指令以初始化 TypeScript 範例:

    azd init --template mcp-sdk-functions-hosting-node  -e mcpsdkserver-node
    

    此命令會從範本存放庫提取專案檔,並初始化目前資料夾中的專案。 -e 旗標會設定目前環境的名稱。 在 azd 中,該環境會為您的應用程式維護一個唯一的部署內容,而您可以定義多個部署內容。 它也會用在你在 Azure 中建立的資源名稱中。

  1. 在終端機中執行此指令以初始化 Python 範例:

    azd init --template mcp-sdk-functions-hosting-python -e mcpsdkserver-python
    

    此命令會從範本存放庫提取專案檔,並初始化目前資料夾中的專案。 -e 旗標會設定目前環境的名稱。 在 azd 中,該環境會為您的應用程式維護一個唯一的部署內容,而您可以定義多個部署內容。 它也會用在你在 Azure 中建立的資源名稱中。

程式碼專案範本是用於開發可以存取公共天氣 API 的 MCP 伺服器工具。

在本地執行 MCP 伺服器

Visual Studio Code 與 Azure Functions Core Tools 整合,讓你能在本地開發電腦上執行此專案。

  1. 在編輯器中開啟終端機(Ctrl+Shift+`
  1. 在根目錄中執行 func start 以啟動伺服器。 「終端機」面板會顯示「核心工具」的輸出。
  1. 在根目錄中執行 npm install 以安裝相依,然後執行 npm run build
  2. 要啟動伺服器,請執行 func start
  1. 在根目錄中執行 uv run func start 以建立虛擬環境、安裝相依性,並啟動伺服器。

使用 GitHub Copilot 測試伺服器

要使用 Visual Studio Code 中的 GitHub Copilot 驗證伺服器,請依照以下步驟操作:

  1. 開啟 mcp.json 目錄中的 .vscode 檔案。

  2. 請選取位於設定之上的開始按鈕以啟動伺服器。

  3. 在 Copilot 聊天 視窗中,確認已 選取代理模型 ,選擇 配置工具 圖示,並確認聊天 MCP Server:local-mcp-server 中已啟用該模式。

  4. 在聊天中執行這個提示:

    Return the weather forecast for New York City using #local-mcp-server
    

    Copilot 應該呼叫其中一個天氣工具來協助回答這個問題。 當被要求執行該工具時,請選擇 「允許在此工作區」 ,這樣你就不必一直重新授權這個權限。

在你驗證工具功能後,可以在本地停止伺服器並將專案程式碼部署到 Azure。

部署至 Azure

此專案已設定為使用 azd up 命令,將此專案部署至 Azure 之彈性使用量方案中的新函數應用程式。 專案包含一組 Bicep 檔案,這些檔案由 azd 用於建立符合最佳實務的安全部署。

  1. 登入 Azure:

    azd login
    
  2. 將 Visual Studio Code 設定為預先授權的用戶端應用程式:

    azd env set PRE_AUTHORIZED_CLIENT_IDS aebc6443-996d-45c2-90f0-388ff96faa56
    

    預先授權的應用程式可以在不需更多同意提示的情況下,驗證並存取你的 MCP 伺服器。

  3. 在 Visual Studio Code 中,按 F1 以開啟命令選擇區。 搜尋並執行命令 Azure Developer CLI (azd): Package, Provision and Deploy (up)。 然後,使用您的 Azure 帳戶登入。

  4. 出現提示時,請提供這些必要的部署參數:

    參數 Description
    Azure 訂用帳戶 建立您資源所在位置的訂用帳戶。
    Azure 位置 要在其中建立包含新 Azure 資源之資源群組所在的 Azure 區域。 只會顯示目前支援彈性使用量方案的區域。

    指令成功完成後,你會看到連結到你建立的資源以及已部署 MCP 伺服器的端點。 請記住您的函數應用程式名稱,會在下一節用到。

    小提示

    如果執行 azd up 指令時發生錯誤,就重新執行該指令。 你可以重複執行 azd up,因為它會跳過已經存在的資源的創建。 部署更新時,您也可以再次呼叫 azd up

連接到遠端的 MCP 伺服器

您的 MCP 伺服器現在正在 Azure 中執行。 要將 GitHub Copilot 連接到遠端伺服器,請在工作區設定中設定。

  1. mcp.json檔案中,對設定選擇local-mcp-server,然後對設定選擇remote-mcp-server,以切換到遠端伺服器。

  2. 當被要求輸入 功能應用程式的網域時,請輸入你在前一節提到的功能應用程式名稱。 當被要求驗證 Microsoft 時,選擇 允許 ,然後選擇你的 Azure 帳號。

  3. 透過以下問題驗證遠端伺服器:

    Return the weather forecast for Seattle using #remote-mcp-server.
    

    副駕駛呼叫其中一個天氣工具來回答這個問題。

小提示

你可以選擇「更多...>」來查看伺服器的輸出展示輸出。 輸出會提供關於可能連線失敗的有用資訊。 你也可以選擇齒輪圖示,將日誌等級改為 追蹤 ,以獲得客戶端(Visual Studio Code)與伺服器之間互動的更多細節。

檢閱程式碼 (選用)

你可以查看定義 MCP 伺服器的程式碼:

MCP 伺服器程式碼定義於專案根目錄中。 伺服器使用官方 C# MCP SDK 來定義以下與天氣相關的工具:

using ModelContextProtocol;
using ModelContextProtocol.Server;
using System.ComponentModel;
using System.Globalization;
using System.Text.Json;

namespace QuickstartWeatherServer.Tools;

[McpServerToolType]
public sealed class WeatherTools
{
    [McpServerTool, Description("Get weather alerts for a US state.")]
    public static async Task<string> GetAlerts(
        HttpClient client,
        [Description("The US state to get alerts for. Use the 2 letter abbreviation for the state (e.g. NY).")] string state)
    {
        using var jsonDocument = await client.ReadJsonDocumentAsync($"/alerts/active/area/{state}");
        var jsonElement = jsonDocument.RootElement;
        var alerts = jsonElement.GetProperty("features").EnumerateArray();

        if (!alerts.Any())
        {
            return "No active alerts for this state.";
        }

        return string.Join("\n--\n", alerts.Select(alert =>
        {
            JsonElement properties = alert.GetProperty("properties");
            return $"""
                    Event: {properties.GetProperty("event").GetString()}
                    Area: {properties.GetProperty("areaDesc").GetString()}
                    Severity: {properties.GetProperty("severity").GetString()}
                    Description: {properties.GetProperty("description").GetString()}
                    Instruction: {properties.GetProperty("instruction").GetString()}
                    """;
        }));
    }

    [McpServerTool, Description("Get weather forecast for a location.")]
    public static async Task<string> GetForecast(
        HttpClient client,
        [Description("Latitude of the location.")] double latitude,
        [Description("Longitude of the location.")] double longitude)
    {
        var pointUrl = string.Create(CultureInfo.InvariantCulture, $"/points/{latitude},{longitude}");
        using var jsonDocument = await client.ReadJsonDocumentAsync(pointUrl);
        var forecastUrl = jsonDocument.RootElement.GetProperty("properties").GetProperty("forecast").GetString()
            ?? throw new Exception($"No forecast URL provided by {client.BaseAddress}points/{latitude},{longitude}");

        using var forecastDocument = await client.ReadJsonDocumentAsync(forecastUrl);
        var periods = forecastDocument.RootElement.GetProperty("properties").GetProperty("periods").EnumerateArray();

        return string.Join("\n---\n", periods.Select(period => $"""
                {period.GetProperty("name").GetString()}
                Temperature: {period.GetProperty("temperature").GetInt32()}°F
                Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()}
                Forecast: {period.GetProperty("detailedForecast").GetString()}
                """));
    }
}

你可以在 Azure Functions .NET MCP SDK 托管 的 GitHub 倉庫中查看完整的專案範本。

MCP 伺服器程式碼定義於檔案中 server.py 。 伺服器使用官方 Python MCP SDK 來定義與天氣相關的工具。 以下是該 get_forecast 工具的定義:

import os
import sys
import warnings
import logging
from typing import Any
from pathlib import Path

import httpx
from azure.identity import OnBehalfOfCredential, ManagedIdentityCredential
from mcp.server.fastmcp import FastMCP
from fastmcp.server.dependencies import get_http_request
from starlette.requests import Request
from starlette.responses import HTMLResponse

# Initialize FastMCP server
mcp = FastMCP("weather", stateless_http=True)

# Constants
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
    """Get weather forecast for a location.

    Args:
        latitude: Latitude of the location
        longitude: Longitude of the location
    """
    # First get the forecast grid endpoint
    points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
    points_data = await make_nws_request(points_url)

    if not points_data:
        return "Unable to fetch forecast data for this location."

    # Get the forecast URL from the points response
    forecast_url = points_data["properties"]["forecast"]
    forecast_data = await make_nws_request(forecast_url)

    if not forecast_data:
        return "Unable to fetch detailed forecast."

    # Format the periods into a readable forecast
    periods = forecast_data["properties"]["periods"]
    forecasts = []
    for period in periods[:5]:  # Only show next 5 periods
        forecast = f"""
{period['name']}:
Temperature: {period['temperature']}°{period['temperatureUnit']}
Wind: {period['windSpeed']} {period['windDirection']}
Forecast: {period['detailedForecast']}
"""
        forecasts.append(forecast)

    return "\n---\n".join(forecasts)

你可以在 Azure Functions Python MCP SDK 托管 的 GitHub 倉庫中查看完整的專案範本。

MCP 伺服器程式碼定義於資料夾中 src 。 伺服器使用官方的 Node.js MCP SDK 來定義與天氣相關的工具。 以下是該 get-forecast 工具的定義:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { ManagedIdentityCredential, OnBehalfOfCredential } from '@azure/identity';

const NWS_API_BASE = "https://api.weather.gov";
const USER_AGENT = "weather-app/1.0";

// Function to create a new server instance for each request (stateless)
export const createServer = () => {
  const server = new McpServer({
    name: "weather",
    version: "1.0.0",
  });
  server.registerTool(
    "get-forecast",
    {
      title: "Get Weather Forecast",
      description: "Get weather forecast for a location",
      inputSchema: {
        latitude: z.number().min(-90).max(90).describe("Latitude of the location"),
        longitude: z
          .number()
          .min(-180)
          .max(180)
          .describe("Longitude of the location"),
      },
      outputSchema: z.object({
        forecast: z.string(),
      }),
    },
    async ({ latitude, longitude }) => {
      // Get grid point data
      const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
      const pointsData = await makeNWSRequest<PointsResponse>(pointsUrl);

      if (!pointsData) {
        const output = { forecast: `Failed to retrieve grid point data for coordinates: ${latitude}, ${longitude}. This location may not be supported by the NWS API (only US locations are supported).` };
        return {
          content: [{ type: "text", text: JSON.stringify(output) }],
          structuredContent: output,
        };
      }

      const forecastUrl = pointsData.properties?.forecast;
      if (!forecastUrl) {
        const output = { forecast: "Failed to get forecast URL from grid point data" };
        return {
          content: [{ type: "text", text: JSON.stringify(output) }],
          structuredContent: output,
        };
      }

      // Get forecast data
      const forecastData = await makeNWSRequest<ForecastResponse>(forecastUrl);
      if (!forecastData) {
        const output = { forecast: "Failed to retrieve forecast data" };
        return {
          content: [{ type: "text", text: JSON.stringify(output) }],
          structuredContent: output,
        };
      }

      const periods = forecastData.properties?.periods || [];
      if (periods.length === 0) {
        const output = { forecast: "No forecast periods available" };
        return {
          content: [{ type: "text", text: JSON.stringify(output) }],
          structuredContent: output,
        };
      }

      // Format forecast periods
      const formattedForecast = periods.map((period: ForecastPeriod) =>
        [
          `${period.name || "Unknown"}:`,
          `Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,
          `Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,
          `${period.shortForecast || "No forecast available"}`,
          "---",
        ].join("\n"),
      );

      const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`;
      const output = { forecast: forecastText };

      return {
        content: [{ type: "text", text: forecastText }],
        structuredContent: output,
      };
    },
  );
  return server;
}

你可以在 Azure Functions TypeScript MCP SDK 的 GitHub 倉庫中查看完整的專案範本。

清理資源

當您完成 MCP 伺服器和相關資源的使用時,請使用此命令從 Azure 刪除函式應用程式及其相關資源,以避免產生進一步的成本:

azd down