The Model Context Protocol has crossed 97 million monthly SDK downloads and over 10,000 public server implementations. Claude, OpenAI, Google Gemini, Cursor, and Windsurf all speak it natively. MCP is now hosted by the Linux Foundation — it is not going away.

If you are still writing custom API integration code for every LLM you work with, you are building on quicksand. One developer reported that after migrating to MCP, deploying a new tool integration dropped from three days to eleven minutes.

This tutorial walks you through building a working MCP server from scratch in TypeScript (Node.js) and Python — covering all three MCP primitives, both transport modes, debugging with the MCP Inspector, and registering your server with Claude Desktop and Claude Code. No prior MCP experience required.

Quick Answer: What Is an MCP Server?An MCP (Model Context Protocol) server is a lightweight program that exposes tools, resources, and prompts to any MCP-compatible AI client — Claude, Cursor, OpenAI, Windsurf, and others. It acts as a bridge between the AI and your data, APIs, files, or services. You write the server once; every MCP-compatible client can use it without any additional integration code.

What Is the Model Context Protocol?

MCP is an open standard — think USB-C for AI. Before MCP, connecting an LLM to external tools meant writing custom function schemas for each provider, reimplementing the same integration code for every model, and rebuilding everything when you switched AI vendors. MCP solves this by defining a protocol layer rather than an API layer.

MCP was introduced by Anthropic in November 2024 and is now an open-source project hosted by the Linux Foundation, with contributions from Anthropic, Microsoft, Google, and the broader developer community.

The Three MCP Primitives

Every MCP server exposes capabilities through one or more of three primitives:

  • Tools — Functions the AI model can call to take actions. Model-controlled — the AI decides when to call them based on the user’s request. Examples: send an email, query a database, run a shell command, call an external API. This is the most commonly used primitive.
  • Resources — Data the AI can read for context. Application-controlled — the client decides when to include them. Examples: file contents, database records, API responses, configuration files. Think of these as read-only context injections.
  • Prompts — Pre-crafted instruction templates that help users accomplish specific workflows. User-controlled — surfaced in the client UI for the user to invoke. Examples: ‘Format this document’, ‘Review this PR’, ‘Summarise this meeting’.

MCP Architecture: Host, Client, and Server

ComponentRoleExamples
MCP HostThe AI application that manages MCP client connections and presents the AI to the userClaude Desktop, Claude Code, Cursor, Windsurf, VS Code with MCP extension
MCP ClientBuilt into the host. Manages the connection to one or more MCP servers, handles tool call routingPart of Claude Desktop / Claude Code internals
MCP ServerYour code. Exposes tools, resources, and prompts over the MCP protocolYour custom server, GitHub MCP, PostgreSQL MCP, Slack MCP, 10,000+ community servers

Transport Options: stdio vs HTTP

  • stdio (Standard I/O) — The server runs as a local subprocess. The host spawns your server process and communicates via stdin/stdout using JSON-RPC 2.0. Best for local development, Claude Desktop integration, and tools that should run on the user’s machine. Never write to stdout from your application code — it corrupts the JSON-RPC stream.
  • Streamable HTTP (formerly HTTP+SSE) — The server runs as a remote HTTP service. The host sends POST requests to a single /mcp endpoint. Best for cloud-deployed servers, multi-user tools, and servers you want to share across teams. This is the transport to use for production deployments.

Prerequisites

This tutorial covers TypeScript (the most popular choice) and Python. Choose one — the concepts are identical across both.

For TypeScript / Node.js

  • Node.js 18 or higher (tutorial tested on Node.js v25.9.0)
  • npm, yarn, or pnpm package manager
  • TypeScript familiarity (basic types and async/await)
  • Claude Desktop or Claude Code installed (for testing)

For Python

  • Python 3.10 or higher
  • uv package manager (recommended) or pip
  • Basic Python async/await familiarity
  • Claude Desktop or Claude Code installed (for testing)

Part 1: Build an MCP Server in TypeScript

Step 1 — Initialise the Project (3 minutes)

Create a new directory and set up the project. Note: you must set “type”: “module” from the start — the MCP SDK requires ES modules.

mkdir my-mcp-server && cd my-mcp-server # Create package.json manually (important: must include type:module)cat > package.json << ‘EOF'{  “name”: “my-mcp-server”,  “version”: “1.0.0”,  “type”: “module”,  “scripts”: {    “build”: “tsc”,    “start”: “node dist/index.js”  }}EOF # Install dependenciesnpm install @modelcontextprotocol/sdk zodnpm install -D typescript @types/node # Initialise TypeScript confignpx tsc –init –target ES2022 –module NodeNext –moduleResolution NodeNext –outDir dist

