Skip to main content

手把手教你写一个 MCP Server:让 Claude 直接查询本地 CSV 数据

学会用 MCP Server 让 Claude 访问本地 CSV 数据。本文包含完整代码示例、架构设计、性能优化和生产部署指南,一步步搭建企业级的数据集成方案。

Dev Guides MCP ServerPython 实战技术文档Est. read15 分钟
2026.04.17 published
手把手教你写一个 MCP Server:让 Claude 直接查询本地 CSV 数据

手把手教你写一个 MCP Server:让 Claude 直接查询本地 CSV 数据

预计阅读时间:15 分钟


引言:为什么要学 MCP?

MCP(Model Context Protocol)是 Anthropic 推出的协议,让 Claude 可以访问本地文件、数据库和自定义工具。本文教你搭建一个 MCP Server,让 Claude 直接查询 CSV 数据——只需要 100 行 Python 代码,就能在 5 秒内回答复杂的数据分析问题。

本文会涵盖:

  1. MCP 是什么与为什么重要(快速入门)
  2. 3 个企业场景:MCP 如何解决实际问题
  3. 动手实战:用 100 行 Python 搭建 MCP Server
  4. 集成到 Claude 工具与生产部署

一、什么是 MCP?为什么你需要了解它

MCP 全称 Model Context Protocol(模型上下文协议),是一个标准化协议,让 AI 模型可以安全地与外部系统交互。

类比: 如果 Claude 是一个助手,MCP 就是让它能:

  • 拿起电话(TCP/HTTP 协议)
  • 查阅办公室的文件柜(本地数据库)
  • 使用办公工具(API、函数)
  • 完全按照你的规则行动(权限控制)

MCP 的架构

┌─────────────┐
│   Claude    │  <-- AI 助手
└──────┬──────┘
       │ 问题:"我们有哪些产品?"


┌─────────────────────────┐
│   MCP 协议              │
│   (标准通信方式)         │
└──────┬──────────────────┘
       │ 调用工具:list_products()


┌─────────────────────────┐
│   MCP Server            │ <-- 你的服务器
│  (提供 Tool 列表)        │
└──────┬──────────────────┘
       │ 返回:全部产品列表


┌─────────────────────────┐
│   本地数据源            │
│  (CSV / DB / API)       │
└─────────────────────────┘
┌─────────────┐
│   Claude    │  <-- AI 助手
└──────┬──────┘
       │ 问题:"我们有哪些产品?"


┌─────────────────────────┐
│   MCP 协议              │
│   (标准通信方式)         │
└──────┬──────────────────┘
       │ 调用工具:list_products()


┌─────────────────────────┐
│   MCP Server            │ <-- 你的服务器
│  (提供 Tool 列表)        │
└──────┬──────────────────┘
       │ 返回:全部产品列表


┌─────────────────────────┐
│   本地数据源            │
│  (CSV / DB / API)       │
└─────────────────────────┘

MCP vs Tool Use:有什么区别?

特性 Tool Use(旧方式) MCP(新方式)
集成方式 在 prompt 中写死工具定义 动态注册工具,标准化
复杂度 简单 API 可以 本地数据库/文件系统
可维护性 改一个工具要改 prompt 更新工具不需要改 prompt
生产级 适合 demo 适合生产环境
安全性 需要手动验证 内置权限管理

简单说:Tool Use 像在便签上写工具清单,MCP 像有一个正式的工具库和权限管理。

MCP 的 5 大工具类型

工具类型 场景示例 用途
Sampling 让 Claude 思考 深度分析问题
Tools 列出可用操作 查询、修改数据
Resources 提供文本/二进制资源 文件访问、内容检索
Prompts 提供提示词模板 自定义 AI 行为
Root Listing 系统能力声明 告诉 Claude 我能做什么

我们今天要实现的是 Tools 类型。


二、3 个企业场景:MCP 如何解决实际问题

场景1:智能数据分析

传统方式:

Sales Manager → 打开 Excel → CTRL+F 查找 → 手动汇总 → 花 30 分钟
Sales Manager → 打开 Excel → CTRL+F 查找 → 手动汇总 → 花 30 分钟

用 MCP + Claude:

Sales Manager: "过去一周销售最好的产品是什么?"
Claude 直接查询数据库 → 一秒得到答案
Sales Manager: "过去一周销售最好的产品是什么?"
Claude 直接查询数据库 → 一秒得到答案

场景2:知识库查询

员工问「如何申请年假?」,传统方式要翻 10 个 Wiki 页面还不确定。用 MCP + Claude,直接引用具体政策段落,一遍过。

