Build Your Own MCP Server in 2026: A Practical Model Context Protocol Guide

·16 min read·Evergreen Tools Team

💡 Tool TipWhile building an MCP server, use Evergreen Tools' JSON Formatter to validate mcpServers config, Regex Tester to debug slugify logic, and Base64 Encode/Decode for auth headers — great companions for server development!

By 2026, MCP (Model Context Protocol) has become the USB-C port of AI agents connecting to external tools. Since Anthropic open-sourced it, OpenAI, Google, and Microsoft have added native support, and hosts like Claude, Cursor, and Windsurf are all compatible. This guide builds a real, runnable MCP server from scratch — understand the architecture, write your first tool, register it with hosts, and handle errors. All code runs as-is.

MCP protocol architecture

From N x M to N + M: one protocol connects all tools

1. What Problem MCP Actually Solves

Before MCP, every model x every tool needed a custom connector — Claude to Slack, GitHub, Jira, each hand-rolled (the N x M problem in code sample 1). After MCP, model hosts (Claude, Cursor, etc.) speak one protocol, and tool providers only expose an MCP server. Connector count drops from N x M to N + M. In 2026 every major model and IDE supports it natively, so write one server and every host can call it.

# The N x M problem MCP solves
# Before MCP: every model x every tool = custom connector
models = ["Claude", "GPT", "Gemini"]
tools = ["Slack", "GitHub", "Jira", "Notion"]
connectors_before_mcp = len(models) * len(tools)   # 12 custom integrations

# After MCP: model hosts speak one protocol, tools expose one server
# models -> MCP host -> MCP protocol -> MCP servers
connectors_after_mcp = len(models) + len(tools)    # 7 total

2. A Minimal Runnable Server: 30 Lines

Writing an MCP server with the official TypeScript SDK takes about 30 lines (code sample 2): create an McpServer, register a tool with server.tool(), connect a StdioServerTransport. The word_count example takes text and returns the word count — small but complete: input schema, async handler, standardized output. Once it runs, any MCP-compatible host can call it directly.

# Minimal MCP server with the official TypeScript SDK
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new McpServer({
  name: "evergreen-utils",
  version: "1.0.0",
});

server.tool(
  "word_count",
  { text: "string" },
  async ({ text }) => ({
    content: [{ type: "text", text: String(text.trim().split(/\s+/).length) }],
  }),
);

const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP server ready on stdio");

3. Register the Server with Hosts

Writing the server is only step one — hosts need to know where to find it. Code sample 3 shows registration for three mainstream hosts: Claude Desktop/Code uses the mcpServers field in ~/.claude.json, Cursor uses ~/.cursor/mcp.json, Windsurf uses ~/.codeium/windsurf/mcp_config.json. The shape is identical: command, args, env. Restart the host after registering to discover the new tools.

# Register your server so Claude / Cursor / Windsurf can find it
# ~/.claude.json (Claude Desktop / Code)
{
  "mcpServers": {
    "evergreen-utils": {
      "command": "node",
      "args": ["/path/to/server.mjs"],
      "env": {}
    }
  }
}

# Cursor: ~/.cursor/mcp.json (same shape)
# Windsurf: ~/.codeium/windsurf/mcp_config.json (same shape)

4. Four Essentials for a Production Server

First, input validation: MCP schema only does basic type checks — enforce business rules in the handler and return isError: true for bad input (code sample 4). Second, logs go to stderr: MCP stdio transport uses stdout for protocol data, so debug logs must go to stderr or they corrupt the protocol stream. Third, idempotent design: agents may retry calls, so tools should be idempotent. Fourth, timeouts and rate limits on external API calls so agents never hang.

# Robust handler: validate input, fail loudly, log for debugging
server.tool(
  "slugify",
  { title: "string", max_len: "number?" },
  async ({ title, max_len = 60 }) => {
    if (!title || title.length === 0) {
      return { content: [{ type: "text", text: "ERROR: title is required" }],
               isError: true };
    }
    const slug = title.toLowerCase()
      .replace(/[^a-z0-9]+/g, "-")
      .replace(/^-+|-+$/g, "")
      .slice(0, max_len);
    console.error("[slugify]", { input: title, output: slug });  // debug log on stderr
    return { content: [{ type: "text", text: slug }] };
  },
);

5. Common Pitfalls and Debugging

The three most common failures: stdout polluted by console.log, which makes hosts report 'connection failed' — use console.error for all debug output; wrong registration path so the host can't find the server — check JSON syntax and absolute paths; tool name collisions when two servers expose the same tool name — use a namespace prefix like evergreen_*. Debug by running the server alone and watching stderr, then verify with the host's built-in MCP inspector.

6. What's Next in 2026: From Tools to the Agent Ecosystem

MCP's 2026 evolution is moving from an integration standard toward a runtime — MCP Apps packaging, Agent-to-Agent transport extensions, and Linux Foundation governance. For you, building your first MCP server now is perfectly timed: it's not just a technical exercise, it's your ticket into the next-generation AI tool ecosystem. Get the minimal example working, then layer on auth, caching, and observability — and your tool becomes part of the AI agent capability map.

AI agents and tool ecosystem

Your ticket into the next-generation AI tool ecosystem

📌 Frequently Asked Questions

What is MCP?

The Model Context Protocol, an open protocol open-sourced by Anthropic that standardizes connections between AI models and external tools/data sources. By 2026 OpenAI, Google, Microsoft, and mainstream IDEs support it natively.

What problem does MCP solve?

The N x M integration problem: every model x every tool needed a custom connector. With MCP, hosts speak one protocol and tools expose a single MCP server.

How much code does an MCP server need?

About 30 lines with the official SDK: create an McpServer, register a tool with server.tool(), and connect a StdioServerTransport.

How do I make Claude, Cursor, or Windsurf use my MCP server?

Register it in the host config's mcpServers field: ~/.claude.json for Claude, ~/.cursor/mcp.json for Cursor, ~/.codeium/windsurf/mcp_config.json for Windsurf — all with the same command/args/env shape.

What are the most common MCP server pitfalls?

stdout polluted by console.log (debug logs must go to stderr), wrong registration paths, and tool name collisions. Debug by running the server alone and checking stderr.