How to Build Your Own MCP Server

Updated May 2026
Building a custom MCP server lets you expose any external system, internal API, or custom capability to AI models through the standard protocol. The official TypeScript and Python SDKs handle all protocol mechanics, so you focus on implementing your tool logic rather than worrying about JSON-RPC, transport management, or capability negotiation. A minimal server can be running in under 100 lines of code.

Step 1: Choose Your SDK and Set Up the Project

The MCP project provides official SDKs for TypeScript and Python. Choose based on your team's language preference and the environment where your tool logic runs. TypeScript servers are typically distributed through npm and run with Node.js. Python servers are distributed through PyPI and run with Python 3.10 or later.

For TypeScript, initialize a new Node.js project and install the MCP SDK package (@modelcontextprotocol/sdk). The SDK provides the Server class, transport implementations, and type definitions for all protocol messages. Set up your tsconfig.json for the module system you prefer (ESM is recommended for new projects).

For Python, create a new project with your preferred package manager (pip, poetry, or uv) and install the mcp package. The Python SDK provides a FastMCP high-level API that simplifies server creation with decorator-based tool and resource definitions, as well as a lower-level Server class for more control.

Both SDKs include comprehensive type definitions that ensure your tool implementations match the protocol specification. TypeScript's type system catches protocol violations at compile time, while Python's type hints provide similar guidance through IDE support and optional type checking.

Step 2: Define Your Tools

Each tool needs three things: a unique name, a clear description, and a JSON schema for its input parameters. The name should be descriptive and follow the convention of lowercase words separated by underscores (like query_database or search_files). The description is critical because the AI model uses it to decide when and how to invoke your tool. Write descriptions that explain what the tool does, what inputs it expects, and what it returns.

Good tool descriptions are specific and action-oriented. Instead of "queries data," write "executes a read-only SQL SELECT query against the connected PostgreSQL database, returning results as a JSON array of row objects with a maximum of 1000 rows." The more precisely the model understands your tool, the better it will use it.

The input schema uses JSON Schema format to define the parameters your tool accepts. Specify required versus optional parameters, data types, allowed values (through enums), and descriptive field names. A well-defined schema prevents the model from generating invalid inputs and reduces the need for input validation in your handler code.

Step 3: Implement Tool Handlers

Tool handlers are the functions that run when the model invokes your tools. Each handler receives the parameters the model provided and returns a result. The SDK handles the protocol wrapping, so your handler simply processes the input and returns the output.

Validate inputs even though the schema provides first-line validation. The model might generate parameters that pass schema validation but are semantically invalid. A file path that passes the "string" type check might reference a file outside the allowed directory. A SQL query that is syntactically valid might attempt operations that should be denied. Defensive input validation is an important security practice.

Handle errors gracefully. When a tool call fails, return a clear error message that helps the model understand what went wrong and potentially retry with corrected inputs. Avoid exposing internal system details (stack traces, connection strings, file paths) in error messages, as these might be included in model responses visible to users.

Keep handler logic focused on the tool's purpose. A database query tool should execute the query and return results, not transform data, generate summaries, or make decisions about what to do next. Let the model handle interpretation and decision-making. The tool's job is to execute the requested operation and return the raw result.

Step 4: Add Resources and Prompts (Optional)

If your server has data that the model should be able to read without executing operations, expose it as resources. Resources are identified by URIs and can be static (always return the same data) or dynamic (generated on demand). A database server might expose table schemas as resources so the model can understand the database structure before writing queries.

Prompts are useful if you want to standardize how the model approaches tasks related to your server. A code review server might include a prompt that defines the review criteria, output format, and evaluation rubric. Users can select the prompt to ensure consistent review quality across different conversations.

Resources and prompts are optional. Many production servers expose only tools, which cover the primary use case of executing operations on behalf of the model. Add resources and prompts when they meaningfully improve the model's effectiveness with your server.

Step 5: Test with an MCP Client

The fastest way to test your server is by connecting it to Claude Desktop or Claude Code. Add your server to the MCP configuration file, specifying the command to run your server (node for TypeScript, python for Python) and any required arguments or environment variables. Restart the host application, and your server should appear in the available tools.

Test each tool by asking the model to use it in natural language. Verify that the model discovers your tools, understands when to use them from the descriptions, generates correct arguments, and handles the results appropriately. If the model struggles to use a tool correctly, the description or schema likely needs improvement.

The MCP Inspector tool (available through npx @modelcontextprotocol/inspector) provides a dedicated testing interface. It connects to your server, displays the available tools with their schemas, lets you invoke tools manually with specific arguments, and shows the raw JSON-RPC messages for debugging. Use the Inspector for systematic testing before connecting to a full AI host.

Step 6: Package and Distribute

For TypeScript servers, package your server as an npm package with a bin entry that points to the server executable. Users install it with npm and run it with npx. Include clear documentation about configuration requirements (API keys, connection strings, environment variables) and the tools your server exposes.

For Python servers, package your server as a PyPI package with a console script entry point. Users install it with pip or uvx and run it directly. Include a README with configuration instructions and tool descriptions.

Write a comprehensive README that includes the list of tools with descriptions, required configuration (environment variables, API keys), example usage with a sample conversation, and security considerations. A good README helps users evaluate whether your server meets their needs and configure it correctly.

Key Takeaway

Building an MCP server is straightforward with the official SDKs. Focus your effort on writing clear tool descriptions and robust handler implementations. The SDK handles all protocol mechanics. Test with the MCP Inspector and Claude Desktop before distribution, and write thorough documentation for users.