ServicesAboutNotesContact Get in touch →
EN FR
Note

FastMCP Server Skeleton

Minimal MCP server examples in Python (FastMCP) and TypeScript (McpServer) — the starting point for any custom server build.

Planted
mcpdata engineering

Both the Python and TypeScript SDKs can produce a working MCP server in under 30 lines. This note covers the minimal skeleton for each, plus how to connect the server to a client. Extending the skeleton with additional tools follows the same pattern as the initial example.

Python FastMCP Example

Create a file named server.py:

from mcp.server.fastmcp import FastMCP
# Initialize the server with a name
mcp = FastMCP("DataEngineering")
@mcp.tool()
def query_database(query: str, database: str = "production") -> str:
"""Execute a SQL query against the specified database.
Args:
query: The SQL query to execute
database: Target database name (default: production)
Returns:
Query results as formatted text
"""
# In a real implementation, you would connect to your database here
return f"Query executed on {database}: {query}"
if __name__ == "__main__":
mcp.run(transport="stdio")

That’s it. A working MCP server.

The @mcp.tool() decorator does several things automatically:

  • Registers the function as an MCP tool
  • Extracts the function signature to build the JSON Schema for inputs
  • Uses the docstring as the tool description the AI sees
  • Handles all JSON-RPC serialization and protocol communication

Run the server:

Terminal window
uv run server.py

The server starts listening on stdin/stdout for MCP protocol messages. You won’t see any output — that’s correct. The server is waiting for a client to connect. Use the MCP Inspector to interact with it.

TypeScript McpServer Example

Create 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: "data-engineering-server",
version: "1.0.0",
});
server.registerTool(
"query_database",
{
description: "Execute a SQL query against the specified database",
inputSchema: {
query: z.string().describe("The SQL query to execute"),
database: z.string().default("production").describe("Target database name"),
},
},
async ({ query, database }) => {
// In a real implementation, connect to your database here
return {
content: [{ type: "text", text: `Query executed on ${database}: ${query}` }],
};
}
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main();

Compile and run:

Terminal window
npx tsc server.ts
node server.js

The TypeScript version is more verbose — you define the input schema explicitly with Zod types, and the return value must be wrapped in the MCP content format. The tradeoff is stronger type safety and explicit schema definitions that your IDE can validate.

Why stdio Transport

Both examples use stdio transport, which means the server communicates over standard input/output streams. This is the default for local development because:

  • No network configuration required
  • The client spawns the server as a subprocess
  • Communication happens through pipes, not sockets
  • Credentials stay in environment variables, never on the network
  • Each client gets its own server instance (process isolation)

stdio is ideal for desktop applications like Claude Desktop or Cursor, and for development with Claude Code. For production deployment with multiple clients, you’d switch to HTTP transport — but start with stdio.

Connecting to a Client

Once the server runs, you need to point a client at it.

For Claude Desktop, add to your claude_desktop_config.json:

{
"mcpServers": {
"my-data-server": {
"command": "uv",
"args": ["run", "/path/to/server.py"],
"env": {
"DATABASE_URL": "postgresql://localhost/analytics"
}
}
}
}

For Claude Code:

Terminal window
claude mcp add my-data-server -- uv run /path/to/server.py

The client configuration tells the host application how to spawn the server process and what environment variables to pass. The host starts the server, performs the MCP initialization handshake, discovers available tools, and from that point the AI can invoke them during conversations.

From Skeleton to Real Server

The skeleton is intentionally trivial. A real server replaces the stub implementation with actual business logic — connecting to databases, calling APIs, querying internal systems. The key insight is that the MCP layer (decorators, schemas, transport) stays the same. You’re only changing what happens inside the function body.

A common progression:

  1. Start with the skeleton, verify it works with MCP Inspector
  2. Add one real tool that connects to your actual system
  3. Test with Inspector, then with Claude Desktop or Claude Code
  4. Add more tools as you discover what the AI needs
  5. Refine descriptions and schemas based on how the AI uses the tools

The MCP Tool Design Patterns note covers how to design tools that work well — good descriptions, structured outputs, validated inputs. The MCP Server Project Setup note covers the full project structure for a production-ready server.