How to Build an MCP Server (Step by Step) in 2026

- Copy this build-and-test path first
- Step 1: Define one capability you can test
- Step 2: Set up a Python MCP server
- Step 3: Add resources or prompts only when the job needs them
- Step 4: Choose stdio or Streamable HTTP before deployment
- Step 5: Inspect the contract before connecting a host
- Step 6: Connect one host after the smoke test passes
- Step 7: Secure a remote MCP server as a service
- Step 8: Check the 2026 protocol boundary
- Troubleshoot the failures that hide in a “working” server
- Conclusion
- FAQ
Your host can show a green “connected” indicator while your MCP server still exposes the wrong tool, accepts unsafe input, or breaks when a dependency fails. Avoid that trap by building one narrow capability and testing its contract before you hand it to a host.
The short answer: to build an MCP server, choose an official SDK, register one focused tool with an explicit input contract, run it over stdio or Streamable HTTP, inspect it directly, and test both success and failure paths. This guide uses Python for the runnable example, then shows where transport, host setup, and HTTP security fit.
Copy this build-and-test path first
Use this checklist before you add a second tool, a database, or a host configuration:
Define one task, its inputs, its success result, and its side effects.
Create the server with a current official SDK.
Register one tool with schema-level and handler-level validation.
Run it over stdio for a local host-launched process, or Streamable HTTP for a network service.
Inspect the advertised tool and schema.
Call it with valid input, invalid input, and a controlled dependency failure.
Connect a host only after those checks pass.
For remote access, authenticate requests and authorize each operation inside the server.
That order is deliberate: it lets you falsify the server’s contract while the surface area is small.

