MCP 入门指南:让 AI 连接真实世界

MCP 入门指南:让 AI 连接真实世界

本文将带你从零开始理解 MCP(Model Context Protocol),并通过 TypeScript 和 Python 两种语言实现一个完整的 MCP Server。

大语言模型很强大,但它们被困在"沙盒"里------无法读取你的本地文件、无法查询你的数据库、无法调用你的内部 API。每个 AI 应用都在重复造轮子:写 Function Calling 的适配代码、处理工具调用的序列化、管理上下文注入...

MCP(Model Context Protocol) 就是为了解决这个问题而生的。它是 Anthropic 在 2024 年底开源的协议标准,目标是成为 AI 模型与外部世界交互的"USB 接口"------一次实现,处处可用。

什么是 MCP?

MCP 是一个开放协议,定义了 AI 应用(如 Claude Desktop、IDE 插件)与外部工具/数据源之间的通信标准。

核心价值:

传统方式 MCP 方式
每个 AI 应用自己实现工具调用 统一协议,一次实现多处复用
工具代码与应用紧耦合 Server 独立部署,即插即用
安全边界模糊 明确的权限模型和沙盒隔离

简单来说:你写一个 MCP Server,就能让所有支持 MCP 的 AI 应用使用你的工具。

核心概念

MCP 架构包含三个核心角色:

复制代码
┌─────────────────────────────────────────────────────────┐
│                      Host                               │
│  (Claude Desktop / IDE / 自定义 AI 应用)                 │
│                                                         │
│   ┌─────────┐   ┌─────────┐   ┌─────────┐              │
│   │ Client  │   │ Client  │   │ Client  │              │
│   └────┬────┘   └────┬────┘   └────┬────┘              │
└────────┼─────────────┼─────────────┼────────────────────┘
         │             │             │
    ┌────▼────┐   ┌────▼────┐   ┌────▼────┐
    │ Server  │   │ Server  │   │ Server  │
    │ (文件)   │   │ (数据库) │   │ (API)   │
    └─────────┘   └─────────┘   └─────────┘
  • Host:运行 AI 模型的宿主应用,负责管理多个 Client 连接
  • Client:由 Host 创建,与 Server 保持 1:1 连接,处理协议通信
  • Server:独立进程,暴露工具(Tools)、资源(Resources)和提示词(Prompts)

通信基于 JSON-RPC 2.0,支持两种传输方式:

  • stdio:通过标准输入/输出通信,适合本地进程
  • HTTP + SSE:适合远程服务,支持服务端推送

MCP 能做什么

MCP Server 可以向 AI 暴露三种能力:

Tools(工具)

让 AI 执行操作。工具由 AI 模型主动调用,类似 Function Calling。

json 复制代码
{
  "name": "search_files",
  "description": "在指定目录中搜索文件",
  "inputSchema": {
    "type": "object",
    "properties": {
      "path": { "type": "string" },
      "pattern": { "type": "string" }
    }
  }
}

典型场景:执行 shell 命令、查询数据库、调用 API、发送消息

Resources(资源)

让 AI 读取数据。资源是只读的,由应用程序控制何时加载。

json 复制代码
{
  "uri": "file:///project/config.json",
  "name": "项目配置",
  "mimeType": "application/json"
}

典型场景:读取文件内容、获取数据库 schema、加载配置信息

Prompts(提示词模板)

预定义的提示词,可带参数,方便复用。

json 复制代码
{
  "name": "code_review",
  "description": "代码审查模板",
  "arguments": [
    { "name": "language", "required": true }
  ]
}

典型场景:代码审查模板、SQL 生成模板、文档生成模板

快速上手

环境准备

TypeScript 版本:

bash 复制代码
# 需要 Node.js 18+
node --version

# 创建项目
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx

# 初始化 TypeScript
npx tsc --init

Python 版本:

bash 复制代码
# 需要 Python 3.10+
python --version

# 创建项目
mkdir my-mcp-server && cd my-mcp-server
python -m venv venv
source venv/bin/activate  # Windows: venv\Scripts\activate

# 安装依赖
pip install mcp

TypeScript 实现

创建 src/index.ts,实现一个简单的天气查询工具:

typescript 复制代码
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// 创建 MCP Server 实例
const server = new McpServer({
  name: "weather-server",
  version: "1.0.0",
});

// 模拟天气数据
const weatherData: Record<string, { temp: number; condition: string }> = {
  beijing: { temp: 22, condition: "晴" },
  shanghai: { temp: 25, condition: "多云" },
  shenzhen: { temp: 28, condition: "阵雨" },
};

// 注册工具
server.tool(
  "get_weather",
  "获取指定城市的天气信息",
  {
    city: z.string().describe("城市名称(拼音)"),
  },
  async ({ city }) => {
    const weather = weatherData[city.toLowerCase()];

    if (!weather) {
      return {
        content: [
          {
            type: "text",
            text: `未找到城市 "${city}" 的天气数据`,
          },
        ],
      };
    }

    return {
      content: [
        {
          type: "text",
          text: `${city} 天气:${weather.condition},温度 ${weather.temp}°C`,
        },
      ],
    };
  }
);

