Skip to content
← All products
Template

Python MCP Starter Template

Skip the blank-file phase of building an MCP server.

A working FastMCP server you rename and extend instead of building an MCP server from scratch. Five example tools cover the shapes most servers need — a trivial tool, a zero-argument tool, an async HTTP call, a filesystem read with path-traversal validation, and a tool that rejects malformed input safely (AST-walked, not eval()'d). An OAuth 2.0 Authorization Code + PKCE helper is included for tools that call a third-party API on the user's behalf. A 38-test pytest suite shows how to test each piece, including the async tool and the OAuth token-refresh flow.

What's included

  • FastMCP server boilerplate — src/mcp_starter/server.py, ready to rename and extend
  • 5 example tools: echo, get_server_time, fetch_url (async), read_local_file (path-validated), calculate (AST-safe, no eval())
  • OAuth 2.0 Authorization Code + PKCE helper — build_authorize_url, exchange_code, refresh_token, ensure_token
  • Local callback server for testing OAuth redirects during development
  • Path traversal + symlink escape protection for any tool that touches the filesystem
  • Environment-driven config (config.py) — same server binary runs unchanged in Claude Desktop, CI, or a container
  • 38-test pytest suite — tools, security, OAuth token expiry/refresh, and the registered server tools
  • pyproject.toml with console script entry point — installs as a `mcp-starter` command
  • README with Claude Desktop config snippet and a guide for renaming the package

Requirements

  • Python 3.9 or later
  • pip / a virtualenv
  • An MCP-compatible client (Claude Desktop, or any MCP SDK client) to connect to it
tools/safe_eval.py
def _eval_node(node: ast.expr):
    """Recursively evaluate an AST node (safe evaluation without eval)."""
    if isinstance(node, ast.Constant):
        if isinstance(node.value, (int, float)):
            return node.value
        raise ValueError(f"Only numeric constants allowed")
    if isinstance(node, ast.BinOp):
        op_type = type(node.op)
        if op_type not in _BINARY_OPS:
            raise ValueError(f"Operator not allowed")
        return _BINARY_OPS[op_type](_eval_node(node.left), _eval_node(node.right))