Go Deeper 7 min read Updated Sep 14, 2026

Building Your Own MCP Server

Every MCP server you've used so far in this series was built by someone wrapping an existing system - a filesystem, a database, an API - in the protocol's standard vocabulary. This guide is about doing that wrapping yourself: the shape of the work, and specifically the security decisions that stop being theoretical the moment a model, not a person, is the one calling your code.

What "building a server" actually involves

At its core, an MCP server is a thin translation layer: it takes the capabilities of something that already exists - your internal CRM's API, a Postgres database, a ticketing system - and exposes a subset of them as tools, resources, and prompts with proper names, descriptions, and input schemas. The official SDKs (Python and TypeScript are the most mature) handle the protocol plumbing - the JSON-RPC message format, the transport handling - so the actual work is almost entirely: decide what to expose, and write the handler functions that do it.

Start narrower than feels necessary

The most common mistake in a first server is exposing too much, too directly - wrapping "run arbitrary SQL" as a tool because the database supports it, rather than wrapping three or four specific, safe queries the model is actually likely to need. A model calling tools is not the same as a developer writing code with full context and code review; it's making a judgment call, in real time, based on a tool description. Narrower, purpose-built tools are both easier for the model to use correctly and much easier to reason about from a security standpoint.

Concrete version: instead of a single run_query tool that accepts raw SQL, expose get_customer_by_id, list_recent_orders, and search_products as separate tools, each with a fixed query shape and validated inputs. More upfront work, far less that can go wrong.

Permissions and scope

A server should authenticate and authorize on its own terms, not assume the model or the host will police what gets called - if your server talks to a system with per-user permissions, the server's credentials (or the OAuth token it validates) should reflect the actual user's access level, not a blanket service account with more privilege than any individual caller needs. The principle is the same one that applies to any API you'd expose to a third party: least privilege, scoped tokens, and no tool that can do more than the use case actually requires.

Treat tool descriptions as a trust boundary

Because the model decides when to call a tool based on reading its name and description, a misleading or overly broad description is itself a kind of risk - it can cause a tool to get called in situations you didn't intend. Write descriptions the way you'd write documentation for a new team member who has to make a fast judgment call: precise about what the tool does, and just as precise about what it doesn't do.

Validate everything the model sends you

Input from a model-driven tool call should be treated the same as input from any other untrusted client - validate types, ranges, and formats server-side, regardless of what the tool's schema claims to enforce. A schema is a hint to the model about the expected shape of an argument; it is not a guarantee about what will actually arrive at your handler function.

Model calls a tool MCP Server narrow tools scoped auth input validation Real system DB, API, files

The server is the checkpoint between a model's judgment call and your real system - narrow scope and validation are what make that checkpoint mean something.

Test it the way you'd test any integration

Beyond unit-testing the handler functions themselves, it's worth actually running your server through a real host and giving the model ambiguous or edge-case requests to see which tool it reaches for - tool selection quality is genuinely part of the server's behavior, not just an implementation detail, and the only way to catch a confusing tool description is to watch a model get confused by it.

Where to go from here: the official MCP documentation has full SDK references and example servers for both Python and TypeScript - the fastest path to a working first server is usually adapting one of those examples rather than starting from a blank file.
Share this guide

Was this guide helpful?

Thanks for the feedback!

Want more hands-on AI builds like this?

APA Mastery runs live, practical sessions on working with modern AI tools - not just theory.

See What's On →