Admissions open for our live online programs · Starts 5 December 2026 · Live online, learn from anywhere

Agentic AI

How to Build an MCP Server: A Step-by-Step Beginner's Guide

Build your first MCP server step by step in Python or TypeScript: tools, stdio vs Streamable HTTP, testing with MCP Inspector, security basics and next steps.

Short answer: An MCP server is a small program that exposes tools (functions a model can call), resources (data a client can read) and prompts (reusable templates) through the Model Context Protocol, so any MCP-compatible AI app can use them. To build one: install an official MCP SDK (Python or TypeScript), register one tool with a clear description and input schema, run it over the stdio transport, test it with MCP Inspector, then connect it to a client such as Claude Desktop. Keep your first server to one read-only tool; most of the real work is writing good tool descriptions and safe permissions, not plumbing.

New to agents? Read What is agentic AI? first. MCP is how an agent's "act" step reaches real systems.

What MCP is, in one minute

The Model Context Protocol is an open standard for connecting AI applications to external tools and data. A host (an AI app such as Claude Desktop, an IDE or your own agent) runs an MCP client, which talks to one or more MCP servers. According to the official tutorial, servers can provide three kinds of capability (MCP: Build an MCP server):

MCP server capabilities (tools, resources, prompts)
CapabilityWhat it isExample
ToolsFunctions the model can call (with user approval)`search_tickets`, `get_forecast`
ResourcesFile-like data a client can reada config file, an API response
PromptsPre-written templates for common tasks"summarise this incident"

Why it matters: instead of writing a custom integration for every AI app, you write one server and any MCP client can use it.

MCP vs a normal API: your REST API is still there. The MCP server is a thin layer that describes a few of its operations in a way a model can choose between, validates the arguments and returns results in a model-friendly form.

Before you start

  • Python 3.10+ (the official Python tutorial requires the Python MCP SDK 2.0.0 or higher) or Node.js 20+ for TypeScript.
  • A use case with one read-only operation: "look up an order", "search my notes", "get open tickets". Not "do everything our API does".
  • An MCP client for testing: MCP Inspector (free, runs locally) and, optionally, Claude Desktop or another MCP-capable app.

Step 1: Choose your transport (start with stdio)

The MCP specification defines two standard transports (MCP: Transports):

stdio vs Streamable HTTP
**stdio****Streamable HTTP**
How it worksThe client launches your server as a local subprocess and exchanges newline-delimited messages over stdin/stdoutEach message is an HTTP POST to a single MCP endpoint
Best forLocal tools, learning, desktop appsRemote/shared servers, multiple users
SetupNo network, no auth to startNeeds hosting, TLS and authorization

Start with stdio. Move to Streamable HTTP when other people or machines need to reach the server.

The one rule beginners break: on a stdio server, never write logs to stdout. stdout is the protocol channel, and a stray `print()` or `console.log()` corrupts the JSON-RPC messages. Log to stderr instead (the official tutorial calls this out for every language).

Step 2: Build a one-tool server in Python

Set up the project with `uv`, as the official tutorial does:

uv init notes-server
cd notes-server
uv venv
source .venv/bin/activate      # Windows: .venv\Scripts\activate
uv add "mcp[cli]"

Create `server.py`. This example searches a local folder of Markdown notes. It is read-only and needs no API key:

import logging
from pathlib import Path

from mcp.server import MCPServer

logger = logging.getLogger(__name__)   # logging writes to stderr, which is safe for stdio
mcp = MCPServer("notes")

NOTES_DIR = Path.home() / "notes"      # change to your folder
MAX_RESULTS = 5


@mcp.tool()
def search_notes(query: str) -> str:
    """Search the user's local Markdown notes for a word or phrase.

    Use this when the user asks what their notes say about a topic.
    Returns up to 5 matching file names, each with the first matching line.

    Args:
        query: Word or short phrase to search for (case-insensitive).
    """
    q = query.strip().lower()
    if not q:
        return "Please provide a search term."
    hits = []
    for path in sorted(NOTES_DIR.glob("*.md")):
        for line in path.read_text(encoding="utf-8", errors="ignore").splitlines():
            if q in line.lower():
                hits.append(f"{path.name}: {line.strip()[:200]}")
                break
        if len(hits) >= MAX_RESULTS:
            break
    logger.info("search_notes query=%r hits=%d", q, len(hits))
    return "\n".join(hits) if hits else f"No notes mention '{query}'."


