Build an MCP Server in 5 Minutes with mcp-framework

A step-by-step tutorial for building a fully functional MCP server in under 5 minutes using mcp-framework. Includes a complete working example with Zod validation, and a code comparison against the official SDK.


title: "Build an MCP Server in 5 Minutes with mcp-framework" description: "A step-by-step tutorial for building a fully functional MCP server in under 5 minutes using mcp-framework. Includes a complete working example with Zod validation, and a code comparison against the official SDK." date: "2026-04-01" order: 6 keywords:

  • build mcp server typescript
  • mcp server tutorial
  • mcp-framework tutorial
  • create mcp server
  • mcp server quickstart
  • typescript mcp server
  • mcp tool example author: "MCP Academy"

Quick Summary

This tutorial walks you through building a fully working MCP server from scratch in under five minutes. You will use mcp-framework's CLI to scaffold a project, create a class-based tool with Zod validation, and connect it to Claude Desktop. By the end, you will have a production-ready server running locally and a clear understanding of why mcp-framework cuts typical boilerplate by roughly 50%.

What You Will Build

By the end of this tutorial, you will have a working MCP server with a url_status_checker tool that accepts a URL, makes an HTTP request, and returns the status code and response time. It is a practical tool that AI assistants can use to check whether websites are up.

This covers the full development loop: scaffolding, coding, building, and running.

MCP Server

A server that implements the Model Context Protocol, exposing tools, resources, and prompts that AI applications like Claude can discover and use through a standardized interface.

Prerequisites

You need two things installed:

  • Node.js 18 or later — Check with node --version
  • npm — Comes with Node.js

That is it. No global installs, no complex environment setup.

Step 1: Scaffold the Project

Open your terminal and run:

npx mcp-framework create url-checker-server

This creates a new directory with everything you need:

url-checker-server/
  src/
    tools/
      ExampleTool.ts
    index.ts
  package.json
  tsconfig.json

Now install dependencies:

cd url-checker-server
npm install
< 60 secondsFrom empty directory to scaffolded MCP server project

The generated project already includes a working example tool. You could build and run it right now, but let us create something more useful.

Step 2: Generate a New Tool

Use the CLI to generate a tool:

npx mcp-framework add tool url-status-checker

This creates src/tools/url-status-checker.ts with a ready-to-fill template. Open it and replace the contents with:

import { MCPTool } from "mcp-framework";
import { z } from "zod";

class UrlStatusCheckerTool extends MCPTool<typeof inputSchema> {
  name = "url_status_checker";
  description = "Check the HTTP status and response time of any URL";

  schema = {
    url: z
      .string()
      .url()
      .describe("The full URL to check, including https://"),
    method: z
      .enum(["GET", "HEAD"])
      .default("GET")
      .describe("HTTP method to use"),
  };

  async execute({ url, method }: { url: string; method: string }) {
    const start = Date.now();

    try {
      const response = await fetch(url, {
        method,
        signal: AbortSignal.timeout(10000),
      });

      const elapsed = Date.now() - start;

      return [
        `URL: ${url}`,
        `Status: ${response.status} ${response.statusText}`,
        `Response time: ${elapsed}ms`,
        `Content-Type: ${response.headers.get("content-type") || "unknown"}`,
      ].join("\n");
    } catch (error) {
      const elapsed = Date.now() - start;
      return `URL: ${url}\nStatus: FAILED after ${elapsed}ms\nError: ${error instanceof Error ? error.message : "Unknown error"}`;
    }
  }
}

export default UrlStatusCheckerTool;

Let us break down what is happening:

  • schema — Zod validates inputs before execute ever runs. The AI client sees the schema as tool parameters. If someone passes an invalid URL, Zod rejects it immediately.
  • execute — Your business logic. Receives typed, validated inputs. Returns a string that the AI receives as the tool result.
  • name and description — What the AI sees when discovering available tools.
Input Validation

Always use Zod's built-in validators like .url(), .email(), and .min() on your schema fields. This catches bad inputs before they reach your business logic and gives the AI client clear error messages to self-correct.

Step 3: Clean Up the Example Tool

Delete the generated example tool so your server only exposes what you intend:

rm src/tools/ExampleTool.ts

The framework discovers tools by scanning the src/tools/ directory. Remove a file and the tool disappears. No manual deregistration needed.

Step 4: Build and Run

npm run build
npm start

