MCP Server Starter

MCP Server Starter

A TypeScript starter template for MCP servers with CI/CD, OIDC npm publishing, and zero-secret setup.

Category
Visit Server

README

<div align="center">

MCP Server Starter

TypeScript + OIDC npm Publishing + CI/CD.

Build your MCP server. One-click publish. Zero secrets needed.

CI License: MIT npm version

English | 한국어

</div>


Part of Starter Series — Stop explaining CI/CD to your AI every time. Clone and start.

Docker Deploy · Discord Bot · Telegram Bot · Browser Extension · Electron App · npm Package · React Native · VS Code Extension · MCP Server · Python MCP Server · Cloudflare Pages


About This Starter

Currently implemented

  • MCP SDK — @modelcontextprotocol/sdk with stdio transport
  • TypeScript — Strict mode (incl. noUncheckedIndexedAccess + verbatimModuleSyntax), ES2023 target, Zod-validated input & output schemas
  • Safety annotations — readOnly/destructive/idempotent hints on every tool
  • Prompts — Guided workflow templates via registerPrompt (SDK v1.29+)
  • Resources — Data exposure pattern with metadata + handler
  • Response helpers — ok() and err() for consistent tool responses
  • Config — Environment variable parsing pattern
  • CI — gitleaks, npm audit, license compliance, ESLint, build, test
  • CodeQL — Static security analysis (push/PR + weekly)
  • CD — OIDC trusted publishing to npm (zero secrets needed)
  • Dependabot — Automated dependency + GitHub Actions updates

Planned

Nothing on a public roadmap. The starter is intentionally finished; downstream projects extend it. Breaking-change waves (Node EOL, SDK majors) land via Dependabot.

Design intent

Bootstrap a publishable MCP server in under a minute. Stdio is the default because every local MCP client speaks it; HTTP transport is shown inline (see below) rather than bundled, to keep the dependency surface minimal. OIDC publishing means contributors never paste an npm token. Globally-unique tool names are enforced by convention — prefix with your module name, since name collisions across servers are the most common foot-gun.

Non-goals

  • Auth primitives, HMAC signing, rate-limiting, audit logging, human-in-the-loop infra. Those belong in a host, not a server template.
  • Express / HTTP transport in the default scaffold. Template stays small; HTTP is documented for those who need it.
  • Opinionated logging, tracing, or observability stack. Pick what fits downstream.

Redacted

None. Public template.

Quick Start

Via create-starter (recommended):

npx @starter-series/create my-mcp-server --template mcp-server
cd my-mcp-server && npm install && npm run dev

Or clone directly:

git clone https://github.com/starter-series/mcp-server-starter my-mcp-server
cd my-mcp-server && npm install && npm run dev

Adding Tools

Tool names must be globally unique across all MCP servers a client connects to. Prefix with your module name (e.g., mymodule_action instead of action).

Create src/tools/your-tool.ts:

import { z } from 'zod';
import { ok, err } from '../helpers.js';

export const name = 'your_tool';

export const config = {
  title: 'Your Tool',
  description: 'What your tool does',
  inputSchema: {
    input: z.string().describe('Input parameter'),
  },
  // Declare `outputSchema` to return structured results — the 2026 MCP spec
  // requires servers to populate `structuredContent` (validated against this
  // schema) alongside the text mirror. Omit `outputSchema` for free-form
  // text-only tools.
  outputSchema: {
    result: z.string().describe('Processed value'),
  },
  annotations: {
    readOnlyHint: true,
    destructiveHint: false,
    idempotentHint: true,
    openWorldHint: false,
  },
};

export async function handler({ input }: { input: string }) {
  try {
    const result = `Processed: ${input}`;
    // Pass the structured payload as the second arg to `ok()` — it's set as
    // `structuredContent` alongside the text mirror in `content[]`.
    return ok(result, { result });
  } catch (e) {
    return err(`Failed: ${e instanceof Error ? e.message : String(e)}`);
  }
}

Register in src/index.ts:

import * as yourTool from './tools/your-tool.js';
server.registerTool(yourTool.name, yourTool.config, yourTool.handler);

Adding Prompts

Prompts are reusable, parameterized message templates the client can surface to the user or feed to the model. Use registerPrompt (SDK v1.29+) for full control — title + description + Zod-validated argsSchema. See src/prompts/code-review.ts for a templated example.

Create src/prompts/your-prompt.ts:

import { z } from 'zod';

export const name = 'your-prompt';
export const title = 'Your Prompt';
export const description = 'Guided workflow description';

export const argsSchema = {
  param: z.string().min(1).describe('Required parameter'),
};

export function handler({ param }: { param: string }) {
  return {
    description,
    messages: [{
      role: 'user' as const,
      content: { type: 'text' as const, text: `Prompt text with ${param}` },
    }],
  };
}

Register in src/index.ts:

import * as yourPrompt from './prompts/your-prompt.js';
server.registerPrompt(
  yourPrompt.name,
  { title: yourPrompt.title, description: yourPrompt.description, argsSchema: yourPrompt.argsSchema },
  yourPrompt.handler,
);

