{"slug": "building-a-custom-mcp-server-for-claude", "title": "Building a custom MCP server for Claude", "summary": "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.", "body_md": "# Building a custom MCP server for Claude\n\n[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\n\n*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.\n\nI 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.\n\n## The architecture of a \"Skill\"\n\nTo 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.\n\n| Component | Role | Example |\n\n| :--- | :--- | :--- |\n\n| **Host** | The UI/IDE | Claude Desktop / Cursor |\n\n| **Server** | The \"Skill\" logic | A Node.js or Python script |\n\n| **Transport** | The pipe | Standard Input/Output (stdio) |\n\n## Setting up your first MCP server in Node.js\n\nDon't overcomplicate this. You don't need a massive framework. You just need the `@modelcontextprotocol/sdk`\n\n.\n\nFirst, initialize your project:\n\n```\nmkdir claude-skills-test && cd claude-skills-test\nnpm init -y\nnpm install @modelcontextprotocol/sdk\n```\n\nNow, 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.\n\n``` js\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { CallToolRequestSchema, ListToolsRequestSchema } from \"@modelcontextprotocol/sdk/types.js\";\nimport os from \"os\";\n\nconst server = new Server({\n  name: \"system-monitor\",\n  version: \"1.0.0\",\n}, {\n  capabilities: {\n    tools: {},\n  },\n});\n\n// 1. Tell Claude what this \"skill\" can do\nserver.setRequestHandler(ListToolsRequestSchema, async () => ({\n  tools: [{\n    name: \"get_system_stats\",\n    description: \"Returns current CPU load and free memory\",\n    inputSchema: {\n      type: \"object\",\n      properties: {}, \n    },\n  }],\n}));\n\n// 2. Define the actual logic\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n  if (request.params.name === \"get_system_stats\") {\n    const freeMem = (os.freemem() / 1024 / 1024 / 1024).toFixed(2);\n    const load = os.loadavg()[0];\n    \n    return {\n      content: [{ \n        type: \"text\", \n        text: `System Status: Load ${load}, Free RAM ${freeMem}GB` \n      }],\n    };\n  }\n  throw new Error(\"Tool not found\");\n});\n\nasync function main() {\n  const transport = new StdioServerTransport();\n  await server.connect(transport);\n}\n\nmain().catch(console.error);\n```\n\n## Wiring it into Claude Desktop\n\nThis is where most devs trip up. You can't just run `node index.js`\n\nand expect Claude to see it. You have to register the server in your config file.\n\nOpen your `claude_desktop_config.json`\n\n.\n\n- MacOS:\n`~/Library/Application Support/Claude/claude_desktop_config.json`\n\n- Windows:\n`%APPDATA%\\Claude\\claude_desktop_config.json`\n\nAdd this block:\n\n```\n{\n  \"mcpServers\": {\n    \"my-system-skill\": {\n      \"command\": \"node\",\n      \"args\": [\"/absolute/path/to/your/claude-skills-test/index.js\"]\n    }\n  }\n}\n```\n\n**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.\n\nRestart 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.\n\n## Moving beyond basic scripts\n\nOnce 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.\n\nI'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.\"\n\nWhen 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.\n\n## Common failure points\n\nIf it's not working, it's usually one of these three things:\n\n1. **Node Path:** Claude can't find `node`\n\n. Use the full path to the binary (find it with `which node`\n\n).\n\n2. **Async Hangs:** If your tool takes more than 30 seconds to respond, Claude will often timeout. Use streaming or optimize your queries.\n\n3. **JSON Schema errors:** If your `inputSchema`\n\nis wrong, Claude will try to pass arguments that your code isn't prepared to handle, leading to a crash.\n\n## The value of a shared ecosystem\n\nBuilding 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.\n\nWhether 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.\n\n## Optimizing for speed\n\nTo make your skills feel snappy, avoid heavy imports inside the request handler.\n\nBad:\n\n``` js\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n  const heavyLib = await import('some-massive-library'); // Slows down every call\n  // ... logic\n});\n```\n\nGood:\n\n``` python\nimport heavyLib from 'some-massive-library'; // Import once at startup\n\nserver.setRequestHandler(CallToolRequestSchema, async (request) => {\n  // ... logic\n});\n```\n\nThe 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.\n\n[Next Claude Code and Codex have a nasty habit of treating your →](/en/threads/3672/)\n\n## All Replies （0）\n\nNo replies yet — be the first!", "url": "https://wpnews.pro/news/building-a-custom-mcp-server-for-claude", "canonical_source": "https://promptcube3.com/en/threads/3687/", "published_at": "2026-07-26 12:51:34+00:00", "updated_at": "2026-07-26 13:08:51.364056+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools", "artificial-intelligence"], "entities": ["Claude", "Model Context Protocol", "Anthropic", "Node.js", "GitHub"], "alternates": {"html": "https://wpnews.pro/news/building-a-custom-mcp-server-for-claude", "markdown": "https://wpnews.pro/news/building-a-custom-mcp-server-for-claude.md", "text": "https://wpnews.pro/news/building-a-custom-mcp-server-for-claude.txt", "jsonld": "https://wpnews.pro/news/building-a-custom-mcp-server-for-claude.jsonld"}}