Claude Tools

Custom MCP Servers for Claude: Domain-Specific Tools for Enterprise Agents

Empower enterprise Claude agents with custom MCP servers. This guide shows how to build Python tools connecting Claude to internal CRMs and ERPs for secure, domain-specific automation.

A

Andrew Snyder

AI & Automation Editor

January 2, 2026 min read
Share:

Introduction to MCP Servers

Model Context Protocol (MCP) servers are lightweight Python-based tools that extend Claude's capabilities by providing domain-specific functions. Unlike general-purpose APIs, MCP servers allow Claude to securely access internal systems like CRMs (e.g., Salesforce, HubSpot) and ERPs (e.g., SAP, Oracle) without exposing sensitive credentials in prompts.

In enterprise settings, custom MCP servers enable Claude agents to perform tasks such as querying customer data, updating inventory, or generating reports— all while maintaining data isolation and compliance. This step-by-step guide walks you through building, testing, and deploying a Python MCP server tailored for enterprise use.

Why Custom MCP Servers for Enterprise Agents?

  • Security: Tools run server-side; Claude only sees structured outputs.
  • Scalability: Handle high-volume requests from multiple Claude instances.
  • Customization: Tailor tools to your exact data models and business logic.
  • Compliance: Enforce RBAC, logging, and auditing natively.
  • Integration: Works seamlessly with Claude Code CLI, API, and agents.

For teams evaluating Claude for enterprise, MCP servers bridge the gap between AI reasoning and proprietary data.

Prerequisites

Before starting:

  • Python 3.10+ installed.
  • Claude Code CLI: pip install claude-code (or download from Anthropic).
  • Anthropic API key (set as ANTHROPIC_API_KEY env var).
  • Familiarity with FastAPI (we'll use it for the server).
  • Access to a CRM/ERP API (use mock data for this tutorial).
  • Docker for deployment (optional but recommended).

Install dependencies:

pip install fastapi uvicorn pydantic anthropic python-dotenv

Step 1: Project Setup

Create a new directory and structure:

mcp-server/
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── tools/
│   │   ├── __init__.py
│   │   ├── crm.py
│   │   └── erp.py
├── .env
├── requirements.txt
└── Dockerfile

Add to .env:

ANTHROPIC_API_KEY=your_key_here
CRM_API_KEY=your_crm_key
ERP_BASE_URL=https://your-erp.com/api

requirements.txt:

fastapi==0.104.1
uvicorn==0.24.0
pydantic==2.5.0
anthropic==0.3.0
requests==2.31.0
python-dotenv==1.0.0

Step 2: Define the MCP Server Structure

MCP servers expose a standardized /tools endpoint that Claude queries via HTTP. Responses follow a JSON schema for tool calls.

In app/main.py:

import os
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from dotenv import load_dotenv

load_dotenv()

app = FastAPI(title="Custom MCP Server")

class ToolCall(BaseModel):
    name: str
    parameters: dict

class ToolResponse(BaseModel):
    content: str
    is_error: bool = False

@app.post("/tools")
async def execute_tool(call: ToolCall):
    # Route to specific tools
    if call.name == "get_customer":
        from .tools.crm import get_customer
        return get_customer(call.parameters)
    elif call.name == "update_inventory":
        from .tools.erp import update_inventory
        return update_inventory(call.parameters)
    else:
        raise HTTPException(status_code=404, detail="Tool not found")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

Run the server:

cd mcp-server
uvicorn app.main:app --reload --port 8000

Test endpoint: curl -X POST http://localhost:8000/tools -H "Content-Type: application/json" -d '{"name":"get_customer","parameters":{"id":"123"}}'

Step 3: Implement CRM Tool

Create app/tools/crm.py for a Salesforce-like CRM integration.

import os
import requests
from pydantic import BaseModel

class Customer(BaseModel):
    id: str
    name: str
    email: str
    status: str

async def get_customer(params: dict) -> dict:
    crm_api_key = os.getenv("CRM_API_KEY")
    customer_id = params.get("id")
    
    if not customer_id:
        return {"content": "Error: customer_id required", "is_error": True}
    
    # Mock CRM call (replace with real API)
    url = "https://mock-crm.com/api/customers/{}".format(customer_id)
    headers = {"Authorization": f"Bearer {crm_api_key}"}
    
    try:
        response = requests.get(url, headers=headers)
        data = response.json()
        customer = Customer(**data)
        return {
            "content": f"Customer {customer.name} ({customer.email}) status: {customer.status}",
            "is_error": False
        }
    except Exception as e:
        return {"content": f"Error fetching customer: {str(e)}", "is_error": True}

This tool securely queries CRM without embedding creds in Claude prompts.

Step 4: Implement ERP Tool

app/tools/erp.py for inventory management:

import os
import requests
from pydantic import BaseModel

class InventoryUpdate(BaseModel):
    item_id: str
    quantity: int

async def update_inventory(params: dict) -> dict:
    erp_url = os.getenv("ERP_BASE_URL")
    item_id = params.get("item_id")
    quantity = params.get("quantity")
    
    if not item_id or quantity is None:
        return {"content": "Error: item_id and quantity required", "is_error": True}
    
    payload = {"quantity": quantity}
    headers = {"Authorization": f"Bearer {os.getenv('ERP_API_KEY')}"}
    
    try:
        response = requests.post(f"{erp_url}/inventory/{item_id}", json=payload, headers=headers)
        if response.status_code == 200:
            return {"content": f"Updated inventory for {item_id} to {quantity}", "is_error": False}
        else:
            return {"content": f"Update failed: {response.text}", "is_error": True}
    except Exception as e:
        return {"content": f"Error: {str(e)}", "is_error": True}

Step 5: Secure the Server

Add authentication middleware in main.py:

from fastapi.security import HTTPBearer

security = HTTPBearer()

@app.post("/tools")
async def execute_tool(call: ToolCall, token: str = Depends(security)):
    if token.credentials != os.getenv("MCP_SECRET"):
        raise HTTPException(status_code=401, detail="Unauthorized")
    # rest of code

Set MCP_SECRET=your_secret in .env. Enable HTTPS in production.

Log all calls:

import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# In execute_tool:
logger.info(f"Tool call: {call.name} by {token.credentials}")

Step 6: Integrate with Claude Code CLI

Configure Claude Code to use your MCP server:

  1. Start server: uvicorn app.main:app --port 8000
  2. Run Claude Code: claude-code --mcp-servers http://localhost:8000 --model claude-3-opus
  3. Prompt Claude: "Use the CRM tool to get customer 123 and check inventory for item ABC."

Claude will automatically discover tools via /tools (extendable to list schema) and call them.

Example interaction:

**User:** Analyze sales for customer 123.
**Claude:** Calling get_customer(id=123)... Customer John Doe (john@example.com) status: Active.
Next, checking related inventory...

Step 7: Advanced Features

Tool Schema Discovery

Add /tools/schema endpoint:

@app.get("/tools/schema")
async def get_tools_schema():
    return [
        {
            "name": "get_customer",
            "description": "Retrieve customer details from CRM",
            "parameters": {
                "type": "object",
                "properties": {"id": {"type": "string"}}
            }
        },
        # Add ERP tool
    ]

Claude Code auto-loads schemas for better prompting.

Error Handling & Retries

Implement idempotency with request_id in params.

Multi-Tool Chaining

Claude agents chain tools natively, e.g., get customer → query orders → update ERP.

Step 8: Deployment

Dockerize for cloud (AWS, GCP, etc.):

Dockerfile:

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Build & run:

docker build -t mcp-server .
docker run -p 8000:8000 --env-file .env mcp-server

Deploy to Kubernetes or serverless (e.g., Fly.io) with Claude API integration.

Best Practices

  • RBAC: Validate user context in tools.
  • Rate Limiting: Use slowapi for FastAPI.
  • Monitoring: Integrate Prometheus/Grafana.
  • Testing: Unit test tools with pytest.
  • Versioning: /v1/tools endpoints.
  • Claude-Specific: Use Opus for complex agents; Haiku for speed.

Example pytest:

# tests/test_crm.py
import pytest
from app.tools.crm import get_customer

@pytest.mark.asyncio
def test_get_customer():
    result = get_customer({"id": "123"})
    assert not result["is_error"]

Real-World Use Cases

  • Sales: Claude queries CRM, generates personalized outreach.
  • Supply Chain: Real-time ERP inventory for demand forecasting.
  • HR: Access employee data for onboarding agents.

Conclusion

Custom MCP servers transform Claude into a domain-aware enterprise powerhouse. Start with this boilerplate, adapt to your systems, and scale with Claude's evolving tool ecosystem. For updates, follow Anthropic news and experiment with Claude 3.5 Sonnet for superior tool use.

Word count: ~1450

The #1 Newsletter in AI

Stay ahead of the AI curve

The most important updates, news, and content — delivered in one weekly newsletter.

No spam. Unsubscribe anytime. Privacy policy

mcp servers
claude tools
enterprise agents
custom tools
domain-specific
ai-agents
A

About Andrew Snyder

AI & Automation Editor

Andrew covers practical AI automation, workflow design, and the tools teams use to streamline everyday operations.

Comments (0)