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 namemcp = 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:
uv run server.pyThe 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:
npx tsc server.tsnode server.jsThe 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:
claude mcp add my-data-server -- uv run /path/to/server.pyThe 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:
- Start with the skeleton, verify it works with MCP Inspector
- Add one real tool that connects to your actual system
- Test with Inspector, then with Claude Desktop or Claude Code
- Add more tools as you discover what the AI needs
- 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.