MCP (Model Context Protocol) is how you extend what an LLM can do. Instead of building custom API integrations for every tool, MCP provides a standard protocol. You already use 10+ MCP servers. Now learn to build your own.
┌──────────┐ stdio/HTTP ┌──────────┐
│ Client │◄──────────────────►│ Server │
│(Claude │ │(Your code)│
│ Code) │ │ │
└──────────┘ └──────────┘
│ │
│ Uses tools, │ Exposes tools,
│ reads resources │ resources, prompts
│ │
An MCP server exposes three things:
search_meetings, create_task)Using @modelcontextprotocol/sdk:
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-project-server",
version: "1.0.0"
});
// Define a tool
server.tool(
"search_notes",
"Search Obsidian meeting notes by keyword and date range",
{
query: z.string().describe("Search keywords"),
project: z.string().optional().describe("Filter by project name"),
since: z.string().optional().describe("ISO date, only notes after this date")
},
async ({ query, project, since }) => {
// Your implementation: search files, filter, return results
const results = await searchObsidianVault(query, project, since);
return {
content: [{ type: "text", text: JSON.stringify(results) }]
};
}
);
// Start the server
const transport = new StdioServerTransport();
await server.connect(transport);
From your experience with 10+ MCP servers, you know what makes a good tool:
1. Do one thing well. search_notes searches. create_note creates. Don't combine them.
2. Clear descriptions. The model reads the description to decide when to use the tool. Be explicit about what it does, what inputs it expects, and what it returns.
3. Return structured data. JSON is easier for the model to process than free text. Include metadata (dates, IDs) that the model can use for follow-up actions.
4. Handle errors gracefully. Return error messages the model can understand and act on. "Note not found" is better than a stack trace.
5. Minimize parameters. Every parameter is a decision the model has to make. Only require what's essential, make the rest optional with sensible defaults.
Add your server to .claude/settings.json:
{
"mcpServers": {
"my-project-server": {
"command": "npx",
"args": ["tsx", "/path/to/your/server.ts"]
}
}
}
Restart Claude Code, and your tools appear alongside the built-in ones.
The highest-value MCP servers bridge your existing tools:
These bridges are where the real productivity gains are — connecting data that currently lives in silos.