if __name__ == "__main__":
    mcp.run(transport="stdio")

What is happening:

  • `MCPServer` uses your function's type hints and docstring to generate the tool definition the model sees, so the docstring is effectively your prompt.
  • The tool caps results and trims lines. Whatever you return goes straight into the model's context window, so return only what is useful.
  • Errors come back as plain sentences the model can act on, not stack traces.

Step 3: The same server in TypeScript

mkdir notes-server && cd notes-server
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/server zod
npm install -D @types/node typescript tsx
mkdir src

`src/index.ts`:

import { McpServer } from "@modelcontextprotocol/server";
import { serveStdio } from "@modelcontextprotocol/server/stdio";
import { z } from "zod";
import { readdir, readFile } from "node:fs/promises";
import { join } from "node:path";
import { homedir } from "node:os";

const NOTES_DIR = join(homedir(), "notes");

function createServer() {
  const server = new McpServer({ name: "notes", version: "1.0.0" });

  server.registerTool(
    "search_notes",
    {
      description:
        "Search the user's local Markdown notes for a word or phrase. Returns up to 5 matching files with the first matching line.",
      inputSchema: z.object({
        query: z.string().min(1).max(100).describe("Word or short phrase to search for"),
      }),
    },
    async ({ query }) => {
      const q = query.toLowerCase();
      const hits: string[] = [];
      for (const file of (await readdir(NOTES_DIR)).filter((f) => f.endsWith(".md"))) {
        const line = (await readFile(join(NOTES_DIR, file), "utf8"))
          .split("\n")
          .find((l) => l.toLowerCase().includes(q));
        if (line) hits.push(`${file}: ${line.trim().slice(0, 200)}`);
        if (hits.length >= 5) break;
      }
      return {
        content: [{ type: "text", text: hits.length ? hits.join("\n") : `No notes mention '${query}'.` }],
      };
    },
  );

  return server;
}

serveStdio(createServer);
console.error("notes MCP server running on stdio"); // stderr, never stdout

Two details from the official TypeScript docs: `inputSchema` is a Zod schema, and the SDK turns it into the JSON Schema the model sees and validates arguments before your handler runs; and `serveStdio` takes a factory (`createServer`) because it may build more than one server instance while negotiating the protocol version with each client (MCP TypeScript SDK: Build your first server). Tight schemas such as `.max(100)` are worth it: models sometimes send unexpected inputs.

Step 4: Test with MCP Inspector before any AI app

MCP Inspector is the official local tool for calling a server's tools directly (MCP Inspector docs):

# Python
npx @modelcontextprotocol/inspector uv run server.py
# TypeScript
npx @modelcontextprotocol/inspector npx tsx src/index.ts

Check three things: the tool appears in the tool list with your description; a normal query returns a short, correct answer; and an empty or odd query returns a helpful message instead of crashing.

Step 5: Connect it to a client

