Skip to main content

Build Your Own MCP Server: Give Claude Direct Access to Local CSV Data

MCP (Model Context Protocol) is Anthropic's open protocol that lets Claude access local files, databases, and custom tools. In this guide, you'll build an MCP Server that allows Claude to query CSV data directly — in under 100 lines of Python, capable of answering complex data analysis questions in under 5 seconds.

Dev GuidesMCP ServerPythonDeveloper GuideTechnical Docs Est. read15min
2026.04.17 published
Build Your Own MCP Server: Give Claude Direct Access to Local CSV Data

Build Your Own MCP Server: Give Claude Direct Access to Local CSV Data

15 min read


Introduction: Why Should You Care About MCP?

MCP (Model Context Protocol) is Anthropic’s open protocol that lets Claude access local files, databases, and custom tools. In this guide, you’ll build an MCP Server that allows Claude to query CSV data directly — in under 100 lines of Python, capable of answering complex data analysis questions in under 5 seconds.

What this guide covers:

  1. What MCP is and why it matters (quick primer) 2.3 real-world enterprise use cases where MCP solves actual problems 3.Hands-on: building an MCP Server in 100 lines of Python 4.Integrating with Claude’s tool system and deploying to production

1. What Is MCP — and Why Does It Matter?

MCP (Model Context Protocol) is a standardized protocol that allows AI models to interact securely with external systems.

Think of it this way: if Claude is an assistant, MCP is what lets it:

  • 📞 Make phone calls (TCP/HTTP communication)
  • 🗂️ Browse the office filing cabinet (local databases)
  • 🛠️ Use workplace tools (APIs and functions)
  • 🔐 Operate strictly within your defined rules (permission control)

MCP Architecture

┌─────────────┐
│   Claude    │  <-- AI Assistant
└──────┬──────┘
       │ Question: "What products do we have?"


┌─────────────────────────┐
│   MCP Protocol              │
│(Standard communication layer)         │
└──────┬──────────────────┘
       │ Invokes tool: list_products()


┌─────────────────────────┐
│   MCP Server            │ <-- Your server
│  (Exposes tool list)    │
└──────┬──────────────────┘
       │ Returns: full product list


┌─────────────────────────┐
│  Local Data Source      │
│  (CSV / DB / API)       │
└─────────────────────────┘
┌─────────────┐
│   Claude    │  <-- AI Assistant
└──────┬──────┘
       │ Question: "What products do we have?"


┌─────────────────────────┐
│   MCP Protocol              │
│(Standard communication layer)         │
└──────┬──────────────────┘
       │ Invokes tool: list_products()


┌─────────────────────────┐
│   MCP Server            │ <-- Your server
│  (Exposes tool list)    │
└──────┬──────────────────┘
       │ Returns: full product list


┌─────────────────────────┐
│  Local Data Source      │
│  (CSV / DB / API)       │
└─────────────────────────┘

MCP vs Tool Use: What’s the Difference?

Feature Tool Use(Old Way) MCP(New Way)
Integration Hard-coded tool definitions in the prompt Dynamic tool registration, standardized
Complexity Works for simple API calls Built for local DBs and file systems
Maintainability Changing a tool means rewriting the prompt 更Update tools without touching the prompt
Production-ready Good for demos Built for production environments
Security Manual validation required Built-in permission management

Conclusion: Tool Use is like scribbling a tool list on a sticky note. MCP is a proper tool registry with permission management built in.

MCP’s 5 Core Tool Types

Tool Type Example Use Case Purpose
Sampling Let Claude think through a problem Deep reasoning and analysis
Tools List available operations Query and modify data
Resources Expose text/binary content File access, content retrieval
Prompts Provide prompt templates Customize AI behavior
Root Listing Declare system capabilities Tell Claude what it can do

In this guide, we’ll be implementing the Tools type.


2. Three Real-World Use Cases: Where MCP Actually Shines

Use Case 1: Intelligent Data Analysis

The old way:

Sales Manager → Opens Excel → Ctrl+F to search → Manual aggregation → 30 minutes later
Sales Manager → Opens Excel → Ctrl+F to search → Manual aggregation → 30 minutes later

With MCP + Claude:

Sales Manager: "What were our best-selling products last week?"
Claude queries the database directly → Answer in under a second
Sales Manager: "What were our best-selling products last week?"
Claude queries the database directly → Answer in under a second

Use Case 2: Internal Knowledge Base Q&A

An employee asks, “How do I apply for annual leave?” The old way means digging through 10 Wiki pages and still not being sure. With MCP + Claude, the answer comes back instantly — pulling the exact policy paragraph, no searching required.

Use Case 3: Cross-System Data Aggregation

A manager asks, “Which sales team was most profitable this year?” Claude’s MCP simultaneously queries the CRM (revenue), finance system (costs), and inventory system (turnover rate) — then synthesizes a complete answer on the spot.


