## Tired of Claude's Built-in Tools Falling Short?
Hey there, fellow Claude enthusiast! If you've been building AI agents with Claude (Opus, Sonnet, or Haiku), you know the power of its native tool-calling. But what happens when you need access to *your* proprietary APIs, internal databases, or niche services? That's where **Model Context Protocol (MCP)** shines. MCP lets you create custom servers and clients that extend Claude's reach seamlessly.
In this post, we'll dive into building MCP servers and clients with Node.js. We'll cover a weather API example and an internal database query tool. By the end, your agents will fetch live data like pros. Let's compare first to see why MCP rules.
## MCP vs. Native Tools vs. Other Extensions: A Quick Comparison
Claude's ecosystem offers several ways to add tools. Here's how MCP stacks up:
| Method | Pros | Cons | Best For |
|--------|------|------|----------|
| **Native Tool Calling** (Claude 3.5 Sonnet+) | Fast, no setup, XML/JSON schema | Limited to predefined functions, no custom state | Simple math, web search |
| **Claude Code CLI** | Local dev integration | Not real-time external | Code gen/editing |
| **MCP Servers/Clients** | Real-time external APIs, secure auth, stateful | Requires server setup | Proprietary tools, databases |
| **Third-party (Zapier/n8n)** | No-code workflows | Latency, less control | Business automations |
MCP wins for developers wanting **full control** over real-time, secure integrations. It's protocol-based: Claude's agent sends JSON requests to your MCP server via HTTP/WebSocket, gets structured responses. Perfect for agents needing dynamic context.
## What is MCP? The Basics
**MCP (Model Context Protocol)** is an open protocol for bidirectional communication between Claude agents and external services. Servers expose tools as endpoints; clients (in your agent prompt/system) invoke them.
Key components:
- **MCP Server**: Node.js/Express app handling `/tools` (list), `/invoke` (call).
- **MCP Client**: JS snippet or prompt instructing Claude to POST to your server.
- **Payload**: JSON with `tool_name`, `params`, `context` (for state).
Anthropic's docs recommend MCP for enterprise extensions—think HR systems, sales CRMs.
## Build Your First MCP Server: Real-Time Weather API
Let's create a server proxying OpenWeatherMap API. Why proxy? Secure API keys and add Claude-friendly formatting.
### Prerequisites
- Node.js 18+
- OpenWeatherMap free key (signup at openweathermap.org)
### Step 1: Setup Server
```bash
mkdir claude-mcp-weather
cd claude-mcp-weather
npm init -y
npm install express axios cors
```
### Step 2: Server Code (`server.js`)
```javascript
const express = require('express');
const axios = require('axios');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
// Your secret key
const API_KEY = 'your-openweathermap-key';
const BASE_URL = 'https://api.openweathermap.org/data/2.5';
// MCP Endpoints
app.get('/tools', (req, res) => {
res.json([
{
name: 'get_weather',
description: 'Get current weather for a city',
parameters: {
type: 'object',
properties: {
city: { type: 'string', description: 'City name' },
units: { type: 'string', enum: ['metric', 'imperial'], default: 'metric' }
},
required: ['city']
}
}
]);
});
app.post('/invoke', async (req, res) => {
const { tool_name, parameters, context } = req.body;
if (tool_name === 'get_weather') {
try {
const { city, units = 'metric' } = parameters;
const response = await axios.get(`${BASE_URL}/weather`, {
params: { q: city, units, appid: API_KEY }
});
const data = response.data;
res.json({
success: true,
result: {
temperature: data.main.temp,
description: data.weather[0].description,
city: data.name,
context: { ...context, last_query: city } // Stateful!
}
});
} catch (error) {
res.json({ success: false, error: error.message });
}
} else {
res.json({ success: false, error: 'Unknown tool' });
}
});
app.listen(3000, () => console.log('MCP Weather Server on http://localhost:3000'));
```
Run with `node server.js`. Test:
- `curl http://localhost:3000/tools` → Tool schema
- `curl -X POST http://localhost:3000/invoke -H "Content-Type: application/json" -d '{"tool_name":"get_weather","parameters":{"city":"London"}}'` → Weather data!
Deploy to Vercel/Render for production.
## Building the MCP Client: Claude Agent Integration
Now, the client side. In your Claude API calls or Artifacts/Console, use this system prompt snippet:
```markdown
You have access to an MCP server at http://localhost:3000.
TOOLS:
- Use POST /invoke with JSON: {"tool_name": "get_weather", "parameters": {"city": "..."}, "context": {}}
When needing weather, call the tool and parse the result.
```
### Full Agent Example (Node.js + Claude SDK)
```javascript
const { Claude } = require('@anthropic-ai/sdk'); // npm i @anthropic-ai/sdk
const claude = new Claude({ apiKey: 'your-anthropic-key' });
const MCP_URL = 'http://localhost:3000';
const system = `You are a weather-aware agent. Use MCP at ${MCP_URL} for real-time data.
To call: Output XML <tool_call> with base64-encoded JSON payload to ${MCP_URL}/invoke.`;
async function agentQuery(userMsg) {
const msg = await claude.messages.create({
model: 'claude-3-5-sonnet-20240620',
max_tokens: 1024,
system,
messages: [{ role: 'user', content: userMsg }],
tools: [{ /* Define get_weather schema here */ }]
});
// Parse tool calls, POST to MCP, feed back to Claude
console.log(msg.content);
}
agentQuery('What's the weather in Tokyo?');
```
Claude will auto-detect tools via schema. For custom MCP, handle tool_calls in a loop:
1. Claude outputs tool call.
2. POST to MCP `/invoke`.
3. Append result as tool_result.
4. Continue conversation.
## Advanced Example: Internal Database Access
Securely query your Postgres DB? MCP to the rescue—keep creds server-side.
### Enhanced Server (`db-server.js`)
```javascript
// npm i pg
const { Pool } = require('pg');
const pool = new Pool({ connectionString: 'postgres://user:pass@localhost/mydb' });
app.get('/tools', (req, res) => {
res.json([
{
name: 'query_employees',
description: 'Search employees by department',
parameters: { dept: { type: 'string' } }
}
]);
});
app.post('/invoke', async (req, res) => {
if (tool_name === 'query_employees') {
const { dept } = parameters;
const result = await pool.query('SELECT * FROM employees WHERE department = $1', [dept]);
res.json({
success: true,
result: result.rows.map(row => ({ name: row.name, role: row.role }))
});
}
});
```
**Security Tips**:
- Use HTTPS.
- Auth via API keys in headers.
- Rate-limit with `express-rate-limit`.
- Context for multi-turn state (e.g., session_id).
Prompt Claude: "List sales team members." → Agent calls MCP → Secure DB results.
## Deploying to Production: Ngrok, Vercel, or Self-Host
- **Dev**: Ngrok for localhost tunneling (`ngrok http 3000`).
- **Prod**: Vercel serverless—adapt to `api/` routes.
- **Scale**: WebSockets for streaming MCP (advanced).
Compare costs: Free tier handles 1000s of calls/day.
## Best Practices for MCP-Powered Agents
- **Prompt Engineering**: "Always check MCP tools first for external data."
- **Error Handling**: Claude retries on `success: false`.
- **Schema Validation**: Use Zod/JSON Schema.
- **Comparisons**: MCP > Functions for state; faster than agentic loops.
- **Monitoring**: Log invokes with Winston.
Common pitfalls:
- Mismatched schemas → Claude confusion.
- Latency >2s → Timeout.
- No context → Stateless fails.
## Level Up Your Claude Agents Today
Custom MCP clients turn Claude into a powerhouse for real-world tasks. Start with weather, scale to your CRM/ERP. Share your builds in comments!
**Word count: ~1450**. Questions? Hit the Claude Directory Discord.
*Next: MCP with n8n for no-code agents.*