GPTMap

Build Your Own MCP Server: From Zero to Published

Build and ship an MCP Server with @modelcontextprotocol/sdk: project setup, declaring tools, choosing stdio vs Streamable HTTP, local testing, OAuth, and a launch checklist.

TL;DR
An MCP Server is the standard way to expose your data or APIs to ChatGPT, Claude, and Cursor. This article walks the full path in TypeScript with @modelcontextprotocol/sdk — project init, schema'd tools, stdio / Streamable HTTP transports, MCP Inspector testing, OAuth, and a pre-launch security checklist.
MCP (Model Context Protocol) is the open standard for LLM tool calling. An MCP Server is a service that exposes tools / resources / prompts and is connected by a Host (ChatGPT / Claude / Cursor) over stdio or Streamable HTTP.

How to

  1. Init the project and install the SDK

    npm init -y then npm i @modelcontextprotocol/sdk; set type=module in package.json, write src/server.ts and src/index.ts, and configure a bin entry.

  2. Register ListTools + CallTool

    server.setRequestHandler(ListToolsRequestSchema, ...) returns the tool list (name/description/inputSchema); implement tool logic in CallToolRequestSchema and return a structured result.

  3. Hook up a transport

    Use StdioServerTransport for local subprocess; for cloud use StreamableHTTPServerTransport plus OAuth metadata. Support one or both.

  4. Test with MCP Inspector

    Run npx @modelcontextprotocol/inspector to open the local debugger and verify the ListTools → CallTool flow, confirming schemas and argument parsing.

  5. Publish and run the security checklist

    Before shipping: OAuth, rate limits, a sensitive-tool allowlist, prompt-injection protection; document per-Host setup in the README and publish to npm / GitHub.

MCP (Model Context Protocol) is the open standard for LLM tool calling, and an MCP Server is the side that "exposes your capability". This article walks the whole path in TypeScript, from init to launch.

1. Init from scratch

mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm i @modelcontextprotocol/sdk

Add "type": "module" to package.json and declare a bin entry:

{
  "type": "module",
  "bin": { "my-mcp-server": "./dist/index.js" }
}

2. Register tools: ListTools + CallTool

A minimal server's core is two handlers. Start with src/server.ts:

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

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

server.registerTool(
  "echo",
  {
    description: "Echo back the input verbatim — a minimal working example",
    inputSchema: { message: z.string().describe("The message to echo back") },
  },
  async ({ message }) => ({
    content: [{ type: "text", text: `You said: ${message}` }],
  })
);

export default server;

The SDK's McpServer.registerTool auto-generates the ListTools and CallTool JSON-RPC handling — tools carry a Zod schema and input is validated automatically.

3. Pick a transport: stdio or Streamable HTTP?

Choose the transport in src/index.ts:

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

const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP server running on stdio");
  • Local subprocessStdioServerTransport; the Host spawns your binary and talks over stdin/stdout.
  • Cloud, multi-userStreamableHTTPServerTransport, an HTTP endpoint paired with OAuth.

Get stdio working locally first, then add HTTP when you need it.

4. Test locally: MCP Inspector

No Host needed — the official debugger validates the whole flow:

npx @modelcontextprotocol/inspector node dist/index.js

In the Inspector you can: browse tools and schemas, call a tool manually, and watch the JSON-RPC messages. Confirm ListTools → CallTool before wiring up any client.

5. Wire it into ChatGPT / Claude / Cursor

For local development, add the server to the client config. Claude Desktop example (claude_desktop_config.json):

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

ChatGPT and Cursor each have their own MCP config entry point — document all of them in the README.

6. Publish: Streamable HTTP + OAuth

For multi-user or cloud access, switch to HTTP:

import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
// Mount the transport in an Express / Hono route and register OAuth metadata
  • Register OAuth 2.0 metadata backed by your own auth backend (use @modelcontextprotocol/sdk/auth).
  • Add rate limits, request-size caps, and a CORS allowlist.
  • Keep sensitive tools on an explicit allowlist with user confirmation.

7. Pre-launch security checklist

  • Treat every tool return value as untrusted input (prompt-injection defense).
  • Destructive actions (delete, send, pay) require Host confirmation.
  • Don't hand credentials to tools; serve sensitive data through OAuth authorized resources.
  • Log calls and periodically review who's invoking which tool.

8. Common errors and troubleshooting

  • Host can't connect → confirm the stdio command and path; for HTTP check OAuth metadata and CORS.
  • CallTool schema validation fails → Zod schema too tight or type mismatch; inspect the args in the Inspector.
  • Tools not appearing → ListTools not registered or registerTool threw; check init order.
  • Wrong response shape → results must be { content: [...] }; use the text type for plain text.

9. What's Next

  • The Complete Guide to MCP: How It Works and Practical Builds — protocol primitives and architecture
  • Function Calling with the Responses API — using tools in the OpenAI ecosystem
  • OpenAI API Getting Started: Your First GPT-5.6 Call

Key points

  • A minimal server with @modelcontextprotocol/sdk is ~50-200 lines of TypeScript; the core is registering ListTools + CallTool
  • Pick the transport by scenario: stdio for local subprocesses, Streamable HTTP for cloud / multi-user services
  • Tools are callable functions with JSON Schema inputs; resources are URI-addressed data; prompts are reusable templates
  • Test locally with MCP Inspector (ListTools → CallTool) before wiring up any Host
  • Before launch: OAuth, rate limits, a sensitive-tool allowlist, and prompt-injection protection
  • Current tool schema version: MCP 2025-03-26 spec; latest stable typescript-sdk

Frequently asked questions

A runnable minimal server is about 50-200 lines of TypeScript: define one or two schema'd tools, hook up the stdio transport, and a Host can connect. The official quickstart's weather example is directly copyable. The protocol itself is thin — the complexity lives in your business logic, auth, and operations.

Official references

Related articles

Subscribe to GPTMap Weekly

One email every Monday: curated OpenAI updates, deep dives, and best practices. No ads, unsubscribe anytime.

GPTMap EditorialPublished 2026-08-06 4 min read
Test environment (EEAT)
Last tested: 2026-08-06
Model used: gpt-5.6