You've probably used Cursor to write code, refactor functions, and chase down bugs. It's fast. It's good. But you're still the one running the terminal commands, checking the git log, reading error messages, and translating the context back to Claude one prompt at a time.
MCP changes that. Model Context Protocol is Anthropic's standard for giving AI models structured access to external tools and context — not just your open files, but shell commands, APIs, databases, custom logic, whatever you wire up. Cursor supports it natively. Once you have an MCP server running, Claude can call your tools directly, mid-conversation, without you relaying information back and forth.
This guide walks you through building one from scratch, configuring it in Cursor, and using it. The same steps work for Windsurf and Claude Desktop.
What MCP actually is
The Model Context Protocol defines a standard interface between an AI model and a set of tools. Your MCP server is a process that exposes named tools — each with a schema describing its inputs — that the model can call at any point in a conversation.
When Claude needs to run a tool, it sends a structured request: tool name + arguments. Your server receives it, executes whatever logic you've written, and returns a result. The model gets the result and continues. From your perspective, it looks like Claude ran a command. Under the hood, it called your server.
The key insight: you define the tools. They can do anything — read your project files, run linters, query a database, hit an internal API, check git status. The model doesn't care how the tool is implemented. It just calls it by name.
Transport note: MCP servers communicate via stdio (standard I/O) or HTTP SSE. Cursor uses stdio by default — your server runs as a subprocess and speaks JSON over stdin/stdout. No ports, no network config required.
Step 1: Scaffold the MCP server
We'll build a TypeScript MCP server using the official @modelcontextprotocol/sdk. The SDK handles the protocol details — you just define your tools.
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
npx tsc --init --module NodeNext --moduleResolution NodeNext --outDir dist --rootDir src
Then create your server entry point at src/index.ts:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { execSync } from "child_process";
import { readFileSync, existsSync } from "fs";
import { join } from "path";
const server = new McpServer({
name: "my-mcp-server",
version: "1.0.0",
});
// Tool 1: Run git status in a given directory
server.tool(
"git_status",
"Get the current git status of a project",
{
projectPath: z.string().describe("Absolute path to the project directory"),
},
async ({ projectPath }) => {
try {
const output = execSync("git status --short", {
cwd: projectPath,
encoding: "utf8",
});
return {
content: [
{
type: "text",
text: output || "Working tree clean",
},
],
};
} catch (err) {
return {
content: [{ type: "text", text: `Error: ${(err as Error).message}` }],
isError: true,
};
}
}
);
// Tool 2: Read a file and return its contents
server.tool(
"read_file",
"Read the contents of a file",
{
filePath: z.string().describe("Absolute path to the file"),
},
async ({ filePath }) => {
if (!existsSync(filePath)) {
return {
content: [{ type: "text", text: `File not found: ${filePath}` }],
isError: true,
};
}
const contents = readFileSync(filePath, "utf8");
return {
content: [{ type: "text", text: contents }],
};
}
);
// Tool 3: Run npm audit and surface critical issues
server.tool(
"npm_audit",
"Run npm audit on a project and return vulnerability summary",
{
projectPath: z.string().describe("Absolute path to the project directory"),
},
async ({ projectPath }) => {
try {
const output = execSync("npm audit --json", {
cwd: projectPath,
encoding: "utf8",
});
const result = JSON.parse(output);
const { critical = 0, high = 0, moderate = 0 } =
result.metadata?.vulnerabilities ?? {};
return {
content: [
{
type: "text",
text: `Vulnerabilities: ${critical} critical, ${high} high, ${moderate} moderate`,
},
],
};
} catch (err) {
return {
content: [{ type: "text", text: `Error running audit: ${(err as Error).message}` }],
isError: true,
};
}
}
);
// Start server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP server running on stdio");
}
main().catch(console.error);
Add a build script to package.json:
{
"scripts": {
"build": "tsc",
"dev": "tsx src/index.ts"
}
}
Build it:
npm run build
Your server is now at dist/index.js. It speaks MCP over stdio. Next, tell Cursor about it.
Step 2: Configure it in Cursor
Cursor reads MCP server configuration from ~/.cursor/mcp.json (global, all projects) or .cursor/mcp.json inside a project folder (project-local). Use the project-local file when the server is project-specific — it's cleaner and keeps the config in version control.
Create .cursor/mcp.json in your project root:
{
"mcpServers": {
"my-mcp-server": {
"command": "node",
"args": ["/absolute/path/to/my-mcp-server/dist/index.js"],
"env": {}
}
}
}
Or, if you're using npx to run a published package (like midas):
{
"mcpServers": {
"midas": {
"command": "npx",
"args": ["merlyn-mcp"],
"env": {}
}
}
}
Path matters: Use the absolute path to your compiled JS file. Relative paths break when Cursor launches the server subprocess from a different working directory. When in doubt, use $(pwd) to confirm the path before pasting it in.
After saving, restart Cursor (or reload the window with Cmd+Shift+P → Reload Window). Open Cursor Settings → MCP to confirm your server shows up with a green status indicator. If it's red, check the server logs — Cursor surfaces stderr output there.
Step 3: Call your tools from Claude
Open Cursor's chat panel (Agent mode). Claude now has access to your registered tools. You don't need to do anything special to invoke them — just describe what you want, and Claude will call the right tool if it determines it needs to.
A few example prompts that trigger tool calls:
# Triggers git_status
"What's the current git state of this project?"
# Triggers read_file
"Read src/auth/session.ts and tell me if token refresh is handled."
# Triggers npm_audit
"Run an npm audit on this project and tell me if there are any critical vulnerabilities."
Claude will show a tool call indicator in the chat — you'll see the tool name and arguments before the result appears. You can inspect exactly what it called and why. If you want to be explicit, you can name the tool directly:
"Use git_status to check /Users/me/Projects/myapp, then read the CHANGELOG.md
and summarize what changed in the last release."
That's a two-tool chain — Claude calls git_status, reads the output, then calls read_file, and synthesizes the result. No copy-pasting. No context relay. The model does the work.
Useful tools to expose
The three tools above are a starting point. Here's what's actually useful to expose — organized by what problem they solve:
The pattern that works best: expose tools that give Claude precise, scoped answers to questions it would otherwise have to guess at. File structure, test results, lint output, git state — these are all things Claude can't see from your open tabs, but changes its recommendations dramatically when it can.
What midas exposes
midas-mcp is an MCP server built specifically for the Golden Code methodology. It exposes tools that run production-readiness audits on your codebase automatically — the kind of checks that catch gaps before they hit production.
A few of the tools it registers:
- midas_completeness — scores your project across all 12 production ingredients
- midas_phase — detects which development phase you're in (PLAN / BUILD / SHIP / GROW)
- midas_audit — runs a targeted audit on a specific ingredient like Auth, Security, or Testing
- midas_preflight — pre-launch checklist that blocks deploy if critical items are missing
- midas_suggest — surfaces context-aware next steps based on your current codebase state
You wire it up the same way — one line in your .cursor/mcp.json:
{
"mcpServers": {
"midas": {
"command": "npx",
"args": ["merlyn-mcp"],
"env": {}
}
}
}
Then ask Claude to run any of those tools by name, or describe what you want and let it pick the right one. "Check if this project is ready to ship" will trigger midas_preflight. "What's my production readiness score?" triggers midas_completeness.
Tips for building reliable MCP tools
A few things that separate a tool Claude actually uses from one it calls once and never again:
- Write descriptive tool names and descriptions. The model decides which tool to call based on your description string — not the implementation.
"git_status"with a clear description beats"run_cmd"with a vague one every time. - Return structured, scannable text. Claude parses your tool output to decide what to do next. Compact JSON or labeled text ("3 critical, 0 high") beats raw terminal output with ANSI codes and noise.
- Handle errors explicitly. Return an
isError: trueresult with a clear message when things go wrong. Throwing an unhandled exception crashes the tool call and leaves Claude without useful feedback. - Keep tools focused. One tool per concern. A
project_healthtool that checks git, tests, lint, and dependencies in one call is harder to invoke selectively than four separate tools. Let Claude compose them. - Log to stderr, not stdout. Your stdout is the MCP wire protocol. If you accidentally write debug output to stdout, you'll corrupt the response and the tool call will fail silently.
Working in Windsurf or Claude Desktop? The same MCP config structure works — both support mcpServers in their config files. Windsurf reads from ~/.codeium/windsurf/mcp_config.json. Claude Desktop reads from ~/Library/Application Support/Claude/claude_desktop_config.json on macOS. The server code is identical.
The mental shift MCP enables is significant. You stop being the context relay between your terminal and your AI IDE. Claude can see what it needs to see, call what it needs to call, and give you answers that are grounded in the actual state of your project — not just what you happened to paste into the chat.
Start small: one or two tools, the things you find yourself copying and pasting most often. Build from there.
Wire up midas in 30 seconds
Add merlyn-mcp to your Cursor config and get production-readiness tools in Claude — no API key, no setup.