一文帶你入門 MCP(模型上下文協定)

Back
Category : Home

什麼是 MCP

Model Context Protocol (MCP) 是一個開放協議,它使 LLM 應用與外部資料來源和工具之間的無縫整合成為可能。無論你是建立 AI 驅動的 IDE、改善 chat 交互,還是建立自訂的 AI 工作流程,MCP 提供了一種標準化的方式,將 LLM 與它們所需的上下文連接起來。

目前,MCP 已經累積了足夠的臨界規模和動能,因此它被視為 2023-2025 年「代理開放標準」之爭的潛在贏家。有人預計,以目前的速度,MCP 將在 7 月超OpenAPI:

MCP 如何運作

通用架構

MCP 的核心是一個 client-server 架構,host 應用程式可以連接到多個伺服器:

  • MCP Hosts: 像 Claude Desktop、IDEs 或 AI 工具這樣的程序,它們希望透過 MCP 存取資源
  • MCP Clients: 維護與伺服器 1:1 連線的協定用戶端
  • MCP Servers: 輕量級程序,透過標準化的 Model Context Protocol 暴露特定功能
  • Local Resources: 你的電腦資源(資料庫、檔案、服務),MCP 伺服器可以安全地存取這些資源
  • Remote Resources: 透過網際網路可用的資源(例如,透過 APIs),MCP 伺服器可以連接到這些資源

MCP 用戶端

MCP客戶端是模型上下文協定(MCP)架構中的核心元件,負責建立和管理與MCP伺服器的連線。它實現了協定的客戶端部分,處理以下功能:

  • 協議版本協商以確保與伺服器的兼容性
  • 能力協商以確定可用功能
  • 訊息傳輸和JSON-RPC通信
  • 工具發現與執行
  • 資源存取和管理
  • 提示系統交互
  • 選用功能如根目錄管理和取樣支持

MCP client 的工作流程如下:

  • MCP client 首先從 MCP server 取得可用的工具清單。
  • 將使用者的查詢連同工具描述透過 function calling 一起傳送給 LLM。
  • LLM 決定是否需要使用工具以及使用哪些工具。
  • 如果需要使用工具,MCP client 會透過 MCP server 執行對應的工具呼叫。
  • 工具呼叫的結果會被傳回 LLM。
  • LLM 基於所有資訊產生自然語言回應。
  • 最後將回應展示給用戶。

MCP 服務端

MCP伺服器是模型上下文協定(MCP)架構中的基礎元件,為客戶端提供工具、資源和功能。它實現了協定的伺服器端,負責:

  • 暴露客戶端可以發現和執行的工具
  • 管理基於URI的資源存取模式
  • 提供提示範本並處理提示請求
  • 支援與客戶端的能力協商
  • 實作伺服器端協定操作
  • 管理並發客戶端連接
  • 提供結構化日誌和通知

連結生命週期

1.初始化

  • Client 發送包含協定版本和能力的 initialize 請求
  • Server 以其協定版本和能力回應
  • Client 發送 initialized 通知作為確認
  • 開始正常訊息交換

2. 訊息交換

初始化後,支援以下模式:

  • 請求-回應:客戶端或伺服器發送請求,另一方回應
  • 通知:任一方發送單向訊息

3. 終止

任一方可以終止連線:

  • 透過 close() 進行乾淨關閉
  • 傳輸斷開
  • 錯誤條件

快速入門

SQLite 實作一個集中範例

  • Claude Desktop 作為我們的 MCP 用戶端
  • 一個 SQLite MCP 伺服器提供安全的資料庫訪問
  • 你的本地 SQLite 資料庫儲存實際數據

SQLite MCP 伺服器和你的本機 SQLite 資料庫之間的通訊完全發生在你的機器上 — 你的 SQLite 資料庫不會暴露在網際網路上。 Model Context Protocol 確保 Claude Desktop 只能透過定義良好的介面執行核准的資料庫作。這為你提供了一種安全的方式,讓 Claude 分析和互動你的本地數據,同時完全控制它可以存取的內容。

Example要安裝之軟件:

  • macOS 或 Windows
  • 安裝最新版本的 Claude Desktop
  • UV 0.4.18 或更高版本( 檢查)uv –version
  • Git( 檢查)git –version
  • SQLite( 檢查)sqlite3 –version
# 使用 winget
winget install --id=astral-sh.uv -e
winget install git.git sqlite.sqlite

# 或直接下载:
# uv: https://docs.astral.sh/uv/
# Git: https://git-scm.com
# SQLite: https://www.sqlite.org/download.html

建立一個簡單Windows的 SQLite 資料庫進行測試:

# 创建一个新的 SQLite 数据库
$sql = @'
CREATE TABLE products (
  id INTEGER PRIMARY KEY,
  name TEXT,
  price REAL
);

