your first MCP server in 10 minutes
Table of content
by Ray Svitla
MCP — model context protocol — is how you give AI agents access to things they can’t normally reach. databases, APIs, local files, whatever. it’s a standard that anthropic open-sourced, and it works with claude code out of the box.
you don’t need a framework. you don’t need a tutorial series. you need about 10 minutes and a text editor.
let’s build one.
what MCP actually is
MCP is a JSON-RPC protocol over stdio (or HTTP with SSE). your server exposes “tools” — functions that the AI can call. the AI decides when to call them, passes arguments, and uses the results.
that’s it. the protocol is simple: the client sends a request, your server returns a response. the complexity lives in what your tools do, not in the protocol itself.
setup
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk
npm install -D typescript @types/node
create tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true
},
"include": ["src/**/*"]
}
update package.json — add "type": "module" and a build script:
{
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
}
the server
create src/index.ts:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
wait — we need zod too:
npm install zod
now the actual server:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "my-first-server",
version: "1.0.0",
});
// a tool that counts words in a string
server.tool(
"count_words",
"count the number of words in a text string",
{
text: z.string().describe("the text to count words in"),
},
async ({ text }) => {
const count = text.trim().split(/\s+/).length;
return {
content: [
{
type: "text",
text: `word count: ${count}`,
},
],
};
}
);
// a more useful tool: get the current timestamp
server.tool(
"timestamp",
"get the current date and time in ISO format",
{},
async () => {
return {
content: [
{
type: "text",
text: new Date().toISOString(),
},
],
};
}
);
// connect via stdio
const transport = new StdioServerTransport();
await server.connect(transport);
build it:
npm run build
that’s your MCP server. two tools, under 50 lines.
connecting to claude code
add it to your project’s .mcp.json (or ~/.claude/mcp.json for global access):
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["/absolute/path/to/my-mcp-server/dist/index.js"]
}
}
}
restart claude code. it will discover the server, see the tools, and use them when relevant.
ask claude code “what time is it?” and it’ll call your timestamp tool. ask “how many words in this paragraph?” and it’ll use count_words.
making it actually useful
word counting is a demo. here’s a pattern that’s actually worth building — a tool that queries your local database:
import Database from "better-sqlite3";
server.tool(
"query_db",
"run a read-only SQL query against the local database",
{
query: z.string().describe("SQL SELECT query to execute"),
},
async ({ query }) => {
// safety: only allow SELECT
if (!query.trim().toUpperCase().startsWith("SELECT")) {
return {
content: [{ type: "text", text: "error: only SELECT queries allowed" }],
isError: true,
};
}
const db = new Database("./data.db", { readonly: true });
try {
const rows = db.prepare(query).all();
return {
content: [
{
type: "text",
text: JSON.stringify(rows, null, 2),
},
],
};
} catch (e: any) {
return {
content: [{ type: "text", text: `query error: ${e.message}` }],
isError: true,
};
} finally {
db.close();
}
}
);
now claude code can query your database. read-only by design — the safety boundary is in your code, not in the AI’s judgment.
what to build next
the pattern is always the same: define a tool name, describe what it does, specify the input schema with zod, return text content.
ideas that take 15 minutes each:
→ file search. grep your codebase with custom filters. → API proxy. hit your staging API and return results. → log reader. tail your application logs and return the last N lines. → environment info. report disk space, memory, running processes. → note taker. append to a markdown file (now the agent has memory).
how it works under the hood
when claude code starts, it reads your MCP config and launches each server as a child process. communication happens over stdin/stdout using JSON-RPC 2.0.
the client sends tools/list to discover available tools. when the AI decides to use a tool, the client sends tools/call with the tool name and arguments. your server executes the function and returns the result.
the protocol also supports “resources” (data the AI can read) and “prompts” (reusable prompt templates), but tools are where 90% of the value lives.
the principle underneath
MCP is just a structured way to let AI call functions. the principle is older than LLMs — it’s remote procedure calls with a schema. if you understand REST APIs, you understand MCP.
this means MCP skills transfer. the protocol will evolve, new clients will adopt it, but the core idea — describe a function, let the AI call it — isn’t going anywhere. you’re not learning a framework. you’re learning a pattern.
build something small. see what the agent does with it. then build something you actually need.
→ claude code context optimization — manage what the agent sees → agent-first design — structure your repo for AI → CLAUDE.md guide — configure claude code per project
Ray Svitla stay evolving