๐ŸŒ 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:

  1. Write a failing unit test (Red)
  2. Write minimum code to pass the unit test (Green)
  3. 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.

MCP Inspector UI

Testing in Inspector

  1. Check Tool list: Verify the 5 registered Tools
  2. Call Tool: Calculate 123 + 456 using the calculate Tool
  3. Verify response: Confirm the result 123 + 456 = 579

MCP Inspector Tool List


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:

MCP Auto Detection

Real Usage Example

You can use MCP Tools in Claude Code:

Claude Code Calculate Demo

User: "Calculate 123 plus 456"
Claude: [Calls calculate Tool: a=123, b=456, operation="add"]
       123 + 456 = 579.

Summary

What We Learned

  1. Model Context Protocol concept: A standard protocol for AI tools to access external data
  2. Architecture: Host โ†’ Client โ†’ MCP Server structure, JSON-RPC 2.0 communication
  3. 3 core features: Tool implementation (functions), Resources (data), Prompts (templates)
  4. MCP Server implementation: TypeScript + @modelcontextprotocol/sdk + zod
  5. 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!