Setting up MCP servers to bridge Claude and your local data

If you want to build tools that actually interact with your filesystem or local APIs, you need to move past simple prompting and into structured context injection.
Getting your first MCP server running locally
You can't just "use" MCP. You need a host—usually Claude Desktop for now—and a server that speaks the protocol. The easiest way to see the magic is to use the pre-built Google Maps or Filesystem servers, but let's build something slightly more customized using the TypeScript SDK.
First, ensure you have Node.js v18 or higher installed. I checked my version with node -v before starting because the older v16 builds kept throwing weird ESM errors during the build step.
Step 1: Initialize the project
Open your terminal and run these commands. Don't skip the initialization; the package.json needs the correct type definition.
mkdir my-mcp-server
cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdkEdit your package.json immediately to include "type": "module". If you forget this, your imports will break, and you'll spend twenty minutes wondering why import isn't recognized.
Step 2: Coding a basic tool
Create a file named index.ts. We aren't building a complex AI; we are building a bridge. The goal is to provide a "tool" that the LLM can call.
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";const server = new Server(
{ name: "local-time-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
// Define what the tool does
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "get_current_time",
description: "Returns the current system time",
inputSchema: { type: "object", properties: {} },
},
],
}));
// Handle the actual logic
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "get_current_time") {
return {
content: [{ type: "text", text: new Date().toISOString() }],
};
}
throw new Error("Tool not found");
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch(console.error);
This looks simple, but the key is the StdioServerTransport. MCP works over standard input/output. This means your server doesn't run a web server on a port; it communicates through the process stream.
Configuring Claude Desktop to recognize your code

This is where most people fail. You can't just run npm start and expect Claude to see it. You have to tell the Claude Desktop app where your executable lives.
On macOS, your config file is located at:~/Library/Application Support/Claude/claude_desktop_config.json
On Windows:%APPDATA%\Claude\claude_desktop_config.json
You need to add your server to the mcpServers object. It looks like this:
{
"mcpServers": {
"my-custom-server": {
"command": "node",
"args": ["/absolute/path/to/my-mcp-server/index.js"]
}
}
}Warning: Use absolute paths. I tried using ~/ in the config and Claude just ignored the server entirely. It doesn't resolve tilde shortcuts.
Testing the connection
Restart Claude Desktop. Completely quit the app (Cmd+Q) and reopen it. Look for a small hammer icon in the chat interface. If that icon isn't there, your JSON is likely malformed or your path is wrong.
| Server Type | Connection Method | Latency (Local) | Best Use Case |
| :--- | :--- | :--- | :--- |
| Stdio (Local) | Standard Input/Output | < 10ms | Local files, SQLite, Scripts |
| SSE (Remote) | HTTP/Server-Sent Events | 50ms - 200ms | Cloud APIs, Shared DBs |
| Custom SDK | Direct Integration | Variable | Specialized enterprise tools |
Debugging the "Silent Fail"
If you're working on complex AI Coding tasks, you'll eventually run into a situation where the tool is listed but fails when called. This usually happens because of environment variables.
When Claude runs your MCP server, it doesn't inherit your terminal's .zshrc or .bashrc settings. If your code needs an API_KEY, you must pass it inside the env object in the claude_desktop_config.json:
"my-custom-server": {
"command": "node",
"args": ["/path/to/index.js"],
"env": {
"MY_SECRET_TOKEN": "sk-12345"
}
}If you don't do this, your server will crash silently, or worse, return a generic "Internal error" to the LLM, leaving you guessing.
Scaling with custom Workflows
Once you have a working server, the real fun begins. You shouldn't just use it for single commands. You can chain these tools together. For instance, if you develop a server that can read your local Jira tickets and another that can read your local code, you can create Workflows that allow an LLM to bridge the gap between project management and actual implementation.
I've found that the most effective way to use this is to build "Read-Only" servers first. If you give an LLM "Write" access to your filesystem without strict constraints, it might decide to delete your node_modules folder just to save space.
If you want to see how others are structuring these connections, checking out the PromptCube homepage can give you a sense of the broader ecosystem. The community there is less about "prompt engineering" and more about actual architectural implementation.
A quick sanity check list for your MCP setup:
package.json set to "type": "module"?If you've checked all these and it's still broken, check the Claude logs. On macOS, they are tucked away in ~/Library/Logs/Claude/. There is usually a very specific error message there about a missing module or a permission denied error that the UI simply won't show you.
All Replies (0)
No replies yet — be the first!