๐ Translation: Translated from Korean.
TL;DR
MCP (Model Context Protocol) is a standard protocol that enables AI tools to access external data and functionality. It solves the most critical external integration challenge in AI development.
What you’ll learn in this post:
- Why Model Context Protocol is necessary and what problems it solves
- Understanding the Host, Client, and MCP Server architecture
- How to implement an MCP Server with TypeScript and Tool implementation methods
- Writing unit tests using TDD approach
- Integrating with Claude Code for real-world AI tool usage
Complete Code: my-first-mcp – An MCP Server tutorial for AI development beginners
The Limitations of AI Tools and the Emergence of Model Context Protocol
The Problem: AI Doesn’t Know the Outside World
AI tools like Claude and GPT only have knowledge based on their training data. Accessing real-time information, local files, databases, or external APIs requires separate integration. This is a core challenge in AI development.
Limitations of existing approaches:
- Function Calling: Different implementation methods for each AI tool provider
- Plugins/Extensions: Separate Tool implementation required for each AI tool
- Custom Integration: Repetitive individual implementations without standards
The Solution: Model Context Protocol (MCP)
Model Context Protocol, announced by Anthropic in November 2024, solves this problem. It provides a standardized protocol for AI tools to access external context.
Core values of Model Context Protocol:
- Standardization: Build an MCP Server once, use it across multiple AI tools
- Flexibility: Connect various data sources and Tool implementations
- Extensibility: Leverage the community-built MCP Server ecosystem
- Security: Explicit permission management and sandboxing
Model Context Protocol Core Architecture
The Relationship Between Host, Client, and MCP Server
Model Context Protocol consists of three components:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Host (Claude Code) โ
โ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โ
โ โ Client โ โ Client โ โ Client โ โ Client โ โ
โ โโโโโโฌโโโโโ โโโโโโฌโโโโโ โโโโโโฌโโโโโ โโโโโโฌโโโโโ โ
โโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโโโโผโโโโโโโโโโ
โ โ โ โ
โผ โผ โผ โผ
โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ
โ MCP โ โ MCP โ โ MCP โ โ MCP โ
โ Server โ โ Server โ โ Server โ โ Server โ
โ (Files) โ โ (Git) โ โ (API) โ โ (DB) โ
โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ
| Component | Role | Example |
|---|---|---|
| Host | AI tool that hosts MCP clients | Claude Code, Claude Desktop |
| Client | Manages 1:1 connection with MCP Server | Automatically created inside the Host |
| MCP Server | Process that provides Tool implementations | File system, GitHub, DB, etc. |
Communication Method: JSON-RPC 2.0
MCP uses the JSON-RPC 2.0 protocol. Requests and responses are clear and standardized.
// Request example
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "calculate",
"arguments": { "a": 10, "b": 5, "operation": "add" }
}
}
// Response example
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [{ "type": "text", "text": "10 + 5 = 15" }]
}
}
The 3 Core Features of MCP
An MCP Server can provide three types of functionality:
1. Tools: Functions That AI Executes
A Tool is a function that AI can call. It performs actual operations like calculations, file processing, and API calls.
// Tool registration example
server.tool(
"calculate", // Tool name
"Performs arithmetic operations on two numbers", // Description
{ // Input schema (zod)
a: z.number(),
b: z.number(),
operation: z.enum(["add", "subtract", "multiply", "divide"])
},
async ({ a, b, operation }) => { // Execution function
// Return result
}
);
2. Resources: Data Provided to AI
A Resource is data that AI can reference. It provides configuration files, documents, status information, and more.
// Resource registration example
server.resource(
"config://app", // Resource URI
"Application configuration", // Description
async () => ({
contents: [{ type: "text", text: JSON.stringify(config) }]
})
);
3. Prompts: Templated Prompts
A Prompt is a predefined prompt template. It’s useful for repetitive tasks like code reviews and translations.
// Prompt registration example
server.prompt(
"code-review",
"Request a code review",
{ code: z.string() },
async ({ code }) => ({
messages: [{
role: "user",
content: { type: "text", text: `Please review the following code:\n${code}` }
}]
})
);
When to Use Which Feature?
| Feature | When to Use | Examples |
|---|---|---|
| Tool | Performing actions | File creation, API calls, calculations |
| Resource | Referencing data | Config files, documents, status |
| Prompt | Reusing templates | Code reviews, translation requests |
Setting Up the Development Environment
Installing Required Tools
Tools needed for MCP Server development:
# Verify Node.js 20+ node --version # v20.x.x or higher # Create project directory mkdir my-first-mcp cd my-first-mcp # Initialize npm npm init -y
TypeScript Configuration
# Install dependencies npm install @modelcontextprotocol/sdk zod npm install -D typescript @types/node vitest
tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
package.json key settings:
{
"name": "my-first-mcp",
"version": "1.0.0",
"type": "module",
"main": "dist/index.js",
"bin": { "my-first-mcp": "dist/index.js" },
"scripts": {
"build": "tsc",
"test": "vitest run",
"inspect": "npx @modelcontextprotocol/inspector dist/index.js"
}
}
Implementing Your First MCP Server
Project Structure
Following TDD principles, we design a testable structure:
my-first-mcp/ โโโ src/ โ โโโ index.ts # MCP Server entry point โ โโโ tools.ts # Core logic (pure functions) โ โโโ tools.test.ts # Unit tests โโโ dist/ # Build output โโโ package.json โโโ tsconfig.json โโโ vitest.config.ts
Core Logic: tools.ts
We separate core logic into testable pure functions:
// src/tools.ts
// Tool 1: Get current time
export type TimeFormat = "full" | "date" | "time";
export interface TimeResult {
formatted: string;
timezone: string;
}
export function formatTime(
date: Date,
timezone: string = "Asia/Seoul",
format: TimeFormat = "full"
): TimeResult {
let options: Intl.DateTimeFormatOptions = { timeZone: timezone };
switch (format) {
case "date":
options = { ...options, dateStyle: "full" };
break;
case "time":
options = { ...options, timeStyle: "long" };
break;
case "full":
default:
options = { ...options, dateStyle: "full", timeStyle: "long" };
break;
}
const formatted = date.toLocaleString("ko-KR", options);
return { formatted, timezone };
}
// Tool 2: Calculator
export type Operation = "add" | "subtract" | "multiply" | "divide";
export interface CalculateResult {
result: number;
expression: string;
isError: boolean;
errorMessage?: string;
}
export function calculate(
a: number,
b: number,
operation: Operation
): CalculateResult {
const symbols: Record<Operation, string> = {
add: "+",
subtract: "-",
multiply: "ร",
divide: "รท",
};
if (operation === "divide" && b === 0) {
return {
result: NaN,
expression: `${a} ${symbols[operation]} ${b}`,
isError: true,
errorMessage: "Error: Cannot divide by zero.",
};
}
let result: number;
switch (operation) {
case "add": result = a + b; break;
case "subtract": result = a - b; break;
case "multiply": result = a * b; break;
case "divide": result = a / b; break;
}
return {
result,
expression: `${a} ${symbols[operation]} ${b} = ${result}`,
isError: false,
};
}
// Tool 3: Random number generation
export function generateRandomNumbers(
min: number,
max: number,
count: number = 1
) {
if (min > max) {
return {
numbers: [],
min, max,
isError: true,
errorMessage: "Error: Minimum value is greater than maximum value.",
};
}
const numbers: number[] = [];
for (let i = 0; i < count; i++) {
numbers.push(Math.floor(Math.random() * (max - min + 1)) + min);
}
return { numbers, min, max, isError: false };
}
// Tool 4: Reverse string
export function reverseString(text: string) {
return {
original: text,
reversed: text.split("").reverse().join(""),
};
}
// Tool 5: Server info
export function getServerInfo() {
return {
name: "my-first-mcp",
version: "1.0.0",
description: "MCP Server Development Tutorial",
tools: [
"get_current_time - Get current time",
"calculate - Arithmetic calculator",
"get_random_number - Generate random numbers",
"reverse_string - Reverse a string",
"get_server_info - Get server information",
],
};
}
MCP Server Entry Point: index.ts
#!/usr/bin/env node
// src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import {
formatTime,
calculate,
generateRandomNumbers,
reverseString,
getServerInfo,
type TimeFormat,
type Operation,
} from "./tools.js";
// Create MCP Server instance
const server = new McpServer({
name: "my-first-mcp",
version: "1.0.0",
});
// Tool 1: Get current time
server.tool(
"get_current_time",
"Returns the current date and time. You can specify the timezone.",
{
timezone: z.string().optional()
.describe("Timezone (e.g., Asia/Seoul, America/New_York)"),
format: z.enum(["full", "date", "time"]).optional()
.describe("Output format: full, date only, or time only"),
},
async ({ timezone, format }) => {
const result = formatTime(
new Date(),
timezone || "Asia/Seoul",
(format || "full") as TimeFormat
);
return {
content: [{
type: "text",
text: `Current time (${result.timezone}): ${result.formatted}`,
}],
};
}
);
// Tool 2: Calculator
server.tool(
"calculate",
"Performs arithmetic operations on two numbers.",
{
a: z.number().describe("First number"),
b: z.number().describe("Second number"),
operation: z.enum(["add", "subtract", "multiply", "divide"])
.describe("Operation type"),
},
async ({ a, b, operation }) => {
const result = calculate(a, b, operation as Operation);
if (result.isError) {
return {
content: [{ type: "text", text: result.errorMessage! }],
isError: true,
};
}
return {
content: [{ type: "text", text: result.expression }],
};
}
);
// Tool 3: Random number generation
server.tool(
"get_random_number",
"Generates random integers within the specified range.",
{
min: z.number().int().describe("Minimum value"),
max: z.number().int().describe("Maximum value"),
count: z.number().int().min(1).max(10).optional()
.describe("Number of random numbers to generate (1-10)"),
},
async ({ min, max, count }) => {
const result = generateRandomNumbers(min, max, count || 1);
if (result.isError) {
return {
content: [{ type: "text", text: result.errorMessage! }],
isError: true,
};
}
const n = result.numbers.length;
const text = n === 1
? `Random number (${min}~${max}): ${result.numbers[0]}`
: `${n} random numbers (${min}~${max}): ${result.numbers.join(", ")}`;
return { content: [{ type: "text", text }] };
}
);
// Tool 4: Reverse string
server.tool(
"reverse_string",
"Reverses the input string and returns it.",
{ text: z.string().min(1).describe("String to reverse") },
async ({ text }) => {
const result = reverseString(text);
return {
content: [{
type: "text",
text: `Original: ${result.original}\nReversed: ${result.reversed}`,
}],
};
}
);
// Tool 5: Server info
server.tool(
"get_server_info",
"Returns MCP Server information and available Tool list.",
{},
async () => {
const info = getServerInfo();
const text = `=== ${info.name} ===\nVersion: ${info.version}\n\nAvailable Tools:\n${info.tools.map((t, i) => `${i + 1}. ${t}`).join("\n")}`;
return { content: [{ type: "text", text }] };
}
);
// Start server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("my-first-mcp server has started.");
}
main().catch((error) => {
console.error("Server startup failed:", error);
process.exit(1);
});
Writing Unit Tests with TDD
In AI development, TDD (Test-Driven Development) is a core methodology for ensuring the quality of MCP Server Tool implementations.
TDD Principles
Write unit tests following the Red โ Green โ Refactor cycle:
- Write a failing unit test (Red)
- Write minimum code to pass the unit test (Green)
- Improve the code (Refactor)
Tidy First principle:
- Separate structural changes from behavioral changes
- Verify with unit tests after structural changes
vitest Configuration
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
environment: "node",
include: ["src/**/*.test.ts"],
},
});
Test Code: tools.test.ts
// src/tools.test.ts
import { describe, it, expect } from "vitest";
import {
formatTime,
calculate,
generateRandomNumbers,
reverseString,
getServerInfo,
} from "./tools.js";
describe("formatTime", () => {
it("formats with default timezone (Asia/Seoul)", () => {
const testDate = new Date("2025-11-28T09:30:00Z");
const result = formatTime(testDate);
expect(result.timezone).toBe("Asia/Seoul");
expect(result.formatted).toContain("2025");
});
it("supports custom timezone", () => {
const testDate = new Date("2025-11-28T09:30:00Z");
const result = formatTime(testDate, "America/New_York");
expect(result.timezone).toBe("America/New_York");
});
it("outputs date only", () => {
const testDate = new Date("2025-11-28T09:30:00Z");
const result = formatTime(testDate, "Asia/Seoul", "date");
expect(result.formatted).toContain("2025");
});
});
describe("calculate", () => {
it("performs addition", () => {
const result = calculate(123, 456, "add");
expect(result.result).toBe(579);
expect(result.expression).toBe("123 + 456 = 579");
expect(result.isError).toBe(false);
});
it("handles division by zero error", () => {
const result = calculate(100, 0, "divide");
expect(result.isError).toBe(true);
expect(result.errorMessage).toBe("Error: Cannot divide by zero.");
});
it("performs multiplication", () => {
const result = calculate(15, 8, "multiply");
expect(result.result).toBe(120);
expect(result.expression).toBe("15 ร 8 = 120");
});
});
describe("generateRandomNumbers", () => {
it("generates random number within range", () => {
const result = generateRandomNumbers(1, 10);
expect(result.numbers).toHaveLength(1);
expect(result.numbers[0]).toBeGreaterThanOrEqual(1);
expect(result.numbers[0]).toBeLessThanOrEqual(10);
});
it("generates multiple random numbers (lottery style)", () => {
const result = generateRandomNumbers(1, 45, 6);
expect(result.numbers).toHaveLength(6);
result.numbers.forEach((num) => {
expect(num).toBeGreaterThanOrEqual(1);
expect(num).toBeLessThanOrEqual(45);
});
});
it("handles min > max error", () => {
const result = generateRandomNumbers(100, 10);
expect(result.isError).toBe(true);
expect(result.errorMessage).toContain("Minimum value is greater than maximum");
});
});
describe("reverseString", () => {
it("reverses English string", () => {
const result = reverseString("hello");
expect(result.reversed).toBe("olleh");
});
it("reverses Korean string", () => {
const result = reverseString("์๋
");
expect(result.reversed).toBe("๋
์");
});
it("reverses string with special characters", () => {
const result = reverseString("Hello MCP!");
expect(result.reversed).toBe("!PCM olleH");
});
});
describe("getServerInfo", () => {
it("returns server name and version", () => {
const info = getServerInfo();
expect(info.name).toBe("my-first-mcp");
expect(info.version).toBe("1.0.0");
});
it("includes list of 5 tools", () => {
const info = getServerInfo();
expect(info.tools).toHaveLength(5);
expect(info.tools.some(t => t.includes("calculate"))).toBe(true);
});
});
Running Unit Tests
# Run unit tests npm test # Results # โ src/tools.test.ts (31 tests) 18ms # Test Files 1 passed (1) # Tests 31 passed (31)
The 31 unit tests written using TDD validate all Tool implementations. In AI development, such unit tests ensure the stability of MCP Servers.
Testing MCP Server with MCP Inspector
MCP Inspector is the official AI tool for testing MCP Server Tool implementations.
Running Inspector
In the AI development and TDD process, MCP Inspector is an essential tool. Test with Inspector before connecting to AI tools like Claude Code.
# Build npm run build # Run Inspector npm run inspect # or npx @modelcontextprotocol/inspector dist/index.js
Access http://localhost:6274 in your browser to open the MCP Inspector UI.

Testing in Inspector
- Check Tool list: Verify the 5 registered Tools
- Call Tool: Calculate
123 + 456using the calculate Tool - Verify response: Confirm the result
123 + 456 = 579

Integrating with Claude Code AI Tool
Registering MCP Server
Once you’ve completed TDD unit tests and Inspector testing, you’re ready to integrate with the Claude Code AI tool.
# Add MCP Server to Claude Code AI tool claude mcp add my-first-mcp -- node /path/to/my-first-mcp/dist/index.js # Check connection status claude mcp list # my-first-mcp: โ Connected
Project-Specific Auto Connection (.mcp.json)
Adding an .mcp.json file to your project allows Claude Code to automatically detect the MCP Server:
{
"mcpServers": {
"my-first-mcp": {
"type": "stdio",
"command": "node",
"args": ["dist/index.js"],
"env": {}
}
}
}
When entering the project directory, an auto-detection dialog is displayed:

Real Usage Example
You can use MCP Tools in Claude Code:

User: "Calculate 123 plus 456"
Claude: [Calls calculate Tool: a=123, b=456, operation="add"]
123 + 456 = 579.
Summary
What We Learned
- Model Context Protocol concept: A standard protocol for AI tools to access external data
- Architecture: Host โ Client โ MCP Server structure, JSON-RPC 2.0 communication
- 3 core features: Tool implementation (functions), Resources (data), Prompts (templates)
- MCP Server implementation: TypeScript + @modelcontextprotocol/sdk + zod
- TDD unit tests: Separate pure functions โ unit tests โ refactoring
Model Context Protocol is becoming an essential technology in AI development. Use the MCP Server and Tool implementation methods learned in this post to build various AI tools.
Project Structure
my-first-mcp/ โโโ src/ โ โโโ index.ts # MCP Server (Handler) โ โโโ tools.ts # Tool implementation logic (Pure Functions) โ โโโ tools.test.ts # Unit tests (31 tests) โโโ dist/ โโโ package.json โโโ tsconfig.json โโโ vitest.config.ts
Key Commands
npm run build # TypeScript build npm test # Run unit tests (31 tests) npm run inspect # Test Tool implementation with MCP Inspector claude mcp add # Register MCP Server with Claude Code AI tool
Next Episode Preview
Day 2: Practical MCP Server – Project Analysis AI Tool
- Tool implementation for file system access
- Providing project configuration via Resources
- Utilizing Prompt templates
- Automating real project analysis
Stay tuned for the next post if you’re interested in AI development and Model Context Protocol!
References
Official Documentation
Tools
This is the first post in the MCP Server development series. Feel free to leave questions or feedback in the comments!
Leave A Comment