Python + OIDC PyPI Publishing + CI/CD.
Build your MCP server. One-click publish. Zero secrets needed.
English | 한국어
Part of Starter Series — Stop explaining CI/CD to your AI every time. Clone and start.
Docker Deploy · Discord Bot · Telegram Bot · Browser Extension · Electron App · npm Package · React Native · VS Code Extension · MCP Server (TS) · MCP Server (Python) · Cloudflare Pages
- MCP SDK —
mcp(FastMCP) with stdio transport - Python 3.11+ — Type hints, async/await, hatchling build
- Safety Annotations — readOnly/destructive/idempotent hints on every tool
- Response Helpers —
ok()anderr()for consistent tool responses - Config — Environment variable parsing pattern
- CI — gitleaks, ruff, license compliance, pytest (3.11/3.12/3.13)
- CD — OIDC trusted publishing to PyPI (zero secrets needed)
- Dependabot — Automated dependency + GitHub Actions updates
git clone https://github.com/starter-series/python-mcp-server-starter.git my-mcp-server
cd my-mcp-server
rm -rf .git && git init
pip install -e ".[dev]"
python -m my_mcp_serverTool names must be globally unique across all MCP servers a client connects to. Prefix with your module name (e.g.,
mymodule_actioninstead ofaction).
Add directly to src/my_mcp_server/server.py:
@mcp.tool(
annotations=ToolAnnotations(
readOnlyHint=True,
destructiveHint=False,
idempotentHint=True,
openWorldHint=False,
),
)
async def your_tool(input: str) -> str:
"""What your tool does.
Args:
input: Input parameter.
"""
return f"Processed: {input}"Create src/my_mcp_server/tools/your_tool.py:
from mcp.server.fastmcp import FastMCP
from mcp.types import ToolAnnotations
def register(mcp: FastMCP) -> None:
@mcp.tool(
annotations=ToolAnnotations(
readOnlyHint=True,
destructiveHint=False,
idempotentHint=True,
),
)
async def your_tool(input: str) -> str:
"""What your tool does."""
return f"Processed: {input}"Then in server.py:
from my_mcp_server.tools.your_tool import register
register(mcp)Environment variables:
| Variable | Default | Description |
|---|---|---|
MCP_DEBUG |
false |
Enable debug logging |
LOG_LEVEL |
INFO |
Log level (DEBUG/INFO/WARNING/ERROR) |
Add your own in server.py.
# Run tests
pytest -v
# Lint
ruff check .
# Run the server (stdio)
python -m my_mcp_server| Check | Tool |
|---|---|
| Secret scanning | gitleaks |
| Large file detection | find (>5 MB) |
| License compliance | pip-licenses (blocks GPL/AGPL) |
| Lint + format | ruff |
| Tests | pytest (Python 3.11, 3.12, 3.13) |
- Bump version in
pyproject.toml - Go to Actions → Publish to PyPI → Run workflow
- OIDC handles auth — no
PYPI_TOKENsecret needed
Setup: PyPI OIDC trusted publishing docs
src/my_mcp_server/
├── __init__.py # Version
├── __main__.py # python -m entry point
├── server.py # FastMCP server + inline tools + helpers
└── tools/
├── __init__.py
└── greet.py # Example modular tool
tests/
└── test_tools.py # Tool tests
.github/
├── workflows/
│ ├── ci.yml # Lint, test, security
│ ├── cd.yml # PyPI OIDC publish
│ ├── stale.yml # Stale issue management
│ └── maintenance.yml # Weekly health check
└── dependabot.yml # Dependency updates
pip install -e ".[dev]" # Install with dev deps
python -m my_mcp_server # Run server
pytest -v # Run tests
ruff check . # Lint
ruff format . # FormatMIT