Building a custom MCP server for Claude Building a custom Model Context Protocol (MCP) server lets Claude execute real-world actions like querying databases or triggering GitHub Actions, moving beyond simple prompting. Anthropic's Claude Desktop uses MCP to connect to a Node.js or Python server via stdio, with tools defined in JSON-RPC. A sample server provides system stats, and developers must register it in the claude_desktop_config.json file with absolute paths to avoid silent failures. Building a custom MCP server for Claude Claude /en/tags/claude/ Skills" means just writing a better prompt. They're wrong. The real power isn't in the chat box; it's in the Model Context Protocol MCP . If you want Claude to actually do things—like query your actual Postgres database, read your Jira tickets, or trigger a GitHub Action—you need to stop prompting and start building a server. I spent three hours last Thursday fighting with a JSON-RPC error because I forgot a single comma in my tool definition. It's a pain, but once it clicks, you stop treating Claude like a chatbot and start treating it like an operator. The architecture of a "Skill" To be clear, when we talk about extending Claude's capabilities, we are talking about creating a bridge. Claude the client sends a request to an MCP /en/tags/mcp/ Server your code , which executes a function and sends the result back. | Component | Role | Example | | :--- | :--- | :--- | | Host | The UI/IDE | Claude Desktop / Cursor | | Server | The "Skill" logic | A Node.js or Python script | | Transport | The pipe | Standard Input/Output stdio | Setting up your first MCP server in Node.js Don't overcomplicate this. You don't need a massive framework. You just need the @modelcontextprotocol/sdk . First, initialize your project: mkdir claude-skills-test && cd claude-skills-test npm init -y npm install @modelcontextprotocol/sdk Now, here is a raw implementation of a "skill" that lets Claude fetch the current system load and memory usage of your machine. This is something Claude can't do natively. js import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import os from "os"; const server = new Server { name: "system-monitor", version: "1.0.0", }, { capabilities: { tools: {}, }, } ; // 1. Tell Claude what this "skill" can do server.setRequestHandler ListToolsRequestSchema, async = { tools: { name: "get system stats", description: "Returns current CPU load and free memory", inputSchema: { type: "object", properties: {}, }, } , } ; // 2. Define the actual logic server.setRequestHandler CallToolRequestSchema, async request = { if request.params.name === "get system stats" { const freeMem = os.freemem / 1024 / 1024 / 1024 .toFixed 2 ; const load = os.loadavg 0 ; return { content: { type: "text", text: System Status: Load ${load}, Free RAM ${freeMem}GB } , }; } throw new Error "Tool not found" ; } ; async function main { const transport = new StdioServerTransport ; await server.connect transport ; } main .catch console.error ; Wiring it into Claude Desktop This is where most devs trip up. You can't just run node index.js and expect Claude to see it. You have to register the server in your config file. Open your claude desktop config.json . - MacOS: ~/Library/Application Support/Claude/claude desktop config.json - Windows: %APPDATA%\Claude\claude desktop config.json Add this block: { "mcpServers": { "my-system-skill": { "command": "node", "args": "/absolute/path/to/your/claude-skills-test/index.js" } } } Pro tip: Use absolute paths. Relative paths will fail silently, and you'll spend twenty minutes wondering why the hammer icon isn't appearing in your chat interface. Restart Claude Desktop completely. If you see a small plug icon or hammer, you've successfully given Claude a new skill. Now you can ask, "How is my RAM doing?" and it will actually execute your JS code to find out. Moving beyond basic scripts Once you have the basics, you start realizing that the bottleneck isn't the code—it's the context. If you feed a tool too much raw data, you waste tokens and confuse the model. I've found that the most effective "skills" are those that act as filters. Instead of a tool that "Reads a whole File," build one that "Searches for a Regex pattern in a File." When comparing AI Models /en/category/ai-models/ , you'll notice some handle tool-calling function calling with much higher precision than others. Claude 3.5 Sonnet is currently the gold standard for MCP because it doesn't "hallucinate" arguments as often as GPT-4o does. Common failure points If it's not working, it's usually one of these three things: 1. Node Path: Claude can't find node . Use the full path to the binary find it with which node . 2. Async Hangs: If your tool takes more than 30 seconds to respond, Claude will often timeout. Use streaming or optimize your queries. 3. JSON Schema errors: If your inputSchema is wrong, Claude will try to pass arguments that your code isn't prepared to handle, leading to a crash. The value of a shared ecosystem Building these in a vacuum is slow. This is why I spend so much time in the PromptCube community. Instead of guessing if a specific tool definition will trigger a hallucination, you can see how others structured their MCP servers for similar tasks. Whether you're trying to automate your PR reviews or connect Claude to a niche API, joining PromptCube gives you access to a peer group of devs who are actually shipping AI-integrated workflows, not just chatting with a bot. You can share your server configs, troubleshoot weird edge cases, and stay updated on the latest protocol changes. Optimizing for speed To make your skills feel snappy, avoid heavy imports inside the request handler. Bad: js server.setRequestHandler CallToolRequestSchema, async request = { const heavyLib = await import 'some-massive-library' ; // Slows down every call // ... logic } ; Good: python import heavyLib from 'some-massive-library'; // Import once at startup server.setRequestHandler CallToolRequestSchema, async request = { // ... logic } ; The difference is usually around 200-500ms. It doesn't sound like much, but when you're in a flow, it's the difference between a tool that feels native and one that feels like a clunky plugin. Next Claude Code and Codex have a nasty habit of treating your → /en/threads/3672/ All Replies (0) No replies yet — be the first