How to Create an MCP Server: A Practical Guide for Web Developers
The Model Context Protocol (MCP) is an open standard developed by Anthropic that defines how AI models communicate with external tools, data sources, and services. Creating an MCP server means building a backend service that exposes capabilities — functions, resources, or data — that an AI client can discover and call in a structured way. Whether you're connecting an AI assistant to a database, a file system, or a third-party API, an MCP server is how you wire that bridge.
What Is an MCP Server, Actually?
An MCP server is a lightweight service that speaks the Model Context Protocol — a JSON-RPC-based communication layer. It listens for requests from an MCP client (typically an AI host application like Claude Desktop, or a custom agent framework), then responds with tools, resources, or prompt templates that the AI can use.
Think of it like a plugin backend. The AI asks "what can you do?" and your server responds with a structured list of capabilities. When the AI decides to use one, it sends a request and your server executes the logic and returns results.
MCP servers can run:
- Locally via standard I/O (stdio), where the client spawns the server as a subprocess
- Remotely via HTTP with Server-Sent Events (SSE), suitable for networked or cloud deployments
Core Concepts Before You Start Building
Understanding these terms will save significant debugging time:
| Concept | What It Means |
|---|---|
| Tool | A callable function the AI can invoke (e.g., query a database, send an email) |
| Resource | A readable data source (e.g., a file, a URL, a config value) |
| Prompt | A reusable prompt template the server exposes to the client |
| Transport | The communication channel — stdio or HTTP/SSE |
| Schema | JSON Schema definitions that describe tool inputs so the AI knows how to call them |
Step-by-Step: Creating an MCP Server in Node.js
The official MCP SDK (available for TypeScript/Node.js and Python) handles the protocol layer so you don't have to implement JSON-RPC from scratch.
1. Set Up Your Project
mkdir my-mcp-server cd my-mcp-server npm init -y npm install @modelcontextprotocol/sdk For TypeScript (recommended for type safety):
npm install typescript tsx @types/node --save-dev 2. Initialize the Server
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; const server = new McpServer({ name: "my-mcp-server", version: "1.0.0" }); 3. Register a Tool
Tools are the most common primitive. Each tool needs a name, description, input schema, and a handler function:
server.tool( "get-weather", "Fetches current weather for a given city", { city: z.string().describe("The city name") }, async ({ city }) => { // Your logic here — call an API, query a DB, etc. const result = await fetchWeatherData(city); return { content: [{ type: "text", text: JSON.stringify(result) }] }; } ); The AI model reads your description and schema to understand when and how to call this tool. Clear descriptions matter — they directly affect how reliably the AI uses your tool.
4. Connect the Transport and Start
const transport = new StdioServerTransport(); await server.connect(transport); For stdio-based servers, that's the entire startup. The client process will launch your server and communicate through standard input/output.
Python Alternative 🐍
If Python fits your stack better, the mcp package follows the same pattern:
pip install mcp from mcp.server.fastmcp import FastMCP mcp = FastMCP("my-mcp-server") @mcp.tool() def get_weather(city: str) -> str: """Fetches current weather for a given city.""" return fetch_weather_data(city) if __name__ == "__main__": mcp.run() FastMCP's decorator syntax makes Python implementations particularly concise for rapid prototyping.
Key Variables That Shape Your Implementation
Building an MCP server isn't one-size-fits-all. Several factors determine the right approach:
Transport choice depends on your deployment model. Stdio transport works well for local tools and desktop integrations. HTTP/SSE transport is necessary when your server needs to run as a persistent network service, handle multiple clients, or operate in a containerized environment.
Authentication and security become critical if your server accesses sensitive systems — databases, internal APIs, or user data. MCP itself doesn't enforce auth; that's your responsibility to layer in. A server with write access to production systems needs substantially more hardening than a read-only reference tool.
Statelessness vs. statefulness matters for performance. Most MCP tools are stateless (each call is independent), but some use cases — like maintaining a browsing session or a database transaction — require managing state between calls, which adds architectural complexity.
Error handling and schema precision affect how reliably an AI model can use your tools. Vague schemas lead to malformed inputs. Unhelpful error messages leave the AI without enough signal to retry intelligently.
What Different Setups Look Like in Practice
A solo developer building a personal productivity tool might run a Python MCP server locally via stdio, exposing a handful of file-system tools and a calendar API integration — straightforward, no networking required.
A small team integrating MCP into an internal workflow platform likely needs an HTTP-based server with proper authentication, environment-based configuration, and structured logging — closer to a production microservice.
An enterprise deployment connecting AI agents to internal databases and business systems introduces concerns around access control, audit logging, secret management, and potentially running multiple specialized MCP servers behind a gateway.
The protocol is the same in all cases. 🔧 The surrounding infrastructure scales with the stakes.
What the SDK Handles vs. What You Own
The MCP SDK manages the protocol handshake, capability negotiation, message routing, and transport abstraction. What you're responsible for is everything inside the tool handler: your business logic, external API calls, data validation beyond the schema, error responses, and any state management.
That clean separation is intentional — it keeps MCP servers composable and focused. One server handles file operations, another handles database queries, another wraps a third-party service. An AI client can connect to multiple servers simultaneously and coordinate across all of them.
The right scope for your server — how many tools, how much responsibility, what transport, how much auth hardening — depends entirely on what systems you're connecting, who's using it, and what an AI model will actually be trusted to do on your behalf.