// 启动服务器
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Weather MCP Server 已启动");
}

main().catch(console.error);

配置 package.json

json 复制代码
{
  "name": "weather-mcp-server",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsx src/index.ts"
  }
}

Python 实现

创建 server.py,实现相同的天气查询工具:

python 复制代码
from mcp.server.fastmcp import FastMCP

# 创建 MCP Server 实例
mcp = FastMCP("weather-server")

# 模拟天气数据
weather_data = {
    "beijing": {"temp": 22, "condition": "晴"},
    "shanghai": {"temp": 25, "condition": "多云"},
    "shenzhen": {"temp": 28, "condition": "阵雨"},
}


@mcp.tool()
def get_weather(city: str) -> str:
    """获取指定城市的天气信息

    Args:
        city: 城市名称(拼音)
    """
    weather = weather_data.get(city.lower())

    if not weather:
        return f'未找到城市 "{city}" 的天气数据'

    return f"{city} 天气:{weather['condition']},温度 {weather['temp']}°C"


# 也可以添加资源
@mcp.resource("weather://cities")
def list_cities() -> str:
    """返回支持的城市列表"""
    return "\n".join(weather_data.keys())


if __name__ == "__main__":
    mcp.run()

运行方式:

bash 复制代码
# 直接运行(stdio 模式)
python server.py

# 或使用 mcp 命令行工具测试
mcp dev server.py

在 Claude Desktop 中测试

编辑 Claude Desktop 配置文件:

  • macOS : ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows : %APPDATA%\Claude\claude_desktop_config.json

TypeScript 版本配置:

json 复制代码
{
  "mcpServers": {
    "weather": {
      "command": "node",
      "args": ["/absolute/path/to/dist/index.js"]
    }
  }
}

Python 版本配置:

json 复制代码
{
  "mcpServers": {
    "weather": {
      "command": "python",
      "args": ["/absolute/path/to/server.py"]
    }
  }
}

重启 Claude Desktop 后,在对话中输入:

"北京今天天气怎么样?"

Claude 会自动调用 get_weather 工具并返回结果。

实战案例:文件搜索工具

下面实现一个更实用的案例------文件搜索工具,支持在指定目录中按名称或内容搜索文件。

TypeScript 版本

typescript 复制代码
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import * as fs from "fs/promises";
import * as path from "path";

const server = new McpServer({
  name: "file-search-server",
  version: "1.0.0",
});

// 递归搜索文件
async function searchFiles(
  dir: string,
  pattern: string,
  results: string[] = []
): Promise<string[]> {
  const entries = await fs.readdir(dir, { withFileTypes: true });

  for (const entry of entries) {
    const fullPath = path.join(dir, entry.name);

    if (entry.isDirectory()) {
      // 跳过 node_modules 和隐藏目录
      if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
        await searchFiles(fullPath, pattern, results);
      }
    } else if (entry.name.toLowerCase().includes(pattern.toLowerCase())) {
      results.push(fullPath);
    }
  }

  return results;
}

// 搜索文件名
server.tool(
  "search_by_name",
  "按文件名搜索文件",
  {
    directory: z.string().describe("搜索的根目录"),
    pattern: z.string().describe("文件名匹配模式"),
  },
  async ({ directory, pattern }) => {
    try {
      const results = await searchFiles(directory, pattern);
      return {
        content: [
          {
            type: "text",
            text:
              results.length > 0
                ? `找到 ${results.length} 个文件:\n${results.join("\n")}`
                : "未找到匹配的文件",
          },
        ],
      };
    } catch (error) {
      return {
        content: [{ type: "text", text: `搜索失败: ${error}` }],
        isError: true,
      };
    }
  }
);

// 搜索文件内容
server.tool(
  "search_by_content",
  "按内容搜索文件",
  {
    directory: z.string().describe("搜索的根目录"),
    keyword: z.string().describe("要搜索的关键词"),
    extension: z.string().optional().describe("文件扩展名过滤,如 .ts"),
  },
  async ({ directory, keyword, extension }) => {
    const results: { file: string; line: number; content: string }[] = [];

    async function search(dir: string) {
      const entries = await fs.readdir(dir, { withFileTypes: true });

      for (const entry of entries) {
        const fullPath = path.join(dir, entry.name);

        if (entry.isDirectory()) {
          if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
            await search(fullPath);
          }
        } else if (!extension || entry.name.endsWith(extension)) {
          try {
            const content = await fs.readFile(fullPath, "utf-8");
            const lines = content.split("\n");

            lines.forEach((line, index) => {
              if (line.includes(keyword)) {
                results.push({
                  file: fullPath,
                  line: index + 1,
                  content: line.trim().slice(0, 100),
                });
              }
            });
          } catch {
            // 跳过无法读取的文件
          }
        }
      }
    }

    await search(directory);

    return {
      content: [
        {
          type: "text",
          text:
            results.length > 0
              ? results
                  .slice(0, 20)
                  .map((r) => `${r.file}:${r.line}\n  ${r.content}`)
                  .join("\n\n")
              : "未找到包含该关键词的文件",
        },
      ],
    };
  }
);

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
}

