Setting Up the Python (FastMCP) and TypeScript MCP SDKs

13 min read Module 2 of 10 Topic 4 of 30

What you'll learn

  • Scaffold a minimal FastMCP server and a minimal TypeScript MCP server
  • Explain precisely what FastMCP handles automatically versus what you write yourself
  • Run a local MCP server and connect to it with the MCP Inspector for interactive testing
  • Lay out a multi-tool MCP server project so it stays maintainable past a handful of tools
Building this at your company? For enterprise and company teams taking this to production: book a 30-minute session with our AI engineers for architecture guidance, code review, and a rollout plan for your use case.
Book a Team Session

Both official SDKs reduce building an MCP server to writing plain functions with type annotations, the SDK handles JSON-RPC routing, schema generation, and the initialization handshake. This lesson scaffolds a minimal server in each language so you can pick whichever matches your team’s stack, examines precisely where the SDK’s responsibility ends and yours begins, and establishes a project layout that stays sane as the server grows past a handful of tools; the rest of the course uses Python for full examples with TypeScript equivalents noted where they diverge meaningfully.

Python: FastMCP Project Setup

mkdir enterprise-mcp-server && cd enterprise-mcp-server
python -m venv .venv && source .venv/bin/activate
pip install "mcp[cli]" pydantic
# server.py
from mcp.server.fastmcp import FastMCP

mcp = FastMCP(
    name="enterprise-mcp-server",
    version="0.1.0",
    description="Starter enterprise MCP server",
)

@mcp.tool()
async def ping(message: str) -> str:
    """Simplest possible tool: echoes the input back. Useful as a smoke test."""
    return f"pong: {message}"

if __name__ == "__main__":
    mcp.run(transport="stdio")  # Local development transport, see Lesson 7
# Run it directly, or launch through the MCP Inspector for an interactive UI
npx @modelcontextprotocol/inspector python server.py

What FastMCP Actually Generates for You

It is worth being concrete about the boundary between what the decorator gives you for free and what remains your responsibility, since misunderstanding this boundary is a common source of early confusion. From the ping function above, FastMCP inspects the function signature (message: str) at import time and generates a JSON Schema ({"type": "object", "properties": {"message": {"type": "string"}}, "required": ["message"]}) that gets advertised verbatim in tools/list. When a tools/call request arrives with {"name": "ping", "arguments": {"message": "hi"}}, FastMCP looks up the registered function by name, validates the incoming arguments against the generated schema (rejecting the call with a structured JSON-RPC error before your function body ever executes if they do not match, the mechanism explored fully in Lesson 6), calls your async function with the validated arguments, and wraps whatever your function returns into the MCP response content format. What FastMCP does not do: it does not know anything about your business logic, your downstream systems, your authentication scheme, or how to handle a downstream timeout, those are all things you write, starting in Lesson 5 for the first two and Module 4 for the third. Keeping this boundary clear early prevents a common mistake of assuming the SDK provides safety guarantees (like authorization) that it does not.

TypeScript: SDK Setup

mkdir enterprise-mcp-server-ts && cd enterprise-mcp-server-ts
npm init -y && npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
// server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({ name: "enterprise-mcp-server", version: "0.1.0" });

server.registerTool(
  "ping",
  {
    description: "Simplest possible tool: echoes the input back.",
    inputSchema: { message: z.string() },
  },
  async ({ message }) => ({
    content: [{ type: "text", text: `pong: ${message}` }],
  })
);

const transport = new StdioServerTransport();
await server.connect(transport);

The TypeScript SDK’s registerTool plays the identical role to FastMCP’s decorator, converting a Zod schema into the JSON Schema advertised over the wire, and routing incoming calls to your handler function, the two SDKs are deliberately symmetrical in what they automate so that the mental model you build in one transfers directly to the other.

Running and Verifying with the MCP Inspector

The Inspector is worth using from the very first tool you write, not just at the end of a build, since it catches schema and routing mistakes immediately rather than after they are buried under ten more tools.

