From workshop-skills
Use when bootstrapping a new Python repo from scratch — especially a FastMCP HTTP server — or when the user mentions FastMCP, `uv init`, `just`, `ty`, `pydantic-settings`, "scaffold", "new Python project", or asks for the canonical Python setup. Apply on greenfield projects or when adding these conventions to an empty repo.
How this skill is triggered — by the user, by Claude, or both
Slash command
/workshop-skills:scaffolding-python-repoThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Canonical setup for a new Python project (used as the workshop example by [vibber.ai](https://vibber.ai)). Stack: **uv + just + ruff + ty + pydantic-settings**, with a `src/` layout, `CLAUDE.md` for AI agents, `.env`-based configuration, and (when applicable) a FastMCP HTTP server as the entry point.
Canonical setup for a new Python project (used as the workshop example by vibber.ai). Stack: uv + just + ruff + ty + pydantic-settings, with a src/ layout, CLAUDE.md for AI agents, .env-based configuration, and (when applicable) a FastMCP HTTP server as the entry point.
just, uv, ty, pydantic-settingsDon't use for: repos with established conventions (follow them); non-Python projects; throwaway scripts.
uv for everything Python — no pip, no requirements.txt, no setup.pyjust recipes that wrap uv runty (Astral, pre-1.0) — never mypy or pyrightSettings field names are UPPER_CASE with no env_prefixmain() branches on settings.TRANSPORT so the same binary can run as stdio when Claude Desktop spawns it (Claude Desktop accepts only stdio + streamable HTTPS — plain HTTP localhost is rejected)main (rename from master after uv init if needed)project-name/
├── Justfile
├── pyproject.toml
├── uv.lock
├── .python-version # "3.13"
├── .env.example
├── .gitignore
├── README.md
├── CLAUDE.md
├── src/
│ └── package_name/
│ ├── __init__.py # empty
│ ├── config.py
│ └── server.py # FastMCP entry, or main module for non-MCP apps
└── tests/
├── __init__.py
└── test_server.py
Init via uv (creates src layout, git repo, .python-version, basic pyproject.toml):
uv init --package --lib --python 3.13 --name <project-name> .
Replace pyproject.toml with the template below (merge if uv added authors/etc.).
Add deps:
uv add fastmcp pydantic-settings
uv add --dev ruff ty pytest pytest-asyncio
Write source files from the templates section. Empty out the hello() function uv generated in __init__.py.
Append to .gitignore:
.env
.env.local
.pytest_cache/
.ruff_cache/
.ty_cache/
Rename branch: git branch -m master main
Verify: just check must pass before declaring the scaffold done.
Commit with a clear initial message.
Remind the user about Claude Desktop registration. A scaffolded server can't be tried in Claude Desktop until it's added to claude_desktop_config.json. Don't do this silently — surface it: "Want me to register this server in Claude Desktop now? See connecting-to-claude-desktop." Use the connecting-to-claude-desktop skill if they say yes.
[project]
name = "project-name"
version = "0.1.0"
description = "..."
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
"fastmcp>=3.2.4",
"pydantic-settings>=2.14.0",
]
[project.scripts]
project-name = "package_name.server:main"
[build-system]
requires = ["uv_build>=0.10.10,<0.11.0"]
build-backend = "uv_build"
[tool.ruff]
line-length = 100
target-version = "py313"
src = ["src", "tests"]
[tool.ruff.lint]
select = ["E", "W", "F", "I", "B", "UP", "SIM", "RUF"]
[tool.ty.src]
include = ["src", "tests"]
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
set windows-shell := ["powershell.exe", "-ExecutionPolicy", "Bypass", "-Command"]
default:
@just --list
install:
@echo '📦 ⬇️ Installing dependencies...'
uv sync
start:
@echo '🚀 Starting server...'
uv run project-name
format args='':
uv run ruff format {{ args }}
format-check args='':
uv run ruff format --check {{ args }}
lint args='':
uv run ruff check --fix {{ args }}
lint-check args='':
uv run ruff check {{ args }}
typecheck args='':
uv run ty check {{ args }}
test args='':
uv run pytest {{ args }}
check: lint-check format-check typecheck test
from typing import Literal
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
extra="ignore",
)
APP_NAME: str = Field(default="project-name")
HOST: str = Field(default="127.0.0.1")
PORT: int = Field(default=8888)
TRANSPORT: Literal["http", "stdio"] = Field(default="http")
settings = Settings()
from fastmcp import FastMCP
from package_name.config import settings
mcp: FastMCP = FastMCP(settings.APP_NAME)
@mcp.tool
def greet(name: str) -> str:
"""Return a friendly greeting."""
return f"Hello, {name}!"
@mcp.resource("config://app")
def app_info() -> dict[str, str | int]:
return {"app_name": settings.APP_NAME, "host": settings.HOST, "port": settings.PORT}
def main() -> None:
if settings.TRANSPORT == "stdio":
mcp.run(transport="stdio")
else:
mcp.run(transport="http", host=settings.HOST, port=settings.PORT)
if __name__ == "__main__":
main()
from fastmcp import Client
from package_name.server import mcp
async def test_greet_tool() -> None:
async with Client(mcp) as client:
result = await client.call_tool("greet", {"name": "Ford"})
assert result.data == "Hello, Ford!"
In-memory Client(mcp) is the standard test pattern — no port binding, no network, fast.
APP_NAME=project-name
HOST=127.0.0.1
PORT=8888
# Transport: "http" for local dev / MCP Jam / Claude Code (default),
# "stdio" when Claude Desktop spawns the server (set in mcpServers env block).
TRANSPORT=http
Always cover these sections (tailor content per project):
just recipes; "Always use just; never call python or pip directly"; "Before claiming work complete: run just check"Settings class in config.py, UPPER_CASE fields, .env workflowClient(mcp) examplesettings.TRANSPORT: http (default) for just start / MCP Jam / Claude Code; stdio for Claude Desktop integrationbunx @mcpjam/inspector@latest → point at http://127.0.0.1:8888/mcp; claude mcp add --transport http <name> http://127.0.0.1:8888/mcp; Claude Desktop snippet (see Optional integrations below)Minimum sections: Requirements (with brew install uv just + bun install commands), Setup, Run, Inspect (MCP Jam), Develop (just recipes), What's exposed, Testing with Claude Code.
Always run just check (lint-check + format-check + typecheck + test) before declaring done. All four must pass.
bunx @mcpjam/inspector@latest → http://127.0.0.1:8888/mcpclaude mcp add --transport http <name> http://127.0.0.1:8888/mcp (then start a fresh session)connecting-to-claude-desktop skill — it edits claude_desktop_config.json for the right OS, sets TRANSPORT=stdio, and handles the macOS GUI PATH gotcha. (Plain http://localhost URLs are rejected by Claude Desktop; only stdio + streamable HTTPS are accepted.)mcp-builder skill is the better reference. If the user doesn't have it, prompt them:
"Install Anthropic's official skills bundle:
/plugin marketplace add anthropics/skills"
superpowers:test-driven-developmentsuperpowers:brainstorming| Mistake | Fix |
|---|---|
Calling python or pip directly | Use just <recipe> or uv run ... |
Adding requirements.txt, setup.py, setup.cfg | uv + pyproject.toml only |
Adding mypy or pyright | Convention is ty |
Lowercase Settings fields, or adding env_prefix | UPPER_CASE, no prefix |
mcp.run() hardcoded to one transport | Branch on settings.TRANSPORT so HTTP and stdio both work |
Trying to register a http://localhost URL with Claude Desktop | Claude Desktop needs stdio or HTTPS — use the TRANSPORT=stdio env block |
Skipping just check before commit | All four checks must pass |
| Adding pre-commit / lefthook unprompted | Only if explicitly requested |
Leaving the branch as master | Rename to main |
| Defaulting port to 8000 or 3000 | Use 8888 to avoid collisions |
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub vibber-ai/workshop-skills --plugin workshop-skills