main().catch(console.error);

Python 版本

python 复制代码
import os
from pathlib import Path
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("file-search-server")


@mcp.tool()
def search_by_name(directory: str, pattern: str) -> str:
    """按文件名搜索文件

    Args:
        directory: 搜索的根目录
        pattern: 文件名匹配模式
    """
    results = []
    root = Path(directory)

    if not root.exists():
        return f"目录不存在: {directory}"

    for path in root.rglob("*"):
        # 跳过隐藏目录和 node_modules
        if any(part.startswith(".") or part == "node_modules"
               for part in path.parts):
            continue

        if path.is_file() and pattern.lower() in path.name.lower():
            results.append(str(path))

    if results:
        return f"找到 {len(results)} 个文件:\n" + "\n".join(results[:50])
    return "未找到匹配的文件"


@mcp.tool()
def search_by_content(
    directory: str,
    keyword: str,
    extension: str | None = None
) -> str:
    """按内容搜索文件

    Args:
        directory: 搜索的根目录
        keyword: 要搜索的关键词
        extension: 文件扩展名过滤,如 .py
    """
    results = []
    root = Path(directory)

    if not root.exists():
        return f"目录不存在: {directory}"

    pattern = f"*{extension}" if extension else "*"

    for path in root.rglob(pattern):
        if any(part.startswith(".") or part == "node_modules"
               for part in path.parts):
            continue

        if not path.is_file():
            continue

        try:
            content = path.read_text(encoding="utf-8")
            for i, line in enumerate(content.split("\n"), 1):
                if keyword in line:
                    results.append(f"{path}:{i}\n  {line.strip()[:100]}")
        except (UnicodeDecodeError, PermissionError):
            continue

    if results:
        return "\n\n".join(results[:20])
    return "未找到包含该关键词的文件"


if __name__ == "__main__":
    mcp.run()

最佳实践

安全注意事项

typescript 复制代码
// 1. 路径验证:防止路径遍历攻击
import * as path from "path";

function validatePath(userPath: string, allowedRoot: string): string {
  const resolved = path.resolve(allowedRoot, userPath);
  if (!resolved.startsWith(allowedRoot)) {
    throw new Error("路径越界");
  }
  return resolved;
}

// 2. 输入验证:使用 zod 定义严格的 schema
const schema = {
  command: z.enum(["read", "list"]),  // 限制允许的命令
  path: z.string().max(500),           // 限制长度
};

// 3. 权限最小化:只暴露必要的工具

错误处理

typescript 复制代码
server.tool("safe_operation", "安全操作示例", {}, async () => {
  try {
    const result = await riskyOperation();
    return {
      content: [{ type: "text", text: result }],
    };
  } catch (error) {
    // 返回友好的错误信息,避免泄露内部细节
    return {
      content: [
        {
          type: "text",
          text: `操作失败: ${error instanceof Error ? error.message : "未知错误"}`,
        },
      ],
      isError: true,  // 标记为错误响应
    };
  }
});

调试技巧

bash 复制代码
# 1. 使用 MCP Inspector 调试(推荐)
npx @modelcontextprotocol/inspector node dist/index.js

# 2. 日志输出到 stderr(stdout 用于协议通信)
console.error("Debug:", someValue);

# 3. Python 使用 mcp dev 命令
mcp dev server.py

性能优化

  • 对于耗时操作,返回进度信息
  • 大量数据分页返回,避免一次性返回过多内容
  • 缓存频繁访问的资源

总结与资源

本文要点

  1. MCP 是什么:AI 与外部工具交互的标准化协议
  2. 核心架构:Host → Client → Server 的分层设计
  3. 三大能力:Tools(执行)、Resources(读取)、Prompts(模板)
  4. 快速开发 :TypeScript 用 @modelcontextprotocol/sdk,Python 用 FastMCP

下一步

  • 尝试编写自己的 MCP Server
  • 探索社区已有的 Server 实现
  • 在实际项目中集成 MCP 能力

资源链接

资源 链接
MCP 官方文档 https://modelcontextprotocol.io
TypeScript SDK https://github.com/modelcontextprotocol/typescript-sdk
Python SDK https://github.com/modelcontextprotocol/python-sdk
官方 Server 示例 https://github.com/modelcontextprotocol/servers
MCP Inspector https://github.com/modelcontextprotocol/inspector

社区优秀 Server 推荐

  • filesystem: 文件系统操作
  • sqlite: SQLite 数据库查询
  • github: GitHub API 集成
  • slack: Slack 消息发送
  • puppeteer: 浏览器自动化

如有问题或建议,欢迎在评论区交流!

相关推荐
NAGNIP3 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab4 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab4 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP8 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年8 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼8 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS9 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区10 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈10 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang10 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx