Tutorial10 min read

How to Build Your Own MCP Server: Complete Tutorial 2026

Learn how to build a custom MCP server from scratch using the TypeScript SDK. Define tools, handle requests, connect to Claude Desktop, and ship a working MCP integration in under an hour.

By MyMCPTools Teamยท

Building a custom MCP server is the fastest way to connect any tool, API, or data source to Claude, Cursor, and other MCP-compatible AI clients. Once your server is running, your AI assistant can call your custom tools just like it calls filesystem or GitHub โ€” conversationally, with context, in real time.

This tutorial walks through building a working MCP server in TypeScript from scratch. By the end, you'll have a server that Claude Desktop can connect to and use.

What Is an MCP Server, Exactly?

An MCP server is a process that exposes structured "tools" to an AI client via the Model Context Protocol. Each tool has a name, description, and input schema. The AI client discovers your tools, decides when to call them, and passes structured arguments. Your server executes the logic and returns a result.

Think of it as a type-safe function call that your AI makes on your behalf โ€” but with natural language deciding when and why.

Prerequisites

  • Node.js 18+ installed
  • Claude Desktop or another MCP client
  • Basic TypeScript familiarity

Step 1: Initialize the Project

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

Update tsconfig.json to target ES2022 with module resolution set to node:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "strict": true
  }
}

Step 2: Define Your Server

Create src/index.ts. This is the full skeleton of an MCP server:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";

const server = new Server(
  { name: "my-mcp-server", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "hello_world",
      description: "Returns a greeting for a given name",
      inputSchema: {
        type: "object",
        properties: {
          name: { type: "string", description: "The name to greet" },
        },
        required: ["name"],
      },
    },
  ],
}));

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === "hello_world") {
    const { name } = request.params.arguments as { name: string };
    return {
      content: [{ type: "text", text: `Hello, ${name}! Your MCP server is working.` }],
    };
  }
  throw new Error(`Unknown tool: ${request.params.name}`);
});

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
}

main().catch(console.error);

Step 3: Add a More Useful Tool

Replace the hello world tool with something practical โ€” a tool that fetches weather data from a public API:

// In ListToolsRequestSchema handler:
{
  name: "get_weather",
  description: "Get current weather for a city",
  inputSchema: {
    type: "object",
    properties: {
      city: { type: "string", description: "City name (e.g. 'San Francisco')" },
    },
    required: ["city"],
  },
}

// In CallToolRequestSchema handler:
if (request.params.name === "get_weather") {
  const { city } = request.params.arguments as { city: string };
  const response = await fetch(
    `https://wttr.in/${encodeURIComponent(city)}?format=3`
  );
  const text = await response.text();
  return { content: [{ type: "text", text }] };
}

Step 4: Connect to Claude Desktop

Add your server to Claude Desktop's config file. On Mac, edit ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "my-mcp-server": {
      "command": "npx",
      "args": ["tsx", "/path/to/my-mcp-server/src/index.ts"]
    }
  }
}

Restart Claude Desktop. In a new conversation, click the tools icon (๐Ÿ”ง) โ€” you should see get_weather listed. Ask Claude "What's the weather in Tokyo?" and watch it call your server.

Step 5: Add Input Validation with Zod

For production servers, validate inputs with Zod to get type safety and clear error messages:

const WeatherInput = z.object({
  city: z.string().min(1).max(100),
});

// In your handler:
const parsed = WeatherInput.safeParse(request.params.arguments);
if (!parsed.success) {
  return {
    content: [{ type: "text", text: `Invalid input: ${parsed.error.message}` }],
    isError: true,
  };
}
const { city } = parsed.data;

Step 6: Add Resources (Optional)

Beyond tools, MCP servers can expose "resources" โ€” persistent data that AI clients can read at any time. This is useful for configuration, documentation, or structured data:

import { ListResourcesRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";

server.setRequestHandler(ListResourcesRequestSchema, async () => ({
  resources: [
    {
      uri: "config://server-info",
      name: "Server Configuration",
      mimeType: "application/json",
    },
  ],
}));

server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  if (request.params.uri === "config://server-info") {
    return {
      contents: [{
        uri: "config://server-info",
        mimeType: "application/json",
        text: JSON.stringify({ version: "1.0.0", tools: ["get_weather"] }),
      }],
    };
  }
  throw new Error(`Unknown resource: ${request.params.uri}`);
});

Best Practices for Production MCP Servers

  • Keep tools focused. One tool per action. AI clients pick tools based on their description โ€” precise tools get picked accurately.
  • Write clear descriptions. The tool description is the interface. "Fetches weather data" is useless. "Returns current temperature, conditions, and humidity for a city name" is actionable.
  • Return structured text. Format output as Markdown when possible โ€” AI clients render it better in conversation.
  • Handle errors gracefully. Return isError: true with a human-readable message instead of throwing โ€” the AI can recover and explain what went wrong.
  • Scope access carefully. Only expose what the AI needs. A filesystem server limited to /home/user/projects is safer than one with unrestricted access.

Publishing Your MCP Server

Once your server works locally, you can:

  • Publish to npm so others can install it with npx your-server
  • Submit it to MyMCPTools to get discovered by thousands of developers
  • Open-source it on GitHub and add it to awesome-mcp-server lists

Related guides:

Recommended Tools

Better Stack

Free Plan

Get alerted when your APIs, browser tests, payment pipelines, or MCP server dependencies go down. Used by 100K+ developers.

Start monitoring free โ†’

1Password

14-day Free Trial

Store and inject API keys, payment credentials, tokens, and file access secrets into your MCP server configs. Trusted by 150K+ developers.

Try 1Password free โ†’

๐Ÿ”ง MCP Servers Mentioned in This Article

๐Ÿ“

Filesystem

Secure file operations with configurable access controls. Read, write, and manage files safely.

Localโœ“
๐ŸŒ

Fetch

Web content fetching and conversion for efficient LLM usage. Extract readable content from any URL.

Localโœ“
๐Ÿง 

Memory

Knowledge graph-based persistent memory system. Store and retrieve contextual information.

Localโœ“
๐Ÿ’ป

Everything

Reference/test server with prompts, resources, and tools. Perfect for testing MCP implementations.

Localโœ“
๐Ÿ’ป

GitHub MCP Server

The GitHub MCP server is GitHub's official Model Context Protocol integration, giving AI assistants like Claude and Cursor direct, authenticated access to the GitHub platform and its full developer surface. With this MCP server, you can ask your AI to read and write repository files, create and merge branches, open and review pull requests, comment on and close issues, trigger GitHub Actions workflows, search across code repositories with GitHub's code search, and inspect commit history โ€” all through natural-language prompts in your AI interface. Developers use it to supercharge code review workflows, automate issue triage, generate PR descriptions from diffs, bulk-update repository settings, and wire AI agents into CI/CD pipelines. The GitHub MCP server connects via a GITHUB_PERSONAL_ACCESS_TOKEN environment variable with scopes for the operations you need, keeping authentication clean and auditable. Install with Docker: `docker run -e GITHUB_PERSONAL_ACCESS_TOKEN=<token> ghcr.io/github/github-mcp-server` โ€” or configure it as a remote MCP server in Claude Desktop, Cursor, VS Code, Windsurf, and Cline. With over 8,000 GitHub stars, it is the most widely deployed official code-platform MCP server and the reference implementation for AI-native GitHub automation.

Auth requiredโœ“

๐Ÿ“š More from the Blog