Self-Hosted MCP Servers for Claude: Building Custom Tool Extensions On-Prem
Tired of cloud dependencies for Claude's tool calling? Discover how self-hosted MCP servers keep your enterprise data secure on-prem with Docker Compose and Rust examples.
Why Self-Host MCP Servers for Claude?
Hey there, Claude enthusiasts! If you're knee-deep in enterprise AI deployments, you've probably hit the wall with cloud-hosted tools exposing sensitive data. Enter self-hosted MCP (Model Context Protocol) servers – your ticket to private, lightning-fast tool extensions for Claude. In this guide, we'll dive into building and deploying these bad boys on-prem, complete with Docker Compose setups and real-world examples for database queries and API calls.
We'll compare cloud vs. on-prem setups, walk through Rust-based servers (because why not go fast and safe?), and share actionable code. By the end, you'll have a secure toolchain that Claude loves. Let's roll!
MCP Servers 101: Claude's Extension Superpower
MCP servers are lightweight HTTP endpoints that supercharge Claude's tool-calling abilities. Claude (Opus, Sonnet, Haiku) can dynamically invoke these for tasks like data retrieval or computations, keeping your prompts context-rich without bloating token limits.
Key perks for Claude users:
- Structured outputs: JSON schemas enforced server-side.
- Stateful sessions: Protocol handles multi-turn context.
- Claude-native: Integrates seamlessly via Anthropic's Messages API.
But here's the rub: Public MCP hubs (like those in the Anthropic ecosystem) route through the cloud. For enterprises? That's a no-go for compliance (GDPR, HIPAA) or latency-sensitive ops.
Cloud-Hosted vs. Self-Hosted MCP: Head-to-Head
Let's break it down conversationally – imagine you're at a coffee shop debating with your dev lead:
| Feature | Cloud-Hosted (e.g., Anthropic Marketplace) | Self-Hosted On-Prem |
|---|---|---|
| Security | Shared infra, potential data exfil | Air-gapped, full control |
| Latency | 100-500ms roundtrips | <50ms local network |
| Cost | Per-call pricing | One-time setup, zero marginal |
| Customization | Limited to templates | Full Rust/any-lang freedom |
| Scalability | Auto-scales | Kubernetes/Docker Swarm |
| Compliance | Vendor audits only | SOC2 in your datacenter |
Self-hosted wins for enterprises handling proprietary data. Claude Enterprise users: This pairs perfectly with VPC deployments.
Prerequisites: Gear Up Your Setup
Before we code:
- Docker & Docker Compose (v2+).
- Rust toolchain (for custom servers):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh. - Claude API key (Opus recommended for complex tools).
- Basic YAML/JSON knowledge.
Test your Claude integration first:
pip install anthropic
anthropic --api-key $ANTHROPIC_API_KEY messages --model claude-3-opus-20240229
Quickstart: Docker Compose for a Basic MCP Server
Spin up a minimal MCP echo server in 5 minutes. MCP protocol basics:
- POST
/mcp/callwith JSON{ "tool": "echo", "params": {...}, "session_id": "uuid" }. - Respond with
{ "result": ..., "next_tools": [...] }.
docker-compose.yml:
version: '3.8'
services:
mcp-server:
image: rust:1.75-slim # Or your custom image
ports:
- "8080:8080"
volumes:
- ./mcp-server:/app
working_dir: /app
command: bash -c "cargo run --release"
environment:
- RUST_LOG=info
# Add Postgres later
Build a Rust server skeleton (Cargo.toml):
[package]
name = "claude-mcp"
version = "0.1.0"
edition = "2021"
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
uuid = { version = "1.0", features = ["v4", "serde"] }
src/main.rs (basic echo):
use axum::{routing::post, Json, Router};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Deserialize)]
struct MCPRequest {
tool: String,
params: serde_json::Value,
session_id: Option<Uuid>,
}
#[derive(Serialize)]
struct MCPResponse {
result: serde_json::Value,
next_tools: Vec<String>,
}
async fn handle_call(Json(req): Json<MCPRequest>) -> Json<MCPResponse> {
let result = match req.tool.as_str() {
"echo" => req.params.clone(),
_ => serde_json::json!({ "error": "Unknown tool" }),
};
Json(MCPResponse {
result,
next_tools: vec![],
})
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/mcp/call", post(handle_call));
let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
Run: docker compose up --build. Test with curl:
curl -X POST http://localhost:8080/mcp/call \
-H 'Content-Type: application/json' \
-d '{"tool":"echo","params":{"msg":"Hello Claude!"},"session_id":"123"}'
Boom – your first MCP server! Now, Claude prompt: "Use tool echo with msg 'Test' via MCP at localhost:8080" (adapt for API).
Real-World Example 1: Database Integration (Postgres)
Secure internal DB queries without exposing creds to Claude. Add Postgres to Compose:
postgres:
image: postgres:16
environment:
POSTGRES_DB: claude_db
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
pgdata:
Extend Rust server (Cargo.toml + sqlx = { version = "0.7", features = ["runtime-tokio", "postgres", "chrono"] }):
use sqlx::{PgPool, postgres::PgPoolOptions};
use once_cell::sync::Lazy;
static POOL: Lazy<PgPool> = Lazy::new(|| {
PgPoolOptions::new().connect("postgres://user:pass@localhost/claude_db").unwrap()
});
async fn query_db(params: serde_json::Value) -> Result<serde_json::Value, String> {
let sql = params["query"].as_str().ok_or("No query")?;
let rows = sqlx::query(sql).fetch_all(&*POOL).await.map_err(|e| e.to_string())?;
Ok(serde_json::to_value(rows).unwrap())
}
// In handle_call:
"db_query" => query_db(req.params).await.map_err(|e| serde_json::json!({ "error": e }))?.into(),
Claude usage: Prompt Claude to call db_query tool for sales data. Zero data leaves your prem!
Real-World Example 2: Internal API Integrations
Proxy calls to your CRM/ERP APIs. Add auth middleware in Rust:
use axum::http::HeaderMap;
async fn api_call(params: serde_json::Value, headers: HeaderMap) -> Result<serde_json::Value, String> {
let client = reqwest::Client::new();
let url = params["url"].as_str().ok_or("No URL")?;
let api_key = std::env::var("INTERNAL_API_KEY").unwrap();
let res = client.get(url)
.header("Authorization", format!("Bearer {api_key}"))
.json(¶ms["body"])
.send().await
.map_err(|e| e.to_string())?;
res.json().await.map_err(|e| e.to_string())
}
Secure with env vars in Docker. Compare to Zapier: No vendor lock-in, sub-10ms latency.
Scaling & Security Best Practices
- Auth: JWT or mTLS for MCP endpoints.
- Scaling:
docker compose scale mcp-server=3or Kubernetes. - Monitoring: Prometheus + Grafana sidecar.
- Claude Tips: Use
tool_choice: autoin API calls; pin schemas.
Edge case: High-volume? Rust's async shines over Node.js (2x throughput in benchmarks).
Wrapping Up: Your On-Prem Claude Arsenal
Self-hosted MCP servers transform Claude from a chatty sidekick to an enterprise powerhouse. We've covered Docker basics, Rust builds, DB/API examples – all deployable today. Dip into Claude Directory for more: API SDKs, agents, and playbooks.
Questions? Drop a comment. Stay secure, code fast! 🚀
(Word count: ~1450)
Comments
More Blog
View allBuilding Voice Agents with Claude API and ElevenLabs: Conversational AI Guide
Build natural voice agents combining Claude API's superior reasoning with ElevenLabs' lifelike TTS. This end-to-end guide creates a conversational web app with STT, AI chat, and speech synthesis.
Claude vs Mistral Large 2: 2025 Data Analysis Benchmarks and Use Cases
As data volumes explode in 2025, choosing between Claude's reasoning depth and Mistral Large 2's efficiency is critical. We benchmark SQL generation, visualizations, and large datasets to reveal the w
Claude Enterprise for Cybersecurity: Threat Modeling and Incident Response
In the high-stakes world of cybersecurity, rapid threat modeling and incident response can mean the difference between containment and catastrophe. Discover how Claude Enterprise empowers security tea
Claude Code in VS Code: Custom Commands for Refactoring Large Codebases
Refactoring sprawling codebases manually? Harness Claude Code's power in VS Code with custom commands to automate AI-driven refactors across TypeScript and Python projects—saving hours of drudgery.
Claude SDK Rust for Blockchain: Smart Contract Auditing Agents
Build blazing-fast smart contract auditing agents in Rust using the Claude SDK. Harness Claude's reasoning to scan Solidity code for vulnerabilities like reentrancy and overflows.
Advanced Claude Artifacts: Collaborative Editing in Multi-User Sessions
Elevate team productivity with Claude Artifacts in multi-user projects—enable real-time iterative editing for code reviews and docs without leaving the interface.