Step 2 — Write Your First Tool (10 minutes)

Create src/index.ts. This server exposes one tool: get_current_time. It returns the current UTC time when the AI calls it. We use Zod for input validation — the MCP SDK uses Zod schemas to generate the JSON Schema that tells the AI what parameters each tool accepts.

import { McpServer } from ‘@modelcontextprotocol/sdk/server/mcp.js’;import { StdioServerTransport } from ‘@modelcontextprotocol/sdk/server/stdio.js’;import { z } from ‘zod’; // Create the MCP serverconst server = new McpServer({  name: ‘my-first-mcp-server’,  version: ‘1.0.0’,}); // Register a tool: get_current_timeserver.tool(  ‘get_current_time’,  ‘Returns the current UTC date and time’,  { timezone: z.string().optional().describe(‘IANA timezone, e.g. Asia/Kolkata’) },  async ({ timezone }) => {    const tz = timezone || ‘UTC’;    const now = new Date().toLocaleString(‘en-GB’, { timeZone: tz, timeStyle: ‘long’, dateStyle: ‘full’ });    return { content: [{ type: ‘text’, text: `Current time in ${tz}: ${now}` }] };  }); // Start the server using stdio transportconst transport = new StdioServerTransport();await server.connect(transport); // IMPORTANT: write debug output to stderr, never stdoutconsole.error(‘MCP server running on stdio’);
Critical: Never Write to stdout in stdio ServersAny output to stdout (console.log, process.stdout.write) corrupts the JSON-RPC message stream and breaks your server silently. Always use console.error() for debug output — it writes to stderr, which MCP clients pipe separately and do not interpret as protocol messages.

Step 3 — Build and Test With MCP Inspector (5 minutes)

Before connecting to Claude, use the official MCP Inspector to verify your server works. It provides a browser-based UI for listing tools, testing invocations, and inspecting the raw JSON-RPC traffic — without involving an LLM.

# Build TypeScriptnpm run build # Launch MCP Inspector (no installation needed)npx @modelcontextprotocol/inspector node dist/index.js # Inspector opens at http://localhost:5173# Click ‘Connect’, then ‘Tools’ — you should see get_current_time listed# Click the tool and test it with and without a timezone argument

Step 4 — Register With Claude Desktop (5 minutes)

Open your Claude Desktop configuration file and add your server:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json
{  “mcpServers”: {    “my-first-server”: {      “command”: “node”,      “args”: [“/absolute/path/to/my-mcp-server/dist/index.js”]    }  }}

Save the file and restart Claude Desktop. You should see a hammer icon in the UI indicating tools are available. Ask Claude: “What time is it in Mumbai?” — it will call your get_current_time tool with timezone: “Asia/Kolkata” and return the result.

Step 5 — Add a Resource (10 minutes)

Resources expose data the AI can read for context. Add a system-info resource that exposes your machine’s runtime information:

import os from ‘os’; // Add after your tool registrationserver.resource(  ‘system-info’,  ‘system://info’,  { mimeType: ‘application/json’ },  async () => {    const info = {      platform: os.platform(),      arch: os.arch(),      hostname: os.hostname(),      cpus: os.cpus().length,      memory_gb: (os.totalmem() / 1e9).toFixed(1),      node_version: process.version,    };    return {      contents: [{        uri: ‘system://info’,        mimeType: ‘application/json’,        text: JSON.stringify(info, null, 2),      }],    };  });

Step 6 — Add a Prompt (5 minutes)

Prompts are reusable instruction templates surfaced in the client UI. Add a code-review prompt:

server.prompt(  ‘review-code’,  ‘Review code for bugs, security issues, and improvements’,  { language: z.string().describe(‘Programming language of the code’) },  ({ language }) => ({    messages: [{      role: ‘user’,      content: {        type: ‘text’,        text: `Please review the following ${language} code. Check for: (1) bugs and logic errors, (2) security vulnerabilities, (3) performance issues, (4) code style and readability. Provide specific, actionable feedback for each issue found.`,      },    }],  }));

Part 2: Build an MCP Server in Python

Python developers can use the official Python MCP SDK (version 1.2.0 or higher required). The uv package manager is recommended — it is significantly faster than pip and handles virtual environments automatically.

Setup and Installation

# Install uv if not already installedcurl -LsSf https://astral.sh/uv/install.sh | sh # Create and initialise the projectuv init my-mcp-server-python && cd my-mcp-server-python # Add MCP dependency (requires Python 3.10+, MCP SDK 1.2.0+)uv add mcp

Basic Python MCP Server

Create server.py. The Python SDK uses decorators to register tools, resources, and prompts — no JSON Schema writing required.

