How to Write an MCP Server: Connect Your Own Tool to Claude in 30 Lines of Python
Want Claude to reach your company's order system, database or internal API? A step-by-step guide to writing your own Model Context Protocol server and connecting it to Claude Desktop and Claude Code.

Contents 10
Claude knows a lot, but it doesn't know your company. It can't see which order is out for delivery, how many units are left in stock or what a customer bought last month. Copying that information into the chat every time is neither practical nor safe.
The Model Context Protocol (MCP) fills that gap. Released by Anthropic as an open standard in late 2024, MCP is a common language for connecting AI applications to external tools and data. In this post we'll write a small MCP server from scratch and connect it to Claude. By the end, when you ask Claude "where is order 12345?", it will get the answer from your own system.
In short:
- MCP is like "USB-C" for AI apps: a server you write once works with every client that supports MCP.
- An MCP server can offer three things: tools, resources and prompt templates.
- With the official Python SDK, defining a tool is as easy as adding a decorator to a function.
- You can connect the same server to Claude Desktop with a JSON setting and to Claude Code with one command.
What is MCP, and why do we need it?
Before MCP, every AI app had to write its own integration for every tool. Ten apps and ten tools meant a hundred separate integrations. MCP changes the equation: the tool owner writes an MCP server, the AI app becomes an MCP client, and the two talk over a shared protocol.
The architecture has three parts:
- Host: the app the user interacts with, such as Claude Desktop, Claude Code or an IDE.
- Client: the component inside the host that manages a one-to-one connection with each server.
- Server: the program you write, which offers tools and data.
What can your server offer?
| Building block | What it does | Example |
|---|---|---|
| Tools | Functions the model can call | Look up an order, create a record |
| Resources | Data the model can read | Product catalog, file contents |
| Prompts | Ready-made prompt templates | "Prepare the weekly sales report" |
WebMCP, which lets websites offer tools to in-browser agents, is the same idea adapted for the web.
Setup
Start by installing the official Python SDK. You'll need Python 3.10 or later:
pip install "mcp[cli]"
Your first MCP server: order lookup
The example below simulates an e-commerce order system. In a real project you'd connect to your database or internal API instead of a dictionary.
# order_server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("orders")
ORDERS = {
"12345": {"customer": "Jane D.", "status": "shipped", "tracking": "TR998877"},
"12346": {"customer": "John K.", "status": "preparing", "tracking": None},
}
@mcp.tool()
def order_status(order_id: str) -> str:
"""Returns the current status and shipping tracking number of an order by its ID."""
order = ORDERS.get(order_id)
if order is None:
return f"Order {order_id} was not found."
tracking = order["tracking"] or "not yet available"
return f"Status: {order['status']}, tracking number: {tracking}"
@mcp.tool()
def pending_orders() -> list[str]:
"""Lists the IDs of orders that haven't shipped yet."""
return [oid for oid, o in ORDERS.items() if o["status"] != "shipped"]
@mcp.resource("catalog://products")
def product_catalog() -> str:
"""The list of products on sale."""
return "Mug ($5), T-shirt ($15), Notebook ($3)"
if __name__ == "__main__":
mcp.run()
That's it. Three things deserve attention:
- Docstrings matter a lot. Claude decides when to use a tool by looking at the function's name and description. Instead of "gets orders", write a clear description of what it returns and when to use it.
- Type hints become the schema. When you write
order_id: str, the SDK automatically tells Claude the tool expects a text parameter. - Return meaningful messages on errors. Returning a descriptive message when an order isn't found, rather than raising an exception, lets Claude give the user a proper answer.
Test it: MCP Inspector
Before connecting to Claude, test the server with the SDK's development mode:
mcp dev order_server.py
This opens the MCP Inspector in your browser. There you can list your server's tools, run them with parameters and see the response. You see what Claude will see, first.
Connecting to Claude Desktop
Claude Desktop reads local MCP servers from the claude_desktop_config.json file, which you can reach from the developer section of Claude Desktop's settings. Add this definition:
{
"mcpServers": {
"orders": {
"command": "python",
"args": ["/full/path/to/order_server.py"]
}
}
}
Use the full path for your system and restart Claude Desktop. Now when you ask Claude "where is my order 12345?", it will ask permission to call the order_status tool and answer from your own system.
Connecting to Claude Code
If you use Claude Code, it's even easier. One terminal command:
claude mcp add orders -- python /full/path/to/order_server.py
claude mcp list shows connected servers. Claude Code can now reach your order system while writing code; for example, ask it to "draft a reminder email template for pending orders" and it will work from real data. For general Claude Code usage, see our Claude Code guide.
Common mistakes
Printing to stdout. Local MCP servers talk to the client over standard input/output (stdio). If any part of your code writes to stdout with print(), the protocol messages get corrupted and the connection breaks. Send debug output to standard error with the logging module or print(..., file=sys.stderr).
Relative file paths. Claude Desktop may start your server from a different working directory. Use full paths in the config and in your code.
Vague tool descriptions. A description like "fetches data" leads Claude to use the tool at the wrong time, or not at all.
Too many tools. Putting dozens of tools in one server makes it harder for the model to choose the right one. Keep tools focused and merge ones that are very similar.
Security: how much power should Claude get?
Your MCP server means Claude can act on real systems on your behalf. That power needs care:
- Least privilege: don't grant write access if read access is enough. Connect to the database with a read-only user.
- Separate destructive actions: keep tools that delete, pay or send email separate, and make sure the client's approval mechanism is on.
- Validate inputs: treat parameters from the model like input from a user. Write parameterized SQL.
- Don't hardcode secrets: read API keys from environment variables. In Claude Desktop's config you can pass them to the server with the
envfield. - Don't install servers you don't trust: someone else's MCP server is a program running on your computer with your permissions.
Frequently asked questions
Does MCP only work with Claude?
No. MCP is an open standard supported by many AI apps and developer tools beyond Anthropic's. A server you write once works with any client that supports MCP.
Which languages besides Python can I use?
The official SDKs include TypeScript, Python, Java, Kotlin, C# and Go, among others. Picking the language your team already uses makes the most sense.
Local or remote server?
The example here is a local (stdio) server: it runs on your computer, for your use only. For servers a team or customers will use, MCP also supports remote servers over HTTP. Then authentication and authorization become mandatory.
Do I need AI expertise to write an MCP server?
No. Writing an MCP server is no different from writing a small API. You aren't training or tuning a model; you're only defining functions the model can call.
MCP is the cleanest way to bring AI together with your company's real data. If you want a secure MCP server or AI integration built for your systems, reach us through our enterprise software development page.


