How do you build a simple MCP server?
To build a simple MCP server, install an official MCP SDK, write one decorated function per tool, and run the file over stdio. In Python that is the mcp package, an MCPServer instance and @mcp.tool(). Test it in the MCP Inspector, then add it to your AI application's config using an absolute path to the server.
Cite
CodeLifter Forum, "How do you build a simple MCP server?", https://codelifter.com/forum/posts/how-do-you-build-a-simple-mcp-server/, accessed 2026-11-12.- CodeLifter editorsStaffStaff, CodeLifter ·
What do you need before you start?
An MCP server is a program that offers tools, resources or prompts to an AI application over the Model Context Protocol; the plain terms thread explains the parts. You build one with an SDK (software development kit), the official library that handles the protocol so you only write the functions.
Check the version first, because the SDKs changed in 2026. As of the docs read on 16 October 2026, the Python SDK README says v2 "is the current stable release line" and supports the 2026-07-28 MCP specification. It also warns that
pip install mcpnow installs 2.x, so older tutorials that importFastMCPwere written for v1. The official build-a-server tutorial lists these requirements:Python TypeScript Runtime Python 3.10 or higher Node.js 20 or higher SDK mcp2.0.0 or higher, installed asmcp[cli]@modelcontextprotocol/server, pluszodServer class MCPServerfrommcp.serverMcpServerfrom@modelcontextprotocol/serverRun over stdio mcp.run(transport="stdio")serveStdio(createServer, ...)Quick test uv run mcp dev server.pynpx @modelcontextprotocol/inspector --cli node build/index.js --protocol-era modern --method tools/listThe steps below use Python. The tutorial also covers Java, Kotlin, C#, Ruby, Rust and Go.
Step by step: a one-tool server in Python
These steps follow the official tutorial and the SDK README. CodeLifter has not run this exact code; read each step against the current docs before you rely on it.
Install
uv, the Python project manager the tutorial uses, then restart your terminal.Create the project:
uv init demo-server cd demo-server uv venv source .venv/bin/activate uv add "mcp[cli]"On Windows, activate with
.venv\Scripts\activateinstead.Create
server.pywith one tool. This is the README's example plus the tutorial's run line:from mcp.server import MCPServer mcp = MCPServer("Demo") @mcp.tool() def add(a: int, b: int) -> int: """Add two numbers.""" return a + b if __name__ == "__main__": mcp.run(transport="stdio")The type hints become the tool's input schema and the docstring becomes its description. The README makes the point directly: "
a: int, b: intis the schema".Open it in the Inspector with
uv run mcp dev server.py. The README says that callingaddwitha=1andb=2returns3.Change the tool to do something useful, one small job per function, with a docstring that says when to use it. The model chooses tools from their names and descriptions, so vague docstrings lead to wrong calls.
Why does a stdio server break when you print?
On the stdio transport, the server and the AI application talk over standard input and standard output. The tutorial is blunt: "Never write to stdout. Writing to stdout will corrupt the JSON-RPC messages and break your server." In Python that rules out
print(). Use the standardloggingmodule, which writes to standard error, with one logger per module fromlogging.getLogger(__name__). In TypeScript, useconsole.error()instead ofconsole.log(). HTTP servers do not have this problem.How do you connect it to Claude Desktop?
The tutorial uses Claude Desktop as the host. Other hosts use a similar block; the key is usually
mcpServers, though VS Code usesservers.Open
~/Library/Application Support/Claude/claude_desktop_config.jsonon macOS, or$env:AppData\Claude\claude_desktop_config.jsonon Windows (PowerShell). Create it if it does not exist.Add the server with an absolute path:
{ "mcpServers": { "demo": { "command": "uv", "args": ["--directory", "/ABSOLUTE/PATH/TO/demo-server", "run", "server.py"] } } }If the app cannot find
uv, put the full path fromwhich uv(orwhere uvon Windows) incommand.Quit the app fully (Cmd+Q on macOS) and reopen it; closing the window does not reload the config.
If the tool does not appear, read
~/Library/Logs/Claude/mcp.logandmcp-server-demo.logon macOS. The second file holds your server's standard error output.
What should you check before you share it?
A server runs with the permissions of whoever starts it. The MCP security best practices recommend "a progressive, least-privilege scope model", and the tools specification tells clients to treat tool annotations as untrusted unless they come from trusted servers. Before you publish:
- Make each tool do one narrow thing, and reject inputs outside what it needs (paths outside one folder, for example).
- Read secrets from environment variables, never from the code; the API keys thread covers why.
- Mark tools that change data clearly in the description so users know what they approve.
- Add a licence and a README with the exact install line.
The CodeLifter MCP directory shows how published servers document their tools and access, and the Filesystem sandbox thread shows how users will try to contain yours.
Short version
- Install the official SDK:
uv add "mcp[cli]"for Python (v2,MCPServer) or@modelcontextprotocol/serverfor TypeScript. - One function per tool; type hints and docstrings become the schema and description.
- Never print to stdout on a stdio server; log to stderr.
- Test with the MCP Inspector, then add the server to your client with an absolute path.
- Scope every tool narrowly before anyone else installs it.
Write something first.
Give people something to work with: at least 30 words on what happened and what you tried.
That is too long. Keep it under 6,000 characters.
Write the question as the title, 15 to 140 characters, no links.
Pick a category.
Add a name (2 to 40 characters, no links).
That email address does not look right.
That was quick. Read the thread, then try again.
The form expired. Reload the page and post again.
Something went wrong with the form. Reload and try again.
Please complete the check and post again.
Limit reached for now. Try again later.
This thread is closed to new replies.
This thread no longer accepts replies.
Something went wrong with the form. Reload and try again.
Post a reply or question first (name and email), then this browser can vote, edit and accept answers.
You cannot vote on your own post.
Only the author (within 30 days) or the forum team can do that.
That email already has an account. Use your sign-in link instead.
New accounts are paused for the moment. Try again later.
That was already posted.
2 answers
- CodeLifter editorsStaffStaff, CodeLifter ·
How do you add your own MCP server to Claude Code? Use the
claude mcp addcommand rather than editing a JSON file. For a local stdio server, the Claude Code MCP documentation gives the formclaude mcp add [options] <name> -- <command> [args...], for example:claude mcp add --transport stdio demo -- uv --directory /ABSOLUTE/PATH/TO/demo-server run server.pyThe
--matters: everything before it is an option for Claude Code (--transport,--env,--scope), and everything after it is passed to your server untouched. Pass secrets with--env KEY=valueinstead of writing them into the command.By default the entry goes into local scope, which means only you, in this project. Add
--scope userto use it in every project, or--scope projectto write it to a.mcp.jsonfile at the project root that you can commit for your team. The docs say a project-scoped server shows as pending until each person approves it by runningclaudeinteractively. Check the result withclaude mcp get demo, which shows whether Claude Code connected, or type/mcpinside a session. If a server's instructions were written for Claude Desktop,claude mcp add-jsonaccepts the object insidemcpServers. - CodeLifter editorsStaffStaff, CodeLifter ·
How do you turn a local MCP server into a remote one? Change the transport from stdio to Streamable HTTP. With the Python SDK, the README shows the same
server.pyserved over HTTP with:uv run mcp run server.py --transport streamable-httpA client can then connect to
http://localhost:8000/mcp. The README adds that "A URL means Streamable HTTP, the transport you deploy." The architecture overview describes Streamable HTTP as HTTP POST for client-to-server messages, with optional server-sent events for streaming, and says it supports bearer tokens, API keys and custom headers, while recommending OAuth to obtain tokens. It also notes that remote servers "will typically serve many MCP clients", unlike a local stdio server, which typically serves one.That changes your security work. A local server runs as you; a remote one is a web service anyone who finds the URL can try, so it needs authentication before any tool that reads private data or writes anything. To add a remote server to Claude Code, the MCP documentation uses
claude mcp add --transport http <name> <url>. The remote or local GitHub MCP server thread shows the same trade-off from the user's side.