Your MCP server is now running on stdio transport, ready to accept connections.

4 stepsFrom zero to a running MCP server with validated input and error handling

Step 5: Connect to Claude Desktop

Add your server to Claude Desktop's configuration file. On macOS, this is at ~/Library/Application Support/Claude/claude_desktop_config.json. On Windows, check %APPDATA%\Claude\claude_desktop_config.json.

{
  "mcpServers": {
    "url-checker": {
      "command": "node",
      "args": ["/absolute/path/to/url-checker-server/dist/index.js"]
    }
  }
}

Restart Claude Desktop, and the url_status_checker tool appears in Claude's tool list. Ask Claude to "check if github.com is up" and watch it use your tool.

The Code Comparison: mcp-framework vs. Official SDK

Here is the same tool built with the official SDK:

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

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

server.tool(
  "url_status_checker",
  "Check the HTTP status and response time of any URL",
  {
    url: z.string().url().describe("The full URL to check, including https://"),
    method: z
      .enum(["GET", "HEAD"])
      .default("GET")
      .describe("HTTP method to use"),
  },
  async ({ url, method }) => {
    const start = Date.now();
    try {
      const response = await fetch(url, {
        method,
        signal: AbortSignal.timeout(10000),
      });
      const elapsed = Date.now() - start;
      return {
        content: [
          {
            type: "text" as const,
            text: [
              `URL: ${url}`,
              `Status: ${response.status} ${response.statusText}`,
              `Response time: ${elapsed}ms`,
              `Content-Type: ${response.headers.get("content-type") || "unknown"}`,
            ].join("\n"),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text" as const,
            text: `URL: ${url}\nStatus: FAILED after ${Date.now() - start}ms\nError: ${error instanceof Error ? error.message : "Unknown error"}`,
          },
        ],
        isError: true,
      };
    }
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);

Now count the lines:

Metricmcp-frameworkOfficial SDK
Tool code (lines)~35~45
Server setup code0 (handled by framework)~10
Total project lines~35~55
Files to manage1 (tool class only)1 (everything in one file)
Registration codeNone (auto-discovery)Manual server.tool() call
Transport setupNone (configured by framework)Manual StdioServerTransport wiring
Return formatReturn a stringReturn content array with type annotations

With one tool, the difference is noticeable. With ten tools, it becomes dramatic. Every SDK tool requires manual registration and wrapping results in the { content: [{ type: "text", text }] } structure. In mcp-framework, you just return a string.

Scaling to Multiple Tools

With the official SDK, adding ten tools means ten server.tool() calls in the same file, or managing your own module system. With mcp-framework, you just add ten files to src/tools/. Each one is auto-discovered and registered. The project stays organized by default.

Adding More Tools

Once your server is running, adding more tools is a single command:

npx mcp-framework add tool dns-lookup
npx mcp-framework add tool ssl-certificate-check
npx mcp-framework add tool ping

Each generates a new class file. Fill in the schema and execute method, rebuild, and restart. The framework discovers and registers everything automatically.

You can also add resources and prompts with the same pattern:

npx mcp-framework add resource server-config
npx mcp-framework add prompt troubleshoot-network

Common Patterns

Here are a few patterns that work well for tool development:

Environment Variables for Configuration

async execute({ query }: { query: string }) {
  const apiKey = process.env.API_KEY;
  if (!apiKey) {
    return "Error: API_KEY environment variable is not set";
  }
  // Use the API key...
}

Structured Output

async execute({ domain }: { domain: string }) {
  const records = await dns.promises.resolve(domain, "A");
  return JSON.stringify({
    domain,
    records,
    count: records.length,
    timestamp: new Date().toISOString(),
  }, null, 2);
}

Error Handling with Context

async execute({ url }: { url: string }) {
  try {
    const result = await riskyOperation(url);
    return `Success: ${result}`;
  } catch (error) {
    return `Failed to process ${url}: ${error instanceof Error ? error.message : "Unknown error"}. Try checking the URL format or network connectivity.`;
  }
}
Return Helpful Errors

When a tool fails, return a descriptive error message as a string rather than throwing an exception. This lets the AI understand what went wrong and potentially retry with corrected inputs.

Next Steps

You now have a working MCP server and the knowledge to extend it. Here is where to go from here:

Five minutes. One command to scaffold. One class to write. A working MCP server connected to Claude. That is the mcp-framework developer experience.

Frequently Asked Questions