3. Hands-On: Build an MCP Server in ~100 Lines of Python

Before You Start: 5-Minute Setup Checklist

# Check Python version (3.11+ required)
python --version
# Install dependencies
pip install mcp anthropic pydantic python-dotenv
# Check Python version (3.11+ required)
python --version
# Install dependencies
pip install mcp anthropic pydantic python-dotenv

Step 1: Prepare Your Data (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

Step 2: Define Your Tools (Tool Definitions)

In your MCP Server, you need to tell Claude exactly which tools are available:

# Tool 1: List all products
{
    "name": "list_products",
    "description": "List all products in the database",
    "inputSchema": {"type": "object", "properties": {}, "required": []}
}
# Tool 2: Query by 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"]
    }
}
# Tool 3: Search by price range
{
    "name": "search_price_range",
    "inputSchema": {
        "type": "object",
        "properties": {
            "min_price": {"type": "number"},
            "max_price": {"type": "number"}
        },
        "required": ["min_price", "max_price"]
    }
}

# Tool 4: Filter by category  /  Tool 5: Get statistics (same pattern)
# Tool 1: List all products
{
    "name": "list_products",
    "description": "List all products in the database",
    "inputSchema": {"type": "object", "properties": {}, "required": []}
}
# Tool 2: Query by 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"]
    }
}
# Tool 3: Search by price range
{
    "name": "search_price_range",
    "inputSchema": {
        "type": "object",
        "properties": {
            "min_price": {"type": "number"},
            "max_price": {"type": "number"}
        },
        "required": ["min_price", "max_price"]
    }
}

# Tool 4: Filter by category  /  Tool 5: Get statistics (same pattern)

Key concept: inputSchema defines what parameters each tool accepts. Claude automatically picks the right tool based on what the user asks — no routing logic needed on your end.

Step 3: Implement the Tool Handler Functions

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}"

Step 4: Create the 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 more tools defined the same way
    ]

@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 more tools defined the same way
    ]

@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()

Step 5: Test It Out

Once running, here’s what Claude can actually do with natural language queries:

User: 「Show me all products under $50」

Claude  →  calls search_price_range(min_price=0, max_price=50)
MCP Server  →  returns 6 results:
  • Claude Opus 4.6   — $2.99
  • Claude Sonnet 4.6 — $1.99
  • Claude Haiku 4.5  — $0.80
  • API Gateway Pro   — $29.99
  • ClaudeAPI Console — $0.00
  • Batch API Access  — $49.99
Claude  →  calls search_price_range(min_price=0, max_price=50)
MCP Server  →  returns 6 results:
  • Claude Opus 4.6   — $2.99
  • Claude Sonnet 4.6 — $1.99
  • Claude Haiku 4.5  — $0.80
  • API Gateway Pro   — $29.99
  • ClaudeAPI Console — $0.00
  • Batch API Access  — $49.99

User: 「“Give me the details for 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"
}

4. Integrating with Claude Clients

Integration with Claude Code

New to Claude API? Check out our Claude API Python 入门教程 to get up and running first.

# Start the MCP Server
import subprocess
server = subprocess.Popen(["python", "server.py"])
# Claude auto-discovers available tools and calls them based on user input
user_input = "Show me all Claude models under 2 dollars"
# Claude  →  calls search_price_range(0, 2)  →  returns results
# Start the MCP Server
import subprocess
server = subprocess.Popen(["python", "server.py"])
# Claude auto-discovers available tools and calls them based on user input
user_input = "Show me all Claude models under 2 dollars"
# Claude  →  calls search_price_range(0, 2)  →  returns results

Integration with Cline

  1. Start the MCP Server:python server.py
  2. Point Cline to your server address in its config
  3. Start prompting — Cline will automatically invoke the right tools

Environment Configuration

# .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")

5. FAQ

Q: How do I handle large datasets (1M+ row CSVs)?

A: Switch to a database — avoid loading everything into memory at once:

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: How do I keep data safe and prevent Claude from modifying records?

A: Expose only read-only tools, and require explicit permission checks for any write operations:

@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: Responses are too slow — how do I speed things up?

A: Add LRU caching and paginate your queries:

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]

6. Wrap-Up & What’s Next

Here’s what you’ve covered in this guide:

  • ✅ How MCP architecture differs from direct Tool Use
  • ✅ How to define tools and implement their handler functions
  • ✅ How to build a full MCP Server and integrate it with Claude
  • ✅ How to handle common performance and security pitfalls

Where to Go from Here Ready to take it further? Here are some natural next steps:

  • 🗄️ Swap in SQLite or PostgreSQL for more complex query support
  • 🔐 Add role-based access control and audit logging
  • 🔗 Connect multiple data sources simultaneously — CSV, database, and external APIs
  • 🐳 Containerize with Docker for production deployment

Further Reading


Written by the ClaudeAPI team.

Related Articles