How to build your own MCP server
Build an MCP server with the official TypeScript or Python SDK, add tools, resources and prompts, test it in the MCP Inspector, and publish it to the registry.
An MCP server is a small program that exposes capabilities to AI clients through the Model Context Protocol. With the official SDKs, a working server takes a few dozen lines. This guide builds one in TypeScript and in Python, covers the rules that keep servers reliable, shows how to test with the MCP Inspector, and walks through publishing to the official MCP Registry and getting listed on MCPpedia.
Tools, resources and prompts
A server can offer three kinds of capability:
- Tools are functions the model can call, such as
search_issuesorcreate_invoice. The model decides when to use them, so they are the main way an agent acts. Most servers are mostly tools. - Resources are data the client can read, identified by a URI such as
file:///notes/today.mdorgreeting://alice. The application decides when to attach them. - Prompts are reusable templates the user picks, often shown as slash commands.
Start with the smallest set of tools that does the job. Each one costs context in every conversation, and five clear tools beat thirty overlapping ones.
Then pick a transport. Start with stdio: the client launches your server as a local process, and there is no hosting, networking or auth to set up. Move to Streamable HTTP when you want to host the server for other people. The local vs remote guide covers the trade-offs.
Build it in TypeScript
The TypeScript SDK's current major version, v2, ships separate packages for servers and clients. Install the server package and Zod:
npm install @modelcontextprotocol/server zod
A complete stdio server with one tool:
import { McpServer } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import * as z from "zod/v4";
const server = new McpServer({ name: "greeting-server", version: "1.0.0" });
server.registerTool(
"greet",
{
description: "Greet someone by name. Use when the user asks for a greeting.",
inputSchema: z.object({ name: z.string().describe("The person's name") }),
},
async ({ name }) => ({
content: [{ type: "text", text: `Hello, ${name}!` }],
}),
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main();
registerTool takes a name, a config with a description and an input schema, and a handler that returns content. Compile it with your usual TypeScript setup and run it with node.
Older tutorials import from @modelcontextprotocol/sdk. That is the v1 package. It still gets bug fixes and security updates, but new projects should start on v2.
Build it in Python
The Python SDK installs as mcp. The cli extra adds the mcp command used for testing:
uv add "mcp[cli]"
The same idea in Python, with a tool, a resource and a prompt:
from mcp.server import MCPServer
mcp = MCPServer("Demo")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers and return the sum."""
return a + b
@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
"""A personalized greeting."""
return f"Hello, {name}!"
@mcp.prompt()
def review_code(code: str) -> str:
return f"Please review this code and point out bugs:\n\n{code}"
if __name__ == "__main__":
mcp.run(transport="stdio")
Type hints become the input schema, and the docstring becomes the description. In version 2 of the Python SDK the FastMCP class was renamed MCPServer. If a tutorial imports mcp.server.fastmcp, it was written for v1.
Rules that keep a server reliable
- Never write to stdout on stdio. Stdout carries the protocol. A stray
printorconsole.logcorrupts it, so send logs to stderr or a file. - Write descriptions for the model. Start with what the tool does, say when to use it, describe every parameter, and state side effects plainly. Our guide to writing tool descriptions goes into detail, and the same criteria feed the tool grades on MCPpedia.
- Return errors the model can act on. Report a failed call as a tool result with the error flag set and a clear message, such as "No issue with id 42. Use search_issues to find valid ids". Don't let the process crash.
- Validate every input. Treat arguments as untrusted. Check paths against an allowed root and never build shell commands from strings.
- Default to read-only. Make writes opt-in, or put them in separate tools that are easy to leave off.
- Read secrets from the environment. Document every variable your server needs. Don't ask users to paste keys into arguments.
Test it with the MCP Inspector
The MCP Inspector is the official tool for testing servers. It connects to your server, lists its tools, resources and prompts, and lets you call them by hand:
# TypeScript (after building)
npx @modelcontextprotocol/inspector node build/index.js
# Python
uv run mcp dev server.py
For quick checks in a script or CI, the Inspector has a command-line mode:
npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list
Once the Inspector is happy, add the server to a real client. Our guides for Claude Code and Cursor show how. If something fails, the troubleshooting guide covers the usual causes.
Going remote
To host your server, switch to Streamable HTTP. In Python, mcp.run(transport="streamable-http", host="0.0.0.0", port=9000) serves it over HTTP. The TypeScript SDK ships middleware packages for Express, Fastify and Hono, and its documentation has full examples.
A public server needs authentication. MCP uses OAuth 2.1, and the specification is strict on one point: your server must not accept tokens that weren't issued for it. Ask for the narrowest scopes that work. The security best practices guide covers both sides.
Publish to the official MCP Registry
The official MCP Registry stores metadata about servers, not the code. Your package stays on npm, PyPI, NuGet, crates.io or a container registry. The registry is still in preview, so expect occasional changes. The flow for an npm package:
- Add the registry name to your package. In
package.json, set"mcpName": "io.github.your-username/your-server". For PyPI, NuGet and Cargo, put the linemcp-name: io.github.your-username/your-serverin the README. For Docker, add the labelio.modelcontextprotocol.server.name. - Publish the package to its registry as usual, for example with
npm publish --access public. - Install the publisher CLI with
brew install mcp-publisher, or download a binary from the registry's GitHub releases. - Generate
server.jsonwithmcp-publisher init, then check the name, description, repository, version and package entry. Thenamemust matchmcpName. - Sign in and publish:
mcp-publisher login github
mcp-publisher publish
With GitHub sign-in, your server name must start with io.github.your-username/. To publish under your own domain, such as com.example/server, use DNS or HTTP authentication instead. Remote servers are published the same way, with a remotes entry pointing at the URL.
Get listed and claim it on MCPpedia
MCPpedia imports servers from the official registry automatically, so a published server reaches the directory on the next sync. Depending on the record, it may go through a short review before it appears. If your server isn't in the registry, submit it with its repository, package or remote URL.
Once it's listed, claim the listing by signing in with the GitHub account that administers the repository. You can then edit the summary, website and logo.
Your listing's trust score and scorecard grades are built from public evidence: a license, recent commits or releases, a README, documented tools and a pinned install. The methodology page lists every rule, so you can see exactly what improves them.
Frequently asked questions
Which language should I use to build an MCP server?
Use the one you know best. The official TypeScript and Python SDKs are the most widely used, and the MCP project maintains SDKs for several other languages too. Both SDKs in this guide support stdio and Streamable HTTP.
What happened to FastMCP in the Python SDK?
Version 2 of the official Python SDK renamed the FastMCP class to MCPServer. Tutorials that import mcp.server.fastmcp were written for version 1. The decorators for tools, resources and prompts work the same way.
How do I test an MCP server without an AI client?
Use the MCP Inspector. npx @modelcontextprotocol/inspector node build/index.js opens a page where you can list and call your server's tools, and uv run mcp dev server.py does the same for a Python server.
Do I need to publish to the official registry to be listed on MCPpedia?
No. Registry servers are imported automatically, which is the easiest route, but you can also submit a server with its repository, package or remote URL. Once it's listed, you can claim it.