场景3:跨系统数据收集

老板问「今年哪个销售团队最赚钱?」,Claude 的 MCP 同时查 CRM(销售额)、财务系统(成本)、库存系统(周转率),综合分析出答案。


三、动手实战:用 100 行 Python 搭建 MCP Server

开始前:5 分钟快速检查清单

# 检查 Python 版本(需要 3.11+)
python --version

# 安装依赖
pip install mcp anthropic pydantic python-dotenv
# 检查 Python 版本(需要 3.11+)
python --version

# 安装依赖
pip install mcp anthropic pydantic python-dotenv

第一步:准备数据(products.csv)

product_id,product_name,category,price,stock,created_date
P001,Claude Opus 4.6,AI Model,2.99,5000,2026-03-15
P002,Claude Sonnet 4.6,AI Model,1.99,8000,2026-03-15
P003,Claude Haiku 4.5,AI Model,0.80,10000,2026-02-20
P004,API Gateway Pro,Infrastructure,29.99,100,2026-04-01
P005,ClaudeAPI Console,Tools,0.00,999999,2026-01-01
P006,MCP Integration Kit,Tools,99.99,50,2026-04-10
P007,Premium Support,Service,199.99,200,2026-03-01
P008,Batch API Access,Feature,49.99,300,2026-04-01
product_id,product_name,category,price,stock,created_date
P001,Claude Opus 4.6,AI Model,2.99,5000,2026-03-15
P002,Claude Sonnet 4.6,AI Model,1.99,8000,2026-03-15
P003,Claude Haiku 4.5,AI Model,0.80,10000,2026-02-20
P004,API Gateway Pro,Infrastructure,29.99,100,2026-04-01
P005,ClaudeAPI Console,Tools,0.00,999999,2026-01-01
P006,MCP Integration Kit,Tools,99.99,50,2026-04-10
P007,Premium Support,Service,199.99,200,2026-03-01
P008,Batch API Access,Feature,49.99,300,2026-04-01

第二步:定义工具(Tool Definition)

在 MCP Server 中,我们需要告诉 Claude 提供哪些工具:

# 工具1:列出所有产品
{
    "name": "list_products",
    "description": "List all products in the database",
    "inputSchema": {"type": "object", "properties": {}, "required": []}
}

# 工具2:按 ID 查询
{
    "name": "query_product",
    "description": "Get details of a specific product by ID",
    "inputSchema": {
        "type": "object",
        "properties": {
            "product_id": {"type": "string", "description": "Product ID (e.g., P001)"}
        },
        "required": ["product_id"]
    }
}

# 工具3:按价格范围搜索
{
    "name": "search_price_range",
    "inputSchema": {
        "type": "object",
        "properties": {
            "min_price": {"type": "number"},
            "max_price": {"type": "number"}
        },
        "required": ["min_price", "max_price"]
    }
}

# 工具4:按类别筛选 / 工具5:获取统计信息(同理)
# 工具1:列出所有产品
{
    "name": "list_products",
    "description": "List all products in the database",
    "inputSchema": {"type": "object", "properties": {}, "required": []}
}

# 工具2:按 ID 查询
{
    "name": "query_product",
    "description": "Get details of a specific product by ID",
    "inputSchema": {
        "type": "object",
        "properties": {
            "product_id": {"type": "string", "description": "Product ID (e.g., P001)"}
        },
        "required": ["product_id"]
    }
}

# 工具3:按价格范围搜索
{
    "name": "search_price_range",
    "inputSchema": {
        "type": "object",
        "properties": {
            "min_price": {"type": "number"},
            "max_price": {"type": "number"}
        },
        "required": ["min_price", "max_price"]
    }
}

# 工具4:按类别筛选 / 工具5:获取统计信息(同理)

核心要点: inputSchema 定义工具接受什么参数,Claude 会根据用户问题自动选择合适的工具。

第三步:实现工具处理函数

import csv, json
from pathlib import Path

products_data = []