Get the Mobile Testing Playbook Used by 800+ QA Teams
Discover 50+ battle-tested strategies to catch critical bugs before production and ship 5-star apps faster.
Step 1: Define one capability you can test
Write the tool contract before writing code. A useful first capability has a specific verb, bounded input, predictable output, and known dependency. “Search a book catalog by title or author” is testable; “help with books” is not.
Capture these five details:
User task: What should the host be able to ask for?
Input: Which fields are required, and what limits apply?
Success result: What information will the caller receive?
Side effects: Is the tool read-only, or can it change data?
Dependency: What database, API, file, or service can fail?
An MCP tool is a formal interface rather than merely a function. The 2026-07-28 tools specification defines tool names, descriptions, input schemas, optional output schemas, annotations, and results. That is why a narrow first tool pays off: you can inspect each part and decide exactly where bad input or a downstream outage should be handled.
Step 2: Set up a Python MCP server
The current Python SDK documentation uses MCPServer and supports starting a server through mcp.run(). It describes stdio as the local process transport and Streamable HTTP as the networked option. Follow the Python SDK’s current run guide when you install or upgrade, because SDK commands and APIs can change between versions.
Create a small project:
Then create server.py:
This is intentionally a placeholder search, not a production catalog. Its value is the contract: one named tool, one typed input, a description, explicit local validation, and one stable result shape. Replace the return statement with a bounded real operation only after the test path below works.
If your service is already TypeScript, use that language instead of introducing Python solely for MCP. The current TypeScript SDK v2 first-server guide documents McpServer, schema-based tool registration, and stdio serving. Keep package names and imports beside that current guide rather than copying a v1 snippet from an older tutorial.
Step 3: Add resources or prompts only when the job needs them
A server can expose tools, resources, and prompts. Tools perform callable operations; resources give clients separately addressable, file-like data; prompts provide reusable templates for user-facing workflows. The official build tutorial introduces all three capabilities.
Start with the one tool above when the user needs one action. Add a resource when a client needs to retrieve read-only data independently. Add a prompt when you have a repeatable interaction pattern that a user should select. Adding every capability to a starter project makes the contract harder to test without making the first use case better.
For the broader architecture behind hosts, clients, and servers, Quash’s MCP overview is useful background; this tutorial stays focused on the implementation path.
Step 4: Choose stdio or Streamable HTTP before deployment
Choose the transport from the deployment boundary, not from personal preference.
Question | Choose stdio | Choose Streamable HTTP |
Who starts the server? | The host launches a child process | A service listens on a network endpoint |
Where will you use it? | A local integration | A deployed or remotely reachable integration |
How do clients connect? | Through the launched process | Through an HTTP endpoint |
What boundary needs protection? | The local launcher and machine | HTTPS, authentication, authorization, and allowed origins |
How should you test it? | Start the actual command in Inspector | Point Inspector at the actual endpoint |
The Streamable HTTP specification says this transport replaced the earlier HTTP+SSE transport and changed again in the 2026-07-28 protocol revision. For a new remote server, use Streamable HTTP; do not begin with HTTP+SSE advice copied from an older post.
For local development, run the Python server under the Inspector workflow:
For an HTTP deployment, configure the server’s run call for Streamable HTTP, for example:
Do not send diagnostics to stdout when using stdio. Stdout carries protocol messages, so print() in Python or console.log() in a TypeScript stdio server can corrupt the JSON-RPC exchange. Send diagnostics to stderr instead.
Step 5: Inspect the contract before connecting a host
The MCP Inspector is the reference tool for testing and debugging MCP servers. It offers browser, CLI, and terminal interfaces through @modelcontextprotocol/inspector, so you can use the same checks interactively and in a repeatable shell workflow.
Run this smoke test against the transport you actually intend to ship:
Start the server. Confirm the process stays alive and emits no protocol noise on stdout.
Discover its protocol identity. In the 2026-07-28 era, call
server/discoverif your SDK or client exposes it. The discovery specification requires servers to report supported versions, capabilities, and identity before other requests.List tools. Confirm the exact name, description, input schema, output schema where present, and annotations.
Call a representative valid input. For this example, try a short book title or author and confirm the result has the shape you promised.
Call boundary inputs. Test an empty string, missing field, wrong type, and a value beyond your chosen limit. The request should fail before unsafe work reaches a dependency.
Trigger a controlled failure. Make the catalog lookup fail in a test environment. Inspect the MCP tool result, including
isErrorwhere applicable, instead of relying only on a process or HTTP status.Check structured output. If you declare an output schema and return structured content, validate the actual result against it. Keep a compatibility text representation when your selected protocol and client combination requires it.
Repeat over the chosen transport. A correct stdio run does not prove your HTTP route, proxy, or authorization works.
Save the evidence. Keep the launch command, inputs, expected results, protocol version, and failure cases in your repository or CI job.
A host connection is not a substitute for this list. It proves only that the host reached or launched the server.
Step 6: Connect one host after the smoke test passes
Host configuration is host-specific. With the Python SDK, the Claude Desktop installation path can be:
Treat that command as a Claude Desktop convenience, not a universal installer. Other hosts can require their own UI or configuration file, but they need the same core information: either the stdio command plus arguments or the Streamable HTTP URL.
After you register the server, test from the host as well: verify it lists search_books, invoke it with a valid query, and try the invalid-input case. If the host has tool-approval UI, confirm that the tool’s description and side-effect behavior make sense to the person approving it.
Step 7: Secure a remote MCP server as a service
A remote server that exposes private data or actions needs authorization at the HTTP boundary and authorization inside each tool. The MCP authorization tutorial explains the OAuth 2.1 model for protecting sensitive server resources and operations; the Python SDK’s authorization reference describes the server-side token-verification role.
Before exposing a remote endpoint, do all of the following:
terminate TLS at the service or a trusted proxy;
validate bearer tokens, including the issuer, audience or resource, expiry, and required scopes for your chosen identity provider;
authorize every tool operation using the authenticated identity and least privilege;
validate the
Originheader to reduce DNS-rebinding risk;bind a local-only development server to
127.0.0.1, not every interface;apply timeouts, body-size limits, rate limits, and bounded tool inputs; and
keep tokens, credentials, and unnecessary personal data out of tool metadata, results, and logs.
These controls apply to HTTP. A local stdio server has no HTTP Authorization header; its security boundary is the host and user account that launch the process. Do not assume a bearer-token layer designed for an HTTP route protects a local child process.
Step 8: Check the 2026 protocol boundary
The 2026-07-28 protocol revision is worth checking before you follow any tutorial that refers to sessions, a GET-based stream, or HTTP+SSE as the default. In this revision, server/discover is the capability handshake, and the protocol is stateless by default. Long-lived notifications are opt-in through subscriptions/listen, which replaced the former resource subscription RPC and HTTP GET endpoint.
You do not need subscriptions for this static one-tool server. You do need to verify that your chosen SDK, Inspector, and host agree on the protocol era. Test a legacy client separately only when you have a real compatibility requirement.
Troubleshoot the failures that hide in a “working” server
Symptom | Likely cause | What to check |
Inspector sees broken or unreadable messages | Logs went to stdout | Move diagnostics to stderr and restart |
Host sees no tools | Wrong launch command or registration failed | Run the exact command in Inspector and list tools |
Valid calls work but invalid calls crash | Validation happens after dependency work | Enforce schema limits and handler validation first |
Result does not match what the host expects | Tool result and declared output contract differ | Compare the returned content and structured data to the schema |
Remote requests stop before a tool runs | Token, scope, or Origin rejection | Test no token, invalid token, insufficient scope, and allowed origin separately |
Host connects but calls do nothing | Transport or endpoint mismatch | Confirm stdio versus Streamable HTTP and the exact configured endpoint |
Conclusion
Build the smallest server that can prove one useful capability. If you can inspect its schema, reject bad input, see a controlled failure, and repeat those checks over the transport you will deploy, you have a contract you can safely give a host. Add capabilities only after that contract is stable—and make your next change earn its place in the same smoke test.
FAQ
Should you start with stdio or Streamable HTTP?
Start with stdio when a local host will launch the process. Start with Streamable HTTP when you need a deployed endpoint or network clients. The deployment boundary should decide the transport.
Do you need resources and prompts in a first MCP server?
No. A single focused tool is enough for a first server. Add resources or prompts only when they solve a separate, specific product need.
Why does stdout break a stdio MCP server?
Stdout is the protocol channel for a stdio server. Extra diagnostic output can corrupt the messages exchanged between host and server, so send logs to stderr.
What should you test before you connect a host?
List the tool contract, call it with representative valid and invalid inputs, and exercise a controlled dependency failure. Repeat the checks over the intended transport.
Is HTTP+SSE the default for a new remote MCP server?
No. The current Streamable HTTP specification identifies Streamable HTTP as the replacement for the earlier HTTP+SSE transport. Use the current SDK documentation for your selected protocol version.
Can an HTTP bearer token secure a local stdio server?
No. Bearer-token authorization applies to HTTP requests. For stdio, the process launcher and local machine boundary control who can run and reach the server.