Adding Resources

Resources expose data to the client (Tools perform actions). See src/resources/server-info.ts for a working example.

Create src/resources/your-resource.ts:

export const name = 'your-resource';
export const uri = 'info://your/resource';

export const metadata = {
  title: 'Your Resource',
  description: 'What this resource exposes',
  mimeType: 'application/json',
};

export async function handler(resourceUri: URL) {
  return {
    contents: [{ uri: resourceUri.href, mimeType: metadata.mimeType, text: '...' }],
  };
}

Register in src/index.ts:

import * as yourResource from './resources/your-resource.js';
server.registerResource(yourResource.name, yourResource.uri, yourResource.metadata, yourResource.handler);

HTTP Transport

This starter uses stdio (the standard for local MCP servers). If you need HTTP transport — for registries like Smithery/mcp.so or remote deployments — use StreamableHTTPServerTransport with Express:

import express from 'express';
import { randomUUID } from 'node:crypto';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';

const app = express();
app.use(express.json());

const sessions = new Map<string, StreamableHTTPServerTransport>();

app.post('/mcp', async (req, res) => {
  const sessionId = req.headers['mcp-session-id'] as string;
  const existing = sessions.get(sessionId);

  if (existing) {
    await existing.handleRequest(req, res);
    return;
  }

  if (!isInitializeRequest(req.body)) {
    res.status(400).json({ error: 'Bad Request: Not an initialize request' });
    return;
  }

  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: () => randomUUID(),
  });

  transport.onclose = () => {
    const id = transport.sessionId;
    if (id) sessions.delete(id);
  };

  const server = createServer(); // your McpServer factory
  await server.connect(transport);
  if (transport.sessionId) sessions.set(transport.sessionId, transport);
  await transport.handleRequest(req, res);
});

app.get('/mcp', async (req, res) => {
  const t = sessions.get(req.headers['mcp-session-id'] as string);
  if (!t) return res.status(400).end();
  await t.handleRequest(req, res);
});

app.delete('/mcp', async (req, res) => {
  const t = sessions.get(req.headers['mcp-session-id'] as string);
  if (!t) return res.status(400).end();
  await t.handleRequest(req, res);
});

app.listen(3000);

Why the complexity? Without isInitializeRequest, every POST creates a new transport → "Already connected" errors. Without GET, clients can't receive server notifications via SSE.

Stateless mode — if your server holds no per-session state (every tool call is independent), construct the transport with sessionIdGenerator: undefined and skip the session map. Stateless is simpler to deploy behind a stateless load balancer and is the recommended default for serverless hosts (Cloudflare Workers, Vercel functions). Keep the stateful pattern above only if you need server-initiated notifications or per-session caches.

See the MCP SDK docs for full details.

Testing Locally

MCP Inspector

npm run build
npx @modelcontextprotocol/inspector node dist/index.js

Claude Desktop

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

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

After Publishing to npm

{
  "mcpServers": {
    "my-mcp-server": {
      "command": "npx",
      "args": ["-y", "my-mcp-server"]
    }
  }
}

CI/CD

CI (every push/PR)

  1. Secret scanning (gitleaks)
  2. Large file detection (>5 MB)
  3. License compliance (blocks GPL/AGPL)
  4. Security audit (npm audit)
  5. Lint (ESLint + TypeScript)
  6. Build (TypeScript compilation)
  7. Test (Jest)

Security & Maintenance

Workflow What it does
CodeQL (codeql.yml) Static analysis for security vulnerabilities (push/PR + weekly)
Maintenance (maintenance.yml) Weekly CI health check — auto-creates issue on failure
Stale (stale.yml) Labels inactive issues/PRs after 30 days, auto-closes after 7 more

CD (manual trigger)

  1. CI gate (must pass)
  2. Version guard (prevents duplicate releases)
  3. npm publish with OIDC + provenance
  4. GitHub Release

Setup: See docs/NPM_PUBLISH_SETUP.md — trusted publishers configured on/after 2026-05-20 must select npm publish as an allowed action, or releases fail with an authorization error.

Project Structure

src/
├── index.ts              # Server entry — tool/resource/prompt registration + transport
├── config.ts             # Environment variable config
├── helpers.ts            # ok() / err() response helpers
├── tools/
│   └── greet.ts          # Example tool with annotations (replace with your own)
├── resources/
│   └── server-info.ts    # Example resource exposing server metadata (replace)
└── prompts/
    ├── hello.ts          # Zero-arg prompt example (replace with your own)
    └── code-review.ts    # Templated prompt with Zod-validated args
tests/
├── greet.test.js         # Tool tests
├── helpers.test.js       # Helper tests
├── server-info.test.js   # Resource tests
├── hello.test.js         # Prompt tests
└── code-review.test.js   # Templated prompt tests

Scripts

Command Description
npm run dev Run with tsx (no build needed)
npm run build Compile TypeScript
npm start Run compiled server
npm test Build + run tests (pretest auto-builds)
npm run lint ESLint
npm run version:patch Bump patch version

License

MIT

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured