Building Custom MCP Connectors for Claude: A Practical Guide
by Sandlabs Team, Founder, Sandlabs
The Model Context Protocol (MCP) is the open standard that lets Claude connect to external tools and data sources. With 12,000+ MCP servers in the wild and adoption from Claude, ChatGPT, Gemini, and VS Code, MCP has become the universal interface between AI and business tools.
But the real power isn't in the pre-built connectors — it's in building custom ones for your proprietary systems. This guide covers everything you need to build, deploy, and maintain custom MCP connectors.
What Is MCP?
MCP defines how AI applications communicate with external tools using a client-server architecture built on JSON-RPC 2.0.
Three participants:
- MCP Host: The AI application (Claude Desktop, Claude Code, ChatGPT)
- MCP Client: Maintains a 1:1 connection to each MCP server
- MCP Server: Your custom connector that exposes tools, resources, and prompts
Three core primitives:
- Tools: Executable functions (e.g., query a database, call an API, send a message)
- Resources: Data sources (e.g., file contents, database records, API responses)
- Prompts: Reusable interaction templates
Two transport mechanisms:
- stdio: For local servers running on your machine (simplest)
- Streamable HTTP: For remote servers accessible over the network (production)
When a user asks Claude to "check the latest sales figures," Claude discovers available tools via MCP, calls the appropriate one, and incorporates the results into its response — all without the user needing to know which API or database was queried.
When to Build a Custom Connector
The built-in connector directory covers 75+ tools (Slack, Google Drive, Salesforce, etc.). Build a custom connector when:
- Your tool isn't in the directory (internal CRM, custom database, proprietary API)
- You need specific operations the built-in connector doesn't support
- You want fine-grained access control over what Claude can do
- You're connecting to internal systems behind a firewall
- You need to combine data from multiple sources into a single tool
Building Your First MCP Server (Python)
The fastest way to build an MCP server is with Python's FastMCP framework.
Installation
pip install "mcp[cli]"
Requires Python 3.10+.
Basic structure
from mcp.server.fastmcp import FastMCP
# Create server
mcp = FastMCP("my-business-tools")
@mcp.tool()
async def get_customer(customer_id: str) -> str:
"""Look up a customer by their ID and return their profile.
Args:
customer_id: The unique identifier for the customer
"""
# Your database/API logic here
customer = await db.customers.find_one({"id": customer_id})
return f"Name: {customer['name']}, Email: {customer['email']}, Plan: {customer['plan']}"
@mcp.tool()
async def list_recent_orders(customer_id: str, limit: int = 10) -> str:
"""List recent orders for a customer.
Args:
customer_id: The customer to look up orders for
limit: Maximum number of orders to return (default 10)
"""
orders = await db.orders.find({"customer_id": customer_id}).limit(limit)
return "\n".join([f"#{o['id']}: {o['product']} - ${o['amount']}" for o in orders])
@mcp.resource("customers://stats")
async def customer_stats() -> str:
"""Current customer statistics."""
total = await db.customers.count_documents({})
active = await db.customers.count_documents({"status": "active"})
return f"Total: {total}, Active: {active}, Churn rate: {((total-active)/total)*100:.1f}%"
def main():
mcp.run(transport="stdio")
if __name__ == "__main__":
main()
FastMCP automatically generates tool definitions from your Python type hints and docstrings. Clear, descriptive docstrings are critical — they help Claude understand when and how to use each tool.
TypeScript alternative
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "my-business-tools",
version: "1.0.0",
});
server.tool(
"get_customer",
"Look up a customer by their ID",
{ customer_id: z.string().describe("The unique customer identifier") },
async ({ customer_id }) => {
const customer = await db.findCustomer(customer_id);
return {
content: [{ type: "text", text: JSON.stringify(customer) }],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
Connecting to Claude Desktop
Add your server to Claude Desktop's configuration (claude_desktop_config.json):
{
"mcpServers": {
"my-business-tools": {
"command": "python",
"args": ["/path/to/server.py"],
"env": {
"DATABASE_URL": "postgresql://..."
}
}
}
}
For Claude Code, use:
claude mcp add my-business-tools python /path/to/server.py
Architecture Decisions
Local vs. Remote
Local (stdio):
- Server runs on the same machine as Claude Desktop
- Simplest setup — no networking, no auth
- Good for: personal tools, development, internal databases accessible via VPN
- Limitation: only works for one user
Remote (Streamable HTTP):
- Server runs on your infrastructure, accessible via HTTPS
- Required for: team deployments, Claude Cowork connectors, multi-user setups
- Needs: OAuth 2.1 authentication, HTTPS, proper security
- Good for: production deployments, shared tools
Tool granularity
Too broad: query_database(sql: str) — gives Claude raw SQL access. Dangerous.
Too narrow: get_customer_email(id: str) — Claude needs to call 10 tools for basic tasks.
Just right: get_customer_profile(id: str) — returns a complete, useful response. search_customers(query: str, filters: dict) — flexible but bounded.
Design tools at the level of business operations, not database queries.
Error handling
Return clear, actionable error messages. Claude uses error messages to decide what to do next:
@mcp.tool()
async def update_customer(customer_id: str, email: str) -> str:
"""Update a customer's email address."""
if not re.match(r"[^@]+@[^@]+\.[^@]+", email):
return "Error: Invalid email format. Please provide a valid email address."
customer = await db.customers.find_one({"id": customer_id})
if not customer:
return f"Error: Customer {customer_id} not found. Use search_customers to find the correct ID."
await db.customers.update_one({"id": customer_id}, {"$set": {"email": email}})
return f"Updated email for {customer['name']} to {email}."
Security Best Practices
MCP servers can access sensitive business data. Security is not optional.
Authentication
- Use OAuth 2.1 for remote servers (Authorization Code Flow with PKCE)
- Never hardcode credentials — use environment variables or secret managers
- Short-lived tokens with rotating refresh tokens
- Per-client consent before forwarding to third-party auth
Authorisation
- Implement per-tool scopes (e.g.,
customers:read,customers:write,orders:read) - Progressive least-privilege — start with read-only, add write access only when needed
- Never give Claude unrestricted database access
Data handling
- Never log access tokens, API keys, or sensitive data
- Validate all inputs before executing
- Sanitise outputs to prevent data leakage
- Use HTTPS for all remote connections
- Block private IP ranges to prevent SSRF attacks
Common vulnerabilities to avoid
- Confused deputy problem — Attackers exploiting proxy servers to steal authorization codes. Mitigation: per-client consent flows.
- SSRF — Malicious servers pointing to internal IPs. Mitigation: enforce HTTPS, block private ranges.
- Scope inflation — Over-broad token scopes increasing blast radius. Mitigation: per-tool scopes.
OWASP has published a dedicated guide for secure MCP server development.
Deploying to Production
For Claude Cowork (Team/Enterprise)
- Deploy your MCP server to your infrastructure (Docker recommended)
- Expose via HTTPS with OAuth 2.1 authentication
- In Claude: Organisation settings > Connectors > "Add custom connector"
- Enter your MCP server URL and configure OAuth credentials
- Team members connect via Settings > Connectors
For Claude Code
# Add with environment variables
claude mcp add \
-e API_KEY=your-key \
-e DATABASE_URL=your-db-url \
my-tools -- python server.py
Monitoring
- Log all tool invocations (without sensitive data) for debugging
- Track error rates, latency, and usage patterns
- Set up alerts for failures and anomalies
- Monitor token consumption — MCP tools contribute to Claude's context window
Real-World Examples
Commission processing (financial services)
We built custom MCP connectors for a mortgage broker platform that process lender remittance files from 24+ aggregators. Claude reads the raw files, extracts commission data, reconciles against expected payments, and generates RCTI invoices — reducing processing time from hours to minutes.
Document management (legal)
A law firm needed Claude to access their internal document management system. We built an MCP connector that lets Claude search case files, extract relevant clauses, and cross-reference with regulatory databases — all through natural language queries.
Inventory and supply chain
An e-commerce business needed Claude to check stock levels, trigger reorders, and generate supplier communications. Custom MCP connectors bridged Claude with their inventory API, Shopify store, and email system.
What It Costs
| Engagement | What You Get | Cost | Timeline |
|---|---|---|---|
| Single MCP connector | One connector for one system | $5K-$15K | 1-2 weeks |
| Cowork Setup Sprint | Cowork config + 2-3 custom connectors + training | $3K-$5K | 1 week |
| Multi-system integration | 3-5 connectors with orchestration | $15K-$40K | 3-6 weeks |
| Enterprise MCP platform | Custom connector framework + security + monitoring | $40K-$80K | 6-10 weeks |
Getting Started
If you need custom MCP connectors for your business:
- Identify your tools — What systems does your team access daily that aren't in Claude's connector directory?
- Map the operations — What specific actions do you need Claude to perform? (read data, create records, trigger workflows)
- Assess security requirements — What data will Claude access? Are there compliance requirements?
- Start with one connector — Build and validate a single connector before expanding.
We build custom MCP connectors for businesses across Australia and the US. Tell us about your workflow to discuss your integration needs.