· 2 min read

Building an MCP Server from Scratch

Building an MCP Server from Scratch

Learn how to create a Model Context Protocol (MCP) server in TypeScript that connects to Claude Desktop. This tutorial covers setting up the project, building a simple tool to fetch Pokémon data from the PokéAPI, and integrating it with Claude for real-time responses.

The Model Context Protocol (MCP) provides a way to connect external tools and services directly into AI assistants like Claude. In this tutorial, we’ll walk step by step through creating a simple MCP server in TypeScript, connecting it to Claude Desktop, and building our first tool.

By the end, you’ll have a working MCP server that can fetch Pokémon data from the PokéAPI and return it to Claude.

You can also jump straight to the code on github


Prerequisites

  • Node.js and pnpm installed
  • Basic familiarity with TypeScript
  • Claude Desktop installed

1. Initialize the Project

We’ll start by creating a new project directory and initializing it with pnpm:

mkdir mcp-pokedex
cd mcp-pokedex
pnpm init

This generates a package.json file, which we’ll expand as we go.


2. Install Dependencies

Install the MCP SDK and the zod library for input validation:

pnpm install @modelcontextprotocol/sdk

👉 For compatibility, I had to install a specific version of zod:

pnpm install zod@^3.25.76

Then add development dependencies for TypeScript:

pnpm install -D typescript @types/node

3. Set Up Project Structure

Create a src directory for your code:

mkdir src

Then add an entry file:

touch src/index.ts

4. Configure package.json

Update your package.json so Node knows how to run the project:

{
  "type": "module",
  "main": "dist/index.js",
  "files": ["dist"],
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js"
  }
}

5. Create a tsconfig.json

Add a TypeScript configuration file to the root of the project:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Node",
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}

6. Build a Minimal MCP Server

Let’s start with a barebones MCP server in src/index.ts:

import { Server } from "@modelcontextprotocol/sdk/server";

const server = new Server({
  name: "pokedex",
  version: "1.0.0",
});

server.start();
console.log("MCP server running...");

At this point, you have a server that Claude can connect to—but it doesn’t do anything yet.


7. Add a Client API Helper

Let’s connect to the PokéAPI. Create a new file src/api.ts:

export async function fetchPokemon(name: string) {
  const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${name}`);
  if (!res.ok) throw new Error("Pokemon not found");
  return res.json();
}

This helper will let our tools query Pokémon data.


8. Create Your First Tool

Now let’s add a tool that fetches Pokémon details. Update src/index.ts:

import { Server } from "@modelcontextprotocol/sdk/server";
import { z } from "zod";
import { fetchPokemon } from "./api.js";

const server = new Server({
  name: "pokedex",
  version: "1.0.0",
});

server.tool(
  "get_pokemon",
  "Fetch details about a Pokémon by name",
  {
    name: z.string()
  },
  async handler({ name }) {
    const data = await fetchPokemon(name);
    const types = data.types.map((t: any) => t.type.name).join(", ");
    return {
      content: [{
        type: "text",
        text: `Pokémon: ${data.name}\nTypes: ${types}`
      }],
    }]
  },
});

server.start();
console.log("MCP server running with get_pokemon tool...");

Now our server can respond to MCP requests.


9. Configure Claude Desktop

To connect Claude to the MCP server, edit its config file:

# On mac
~/Library/Application\ Support/Claude/claude_desktop_config.json

# On Linux
/etc/claude-code/managed-settings.json

# On Windows
C:\ProgramData\ClaudeCode\managed-settings.json

Add an entry for your server:

"pokedex": {
  "command": "node",
  "args": [
    "/Users/dev/code/hollanddd/mcp-servers/pokedex/dist/index.js",
    "--port",
    "3000"
  ]
}

Save and close.


10. Build and Run

Compile your TypeScript:

pnpm run build

Restart Claude Desktop (it won’t pick up changes if it’s already running).

Now you should see the pokedex server listed, and you can test your new tool by asking Claude to fetch details about a Pokémon:

“Fetch details about Pikachu.”


11. Troubleshooting

Sometimes your server might not show up in Claude, or tools don’t respond as expected. Here are some steps to debug:

Check the logs

Claude Desktop logs MCP server activity. You can tail the logs like this:

tail -n 20 -f ~/Library/Logs/Claude/mcp-server-pokedex.log

This shows the last 20 lines and streams new logs in real time.

Common issues

  • Claude not detecting the server → Make sure you restarted Claude Desktop after editing the config.
  • Port conflicts → Ensure no other process is running on the same port (e.g., 3000). Change the port in config if needed.
  • Build errors → Run pnpm run build again and check for TypeScript errors.
  • JSON config errors → Verify that claude_desktop_config.json has valid JSON syntax.

🎉 Done

You’ve built and connected your first MCP server:

  • Set up a TypeScript project
  • Created an MCP server with @modelcontextprotocol/sdk
  • Added a tool that integrates with the PokéAPI
  • Connected it to Claude Desktop
  • Debugged with log tailing

From here, you can expand your server with more tools, custom APIs, and even local integrations.