Skip to main content
Glama
LKocaj

MCP Starter

by LKocaj

MCP Starter

A production-grade Model Context Protocol server you can fork for client work. Both transports are implemented and tested: stdio (local) and Streamable HTTP (remote, stateless).

54 tests, clean typecheck, no any in the source.

npm install
npm test          # 54 passing
npm run build
npm start         # stdio
npm run start:http
npm run inspect   # official MCP Inspector UI

What MCP is

MCP is a standard way to give an AI assistant access to somebody else's system. Before it, every integration was one-off: custom code for Shopify, more for HubSpot, more for the client's internal database. MCP replaces that with one protocol, so any assistant that speaks it — Claude Desktop, Claude Code, Cursor, ChatGPT — can use any MCP server.

A server exposes three things:

Concept

What it is

In this repo

Tools

Actions the model can take

lookup_order, refund_order

Resources

Read-only data pulled into context

the return policy, an order record

Prompts

Reusable templates the user picks

draft_reply

Tools are what clients pay for. Resources and prompts are polish.

Transport is how the assistant reaches the server. stdio: the host launches the server as a local process and talks over stdin/stdout — used for anything touching a local machine, zero hosting cost. Streamable HTTP: the server lives at a URL — used for multi-user or SaaS-backed work, costs hosting but the customer installs nothing. Same server code, different entrypoint.


Related MCP server: etsy-mcp

What makes this production grade

Most MCP examples online are a server.tool() call and a console.log. Here is the difference.

Destructive actions are gated by a signed, single-use token

refund_order moves money. Calling it without a confirmToken does not refund — it returns a summary plus a token. The model must show the summary to the user and call again with the token.

The token is an HMAC bound to the exact arguments, so the model cannot get approval for a $1 refund and then execute a $189 one. It is single-use, so a retry loop can't fire twice, and it expires in five minutes. All four properties are tested, including the escalation attempt.

Errors never leak internals to the model

Every handler is wrapped. Domain errors become a clean isError tool result the model can recover from; unknown throws become a generic message while the real one goes to the logs. A test asserts a Postgres connection string in an exception never reaches the model-visible text.

Errors are also classified retryable vs not, and the retry logic respects it — a 404 fails once instead of burning the full retry budget.

Upstream calls have deadlines, retries, and jitter

Hard per-attempt timeout (a hung vendor API would otherwise hang the whole assistant turn), exponential backoff with full jitter, Retry-After honored, caller cancellation respected without retrying. Tested with an injected fetch and clock, so the suite is fast and deterministic.

Auth is constant-time; the server refuses to run open in production

Tokens are SHA-256 hashed before comparison, so timingSafeEqual gets equal-length buffers and comparison time leaks nothing. With NODE_ENV=production and no tokens configured, the server returns 500 rather than serving an open endpoint.

DNS-rebinding protection that actually works

A local MCP server is reachable from any web page the user visits: the page resolves an attacker domain to 127.0.0.1 and POSTs to it. Checking the Host header blocks this.

The SDK ships this feature, but it does an exact string compare against the raw header — which includes the port — so an allowlist of 127.0.0.1 silently 403s every request on any non-default port. Our integration test caught it. host-guard.ts compares hostname and port separately: an entry without a port matches any port, an entry with one must match both. IPv6 literals and case-insensitivity are handled and tested.

Everything else

  • Structured tool output (outputSchema + structuredContent) so hosts get typed data, not a string to re-parse

  • Tool annotations (readOnlyHint, destructiveHint, idempotentHint) so hosts can prompt appropriately

  • Per-credential rate limiting (token bucket, lazy refill, swept to bound memory)

  • Single-flight TTL cache — a model firing the same tool three times in a turn makes one upstream call

  • Structured NDJSON logging to stderr with request IDs, child loggers, and automatic secret redaction

  • Config validated at boot — bad config kills the process with a readable message

  • Graceful shutdown — SIGTERM drains in-flight requests with a hard-stop backstop

  • Multi-stage Dockerfile, non-root, with a healthcheck

  • CI across Node 20/22/24 plus npm audit


Layout

src/
  config.ts               zod-validated env
  logger.ts               NDJSON to stderr, redaction
  errors.ts               error taxonomy, public vs internal messages
  http-client.ts          timeouts, retries, jitter
  cache.ts                TTL + single-flight
  rate-limit.ts           token bucket
  confirm.ts              HMAC confirmation tokens
  factory.ts              dependency graph + server assembly
  domain/orders.ts        upstream adapter (HTTP + in-memory)
  tools/
    wrap.ts               timing, logging, error normalization
    orders.ts             the tools themselves
  transports/
    stdio.ts              local entrypoint
    http.ts               remote entrypoint
    host-guard.ts         DNS-rebinding protection
test/
  unit.test.ts            37 tests
  integration.test.ts     17 tests, real MCP client + real HTTP server

Wire it into Claude Desktop

~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "starter": {
      "command": "node",
      "args": ["/absolute/path/to/mcp-starter/dist/transports/stdio.js"]
    }
  }
}

Restart Claude Desktop.

Deploy the HTTP version

docker build -t mcp-starter .
docker run -p 3000:3000 \
  -e NODE_ENV=production \
  -e MCP_AUTH_TOKENS="$(openssl rand -hex 32)" \
  -e ALLOWED_HOSTS=your-domain.com \
  mcp-starter

Or push to GitHub and point Railway at it with start command npm run start:http. Set MCP_AUTH_TOKENS and ALLOWED_HOSTS. See .env.example for every setting.


Building a client's server

  1. Replace HttpOrderService in src/domain/orders.ts with their API. Parse responses through a zod schema so an upstream change surfaces as one validation error instead of malformed data reaching the model.

  2. Rewrite the tools around what their staff does by hand every day. One tool per job to be done, not per API endpoint. Five well-named tools beat twenty.

  3. Write descriptions that say when to call the tool. That single line drives most of the accuracy.

  4. Mark every destructive tool destructiveHint: true and route it through ConfirmationGate.

  5. Keep InMemoryOrderService working. It's how the tests stay fast and how you demo without credentials.

Gotchas that bite people

  • Never write to stdout in a stdio server. One console.log corrupts the protocol stream. Everything here logs to stderr.

  • Don't return raw API dumps. Every token of tool output is context the model must read.

  • Stateless HTTP scales; sessions don't. This creates a fresh server per request, so it runs on any number of instances with no sticky routing. If you need session state, switch sessionIdGenerator to randomUUID — but then you own session eviction.


Scoping this as paid work

  • Wrap one internal API for Claude Desktop — stdio, no hosting, clean one-time deliverable.

  • Hosted multi-tenant server with auth — HTTP, needs hosting, key rotation, and upkeep when the upstream API changes. Never sell this as a fixed one-time build; attach a retainer.

  • Fix or finish a half-built MCP server — fits the Fix & Finish line.

F
license - not found
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/LKocaj/mcp-starter'

If you have feedback or need assistance with the MCP directory API, please join our Discord server