DEV Community

KJ Labs
KJ Labs

Posted on

How to Ship a Production-Ready MCP Server in 15 Minutes (Not Another Toy Example)

If you've built an MCP server before, you know the drill. You follow a tutorial, get a working "hello world," and then spend the next two weeks bolting on authentication, input validation, error handling, logging, and security hardening before it's anywhere close to deployable.

This post walks through a TypeScript starter kit that handles all of that out of the box, so you can skip straight to writing your tool's actual logic.

What is MCP?

Model Context Protocol is an open standard that lets AI assistants (Claude, Cursor, Windsurf, etc.) call your code as tools. Think of it as a structured API layer between an LLM and your services. You define tools, the assistant discovers them, and calls them with validated inputs. Spec here.

The problem with most MCP tutorials

Every "Build Your First MCP Server" post gets you to a working stdio server in 10 minutes. Great. But then you need to actually ship it, and you're staring at a checklist like this:

  • Input validation (not just "trust the LLM")
  • Authentication (API keys? JWT?)
  • Rate limiting
  • SSRF protection (your server can make HTTP requests -- can it be tricked into hitting 169.254.169.254?)
  • Sandboxed file access (the AI asked to read a file -- should it read /etc/passwd?)
  • Structured logging (not console.log)
  • Error handling that doesn't crash the process
  • Tests. Any tests at all.

Most of us end up copy-pasting from three different repos, Googling "node.js SSRF prevention," and hoping we didn't miss anything. I got tired of this, so I built a starter kit that solves the boring parts.

The starter kit

Repo: github.com/junna-legal/mcp-starter-kit (MIT license, free forever)

It's a TypeScript project with strict mode, ESM, and 250 tests (including 30+ security-focused cases). It supports both stdio and HTTP (SSE) transports, and works with Claude Code, Claude Desktop, Cursor, and Windsurf.

Quick start

git clone https://github.com/junna-legal/mcp-starter-kit.git my-mcp-server
cd my-mcp-server
npm install
cp .env.example .env
npm run db:seed    # seeds a sample SQLite database
npm run build
npm run dev        # starts with hot-reload
Enter fullscreen mode Exit fullscreen mode

To verify everything works, open the MCP Inspector:

npm run inspector
Enter fullscreen mode Exit fullscreen mode

This launches an interactive debugger in your browser where you can call every tool and see the responses. If you see results from database-query, you're good.

What's included

The kit ships with 6 reference tool implementations you can study, copy, or delete:

  1. database-query -- SQLite CRUD with parameterized queries (no injection)
  2. api-connector -- fetch from external REST APIs with SSRF protection
  3. file-manager -- sandboxed file operations with symlink escape detection
  4. cache-store -- TTL-based key-value cache with namespaces
  5. semantic-search -- local RAG with embeddings (3 tools + 2 resources + 1 prompt)
  6. webhook-notifier -- async webhook delivery with HMAC-SHA256 signatures

Beyond the reference tools, here's what the kit handles for you versus what you'd get from a typical tutorial:

Starter Kit Typical Tutorial
Transport HTTP (SSE) + Stdio Stdio only
Validation Zod schemas Manual or none
Logging Structured JSON (pino) console.log
Error handling Graceful + MCP error codes Process crash
Auth API Key + JWT strategies None
Rate limiting Token bucket None
CI/CD GitHub Actions None

Security, specifically

This is where most hand-rolled servers fall short. The kit includes:

  • SSRF protection that blocks private/internal networks (IPv4 + IPv6), including cloud metadata endpoints. DNS resolution is validated to prevent rebinding attacks.
  • JWT validation that rejects alg:none and algorithm downgrade attacks.
  • Sandboxed file access that prevents path traversal and symlink escapes.
  • SQL injection prevention via strict parameter binding.
  • 30+ security test cases covering OWASP top threats.

You don't have to think about any of this. It's already tested and wired in.

Adding your own tool

The kit includes a scaffolding script. Say you want to build a send-email tool:

npm run create-tool send-email
Enter fullscreen mode Exit fullscreen mode

This generates a directory with three files:

src/tools/send-email/
  index.ts          # registration
  schema.ts         # Zod input schema
  handler.ts        # your business logic
  __tests__/
    handler.test.ts # unit test scaffold
Enter fullscreen mode Exit fullscreen mode

Define your input schema:

// src/tools/send-email/schema.ts
import { z } from "zod";

export const sendEmailShape = {
  to: z.string().email("Must be a valid email address"),
  subject: z.string().min(1).max(200).describe("Email subject line"),
  body: z.string().min(1).describe("Plain-text email body"),
  priority: z.enum(["low", "normal", "high"]).default("normal"),
};
Enter fullscreen mode Exit fullscreen mode

Write your handler:

// src/tools/send-email/handler.ts
import { logger } from "../../lib/logger.js";
import { ToolError } from "../../lib/errors.js";
import type { SendEmailInput } from "./schema.js";

export async function handleSendEmail(input: SendEmailInput) {
  logger.info({ to: input.to, subject: input.subject }, "Sending email");

  if (!process.env.SMTP_HOST) {
    throw new ToolError("SMTP_HOST environment variable is not configured");
  }

  // Your actual email logic here (nodemailer, SendGrid, Resend, etc.)
  return { status: "sent", timestamp: new Date().toISOString() };
}
Enter fullscreen mode Exit fullscreen mode

That's it. The framework handles validation, error serialization, and registration with the MCP server automatically. Throw ToolError for expected failures; unexpected exceptions are caught and returned as structured MCP error responses.

Run npm test to make sure nothing broke, and you're shipping.

Connecting to your client

For Claude Code, drop this in your project's .mcp.json:

{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["dist/index.js"],
      "env": {
        "LOG_LEVEL": "info",
        "DB_PATH": "./data/sample.db",
        "SANDBOX_ROOT": "./data/sandbox"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Cursor, Windsurf, and Claude Desktop all work too -- see the setup docs for their config formats.

Wrapping up

The repo is MIT-licensed. Clone it, delete the reference tools you don't need, add your own, and ship. If you run into issues, the troubleshooting guide covers the common ones.

Repo: github.com/junna-legal/mcp-starter-kit

If you find it useful, a star on the repo would be appreciated. If you find a bug, open an issue -- or better yet, a PR.

Top comments (0)