手把手教你写一个 MCP Server:让 Claude 直接查询本地 CSV 数据
预计阅读时间:15 分钟
引言:为什么要学 MCP?
MCP(Model Context Protocol)是 Anthropic 推出的协议,让 Claude 可以访问本地文件、数据库和自定义工具。本文教你搭建一个 MCP Server,让 Claude 直接查询 CSV 数据——只需要 100 行 Python 代码,就能在 5 秒内回答复杂的数据分析问题。
本文会涵盖:
- MCP 是什么与为什么重要(快速入门)
- 3 个企业场景:MCP 如何解决实际问题
- 动手实战:用 100 行 Python 搭建 MCP Server
- 集成到 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 集成
- 启动 MCP Server:
python server.py - 在 Cline 配置中指定 Server 地址
- 开始提问,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 团队出品。



