How to Build an MCP Server Step by Step

Introduction
Learning how to build an MCP server step by step is now one of the most useful skills for anyone connecting language models to real systems. Anthropic reported in December 2025 that the protocol had more than 10,000 active public servers and over 97 million monthly SDK downloads across Python and TypeScript. A Model Context Protocol server is a small program that exposes your data, tools, and prompts to any compatible assistant through one standard interface. Before the protocol existed, every assistant needed a custom integration for every system, and those integrations rarely survived the next product release. A single well-built server now works in Claude Desktop, Cursor, Visual Studio Code, and every other client that speaks the protocol. This guide explains the architecture, the design choices, the security risks, and the operating habits behind a server you can trust. It ends with a complete Python implementation, real deployments, and answers to the questions builders ask most often.
Quick Answers on How to Build an MCP Server Step by Step
How do you build an MCP server step by step?
To build an MCP server step by step, install an SDK, define tools with typed functions, run the server over stdio, test it with the Inspector, then register it in your host.
What language should you use to build an MCP server?
Python and TypeScript have the most mature official SDKs, so most builders choose one of them. Pick the language your team already uses for the systems the server will wrap.
Is an MCP server safe to expose to a model?
Only with controls in place. Tool descriptions can carry hidden instructions, so scope credentials narrowly, validate every input, require approval for risky actions, and review each server before connecting it.
Key Takeaways
- A working MCP server needs only a few typed tool functions, a transport, and a host configuration entry, so a first version takes under an hour.
- Tool names, descriptions, and schemas are the real interface for the model, which means careful wording matters more than clever code.
- Stdio suits local servers and Streamable HTTP suits shared or remote servers, and each choice carries its own security duties.
- Testing, logging, least privilege credentials, and version pinning turn a demo server into infrastructure that a team can rely on.
Table of contents
- Introduction
- Quick Answers on How to Build an MCP Server Step by Step
- Key Takeaways
- What Is an MCP Server and How Do You Build One?
- Why the Model Context Protocol Exists
- Hosts, Clients, and Servers in the Architecture
- Tools, Resources, and Prompts Compared
- Choosing Between Stdio and Streamable HTTP
- Picking an SDK and Language
- Designing Tools the Model Can Use Well
- Writing Schemas, Descriptions, and Error Messages
- Authentication and Authorization Basics
- Testing with the Inspector and Automated Checks
- Connecting Your Server to Claude Desktop and Other Hosts
- Deploying Remote Servers and Scaling
- Industry Use Cases for Custom MCP Servers
- Logging, Observability, and Cost Control
- Security Risks Every Builder Should Know
- Versioning and Maintaining a Server Over Time
- Ethics, Accountability, and Consent
- Common Mistakes Teams Make When Building Servers
- Performance Tuning and Tool Count Limits
- The Future of the Model Context Protocol
- How to Build an MCP Server Step by Step: A Working Implementation
- Key Insights
- Real-World Examples of Building and Running MCP Servers
- Case Studies in Securing MCP Servers and Clients
- Frequently Asked Questions on How to Build an MCP Server Step by Step
What Is an MCP Server and How Do You Build One?
Knowing how to build an MCP server step by step means creating a program that exposes tools, resources, and prompts to AI assistants through the Model Context Protocol, an open standard using JSON-RPC over stdio or HTTP.
An Interactive From AIplusInfo
How Much Context Does Your MCP Server Cost Per Request?
Adjust the number of tools, the size of each definition, and the deployment style to see the context budget and the controls you need.
Local stdio
0
Tokens of tool definitions per request
0%
Share of a 200,000 token context window
0
Security controls to put in place
Method: tokens per request equal the number of tools multiplied by the average definition size, and the window share assumes a 200,000 token context. Block reported that employees who enabled every tool just in case made its agent slow and sometimes unusable, which is why a budget helps. Control counts are an illustrative checklist (validation, least privilege, logging, version pinning, then authentication, TLS, rate limits, and Origin checks), anchored to guidance in the MCP transport specification. Replace every assumption with values measured on your own server.
Interactive by AIplusInfo
Why the Model Context Protocol Exists
Language models are strong at reasoning over text, yet they cannot see your calendar, your database, or your ticket queue without help. Early integrations solved this with one-off plugins, and each plugin spoke its own dialect of authentication, schemas, and error handling. The result was an N times M problem, where every assistant needed a custom connector for every system a team wanted to reach. The Model Context Protocol replaces that tangle with a single open specification that any client and any server can implement. The site’s overview of the Model Context Protocol and AI integration covers the original motivation in more detail.
Adoption of the protocol has moved faster than most open standards manage in their first year. Anthropic donated the protocol to the Agentic AI Foundation, a directed fund under the Linux Foundation that was co-founded by Anthropic, Block, and OpenAI in December 2025. Google, Microsoft, AWS, Cloudflare, and Bloomberg joined as supporters, which signals that no single vendor controls the direction. Neutral governance matters to builders because a server written today should keep working when the assistants around it change. That stability is the practical reason to invest in learning how to build an MCP server step by step instead of writing another private connector.
The protocol also changes who is able to build and publish integrations for assistants. A developer who owns an internal service can publish a server without asking each assistant vendor for permission. Vendors, in turn, gain thousands of capabilities they never had to code themselves. Earlier coverage tracked the first wave of that ecosystem growth. The same pattern appears in every successful standard, where cheap interoperability pulls more builders in and each builder makes the standard more valuable.
Hosts, Clients, and Servers in the Architecture
The protocol defines three roles that are easy to confuse at first. The host is the application a person uses, such as Claude Desktop or an editor with an assistant panel. Inside the host, one client object maintains a dedicated connection to each server, and each server exposes capabilities over that connection. Every connection is a separate, stateful session that begins with a handshake in which both sides declare what they support. That handshake is why a server written years ago can still talk to a newer client without breaking.
Messages travel as JSON-RPC, which keeps the wire format simple and language neutral. A client sends requests such as listing tools or calling a tool, and the server answers with structured results or errors. Servers can also send notifications, for example to announce that their tool list has changed. The official build an MCP server tutorial walks through this exchange with a weather example. Understanding the roles early prevents a common mistake, which is putting model logic inside the server when the host is supposed to own it.
A server can offer three kinds of capability, and choosing the right one shapes the whole design. Tools are functions that the model can decide to call, usually with user approval, and they can read or change the world. Resources are file-like data that the client can read, such as an API response or a document, and they are chosen by the application rather than by the model. Prompts are reusable templates that a person selects to start a task in a consistent way. The tutorial states that the walkthrough focuses mainly on tools because they are the most common starting point.
The distinction between the three comes down to who is in control of each interaction. The model controls tools, the application controls resources, and the user controls prompts. A read-only knowledge base fits resources well, because the host can attach a document without a model deciding to fetch it. An action such as creating a ticket belongs in a tool, because the model must choose when to run it. Builders who ignore this split often expose everything as a tool, which crowds the model’s choices and weakens its accuracy.
Prompts deserve far more attention than most server authors usually give them. A prompt can encode a team’s best practice, such as a code review checklist or a weekly report format, and expose it as a slash command. Because the user picks the prompt explicitly, it avoids the unpredictability of the model choosing a tool on its own. Teams that publish a few strong prompts alongside their tools often see more consistent results than teams that rely on tool descriptions alone. The comparison of Model Context Protocol and function calling shows where each mechanism fits.
Use a simple decision rule when you are unsure which capability to build. If the model should decide when to act, build a tool. If the application should supply context without a model decision, build a resource. If a human should trigger a repeatable workflow, build a prompt. Many mature servers combine all three, with prompts guiding the user, resources supplying context, and tools performing the actions. Starting with two or three tools and one prompt keeps the first release small enough to test properly.
Choosing Between Stdio and Streamable HTTP
Building on those capabilities, the next decision is how the server will talk to its clients. The specification defines two standard transports, stdio and Streamable HTTP. In stdio, the client launches your server as a subprocess and exchanges newline-delimited JSON-RPC messages through standard input and output. The transport specification says a stdio server must never write anything to standard output that is not a valid protocol message. Logs therefore belong on standard error, and a single stray print statement can corrupt the stream.
Streamable HTTP suits servers that run as independent processes and serve many clients. It exposes one endpoint that accepts POST requests and may answer with either a single JSON response or a stream of server-sent events. It replaced the older HTTP plus SSE transport from the 2024-11-05 revision. The specification requires servers to validate the Origin header and recommends binding local servers to 127.0.0.1 rather than every network interface. Those rules exist to block DNS rebinding attacks, in which a malicious website talks to a server running on a visitor’s laptop.
Most builders should start with stdio and move to HTTP only when sharing becomes necessary. Stdio needs no ports, no certificates, and no network authentication, because the host and server share a machine and a user account. HTTP adds deployment, scaling, and identity concerns, which the later sections cover in depth. Clients are encouraged to support stdio wherever possible, so a stdio server reaches the widest audience on day one. A server can later gain an HTTP mode without rewriting its tools, since the transport sits outside the tool logic.
Picking an SDK and Language
Turning to implementation choices, official SDKs exist for several languages, and the Python and TypeScript packages are the most mature. Python suits data, automation, and machine learning teams, while TypeScript suits web teams that already ship Node services. Both let you declare a tool as an ordinary typed function, and the SDK generates the JSON schema the model sees. The current official tutorial notes that the Python SDK requires version 2.0.0 or higher and Python 3.10 or newer. Check the version in your environment before copying older examples, because class names and import paths have changed between releases.
Many Python teams also use FastMCP, a higher-level framework with a decorator style that keeps servers short. Its tutorial shows that a plain function with the tool decorator becomes a tool, with the function name, docstring, and type hints supplying the name, description, and schema. Whichever library you choose, the contract with the model stays identical, so portability between libraries is a minor concern. This guide uses FastMCP for its code because the same patterns translate directly to the official SDK. Pick the library that your team can read, debug, and upgrade comfortably.
Building on the choice of SDK, the quality of your tool design determines most of your server’s success. A model sees only the tool name, its description, and its parameter schema, and it decides from those alone whether and how to call the tool. Vague names such as process or handle give it nothing to work with. Specific names such as create_support_ticket or search_invoices tell it exactly when the tool applies. Write each description as if you were briefing a new colleague who cannot ask follow-up questions.
Keep each tool small and focused on one clearly defined job for the user. A tool that takes a mode parameter and does five different things forces the model to guess which mode it needs. Five narrow tools with clear names are easier to select and easier to test. Return concise, structured results, because long raw dumps waste the model’s context window and bury the useful fields. Design each tool around a task the user would recognize, not around the internal functions of your codebase. The guide on context engineering for LLM agents explains why every extra token in a tool result competes with the rest of the prompt.
Think about failure from the very start of the design process. A tool should return a clear message when input is invalid, when a record is missing, or when a downstream service is down. The message should tell the model what went wrong and what it could try next, such as choosing a different identifier. Silent failures and stack traces both lead to confused retries. Idempotent tools, which can be called twice without harm, are safer because models sometimes repeat a call after a timeout.
Writing Schemas, Descriptions, and Error Messages
Shifting from design to detail, the schema is where precision pays off most. Type hints produce the basic schema, but constraints such as minimum length, allowed values, and numeric ranges make the contract stricter. An enumerated status field prevents the model from inventing a status that your database does not know. Descriptions on individual parameters should include an example value and the expected format, for instance a date in ISO form. Every constraint you add to the schema is a mistake you no longer have to catch in the tool body.
Error messages deserve the same care that you give to your success messages. Return plain sentences that name the problem and the fix, and avoid exposing internal paths, query text, or credentials. The guidance in the security overview for MCP servers treats verbose errors as a leak risk as well as a usability problem. Mark destructive tools clearly in their descriptions so that hosts can ask for confirmation before they run. Consistent wording across tools also helps the model learn your conventions within a single session.
Authentication and Authorization Basics
Turning to access control, a local stdio server inherits the permissions of the user who launched it. That convenience is also a risk, because a tool that can read files or call internal APIs acts with the person’s full authority. Give the server its own narrowly scoped credential instead of reusing a personal administrator token. Store secrets in environment variables or a secret manager, and never in the tool descriptions or the repository. Short-lived credentials limit the damage if a server is compromised.
Remote servers need real authentication on every connection, because anyone who can reach the endpoint can call its tools. The specification says servers should implement proper authentication for all Streamable HTTP connections. Authorization is separate from authentication, and each tool should check whether the current user may perform that specific action. A model acting for a junior analyst must not inherit the permissions of the service account that runs the server. Passing the end user’s identity through to the downstream system keeps audit trails honest and prevents confused deputy problems.
Plan for user consent and approval flows as part of the same design. Hosts typically ask the person to approve tool calls, yet approval fatigue sets in quickly when every call prompts a dialog. Reserve explicit confirmation for actions that change data, spend money, or send messages outside the organization. Read-only tools can run with much lighter friction because they change nothing. Teams that classify tools by risk level and document the classification find reviews faster and incidents rarer. The article on prompt injection attacks and defenses explains why approvals alone are not enough.
Testing with the Inspector and Automated Checks
Moving on to quality, testing an MCP server is easier than testing most services because the protocol is small and well defined. The MCP Inspector is an interactive developer tool that connects to your server, lists its tools, and lets you call them with sample input. Run it early, before you involve any assistant, so that schema mistakes appear while the cause is still obvious. Watch both the results and the raw messages, because a malformed response often explains a baffling client error. Fix everything the Inspector shows before you blame the model, since most early failures are server bugs.
Add automated tests as soon as the first tool works. FastMCP lets a test connect to a server object directly in memory, call a tool by name, and assert on the result without starting a process. Test the happy path, the invalid input path, and the downstream failure path for every tool. The article on evaluating outputs with an LLM judge shows how to extend this idea to scoring the model’s behavior with your tools. Keep the tests in continuous integration so that a schema change cannot ship unnoticed.
Finally, test with a real model and a small set of realistic tasks. Write ten requests that a user would plausibly make, run them through your host of choice, and record which tools the model selects. Misrouted calls reveal weak names or overlapping descriptions faster than any code review. Repeat the exercise whenever you add a tool, because each addition changes the choices the model faces. Treat this task set as a regression suite and grow it with every bug report.
Connecting Your Server to Claude Desktop and Other Hosts
Moving on to integration, a host learns about your server through a configuration entry that names the command to launch. For Claude Desktop this is a JSON file with an mcpServers key, and each entry gives a command and arguments. The official tutorial shows the exact configuration file locations for macOS, Windows, and Linux. Use absolute paths in the arguments, because the host does not run from your project directory. Restart the host after editing, since most hosts read the file only at startup.
When the server does not appear, check the basics before suspecting the protocol. Confirm that the command resolves, which sometimes requires the full path to the executable. Look at the host’s log files for startup errors, and run the same command in a terminal to see whether it exits immediately. A server that prints anything to standard output will fail silently in most hosts, so check your logging first. Cursor, Visual Studio Code, and other clients use similar configuration files, so one working server usually works everywhere with minor edits.
Deploying Remote Servers and Scaling
Building on a working local server, the next step comes when it must serve a whole team and moves from a local subprocess to a hosted service. The Streamable HTTP transport lets one process handle many clients through a single endpoint, which you can place behind a standard reverse proxy. Containerize the server, pin its dependencies, and deploy it like any other small web service. Health checks, graceful shutdown, and horizontal scaling then work exactly as they do for your other APIs. The article on building custom AI agents for workflow automation shows how such services fit into larger agent systems.
State is the main scaling question that every remote server must eventually answer. The specification lets a server assign a session identifier and requires clients to send it on later requests, which ties a client to server-side memory. Stateless servers are far easier to scale, so keep session state small or move it into a shared store. If you must keep per-session data, use sticky routing or an external cache so that any instance can answer. Plan for a session ending without warning, because the specification allows a server to expire sessions and answer with a not found status.
Remote deployment also changes your identity story in several important ways. Put the endpoint behind TLS, authenticate every request, and decide how user identity reaches the downstream systems. Bloomberg describes building remote, multi-tenant MCP servers with identity-aware middleware so that many teams can reuse the same tools safely. Start with one region and a small replica count, then add capacity when the metrics justify it. Rate limits on each tool protect your downstream systems from a model that loops.
Industry Use Cases for Custom MCP Servers
Beyond the demos, the strongest servers wrap systems that people already use every day. Engineering teams expose issue trackers, deployment pipelines, and code search so that an assistant can answer questions about the codebase without copy and paste. Support teams connect ticketing and knowledge bases, which lets an assistant draft replies grounded in the real policy. Product managers connect roadmap tools and analytics dashboards, so that questions about usage get answered from live numbers. Each of these servers saves a few minutes per task, and those minutes add up across a team over a quarter. The coverage of AI supercharging the MCP developer workflow shows how quickly developers adopt servers that remove copy and paste work.
Finance and operations teams value servers that expose governed data. A server can offer a handful of vetted queries against a warehouse, so an analyst asks a question in plain language and the server runs only approved SQL. Narrow, pre-approved queries are safer than handing a model a general database connection. Sales operations teams connect customer relationship systems to reconcile records and prepare reports. Each of these servers succeeds because it exposes a small number of high-value actions rather than the whole system.
Healthcare, legal, and other regulated industries add stricter requirements to every server. A server in these settings should log every call, redact sensitive fields before returning results, and refuse actions that lack explicit user consent. Many regulated teams also keep the server inside their own network and allow only preapproved connections. The overview of agentic AI adoption statistics shows how fast these organizations are moving despite the added caution. Compliance review is easier when the server’s tool list is short and each tool has a documented purpose.
Personal and small business uses matter just as much as the large enterprise deployments. A solo founder can build a server that reads a local notes folder, a spreadsheet of invoices, or a calendar export. These servers run over stdio on one laptop, need no hosting, and can be written in an afternoon. They also teach the patterns that scale up later, including schema design, error handling, and least privilege. Many teams discover their first production server by promoting a personal tool that a colleague asked to borrow.
Logging, Observability, and Cost Control
Moving on to operations, observability decides whether you can trust the server after launch. Record every tool call with its name, arguments, caller identity, duration, and outcome, and redact secrets before storage. For stdio servers the logs must go to standard error or to a file, because the official tutorial warns that writing to standard output corrupts the protocol messages. HTTP servers can log to standard output without that problem. Structured logs with a request identifier let you trace one user request from the model’s decision down to the database query.
Cost control becomes much easier when you measure usage separately for every tool. Count calls, latency, and result size, and alert on sudden spikes that suggest a looping model. Large results consume the model’s context window and increase the host’s token bill, so trim fields the model does not need. The guide to measuring agent cost per task explains how to connect tool usage to spend. Review the dashboards weekly during the first month, since real traffic always differs from your test plan.
Security Risks Every Builder Should Know
Turning to security, the protocol moves fast and so do the attackers. A Cloud Security Alliance research note calls the tool description field an unsanitized attack surface because models can follow instructions hidden in it. The MCPTox benchmark tested 353 tools from 45 live servers across 20 models and found an average attack success rate of 36.5 percent. More capable models were often more susceptible, because stronger instruction following also means stronger obedience to hidden instructions. Treat every third-party server, and every description it returns, as untrusted input.
Classic software flaws still dominate the published vulnerability reports for this protocol. One analysis counted more than 30 CVEs filed against MCP servers, clients, and infrastructure in January and February 2026, and 43 percent involved shell or command injection. A survey of 2,614 implementations found that 82 percent had file operations exposed to path traversal. Between 38 and 41 percent of the surveyed servers lacked any form of authentication. These numbers describe ordinary mistakes, such as building shell commands from model input, and ordinary mistakes are fixable.
Defense therefore starts with the server’s own code and the inputs that it accepts. Validate every argument against a strict schema, use parameterized queries, resolve file paths against an allowed root, and never pass model input to a shell. Run servers in containers or virtual machines without host credentials, and use short-lived tokens. Each control should be written into the server’s review checklist and tested before release. Pin versions instead of using latest, because a silent update to a dependency can change behavior overnight.
Versioning and Maintaining a Server Over Time
Looking after a server over its lifetime begins with a clear and written version policy. Tools are a public interface, so renaming a tool or changing a parameter can break prompts and workflows that rely on it. Add new optional parameters instead of changing existing ones, and deprecate tools in the description before removing them. Publish a changelog that tells users what changed and why. The protocol also evolves, so track specification revisions, because the documentation now carries dated versions such as the 2025-11-25 transport rules.
Dependency hygiene matters just as much as the quality of your own code. Pin exact versions in a lock file, scan for known vulnerabilities in continuous integration, and update on a schedule rather than in a panic. Treat any change to a tool description as a security-relevant change that needs the same review as a code change. A modified description can alter model behavior without touching a single line of logic. Clients that bind approval to a server name rather than its configuration make this risk worse, as the Cursor cases later in this guide show.
Plan for clear ownership of every production server as well. Name an owner for every production server, document the on-call path, and set a retirement date for experiments. Abandoned servers keep running with old credentials and become silent liabilities. The article on using AI coding agents with live API docs shows how teams keep integrations current as the underlying APIs evolve. A short quarterly review that lists every server, its owner, and its permissions catches most drift before it hurts.
Ethics, Accountability, and Consent
Stepping back to the human side, a server gives a model real power over real data, and someone must answer for how that power is used. The owner of the server is accountable for what its tools do, even when a model chose to call them. Document each tool’s purpose, the data it touches, and the people it could affect. Users deserve to know what an assistant can do on their behalf before it does it. Clear descriptions and honest consent prompts are part of building the server, not an afterthought.
Privacy also deserves explicit design attention from the first day of the project. Collect only the fields a tool needs, avoid returning personal data the task does not require, and honor retention limits. Be especially careful with servers that read email, chat, or health records, since a single prompt injection can turn them into data exfiltration channels. The incident coverage of an agent flaw opening email attacks illustrates how quickly convenience can become exposure. Review high-impact tools with a colleague outside the build team.
Common Mistakes Teams Make When Building Servers
Stepping back from the checklists, a handful of mistakes account for most failed servers. The first is exposing too many tools, which overwhelms the model and slows every response. Block’s engineers saw this when employees enabled every tool just in case, which made the agent slow and sometimes unusable, according to a report on the company’s rollout. The fix was automatic tool selection and a curated set of approved servers. Smaller, task-focused servers avoid the problem entirely from the very start of a project.
Another common mistake is treating the model as a trusted caller. Teams skip validation because the model usually sends sensible input, then discover that a crafted prompt produces dangerous arguments. Validate tool input on the server as strictly as you would validate input from an anonymous internet user. Others forget logging, hardcode secrets, or return enormous payloads that bury the answer. Writing the mistakes down as a team checklist turns each painful lesson into a review item.
A quieter but equally costly mistake is skipping testing with a real model. A server can pass every unit test and still fail because two tool descriptions overlap or a parameter name confuses the model. Run realistic tasks through a real host before each release and record the tool choices. The guide on building a simple OpenAI app in Python is a useful companion for newcomers who want to see the model side of the loop. Small and repeatable experiments beat intuition and guesswork almost every time.
Shifting to speed, most latency comes from the downstream systems rather than the protocol. Cache slow lookups, set timeouts on every outbound call, and run independent requests concurrently with asynchronous code. Return only the fields the model needs, because smaller results reduce both transfer time and token cost. Fewer, better tools consistently outperform large catalogs, because the model chooses more accurately from a short list. Measure before you optimize anything, and always fix the slowest tool first.
Tool count deserves its own budget in the same way that memory does. Every tool name, description, and schema occupies space in the model’s context on each request. A server with fifty tools can consume thousands of tokens before the user says a word. Group related actions, split rarely used tools into a separate server, and let hosts enable only what a task needs. The patterns in context engineering for LLM agents apply directly to this budgeting problem.
The Future of the Model Context Protocol
Looking ahead, the protocol’s governance now sits with a vendor-neutral foundation, which should keep the specification stable and widely implemented. Expect richer authorization flows, better discovery of servers, and more support for long-running and asynchronous tasks. Clients will likely get better at managing large tool catalogs, perhaps by searching tools on demand instead of loading them all. The coverage of Anthropic launching the open-source AI connection protocol shows how much has changed since the first release. Builders who learn the fundamentals now will adapt easily as the details evolve.
Security tooling will mature alongside the protocol over the coming year. Scanners that inspect tool descriptions, registries that verify publishers, and clients that bind approvals to exact configurations are already appearing. The servers that last will be the ones whose authors treated security and clarity as features from the first commit. Enterprises will demand audit trails, policy engines, and managed gateways before they connect sensitive systems. Small teams that build those habits early will find compliance far less painful.
Interoperability between different protocols is also coming into sharper focus for builders. Agent-to-agent standards and tool protocols address different layers, and most architectures will use both. A well-designed server stays useful in either world, because its tools are small, typed, and documented. Keep an eye on the specification’s release notes, and test your server against each new revision. The skills you build writing one good server transfer directly to the next one.
Chart From AIplusInfo
Where MCP vulnerabilities come from
Share of 30+ MCP-related CVEs by attack category, January and February 2026, in percent.
Source: MCP Security 2026: 30 CVEs in 60 Days.
Chart by AIplusInfo
How to Build an MCP Server Step by Step: A Working Implementation
This walkthrough builds a small task tracker server in Python that you can run locally and connect to an assistant. It uses the FastMCP framework, a SQLite database from the standard library, and a handful of typed functions. The project exposes two tools, one resource, and one prompt, which is enough to show every major capability. Each step ends with a runnable state, so you can stop at any point and still have a working server. Adapt the data layer to your own system once the pattern is clear, and validate every part against your own requirements before you rely on it.
Step 1 – Prepare the environment
Begin with a clean virtual environment so the server’s dependencies stay isolated from the rest of your machine. You need Python 3.10 or newer, and the FastMCP package installs everything else the server requires. The FastMCP tutorial documents the install command and confirms that a standard function becomes a tool when decorated. Create a project folder, activate the environment, and freeze the dependency versions into a requirements file. Pro tip: pin exact versions from the first day, because protocol libraries change quickly and a silent upgrade can break your host integration.
mkdir task-server && cd task-server
python3 -m venv .venv
source .venv/bin/activate
pip install fastmcp
pip freeze > requirements.txt
Step 2 – Create the data layer
Keep data access in its own module so the tool functions stay short and easy to test. The module below opens a SQLite file next to the code and creates a table with 4 columns on first use. Using the standard library avoids extra dependencies and works the same on every platform. The table stores an identifier, a title, a status, and a creation timestamp for each task. Parameterized queries are used everywhere later, which prevents the injection flaws that dominate public vulnerability reports. Swap this module for your real database client when you adapt the pattern to production data. Keep the interface to one function, because the rest of the server then never needs to know which database sits underneath.
# tasks.py
import sqlite3
from pathlib import Path
DB_PATH = Path(__file__).with_name("tasks.db")
def get_db() -> sqlite3.Connection:
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
conn.execute(
"CREATE TABLE IF NOT EXISTS tasks ("
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
"title TEXT NOT NULL, "
"status TEXT NOT NULL DEFAULT 'open', "
"created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP)"
)
return conn
Step 3 – Define the server and the first tools
Now create the server object and register the first 2 of the 4 tools with the tool decorator. FastMCP reads each function name, docstring, and type hints and turns them into the name, description, and input schema the model sees. Write the docstrings carefully, because they are the only guidance the model receives about when to call each tool. Configure logging before anything else, and keep it away from standard output, since the stdio transport uses that stream for protocol messages. The transport specification explains why a stray write to standard output breaks the connection.
The add task function inserts a row with a parameterized statement and returns the new record. The list tasks function filters by status and returns plain dictionaries that serialize cleanly. Returning small structured objects keeps results compact and easy for the model to read. Both functions use the context manager on the connection so that changes commit automatically. The final lines run the server over stdio, which is the default transport for local hosts.
# server.py
import logging
from fastmcp import FastMCP
from tasks import get_db
logging.basicConfig(level=logging.INFO) # writes to stderr, never stdout
log = logging.getLogger("task-server")
mcp = FastMCP("Task Tracker")
@mcp.tool
def add_task(title: str) -> dict:
"""Create a new open task. Use when the user asks to remember or track something."""
with get_db() as conn:
cur = conn.execute("INSERT INTO tasks (title) VALUES (?)", (title.strip(),))
log.info("created task %s", cur.lastrowid)
return {"id": cur.lastrowid, "title": title.strip(), "status": "open"}
@mcp.tool
def list_tasks(status: str = "open") -> list[dict]:
"""List tasks filtered by status, either 'open' or 'done'."""
with get_db() as conn:
rows = conn.execute(
"SELECT id, title, status FROM tasks WHERE status = ? ORDER BY id", (status,)
).fetchall()
return [dict(r) for r in rows]
if __name__ == "__main__":
mcp.run()
Step 4 – Add a resource and a prompt
Tools alone make a useful server, but 1 resource and 1 prompt round out the experience. A resource exposes read-only data at a stable address, and the host can attach it to a conversation without the model deciding to fetch it. A prompt packages a repeatable instruction that a person can launch deliberately. Here the resource reports task counts by status, and the prompt starts a weekly review. Add both functions above the run block in the server file, and keep the descriptions short and specific. Hosts display these descriptions to people, so plain language helps users decide which capability to choose.
@mcp.resource("tasks://summary")
def task_summary() -> str:
"""Counts of tasks grouped by status."""
with get_db() as conn:
rows = conn.execute(
"SELECT status, COUNT(*) AS n FROM tasks GROUP BY status"
).fetchall()
return ", ".join(f"{r['status']}: {r['n']}" for r in rows) or "No tasks yet."
@mcp.prompt
def weekly_review() -> str:
"""Start a weekly review of open tasks."""
return (
"List my open tasks, group them by theme, and suggest the three "
"that matter most this week."
)
Step 5 – Validate input and fail clearly
Never trust the arguments a model sends, even when they look reasonable on the first 10 or 20 calls. Replace the free-form status string with a literal type so the schema itself rejects unknown values. Add length limits on titles, and raise a tool error with a plain-language message when input is invalid or a record is missing. FastMCP returns that message to the model, which can then correct itself instead of retrying blindly. These few lines remove whole classes of bugs and make the server far safer to expose. Replace the earlier add and list functions with the stricter versions shown below.
Add a complete task function in the same style, because a tracker that cannot close tasks is not very useful. It should confirm that the task exists before changing it, and it should be safe to call twice. Idempotent behavior matters because models sometimes repeat a call after a timeout. Keep the error text free of file paths, query text, and stack traces. Consistent, friendly errors also help the model learn your conventions during a session.
from typing import Literal
from fastmcp.exceptions import ToolError
@mcp.tool
def add_task(title: str) -> dict:
"""Create a new open task. Use when the user asks to remember or track something."""
title = title.strip()
if not 1 <= len(title) <= 200:
raise ToolError("Title must be between 1 and 200 characters.")
with get_db() as conn:
cur = conn.execute("INSERT INTO tasks (title) VALUES (?)", (title,))
return {"id": cur.lastrowid, "title": title, "status": "open"}
@mcp.tool
def list_tasks(status: Literal["open", "done"] = "open") -> list[dict]:
"""List tasks filtered by status."""
with get_db() as conn:
rows = conn.execute(
"SELECT id, title, status FROM tasks WHERE status = ? ORDER BY id", (status,)
).fetchall()
return [dict(r) for r in rows]
@mcp.tool
def complete_task(task_id: int) -> dict:
"""Mark a task as done. Safe to call more than once."""
with get_db() as conn:
row = conn.execute("SELECT id, title FROM tasks WHERE id = ?", (task_id,)).fetchone()
if row is None:
raise ToolError(f"No task with id {task_id} exists. Call list_tasks to see valid ids.")
conn.execute("UPDATE tasks SET status="done" WHERE id = ?", (task_id,))
return {"id": row["id"], "title": row["title"], "status": "done"}
Step 6 – Test with a client and the Inspector
Test the server thoroughly before any assistant is allowed to touch it, and aim to finish this step in under 15 minutes. A short script can connect to the server object in memory, list its tools, and call one, with no subprocess and no configuration. This catches schema and logic errors in seconds and fits naturally into continuous integration. Then launch the MCP Inspector, an interactive tool that connects to your server and lets you call each tool by hand. Compare the raw messages with your expectations, and fix every problem before moving on.
Write at least three checks for each tool, covering the normal path, invalid input, and a missing record. Run them in your pipeline on every change, so a schema edit cannot ship unnoticed. The earlier guide on evaluating outputs with an LLM as a judge shows how to extend testing to the model’s own behavior. Keep a short list of realistic user requests for later manual trials with a real host. Together these layers give you confidence that changes are safe.
# test_server.py
import asyncio
from fastmcp import Client
from server import mcp
async def main():
async with Client(mcp) as client:
tools = await client.list_tools()
print([t.name for t in tools])
created = await client.call_tool("add_task", {"title": "Write the MCP guide"})
print(created.data)
print((await client.call_tool("list_tasks", {})).data)
asyncio.run(main())
python3 test_server.py
npx @modelcontextprotocol/inspector python server.py
Step 7 – Connect the server to Claude Desktop
A host finds your server through 1 configuration file that names the command to launch. The official build tutorial lists the file location for each operating system and shows the expected structure. Add an entry that points to the Python interpreter inside your virtual environment and to the absolute path of the server file. Absolute paths matter because the host does not start from your project folder. Save the file, then restart the host so that it reads the new entry. Most hosts read their configuration only at startup, so changes made while the application is open will not take effect.
After the restart, ask the assistant to add a task and then to list open tasks. The host should show a tool approval prompt the first time, which confirms that the connection works and that consent is enforced. If nothing appears, run the same command in a terminal to see whether it exits with an error. Check the host’s log files as well, since startup failures are recorded there. Remember that any output on standard output from your code will break the session.
{
"mcpServers": {
"task-tracker": {
"command": "/ABSOLUTE/PATH/TO/task-server/.venv/bin/python",
"args": ["/ABSOLUTE/PATH/TO/task-server/server.py"]
}
}
}
Step 8 – Serve over Streamable HTTP and harden
When colleagues need the same server, switch the transport to Streamable HTTP and run it as a service in step 8 of this build. Change only the run call, because the tools, resource, and prompt stay exactly the same. Bind to the loopback address while you test, so that nothing outside your machine can connect. The specification requires servers to validate the Origin header and recommends local binding for exactly this reason. Put the service behind a reverse proxy with TLS and real authentication before you expose it to anyone else.
Hardening work continues long after the code appears to work correctly. Run the service in a container with no access to host credentials, and give it a database user with only the permissions it needs. Add rate limits on each tool and alerts on unusual call volumes. Pin dependencies, scan them in continuous integration, and review any change to a tool description as carefully as a code change. Finally, document the owner, the on-call path, and the retirement plan so that the server never becomes an orphan.
if __name__ == "__main__":
mcp.run(transport="http", host="127.0.0.1", port=8000)
Recommended by AIplusInfo
Books to go deeper on building agent tools
Hand-picked titles that map to the tool design and agent work described above.
As an Amazon Associate, AIplusInfo earns from qualifying purchases.
Book
AI Engineering: Building Applications with Foundation Models
Covers tool use, agents, evaluation, and context design, the practical backdrop to deciding what a server should expose to a model.
Buy on Amazon
Book
Building Applications with AI Agents: Designing and Implementing Multiagent Systems
Explains how agents select and call tools, which helps builders design server tools that a model can choose between reliably.
Buy on Amazon
Key Insights
- Anthropic reported more than 10,000 active public MCP servers and over 97 million monthly SDK downloads in December 2025, so building a server now means joining a mainstream ecosystem.
- Block rolled its Goose agent out to all 12,000 employees in eight weeks with over 60 internal MCP servers ready after week one, proving servers can be built fast.
- Researchers counted more than 30 CVEs against MCP servers, clients, and infrastructure in two months, and 43 percent involved shell injection, so basic input validation prevents many incidents.
- A survey of 2,614 implementations found that 82 percent exposed file operations to path traversal, which makes allowed-root checks a mandatory part of any file tool.
- The MCPTox benchmark measured an average attack success rate of 36.5 percent across 20 models, so tool descriptions from unreviewed servers should never be trusted.
- Between 38 and 41 percent of surveyed servers had no authentication at all, which means that a remote server without access control is still the norm to avoid.
- Bloomberg reported that its remote MCP approach cut experimentation cycles from weeks to minutes, showing the payoff of reusable and shared tool servers.
Taken together, these figures describe a protocol that has moved from experiment to infrastructure in barely a year. Adoption numbers and corporate rollouts show that builders can ship useful servers quickly and that organizations see real value. The security data shows the other side, because the same speed produced flaws in input handling, file access, and authentication. The pattern in the vulnerability counts is reassuring in one respect, since most issues are ordinary and preventable with disciplined engineering. Teams that pair rapid prototyping with validation, least privilege, and review will capture the benefits while avoiding the worst incidents.
| Dimension | Local stdio | Streamable HTTP, private | Streamable HTTP, public |
|---|---|---|---|
| Best for | Personal tools and prototypes | Team servers behind a firewall | Public products and partner access |
| Setup effort | Minutes, no ports needed | Hours, needs hosting and TLS | Days, needs full security review |
| Authentication | Inherits the launching user | Tokens from your identity provider | Tokens plus per-client scopes and quotas |
| Scaling | One process per user | Horizontal with shared session state | Horizontal with rate limits and abuse controls |
| Origin and network controls | Not applicable | Bind to private networks, validate Origin | Validate Origin, enforce TLS, add a gateway |
| Logging | Standard error or a log file | Central structured logs | Central logs with audit trails and alerting |
| Main risk | Tools act with the full user authority | Shared credentials and session leaks | Internet-scale attacks and abuse |
| Typical host | Claude Desktop, editors, command line | Internal assistants and agents | Third-party assistants and partner apps |
Real-World Examples of Building and Running MCP Servers
Block’s Goose Agent and Its Internal MCP Servers
Block deployed its internal agent, Goose, after rewriting it to speak the Model Context Protocol. The company connected the agent to preapproved internal servers for Slack, Google Calendar, the data warehouse, and incident tools. According to a report on the company’s rollout, the agent reached all 12,000 employees in eight weeks with more than 60 internal servers ready after week one. One sales operations employee analyzed 80,000 sales records in about an hour, work that had previously taken days of spreadsheet effort. The deployment also had limits, because employees who enabled every tool just in case made the agent slow and sometimes unusable. Block answered with automatic tool selection and conversation summarization, and it restricted installs to approved servers because it operates in a regulated industry. The figures come from a conference talk by an executive, so they have not been independently verified.
Bloomberg’s Remote and Multi-Tenant MCP Servers
Bloomberg faced a gap between quick generative AI demos and slow production launches, caused by custom integrations and many handoffs between engineering, legal, product, and compliance teams. Its engineers built remote, multi-tenant MCP servers with identity-aware middleware, so that agents, tools, and models could be configured and swapped instead of rewritten. A summary of the conference talk says the approach reduced experimentation cycles from weeks to minutes across a company with more than 9,500 engineers. Teams reuse the same tool servers across departments, which removes duplicated integration work. The source still gives no production timelines, adoption rates, or outcome measures, so the benefit cannot be fully checked. Its sample code is also labeled illustrative, and the authentication example only copies a user identifier header without verifying it.
The Official Weather Server Quickstart
The protocol maintainers publish a reference weather server that most newcomers build first. A developer can follow it in a few hours and see the whole pattern in under 100 lines. The server exposes two tools, one for state alerts and one for point forecasts, and it calls the public National Weather Service API behind them. The quickstart repository includes complete Python and TypeScript versions that connect to Claude Desktop through a short configuration entry. The forecast tool returns only the next five periods, which keeps results compact and easy for a model to read. The example is still limited to United States locations, and it handles errors with a generic message, so production servers need richer failure handling. It remains the clearest minimal template for learning how tools, schemas, and transports fit together.
Case Studies in Securing MCP Servers and Clients
Case Study: Tool Poisoning and the Hidden Instruction Problem
Security researchers at Invariant Labs showed in April 2025 that a tool description can carry instructions that the user never sees. Their proof of concept used a poisoned calculator tool that silently exfiltrated an SSH private key from Cursor. The problem was structural, because the description field is passed to the model as trusted text while the interface shows the person only a short summary. A Cloud Security Alliance research note calls the description field an unsanitized attack surface. The MCPTox benchmark later tested 353 tools from 45 live servers across 20 models. The average attack success rate was 36.5 percent, and the highest single model reached 72.8 percent.
The recommended response to these findings combines several complementary controls, and vendors introduced stricter approval prompts afterward. Teams should scan tool descriptions for embedded exfiltration directives before deployment, run servers in containers without host credentials, and start new servers at a read-only trust level. Claude 3.7 Sonnet had the highest explicit refusal rate in the study and still complied in about 34 percent of cases, so refusal training alone is not enough. More capable models were often more susceptible, which the authors attribute to stronger instruction following. The original demonstration was a proof of concept rather than a widespread campaign, so real-world prevalence remains uncertain. Even so, the result changed how reviewers treat third-party servers.
Case Study: The mcp-remote Command Injection Flaw
A popular helper package named mcp-remote, which lets local clients reach remote servers and is widely used by developers, carried a command injection flaw tracked as CVE-2025-6514. The flaw scored 9.6 on the CVSS scale, and the package had been downloaded more than 437,000 times. The problem was a familiar one, where untrusted data from a connection could reach a shell. An analysis of MCP security in 2026 found more than 30 CVEs filed in just two months across servers, clients, and infrastructure. Shell and command injection made up 43 percent of that total.
The advice that followed the disclosure was practical rather than exotic or complicated, and it asks teams to build basic hygiene into their process. Practical solutions were available, and teams that adopted version pinning and scanning reduced their exposure to this class of flaw. Pin versions instead of using the latest tag, run a scanner against your MCP configuration, remove unused servers, and give credentials the minimum permissions. Teams should log every tool invocation and restrict which tools can run without explicit approval. The same analysis surveyed 2,614 implementations and found that 82 percent had file operations exposed to path traversal. Counts like these come from a single secondary report, so the exact percentages deserve cautious reading. The direction is still clear, since basic validation would have prevented most of the listed flaws.
Case Study: Cursor’s CurXecute and MCPoison Vulnerabilities
Within four days of each other in August 2025, two separate research teams disclosed flaws in how Cursor handled MCP configuration. AIM Security reported CurXecute, tracked as CVE-2025-54135 with a CVSS score of 8.6, in which a prompt injection delivered through Slack rewrote the user’s MCP configuration file. Check Point reported MCPoison, tracked as CVE-2025-54136, in which approval was bound to the server name instead of its command. A later change to the command therefore ran without asking the user again. The Cloud Security Alliance note summarizes both disclosures in a single table.
The vendor introduced fixes in version 1.3.9, so the fix required updating Cursor to that release or later, and the note recommends treating MCP configuration changes as code review events. It also advises inventorying every installed server and removing any that are not on an allowlist. The lesson for builders is that approval should bind to the exact configuration, not just a friendly name. Later disclosures in 2026 still affected other coding tools, which shows that the problem is industry wide rather than specific to one product. The note is a research summary, so teams should confirm patch details in each vendor’s own advisory before acting.
Frequently Asked Questions on How to Build an MCP Server Step by Step
An MCP server is a program that exposes tools, resources, and prompts to AI assistants through the Model Context Protocol. It receives JSON-RPC requests from a client and returns structured results. One server can work with many different hosts without custom integration code. Think of it as a standard adapter between your systems and any compatible assistant.
A basic server with two or three typed tools can be running in under an hour with the Python or TypeScript SDK. Most of the time goes into writing clear descriptions and testing, not into protocol code. Production readiness takes longer because of authentication, logging, and security review. Plan a few days for a server that a team will depend on.
Python and TypeScript have the most mature official SDKs, so most builders choose one of them. Python suits data and automation teams, while TypeScript suits teams that already ship Node services. The contract with the model is identical in both languages. Choose the language your team can read, test, and maintain comfortably.
A tool is a function the model can decide to call, a resource is read-only data the application attaches, and a prompt is a template a person selects. The difference is about who controls each interaction in the conversation. Use tools for actions, resources for context, and prompts for repeatable workflows that people start deliberately. Many good servers combine all three capabilities in one package.
Use stdio for local servers that run on the same machine as the host, because it needs no ports or network authentication. Use Streamable HTTP when many users share one server or when the server must run remotely. HTTP adds deployment, TLS, and identity concerns that you must plan for. A server can usually support both without changing its tools.
The most common cause is writing to standard output from a stdio server, which corrupts the protocol messages. Send every log line to standard error instead of standard output. Also check that the configuration uses absolute paths and that the command resolves in the host’s environment. The host’s own log files usually show the startup error clearly.
Use the MCP Inspector, which connects to your server and lets you list and call tools by hand. You can also write automated tests that connect to the server object in memory and call tools directly. Test valid input, invalid input, and downstream failure for every tool. Finish with a small set of realistic tasks in a real host.
Not automatically, because tool descriptions can carry hidden instructions that the user never sees. Research found an average attack success rate of 36.5 percent across 20 models in one benchmark. Review the publisher, pin versions, read the descriptions, and run unknown servers in isolation. Start every new server with read-only access and heavy supervision.
Local stdio servers inherit the launching user’s permissions, so give them a narrowly scoped credential of their own instead. Remote servers must authenticate every connection, typically with tokens issued by your identity provider. Check authorization separately for each tool and pass the end user’s identity downstream. Short-lived credentials also limit the damage if anything ever leaks.
Fewer is better, because every tool adds its name, description, and schema to the model’s context on each request. A focused server with a handful of well-named tools usually outperforms a very large catalog. Split rarely used tools into a separate server that hosts can enable on demand. Measure which tools the model actually selects and remove the ones it ignores.
Raise clear tool errors with plain-language messages that explain the problem and suggest a possible fix. Avoid exposing file paths, query text, or stack traces to the model or the user. Make destructive tools idempotent so that a repeated call does no harm. Consistent error wording also helps the model recover without blind retries.
Yes, by running it over Streamable HTTP behind TLS and authentication. Containerize it, pin dependencies, and add rate limits and logging. Keep session state small so the service can scale horizontally. Bloomberg describes remote multi-tenant servers with identity-aware middleware as one way to share tools safely.
Pin dependency versions, scan them in continuous integration, and review changes to tool descriptions like code changes. Log every call together with the caller and the arguments that were used. Run servers in containers that hold no host credentials of any kind. Assign a named owner to each server and review the permissions of every server each quarter.



