MCP (Model Context Protocol) is Anthropic’s protocol that gives Claude custom tools: API calls, database queries, file manipulation. Here’s how to build your own MCP server.
What an MCP Server Does
An MCP server exposes tools (functions Claude can call) and optionally resources (readable data) and prompts (pre-defined templates).
Claude decides when to call a tool, with what parameters, and how to use the result — like the API tool use, but via a standardized protocol.
Minimal Setup
npm init -y
npm install @modelcontextprotocol/sdk
npm install -D typescript tsx @types/node
Basic MCP Server
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-mcp-server",
version: "1.0.0",
});
// Define a tool
server.tool(
"search_customer",
"Search a customer in the database by name or email",
{
query: z.string().describe("Customer name or email"),
limit: z.number().optional().default(5).describe("Max results"),
},
async ({ query, limit }) => {
// Your logic here — database, internal API, etc.
const results = await searchDatabase(query, limit);
return {
content: [
{
type: "text",
text: JSON.stringify(results, null, 2),
},
],
};
}
);
// Start the server
const transport = new StdioServerTransport();
await server.connect(transport);
Adding a Resource
Resources are data Claude can read on demand — not callable functions.
server.resource(
"config",
"config://app/settings",
async (uri) => ({
contents: [
{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify({ env: "production", version: "2.1.0" }),
},
],
})
);
Error Handling
An MCP tool must always return something readable — not throw a bare exception.
server.tool("external_api_call", "...", { url: z.string() }, async ({ url }) => {
try {
const response = await fetch(url);
if (!response.ok) {
return {
content: [{ type: "text", text: `HTTP error ${response.status}: ${response.statusText}` }],
isError: true,
};
}
const data = await response.json();
return { content: [{ type: "text", text: JSON.stringify(data) }] };
} catch (err) {
return {
content: [{ type: "text", text: `Network error: ${err instanceof Error ? err.message : String(err)}` }],
isError: true,
};
}
});
Configuration in Claude Code
Add your server to ~/.claude/settings.json (or the project’s settings.json):
{
"mcpServers": {
"my-server": {
"command": "npx",
"args": ["tsx", "/path/to/server.ts"],
"env": {
"DATABASE_URL": "postgresql://..."
}
}
}
}
Real-World Use Cases
Internal MCP server I use: access to a PostgreSQL database + calls to internal business APIs. Claude can query data directly in natural language without me coding a new endpoint every time.
Recommended pattern: keep your MCP server simple and domain-specific. One server per business domain (CRM, billing, support) rather than one omniscient server — easier to maintain and secure.
Stéphanie Caumont
AI Product Owner · Learn more