Building Your First MCP Server
Expose a real tool the model can call — with input validation and a clear description.
Let’s build. In this lesson you’ll create a minimal MCP server that exposes one tool, run it over stdio, and connect it to a host. We’ll use the official TypeScript SDK, but the concepts transfer to the Python SDK too.
🎯 Learning Objectives
- Scaffold an MCP server with the official SDK
- Define a tool with an input schema and handler
- Run the server over stdio
- Connect and test it from a host
Step 1 — Set Up the Project
Install the official MCP SDK and a schema validator:
mkdir devops-mcp && cd devops-mcp
npm init -y
npm install @modelcontextprotocol/sdk zodzod lets us declare and validate the tool’s input schema — critical, because the model will send arguments and we must not trust them blindly.
Step 2 — Create the Server & a Tool
Here’s a complete, minimal server exposing one tool, get_service_status, that reports whether a named service is healthy:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// 1. Create the server
const server = new McpServer({
name: "devops-tools",
version: "1.0.0",
});
// 2. Register a tool: name, description, input schema, handler
server.tool(
"get_service_status",
"Check whether a named service is currently healthy",
{ service: z.string().describe("The service name, e.g. 'payments'") },
async ({ service }) => {
// (in real life: query your monitoring / health endpoint)
const healthy = await checkHealth(service);
return {
content: [
{
type: "text",
text: healthy
? service + " is healthy ✅"
: service + " is DOWN ❌",
},
],
};
}
);
// 3. Connect over stdio
const transport = new StdioServerTransport();
await server.connect(transport);Three things to notice:
- The description tells the model when to use the tool.
- The zod schema (
service: z.string()) validates input automatically. - The handler returns content the model reads back.
⚠ Validate and constrain every input
The model can call your tool with any arguments. The zod schema rejects malformed input, but you also enforce business rules in the handler — e.g. only allow known service names, never pass raw input into a shell command.
Step 3 — Run and Register It
An stdio server isn’t run directly by you — the host launches it as a subprocess. You tell the host how to start it via config. For a typical desktop host:
{
"mcpServers": {
"devops-tools": {
"command": "node",
"args": ["/path/to/devops-mcp/server.js"]
}
}
}On start, the host runs that command, performs the initialize handshake, calls tools/list, and your get_service_status tool appears — ready for the model to call.
Step 4 — Test It
Before wiring it into a host, test with the MCP Inspector, an official dev tool that acts as a client:
npx @modelcontextprotocol/inspector node server.jsSeeing the tool listed and returning a result means your server is correct — now any MCP host can use it.
💡 Inspector first, host second
Always test a new server with the Inspector before connecting it to a real AI host. It isolates your server’s behaviour from the model’s decisions, so you debug one thing at a time.
🧪 Hands-on Lab
Add a 'restart_service' Tool (Safely)
Sketch a second tool restart_service that takes a service name from an allowed list only, and returns a confirmation. Focus on the schema and the safety check.
🧠 Knowledge Check
Why is an input schema (e.g. with zod) important when defining an MCP tool?
How is a stdio MCP server typically started?
💼 Interview Preparation
What are the key things to get right when building an MCP tool?
Summary
You built a working MCP server, defined and validated a tool, ran it over stdio, and tested it with the Inspector. You now know how to give an AI safe, structured access to a system. Finally, let’s see how MCP is used in real DevOps workflows.