def load_csv():
    global products_data
    with open("products.csv", "r", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        products_data = [row for row in reader]

def list_all_products():
    result = "All Products:\n"
    for idx, p in enumerate(products_data, 1):
        result += f"{idx}. {p['product_name']} (ID: {p['product_id']})\n"
    return result

def query_product_by_id(product_id):
    for p in products_data:
        if p['product_id'].lower() == product_id.lower():
            return json.dumps(p, indent=2)
    return f"Product {product_id} not found"

def search_by_price_range(min_price, max_price):
    results = [p for p in products_data
               if min_price <= float(p['price']) <= max_price]
    return f"Found {len(results)} products:\n" + \
           "\n".join(f"  • {p['product_name']} - CNY{p['price']}" for p in results)

def filter_by_category(category):
    results = [p for p in products_data
               if p['category'].lower() == category.lower()]
    return f"Found {len(results)} products in {category}"

def get_stats():
    total = len(products_data)
    categories = set(p['category'] for p in products_data)
    avg_price = sum(float(p['price']) for p in products_data) / total
    return f"Total: {total} | Categories: {len(categories)} | Avg Price: CNY{avg_price:.2f}"
import csv, json
from pathlib import Path

products_data = []

def load_csv():
    global products_data
    with open("products.csv", "r", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        products_data = [row for row in reader]

def list_all_products():
    result = "All Products:\n"
    for idx, p in enumerate(products_data, 1):
        result += f"{idx}. {p['product_name']} (ID: {p['product_id']})\n"
    return result

def query_product_by_id(product_id):
    for p in products_data:
        if p['product_id'].lower() == product_id.lower():
            return json.dumps(p, indent=2)
    return f"Product {product_id} not found"

def search_by_price_range(min_price, max_price):
    results = [p for p in products_data
               if min_price <= float(p['price']) <= max_price]
    return f"Found {len(results)} products:\n" + \
           "\n".join(f"  • {p['product_name']} - CNY{p['price']}" for p in results)

def filter_by_category(category):
    results = [p for p in products_data
               if p['category'].lower() == category.lower()]
    return f"Found {len(results)} products in {category}"

def get_stats():
    total = len(products_data)
    categories = set(p['category'] for p in products_data)
    avg_price = sum(float(p['price']) for p in products_data) / total
    return f"Total: {total} | Categories: {len(categories)} | Avg Price: CNY{avg_price:.2f}"

第四步:创建 MCP Server

from mcp.server import Server
from mcp.types import Tool
import mcp.types as types

server = Server("mcp-csv-server")

@server.list_tools()
async def handle_list_tools() -> list[Tool]:
    return [
        Tool(name="list_products", description="List all products",
             inputSchema={"type": "object", "properties": {}, "required": []}),
        # ... 其他 4 个工具
    ]

@server.call_tool()
async def handle_call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    if name == "list_products":
        result = list_all_products()
    elif name == "query_product":
        result = query_product_by_id(arguments["product_id"])
    elif name == "search_price_range":
        result = search_by_price_range(arguments["min_price"], arguments["max_price"])
    elif name == "filter_category":
        result = filter_by_category(arguments["category"])
    elif name == "get_stats":
        result = get_stats()
    else:
        result = f"Unknown tool: {name}"
    return [types.TextContent(type="text", text=result)]

def main():
    load_csv()
    server.run_stdio()

if __name__ == "__main__":
    main()
from mcp.server import Server
from mcp.types import Tool
import mcp.types as types

server = Server("mcp-csv-server")

@server.list_tools()
async def handle_list_tools() -> list[Tool]:
    return [
        Tool(name="list_products", description="List all products",
             inputSchema={"type": "object", "properties": {}, "required": []}),
        # ... 其他 4 个工具
    ]

@server.call_tool()
async def handle_call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    if name == "list_products":
        result = list_all_products()
    elif name == "query_product":
        result = query_product_by_id(arguments["product_id"])
    elif name == "search_price_range":
        result = search_by_price_range(arguments["min_price"], arguments["max_price"])
    elif name == "filter_category":
        result = filter_by_category(arguments["category"])
    elif name == "get_stats":
        result = get_stats()
    else:
        result = f"Unknown tool: {name}"
    return [types.TextContent(type="text", text=result)]

def main():
    load_csv()
    server.run_stdio()

if __name__ == "__main__":
    main()

第五步:测试结果

实际运行后,Claude 可以这样回答问题:

用户: 「有哪些 50 元以下的产品?」

Claude → 调用 search_price_range(min_price=0, max_price=50)
MCP Server → 返回 6 个结果:
  • Claude Opus 4.6 - CNY2.99
  • Claude Sonnet 4.6 - CNY1.99
  • Claude Haiku 4.5 - CNY0.80
  • API Gateway Pro - CNY29.99
  • ClaudeAPI Console - CNY0.00
  • Batch API Access - CNY49.99
Claude → 调用 search_price_range(min_price=0, max_price=50)
MCP Server → 返回 6 个结果:
  • Claude Opus 4.6 - CNY2.99
  • Claude Sonnet 4.6 - CNY1.99
  • Claude Haiku 4.5 - CNY0.80
  • API Gateway Pro - CNY29.99
  • ClaudeAPI Console - CNY0.00
  • Batch API Access - CNY49.99

用户: 「Claude Sonnet 4.6 的详细信息?」

{
  "product_id": "P002",
  "product_name": "Claude Sonnet 4.6",
  "category": "AI Model",
  "price": "1.99",
  "stock": "8000",
  "created_date": "2026-03-15"
}
{
  "product_id": "P002",
  "product_name": "Claude Sonnet 4.6",
  "category": "AI Model",
  "price": "1.99",
  "stock": "8000",
  "created_date": "2026-03-15"
}


四、集成到 Claude 工具

与 Claude Code 集成

如果你还没有部署 Claude 应用,可先阅读 Claude API Python 入门教程 快速上手。

# 启动 MCP Server
import subprocess
server = subprocess.Popen(["python", "server.py"])

# Claude 自动检测到可用工具,用户提问时自动调用
user_input = "Show me all Claude models under 2 dollars"
# Claude → 调用 search_price_range(0, 2) → 返回结果
# 启动 MCP Server
import subprocess
server = subprocess.Popen(["python", "server.py"])

# Claude 自动检测到可用工具,用户提问时自动调用
user_input = "Show me all Claude models under 2 dollars"
# Claude → 调用 search_price_range(0, 2) → 返回结果

与 Cline 集成

  1. 启动 MCP Server:python server.py
  2. 在 Cline 配置中指定 Server 地址
  3. 开始提问,Cline 会自动调用工具

环境配置

# .env
CSV_FILE=./products.csv
MCP_PORT=8000
DEBUG=false
# .env
CSV_FILE=./products.csv
MCP_PORT=8000
DEBUG=false
from dotenv import load_dotenv
import os

load_dotenv()
csv_file = os.getenv("CSV_FILE", "products.csv")
from dotenv import load_dotenv
import os

load_dotenv()
csv_file = os.getenv("CSV_FILE", "products.csv")


五、常见问题

Q: 如何处理大数据集(100 万行 CSV)?

A: 改用数据库,避免一次性加载到内存:

import sqlite3
conn = sqlite3.connect("products.db")
cursor = conn.cursor()

def search_by_price_range(min_price, max_price):
    cursor.execute(
        "SELECT * FROM products WHERE price BETWEEN ? AND ?",
        (min_price, max_price)
    )
    return cursor.fetchall()
import sqlite3
conn = sqlite3.connect("products.db")
cursor = conn.cursor()

def search_by_price_range(min_price, max_price):
    cursor.execute(
        "SELECT * FROM products WHERE price BETWEEN ? AND ?",
        (min_price, max_price)
    )
    return cursor.fetchall()

Q: 如何保证数据安全,防止 Claude 修改数据?

A: 只提供 read-only 工具,写操作单独验证权限:

@server.call_tool()
async def handle_call_tool(name, args):
    if name.startswith("write_") or name.startswith("delete_"):
        if not verify_user_permission(user_id):
            return "Permission denied"
    return execute_tool(name, args)
@server.call_tool()
async def handle_call_tool(name, args):
    if name.startswith("write_") or name.startswith("delete_"):
        if not verify_user_permission(user_id):
            return "Permission denied"
    return execute_tool(name, args)

Q: 响应太慢怎么办?

A: 加缓存 + 分页查询:

from functools import lru_cache

@lru_cache(maxsize=100)
def get_products_by_category(category):
    return filter_category(category)

def list_products(page=1, page_size=20):
    start = (page - 1) * page_size
    return products_data[start:start+page_size]
from functools import lru_cache

@lru_cache(maxsize=100)
def get_products_by_category(category):
    return filter_category(category)

def list_products(page=1, page_size=20):
    start = (page - 1) * page_size
    return products_data[start:start+page_size]

六、总结与下一步

你已经学会了:

  • MCP 的架构和与 Tool Use 的区别
  • 如何定义工具和实现处理函数
  • 如何搭建完整的 MCP Server 并与 Claude 集成
  • 常见的性能和安全问题处理

下一步可以尝试:

  • 改用 SQLite / PostgreSQL 支持更复杂的查询
  • 添加权限管理和审计日志
  • 同时连接多个数据源(CSV + 数据库 + API)
  • Docker 容器化部署

相关阅读


本文由 ClaudeAPI 团队出品。

Related Articles