import asyncioimport sysfrom datetime import datetimefrom zoneinfo import ZoneInfofrom mcp.server import Serverfrom mcp.server.stdio import stdio_serverfrom mcp import types app = Server(‘my-python-mcp-server’) @app.list_tools()async def list_tools() -> list[types.Tool]:    return [types.Tool(        name=’get_current_time’,        description=’Returns the current date and time in the specified timezone’,        inputSchema={            ‘type’: ‘object’,            ‘properties’: {                ‘timezone’: {‘type’: ‘string’, ‘description’: ‘IANA timezone, e.g. Asia/Kolkata’}            }        }    )] @app.call_tool()async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:    if name == ‘get_current_time’:        tz_name = arguments.get(‘timezone’, ‘UTC’)        tz = ZoneInfo(tz_name)        now = datetime.now(tz).strftime(‘%A, %d %B %Y %H:%M:%S %Z’)        return [types.TextContent(type=’text’, text=f’Current time in {tz_name}: {now}’)]    raise ValueError(f’Unknown tool: {name}’) async def main():    # IMPORTANT: never print() to stdout in stdio servers    print(‘Server starting’, file=sys.stderr)    async with stdio_server() as streams:        await app.run(streams[0], streams[1], app.create_initialization_options()) if __name__ == ‘__main__’:    asyncio.run(main())

Register Python Server With Claude Desktop

{  “mcpServers”: {    “my-python-server”: {      “command”: “uv”,      “args”: [        “–directory”, “/absolute/path/to/my-mcp-server-python”,        “run”, “server.py”      ]    }  }}

Practical MCP Tool Ideas: What to Build Next

Once your first server is working, here are high-value tools the SSNTPL engineering team builds for clients:

Tool IdeaWhat It DoesDifficulty
Database query toolRuns read-only SQL queries against your PostgreSQL or MySQL database and returns results as JSON. Lets Claude answer questions about your data directly.Beginner
File system toolReads, writes, lists, and searches files in a specified directory. The basis of most coding agent setups.Beginner
REST API wrapperWraps your internal or third-party REST API as MCP tools, enabling Claude to take actions through your existing API surface.Beginner
Git operations toolReads commit history, diffs, branch lists, and file contents from a local Git repository. Useful for code review and changelog generation.Intermediate
Jira / Linear ticket toolCreates and updates project management tickets directly from Claude. Claude can create a ticket, assign it, and set priority in a single conversation.Intermediate
Email tool (Gmail/Outlook)Reads unread emails, searches by subject or sender, drafts and sends replies. Requires OAuth authentication setup.Intermediate
Database write tool (with approval)Allows Claude to propose database writes that require explicit human approval before execution. Useful for AI-assisted data management.Advanced
CI/CD pipeline triggerTriggers GitHub Actions or GitLab CI pipelines, polls for status, and reports results. Enables conversational DevOps.Advanced

MCP Server Security: What You Must Get Right

MCP servers can be powerful — and that power needs boundaries. The SSNTPL engineering team applies these security practices to every MCP server we build for clients:

  • Principle of least privilege — Your MCP server should only have access to what it genuinely needs. A server that reads files should not also have write access unless required. A database tool should use a read-only connection unless writes are explicitly in scope.
  • Input validation on every tool — Validate all arguments before use — not just for type correctness, but for value safety. A file path argument should be checked against an allow-list of directories. An SQL argument should use parameterised queries, never string interpolation.
  • Never expose secrets through tools — Do not expose environment variables, API keys, or credentials through tool outputs. Store secrets in environment variables and access them server-side only.
  • Rate limiting for HTTP servers — Remote MCP servers (HTTP transport) should implement rate limiting per client. An AI model in an agentic loop can call tools hundreds of times in minutes — without rate limiting, a misconfigured agent can exhaust external API quotas or overload databases.
  • Audit logging — Log every tool call with its arguments and result (redacting sensitive values). When something goes wrong in an agentic workflow, audit logs are the only way to understand what the AI did.
  • Tool descriptions are your security boundary — The AI decides which tools to call based on their names and descriptions. A poorly named or described tool can be called in unexpected contexts. Be specific and restrictive in your descriptions.

Debugging MCP Servers

Using the MCP Inspector

The MCP Inspector is the first tool to reach for when something is not working. It is a browser-based interface that connects to your server directly — no Claude, no host, no guessing what the AI is sending.

# For a Node.js servernpx @modelcontextprotocol/inspector node dist/index.js # For a Python servernpx @modelcontextprotocol/inspector uv run server.py # Opens at http://localhost:5173# Use it to: list tools, test tool calls, inspect JSON-RPC messages

Common Errors and Fixes

