How to Build an MCP Server: A Practical Guide for Developers
Learn how MCP clients and servers work, how to expose safe tools and resources, and how to build, test, secure, and deploy a small Python MCP server.
On this page
Model Context Protocol (MCP) is an open protocol for connecting AI applications to external tools, resources, and reusable prompts. An MCP server is the process that exposes those capabilities. Instead of building a separate integration for every AI client, a team can implement a well-defined server and let compatible clients discover and invoke it.
MCP does not make an operation safe by itself. The server still owns authentication, authorization, validation, rate limits, audit logs, and the decision about whether an action is read-only or mutating.
#What problem does an MCP server solve?
Without a shared protocol, every assistant integration invents its own tool schema, discovery mechanism, and transport rules. MCP standardizes the conversation between a client and a server at a useful level. A client can discover the capabilities that a server offers, present them to a model, and route approved calls back to the server.
The protocol is an integration boundary, not a replacement for your domain API. Keep business logic in reusable application services so that an HTTP API, a background job, and an MCP tool can share the same authorization and validation rules.
#Clients, servers, tools, resources, and prompts
Client: an AI application or host that connects to one or more MCP servers.
Server: a process that exposes capabilities through MCP.
Tool: an operation the model can request, such as searching tickets or creating a draft.
Resource: contextual data the client can read, such as a document or schema.
Prompt: a reusable prompt template or workflow starter.
Tools should be narrow and descriptive. A tool name and description are part of the model-facing interface, so state what the tool does, what it returns, and what side effects it can cause.
#How communication works
At a high level, the client starts a session, negotiates supported capabilities, and asks the server what it exposes. The client can then read a resource or invoke a tool with structured arguments. The server validates the request, performs the operation, and returns structured content or an error.
Local servers commonly use standard input and output as their transport. Remote servers need a network transport, TLS, authentication, request limits, and an operational plan. Do not assume that moving a local server to the internet is a deployment strategy.
#Build a small MCP server in Python
Install the official Python SDK in an isolated environment with python -m venv .venv, activate it, and run pip install mcp. The following complete example exposes a safe calculation tool over standard input and output:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("calculator")
@mcp.tool()
def add_numbers(first: int, second: int) -> int:
"""Add two integers without side effects."""
return first + second
if __name__ == "__main__":
mcp.run(transport="stdio")
The exact client configuration depends on the host application. Keep the server process free to use standard input and output for protocol messages; send diagnostics to a separate logging stream.
#Expose an existing application API
Start by writing an adapter, not by copying your entire API surface into tools. For a ticket search service, define a tool such as search_tickets(query, status, limit). Validate the limit, enforce the caller’s project permissions, call the existing service layer, and return a small result set with stable identifiers.
List the API operations that are safe and useful in an assistant workflow.
Define tool names, argument schemas, return shapes, and side-effect labels.
Reuse domain authorization and validation instead of creating a second policy system.
Add audit events that identify the user, client, tool, arguments summary, and result.
Test the adapter with valid, invalid, unauthorized, slow, and empty responses.
#Use cases
#AEM
An MCP server could expose read-only content search, metadata lookup, or a controlled draft-review workflow. Publishing, deleting, and changing permissions should require explicit authorization and possibly human approval.
#GitHub
Useful tools include searching issues, reading pull requests, or summarizing changed files. Mutating tools such as merging or changing branch protection deserve stronger confirmation and least-privilege tokens.
#Databases
Prefer parameterized, read-only query tools with an allowlisted schema over a general-purpose SQL execution tool. Return bounded rows and remove secrets or personal data before they enter model context.
#Internal applications
Expose the narrow workflows that reduce repetitive navigation: find a customer, check a deployment, or draft a report. Keep the system of record authoritative and make changes traceable.
#Authentication and authorization
For local development, the operating system account and process boundary may provide a useful starting point, but they are not enough for a shared server. For remote deployments, use an authenticated transport and verify the token before dispatching a tool. Map identity to application roles, tenant boundaries, and resource permissions.
Authorization must happen on the server for every request. A model’s request is not proof that the user is allowed to perform the action.
#Security considerations
Grant tools the minimum permissions they need.
Validate every argument, including paths, URLs, filters, and limits.
Protect against prompt injection in tool results and retrieved documents.
Do not return secrets, unrestricted database rows, or unredacted logs.
Use timeouts, output limits, concurrency limits, and rate limits.
Require confirmation for destructive or externally visible actions.
Record enough context for incident investigation without storing unnecessary sensitive data.
#Error handling and observability
Return errors that distinguish invalid input, denied access, unavailable dependencies, and unexpected failures. Do not expose stack traces or credentials to the model. Give each request a correlation identifier and log connection lifecycle, tool name, duration, outcome, and a redacted argument summary.
Metrics should show tool-call volume, error rate, latency, timeouts, and rejected authorization attempts. A health check can confirm process availability, but only a real test can confirm that a dependency works.
#Testing an MCP server
Test the server at three levels. Unit-test the domain service and argument validation. Contract-test tool discovery, schemas, result shapes, and errors. Then run an end-to-end test with a real compatible client or protocol harness.
Include malicious and awkward cases: missing fields, oversized strings, invalid identifiers, empty results, slow upstream services, repeated calls, and a tool result containing instructions that should not be followed as policy.
#Deployment considerations
Local stdio servers are straightforward for a developer workstation. Remote servers need a process supervisor, TLS termination, secrets management, network policy, autoscaling or capacity limits, and a clear upgrade strategy. Keep the server stateless when possible and make reconnects safe.
Pin dependencies through your normal supply-chain process, review SDK changes, and roll out new tools behind feature flags when the client population is large. A server should remain useful when an optional dependency is unavailable.
#Common mistakes
Exposing every internal endpoint instead of a small task-oriented surface.
Trusting the model to enforce permissions.
Allowing arbitrary SQL, shell commands, or filesystem access.
Returning huge documents that crowd out useful context.
Logging full prompts and results without a privacy review.
Skipping client compatibility and reconnect testing.
#Frequently asked questions
#Is MCP an AI model?
No. It is a protocol for connecting AI applications to external capabilities.
#Can an MCP server run locally?
Yes. Local processes commonly communicate over standard input and output.
#Should every API become an MCP tool?
No. Expose the smallest set of operations that has clear user value and a defensible security boundary.
#Does MCP replace authentication?
No. Authentication and authorization remain application responsibilities, especially for remote servers.
#How should I start?
Build one read-only tool over a well-tested service, exercise it with a compatible client, and add observability before adding mutations.
#Conclusion
An MCP server is a useful adapter between an AI client and application capabilities, but its quality depends on ordinary engineering discipline. Start with narrow tools, reuse your existing authorization, validate every argument, cap outputs, and test failure paths. Once the boundary is safe and observable, MCP can make AEM, GitHub, databases, and internal workflows available to assistants without turning your application into an uncontrolled command runner.