For Claude Desktop, add your server under the `mcpServers` key of `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/`, Windows: `%AppData%\Claude\`), using absolute paths, then fully quit and restart the app:

{
  "mcpServers": {
    "notes": {
      "command": "uv",
      "args": ["--directory", "/ABSOLUTE/PATH/TO/notes-server", "run", "server.py"]
    }
  }
}

Then ask a question your tool can answer ("What do my notes say about Kafka?") and watch the model decide to call it. If nothing shows up, the official tutorial's troubleshooting section points you to the client's MCP log files.

Step 6: Design tools a model can actually use

This is where most of your time goes, and it is normal:

  • Design around tasks, not endpoints. Five to ten tools shaped like real requests ("find overdue invoices for a client") beat one tool per API endpoint.
  • Write descriptions like instructions. Say what the tool does, when to use it and what it returns. "Get data" is useless to a model; "Fetch the last 30 days of invoice summaries for one customer" is not.
  • Return small, structured results. Trim fields, paginate long lists and summarise.
  • Make failures readable. "API unreachable, try again later" lets the model recover; a stack trace does not.
  • Add timeouts and limits on any external call. Models can call tools far more often than a human would.

Step 7: Security basics (before you share it)

  • Least privilege: start read-only. Put anything that writes, deletes, pays or sends behind explicit human approval.
  • Treat tool inputs and fetched content as untrusted. Text your server reads (web pages, tickets, emails) can contain instructions aimed at the model. That is prompt injection, the first risk on OWASP's LLM Top 10 (OWASP LLM01).
  • Don't pass user tokens straight through to downstream APIs, and validate who is calling a remote server. The MCP specification's Security Best Practices page covers attacks such as the confused deputy problem, token passthrough and local server compromise, with mitigations.
  • Keep secrets out of code: use environment variables or a secrets manager.

Common beginner mistakes

  • Logging to stdout on a stdio server
  • Exposing the whole API on day one instead of one useful read operation
  • Relative paths in the client config
  • Vague tool descriptions, then blaming the model for picking the wrong tool
  • Returning huge raw JSON that fills the context window
  • Implementing a full OAuth flow before the first tool even works

Where MCP fits in your learning path

MCP sits on top of the basics: calling a model API, tool use, and an agent loop. Once your server works, good next steps are a retrieval tool (see RAG vs fine-tuning), an orchestrated multi-step agent (LangGraph vs CrewAI vs AutoGen), and turning the server into a portfolio project (project 8 in our agentic AI projects guide). If you are choosing between a visual workflow tool and a code-first agent framework, see n8n vs LangGraph.

Learn MCP with a mentor

Datamarcos's Applied Agentic AI Certification is an 8-week, live online, mentor-led program with a hands-on project in every module, using n8n, LangChain, LangGraph, CrewAI and MCP and ending in an integrated multi-agent capstone. No prior AI experience is required; you should be open to learning basic Python. For a deeper engineering path, see Generative AI & Agentic AI Engineer; if you want to deploy agents inside client systems, see Forward Deployed AI Engineer. Live online, so you can join from anywhere. There is no placement guarantee.

Securing agents and MCP servers for a security team? See AI Security & SecOps. Training an engineering team? See corporate training; Datamarcos has trained teams at 200+ organizations.

CTA: Talk to a counsellor to pick the right program for your background.

FAQ

What is an MCP server?

A program that exposes tools, resources and prompts through the Model Context Protocol so that any MCP-compatible AI application can use them, instead of you building a separate integration for each app.

Which language should I use to build an MCP server?

Use the one you know. Official SDKs and tutorials exist for Python, TypeScript, Java, Kotlin, C#, Ruby, Rust and Go. Python and TypeScript have the most beginner material.

What is the difference between stdio and Streamable HTTP?

stdio runs the server as a local subprocess of the client and is simplest for learning. Streamable HTTP runs the server as a web service that remote clients reach over HTTP, which needs hosting and authorization.

Is an MCP server the same as an API?

No. An MCP server usually wraps an existing API, describing a few operations so a model can choose and call them safely. The API still does the work.

How do I test an MCP server?

Use MCP Inspector to list and call your tools directly, then connect the server to a client such as Claude Desktop and try real questions.

Is MCP secure?

It can be, if you design for it: least-privilege tools, human approval for actions with side effects, treating fetched content as untrusted, and following the specification's security best practices for remote servers.

Can I add an MCP server to my resume?

Yes, if it solves a real problem. Publish the code, a README with an architecture diagram, a few test cases and a note on what went wrong and how you fixed it.

Related program

Applied Agentic AI Certification

8 weeks, live and mentor-led: a project in every module, from LLM foundations and production RAG to multi-agent systems and MCP, ending in an integrated capstone.

See the 8-week syllabus

Sources

Accessed 10 November 2026.

Contact Us