ErrorLikely CauseFix
‘Claude was unable to connect’Server process crashes on startup, or wrong path in configCheck the absolute path. Run the server manually in terminal first. Check stderr for startup errors.
Tool not appearing in ClaudeServer connected but tool registration failedUse MCP Inspector to list tools. Check for Zod schema errors or typos in tool registration code.
Corrupted JSON-RPC streamconsole.log() or other stdout writes in stdio serverReplace all console.log() with console.error(). Never write to stdout in stdio servers.
Tool returns empty resultHandler returns wrong shapeReturn { content: [{ type: ‘text’, text: ‘…’ }] } — the content array is required.
‘Unknown tool’ errorTool name mismatch between registration and call handlerEnsure the tool name string is identical in registration and the call handler switch/if.
Server disconnects after idleNo keepalive on HTTP transportImplement heartbeat/ping for long-running HTTP connections, or use stdio for local tools.

Publishing Your MCP Server

Publishing to npm (TypeScript)

Add a bin entry to package.json so users can run your server via npx without installing it. Set the bin path to your compiled entry point:

// In package.json”bin”: { “my-mcp-server”: “./dist/index.js” } // Users can then run it in their Claude Desktop config as:// “command”: “npx”, “args”: [“my-mcp-server”]

Registering on the MCP Registry

The official MCP Registry (registry.modelcontextprotocol.io) lists public MCP servers. Publishing your server there makes it discoverable to the 97M+ monthly SDK users. Registry submission is done via a pull request to the MCP registry GitHub repository with your server’s metadata.

Frequently Asked Questions

What is an MCP server in simple terms?

An MCP server is a program you write that exposes tools, data, or instruction templates to AI assistants like Claude. When a user asks Claude to do something that requires external data or actions — check a database, read a file, call an API — Claude calls your MCP server to do it. You write the server once; any MCP-compatible AI client can use it.

What languages can I use to build an MCP server?

Official SDKs exist for TypeScript/JavaScript and Python. Community SDKs are available for Go, Rust, Java, C# (.NET, maintained with Microsoft), Elixir, and Kotlin. TypeScript is the most popular choice — the SDK is most mature, the community is largest, and npm publishing makes distribution easy.

Is MCP only for Claude?

No. MCP is an open standard hosted by the Linux Foundation. As of 2026, it is natively supported by Claude (Anthropic), OpenAI, Google Gemini, Cursor, Windsurf, VS Code via extensions, and a growing list of other AI tools. A server you write today works with any current and future MCP-compatible client.

What is the difference between MCP tools and function calling?

Function calling (or tool use) is a model-specific feature where you define tools in the API request and the model returns structured calls to them. MCP is a protocol layer that sits on top — it standardises how tools are discovered, described, and invoked across any model and any client. MCP wraps your APIs and tools into a standard interface so you do not need to rewrite integrations for each AI provider.

How do I secure an MCP server that is exposed over HTTP?

For remote HTTP MCP servers: implement authentication (OAuth 2.0 or API key validation in request headers), rate limiting per client, input validation on all tool arguments, audit logging of all tool calls, and HTTPS-only access. Do not expose sensitive data in tool outputs. Treat your MCP server like any other production API — because it is one.

How do I test my MCP server without Claude?

Use the official MCP Inspector: npx @modelcontextprotocol/inspector <your-server-command>. It opens a browser UI at localhost:5173 where you can list tools, invoke them with test arguments, and inspect the raw JSON-RPC messages. Test your server thoroughly in the Inspector before registering it with Claude Desktop.

Can one MCP server expose multiple tools?

Yes — and most production MCP servers expose many tools. A GitHub MCP server, for example, exposes tools for creating issues, opening pull requests, reading file contents, searching repositories, and more. The only limit is what your server’s backend supports. Group related tools in the same server, and separate unrelated concerns into different servers.

What happens if my MCP server crashes while Claude is using it?

For stdio servers: the host detects the process exit and reports a connection error. Claude will inform the user that the tool is unavailable. For HTTP servers: the host receives a connection error or timeout and handles it similarly. Implement proper error handling in your server so crashes are caught, logged, and — where possible — gracefully reported back to the client as error responses rather than process crashes.

Need an MCP Server Built for Your Product?

SSNTPL’s engineering team builds production MCP servers for enterprise clients — connecting Claude and other AI agents to internal databases, APIs, CRM systems, DevOps pipelines, and custom business logic.

Whether you need a single integration or a full suite of MCP tools for your organisation, we can design, build, test, and deploy it. We also run MCP architecture workshops for engineering teams building their own AI integration capabilities.

Schedule a free 30-minute consultation →

Leave a Reply

Share