ClaudeSkills" 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
dothings—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 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.
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, 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:
- Node Path: Claude can't find
node
. Use the full path to the binary (find it with which node
).
-
Async Hangs: If your tool takes more than 30 seconds to respond, Claude will often timeout. Use streaming or optimize your queries.
-
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:
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const heavyLib = await import('some-massive-library'); // Slows down every call
// ... logic
});
Good:
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 →
All Replies (0) #
No replies yet — be the first!