npx @modelcontextprotocol/inspector python server.py
# Opens a local web UI: connect, call tools/list, manually invoke `ping`
# with a test message, and inspect the raw JSON-RPC request/response pairs.

Use the Inspector’s raw request/response view specifically to confirm the generated input schema matches what you intended, a common early mistake is an Optional[str] = None type hint generating a schema that technically allows null but the downstream code does not actually handle a None value gracefully, something obvious once you see the raw schema and try it, easy to miss otherwise.

Project Layout for This Course

The rest of this course builds out a single enterprise MCP server incrementally. A clean layout separates protocol wiring from business logic, this matters once you have dozens of tools across multiple internal systems, and it maps directly onto the ownership model from Module 5 and Module 10, where different teams own different integration modules within (or across) servers.

flowchart TD
    Root["enterprise-mcp-server/"]
    Root --> ServerPy["server.py\n(FastMCP instance, imports + registers\ntools from each module below)"]
    Root --> Tools["tools/\ncrm.py, warehouse.py, ticketing.py\n(one module per wrapped system)"]
    Root --> Schemas["schemas/\n(Pydantic input/output models,\nshared across tools/ modules)"]
    Root --> Clients["clients/\n(typed wrappers around internal APIs, DBs)"]
    Root --> Auth["auth/\n(middleware: API keys, OAuth)"]
    Root --> Tests["tests/\n(contract tests per tool, Lesson 26)"]

    style Root fill:#f0fdf9,stroke:#0D9488,color:#0F172A
    style ServerPy fill:#EEF0F7,stroke:#6366F1,color:#0F172A
    style Tools fill:#EEF0F7,stroke:#6366F1,color:#0F172A
    style Schemas fill:#EEF0F7,stroke:#6366F1,color:#0F172A
    style Clients fill:#EEF0F7,stroke:#6366F1,color:#0F172A
    style Auth fill:#EEF0F7,stroke:#6366F1,color:#0F172A
    style Tests fill:#EEF0F7,stroke:#6366F1,color:#0F172A
# server.py: composition root, imports tool modules and registers them
from mcp.server.fastmcp import FastMCP
from tools import crm, warehouse, ticketing

mcp = FastMCP(name="enterprise-mcp-server", version="0.1.0")

crm.register(mcp)         # tools/crm.py defines its own @mcp.tool()-style registration
warehouse.register(mcp)
ticketing.register(mcp)
# tools/crm.py: one team's tools, isolated from every other system's code
def register(mcp):
    @mcp.tool(name="crm.search_accounts")
    async def search_accounts(query: str) -> dict:
        """Owned by the CRM/RevOps team; see clients/crm_client.py for the API wrapper."""
        from clients.crm_client import crm_client
        return await crm_client.search(query)

With the scaffold running and verified in the Inspector, and a project layout that will not collapse under its own weight, the next lesson adds real tools, resources, and prompts backed by typed schemas rather than a placeholder ping.

Knowledge Check

3 questions to test your understanding

1 What does FastMCP's @mcp.tool() decorator handle automatically that you would otherwise write by hand?

2 Why is the MCP Inspector useful during development?

3 A team's FastMCP server has grown to 40 tools all registered directly in one server.py file, and code review has become difficult because unrelated tools are interleaved. What does the project layout in this lesson recommend to fix this?

Go further with expert guidance

Ready to build production AI?
Talk to our R&D team.

These courses give you the foundation. Our embedded AI teams take you from prototype to production in 30–90 days, with your team, your codebase, your goals. Book a free strategy call to see how we can accelerate your AI initiative.

30 minutes · No obligation · Expert AI engineers, not sales reps

AI Architecture Review

Audit your current stack and identify high-impact improvements

Project Review

Get expert feedback on your AI implementation and codebase

Team Mentoring

Upskill your engineers with hands-on AI coaching sessions

AI Strategy

Define your AI roadmap, prioritization, and implementation plan