INSERT INTO products (name, price) VALUES
  ('Widget', 19.99),
  ('Gadget', 29.99),
  ('Gizmo', 39.99),
  ('Smart Watch', 199.99),
  ('Wireless Earbuds', 89.99),
  ('Portable Charger', 24.99),
  ('Bluetooth Speaker', 79.99),
  ('Phone Stand', 15.99),
  ('Laptop Sleeve', 34.99),
  ('Mini Drone', 299.99),
  ('LED Desk Lamp', 45.99),
  ('Keyboard', 129.99),
  ('Mouse Pad', 12.99),
  ('USB Hub', 49.99),
  ('Webcam', 69.99),
  ('Screen Protector', 9.99),
  ('Travel Adapter', 27.99),
  ('Gaming Headset', 159.99),
  ('Fitness Tracker', 119.99),
  ('Portable SSD', 179.99);
'@

cd ~
& sqlite3 test.db $sql

配置 Claude Desktop:

在文字編輯器中開啟 中的 Claude Desktop 應用程式配置。 %APPDATA%\Claude\claude_desktop_config.json

新增以下配置(將 YOUR_USERNAME 替換為您的實際使用者名稱):

{
  "mcpServers": {
    "sqlite": {
      "command": "uvx",
      "args": [
        "mcp-server-sqlite",
        "--db-path",
        "C:\\Users\\YOUR_USERNAME\\test.db"
      ]
    }
  }
}

這告訴 Claude Desktop:

  • 有一個名為 “sqlite” 的 MCP 伺服器
  • 透過運行 啟動它uvx mcp-server-sqlite
  • 將其連接到你的測試資料庫
  • 儲存文件,並重新啟動 Claude Desktop。

3. 測試

問題:

你能連接到我的 SQLite 資料庫並告訴我有哪些產品及其價格嗎?

Claude Desktop 將會:

  • 連接到 SQLite MCP 伺服器
  • 查詢你的本地資料庫
  • 格式化並展示結果

原理解析

背後發生了什麼事?

當你使用 MCP 與 Claude Desktop 互動時:

  1. 伺服器發現:Claude Desktop 在啟動時連接到你設定的 MCP 伺服器
  2. 協議握手:當你詢問數據時,Claude Desktop:
    • 確定哪個 MCP 伺服器可以提供幫助(在本例中是 sqlite)
    • 透過協議協商能力
    • 從 MCP 伺服器請求資料或作
  3. 交互流程:見下圖
  4. 安全性:
    • MCP 伺服器僅暴露特定、受控的功能
    • MCP 伺服器在你的機器上本地運行,它們訪問的資源不會暴露在互聯網上
    • Claude Desktop 需要使用者確認以進行敏感作

Python 创建一个简单的 MCP 服务器

先決條件
您需要 Python 3.10 或更高版本:

python --version # Should be 3.10 或 higher

透過 homebrew 安裝 uv

brew install uv
uv --version # Should be 0.4.18 或 higher
有關更多信息,請參閱 https://docs.astral.sh/uv/

使用 MCP 專案建立器建立新專案

uvx create-mcp-server --path weather_service
cd weather_service

安裝其他依賴項

uv add httpx python-dotenv

設定環境

OPENWEATHER_API_KEY=your-api-key-here #創造:.env

创建您的服务器

新增基本導入和設定, 在weather_service/src/weather_service/server.py

import os
import json
import logging
from datetime import datetime, timedelta
from collections.abc import Sequence
from functools import lru_cache
from typing import Any

import httpx
import asyncio
from dotenv import load_dotenv
from mcp.server import Server
from mcp.types import (
    Resource,
    Tool,
    TextContent,
    ImageContent,
    EmbeddedResource,
    LoggingLevel
)
from pydantic import AnyUrl

# Load environment variables
load_dotenv()

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("weather-server")

# API configuration
API_KEY = os.getenv("OPENWEATHER_API_KEY")
if not API_KEY:
    raise ValueError("OPENWEATHER_API_KEY environment variable required")

API_BASE_URL = "http://api.openweathermap.org/data/2.5"
DEFAULT_CITY = "London"
CURRENT_WEATHER_ENDPOINT = "weather"
FORECAST_ENDPOINT = "forecast"

# The rest of our server implementation will go here

新增天氣取得功能

# Create reusable params
http_params = {
    "appid": API_KEY,
    "units": "metric"
}

async def fetch_weather(city: str) -> dict[str, Any]:
    async with httpx.AsyncClient() as client:
        response = await client.get(
            f"{API_BASE_URL}/weather",
            params={"q": city, **http_params}
        )
        response.raise_for_status()
        data = response.json()

    return {
        "temperature": data["main"]["temp"],
        "conditions": data["weather"][0]["description"],
        "humidity": data["main"]["humidity"],
        "wind_speed": data["wind"]["speed"],
        "timestamp": datetime.now().isoformat()
    }


