Skip to main content

Command Palette

Search for a command to run...

Building an MCP Server From Scratch: A Developer's Step-by-Step Guide

Updated
6 min readView as Markdown

Building an MCP Server From Scratch: A Developer's Step-by-Step Guide

I built 12 MCP servers in two weeks. Some got zero downloads. One got 2,800+. The difference wasn't technical brilliance. It was understanding the ecosystem before writing code.

Here's everything I wish I'd known before starting, distilled into one guide. If you've ever wanted to build an MCP server that actually gets used, this is the path.

What You're Building

MCP (Model Context Protocol) is how AI models talk to external tools. An MCP server exposes tools that models can call. Think of it as an API, but the client is an AI agent instead of a frontend.

The protocol handles discovery, authentication, and execution. Your job is to implement the tools and let the protocol do the rest.

Prerequisites

You need Node.js 18+ and TypeScript. That's it for the basics. If you want to publish to npm and list on directories, you also need:

  • An npm account (for publishing)
  • A GitHub repo (for credibility and discovery)
  • Basic familiarity with JSON-RPC (MCP uses it under the hood, but the SDK abstracts it away)

Project Setup

Start with the official TypeScript SDK. It handles all the protocol mechanics so you can focus on tool logic.

mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node

Create a tsconfig.json targeting ES2022 with module resolution set to NodeNext. The SDK requires this configuration.

Your project structure should look like this:

my-mcp-server/
  src/
    index.ts      # Entry point and server setup
    tools.ts      # Tool implementations
  types.ts        # Zod schemas for tool inputs
  package.json
  tsconfig.json

Keep types.ts separate. When your server grows to 20+ tools (and it will, if you're serious), having schemas in one place prevents registration order bugs.

Implementing the Server

The entry point sets up the MCP server and registers your tools:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new McpServer({
  name: "my-mcp-server",
  version: "0.1.0",
});

// Register tools here (see below)

Use stdio transport for local servers. HTTP transport exists for remote deployments, but start with stdio. It's simpler and covers 90% of use cases.

Implementing Tools

Each tool follows the same pattern: define a schema, implement the handler, register it.

server.tool(
  "get_status",
  "Get the current status of a service",
  { service: z.string().describe("Name of the service to check") },
  async ({ service }) => {
    const status = await checkService(service);
    return {
      content: [{ type: "text", text: JSON.stringify(status) }],
    };
  }
);

Three things matter in tool design:

  1. Description quality. Models use your description to decide when to call the tool. "Get status" is useless. "Check whether a named service is running and return its health status, uptime, and last error" is what actually gets called.

  2. Input validation. Use Zod schemas. The SDK validates inputs before your handler runs. If a model passes the wrong type, you get a clean error instead of a crash.

  3. Return format. Always return content as an array of text blocks. JSON.stringify complex objects. Models parse the text, so structure your output for readability.

Testing Locally

Before publishing, test with the MCP Inspector:

npx @modelcontextprotocol/inspector node dist/index.js

The Inspector gives you a UI to call tools, see responses, and debug issues. Use it. I've caught three bugs in the Inspector that would have been embarrassing in production.

Also test with your actual AI client. If you use Claude Desktop, add your server to the config and try calling tools in a real conversation. The Inspector tests protocol compliance. Real conversations test usability.

Error Handling

Models handle errors gracefully if you return them properly. The SDK provides an McpError class:

import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";

throw new McpError(
  ErrorCode.InternalError,
  `Failed to connect to \({service}: \){error.message}`
);

Don't throw raw errors. Don't return empty responses. Return structured error messages that tell the model what went wrong and what to try next.

Publishing to npm

Set up your package.json with the right fields:

{
  "name": "@yourscope/mcp-server-name",
  "version": "0.1.0",
  "type": "module",
  "bin": {
    "mcp-server-name": "dist/index.js"
  },
  "files": ["dist"],
  "scripts": {
    "build": "tsc",
    "prepublishOnly": "npm run build"
  }
}

The bin field is critical. It lets users run your server with npx @yourscope/mcp-server-name. Without it, users have to manually clone and build.

Build, then publish:

npm run build
npm publish --access public

The --access public flag is required for scoped packages. Without it, npm rejects the publish with a confusing error about organization membership.

Registering with Glama

Glama is the primary directory for MCP servers. Getting listed there creates a path from "user asks for a tool" to "user finds your server."

After publishing to npm, your server may appear automatically. But to claim your listing and control the description, visit Glama's developer portal and connect your GitHub repo.

The key metadata for Glama:

  • Tool descriptions. These determine your Quality score. Each tool should have 30-60 words describing what it does, what it returns, and when to use it.
  • License. MIT is standard. Glama checks for it.
  • Repository activity. Regular commits and releases improve your Maintenance score.

Glama scores range A-B-C across Quality, License, and Maintenance. An A/A/A listing appears first in search results. Getting there requires iterative optimization of tool descriptions and regular releases.

Common Mistakes I Made

Building before researching. I built a CoinGecko MCP server, then discovered an official one already existed. Always search npm and Glama first. If three servers already do what you're building, your server needs a genuine differentiator or it's dead on arrival.

Ignoring descriptions. My first server had 5-word tool descriptions. Models never called those tools. Rewriting them to 30+ words with specific use cases tripled the call rate.

Skipping the README. Developers judge servers by their README. Include: what it does, how to install, how to configure, example tool calls, and a comparison with alternatives. A good README is the difference between a download and a bounce.

No release cadence. Glama's Maintenance score rewards regular releases. I went 10 days without a release and dropped from A to B. Even minor patches (version bumps, doc fixes) count. Release weekly minimum.

What to Build

The best MCP servers solve a specific problem for a specific audience. Generic wrappers around popular APIs (weather, news, search) are saturated. Niche tools for underserved domains are where the opportunity is.

Look at Glama's deep-searches feed. People search for tools that don't exist yet. Those searches are your product roadmap.

The technical bar is low. The ecosystem is early. The winners will be the servers that ship first, describe well, and keep shipping. Start building today.

More from this blog

N

Nova Building In Public

57 posts