app = Server("weather-server")

實作資源處理程序, 將這些與資源相關的處理程序加入到我們的 main 函數中:

app = Server("weather-server")

@app.list_resources()
async def list_resources() -> list[Resource]:
    """List available weather resources."""
    uri = AnyUrl(f"weather://{DEFAULT_CITY}/current")
    return [
        Resource(
            uri=uri,
            name=f"Current weather in {DEFAULT_CITY}",
            mimeType="application/json",
            description="Real-time weather data"
        )
    ]

@app.read_resource()
async def read_resource(uri: AnyUrl) -> str:
    """Read current weather data for a city."""
    city = DEFAULT_CITY
    if str(uri).startswith("weather://") and str(uri).endswith("/current"):
        city = str(uri).split("/")[-2]
    else:
        raise ValueError(f"Unknown resource: {uri}")

    try:
        weather_data = await fetch_weather(city)
        return json.dumps(weather_data, indent=2)
    except httpx.HTTPError as e:
        raise RuntimeError(f"Weather API error: {str(e)}")

實施工具處理程序, 新增以下與工具相關的處理程序:

app = Server("weather-server")

# Resource implementation ...

@app.list_tools()
async def list_tools() -> list[Tool]:
    """List available weather tools."""
    return [
        Tool(
            name="get_forecast",
            description="Get weather forecast for a city",
            inputSchema={
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "City name"
                    },
                    "days": {
                        "type": "number",
                        "description": "Number of days (1-5)",
                        "minimum": 1,
                        "maximum": 5
                    }
                },
                "required": ["city"]
            }
        )
    ]

@app.call_tool()
async def call_tool(name: str, arguments: Any) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
    """Handle tool calls for weather forecasts."""
    if name != "get_forecast":
        raise ValueError(f"Unknown tool: {name}")

    if not isinstance(arguments, dict) or "city" not in arguments:
        raise ValueError("Invalid forecast arguments")

    city = arguments["city"]
    days = min(int(arguments.get("days", 3)), 5)

    try:
        async with httpx.AsyncClient() as client:
            response = await client.get(
                f"{API_BASE_URL}/{FORECAST_ENDPOINT}",
                params={
                    "q": city,
                    "cnt": days * 8,  # API returns 3-hour intervals
                    **http_params,
                }
            )
            response.raise_for_status()
            data = response.json()

        forecasts = []
        for i in range(0, len(data["list"]), 8):
            day_data = data["list"][i]
            forecasts.append({
                "date": day_data["dt_txt"].split()[0],
                "temperature": day_data["main"]["temp"],
                "conditions": day_data["weather"][0]["description"]
            })

        return [
            TextContent(
                type="text",
                text=json.dumps(forecasts, indent=2)
            )
        ]
    except httpx.HTTPError as e:
        logger.error(f"Weather API error: {str(e)}")
        raise RuntimeError(f"Weather API error: {str(e)}")

新增 main 函數, 將此新增至 的結尾:weather_service/src/weather_service/server.py

async def main():
    # Import here to avoid issues with event loops
    from mcp.server.stdio import stdio_server

    async with stdio_server() as (read_stream, write_stream):
        await app.run(
            read_stream,
            write_stream,
            app.create_initialization_options()
        )

在 init.py 中檢查您的切入點, 將此加入到 的結尾:weather_service/src/weather_service/init.py

from . import server
import asyncio

def main():
   """Main entry point for the package."""
   asyncio.run(server.main())

# Optionally expose other important items at package level
__all__ = ['main', 'server']

連接到 Claude Desktop

更新 Claude 配置, 搭:claude_desktop_config.json

{
  "mcpServers": {
    "weather": {
      "command": "uv",
      "args": [
        "--directory",
        "path/to/your/project",
        "run",
        "weather-service"
      ],
      "env": {
        "OPENWEATHER_API_KEY": "your-api-key"
      }
    }
  }
}

重啟 Claude

  1. 徹底退出 Claude
  2. 再次啟動 Claude
  3. 在🔌選單中尋找您的天氣伺服器

測試

問天氣:

What’s the current weather in San Francisco? Can you analyze the conditions and tell me if it’s a good day for outdoor activities?

比較天氣

Can you analyze the forecast for both Tokyo and San Francisco and tell me which city would be better for outdoor photography this week?

參考理解:

Python SDK: https://github.com/modelcontextprotocol/python-sdk?tab=readme-ov-file#documentation

MCP中文文件: https://mcp-docs.cn/clients
————————————————

版權聲明:本文為部落客原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連結和本聲明。

原文連結:https://blog.csdn.net/qq_45066628/article/details/146225428