MCP-Template
CLAUDE.md1 starsChanged 5 months agoMITView on GitHub
Super-opinionated Python template that ships one codebase, three interfaces (CLI, MCP server, HTTP API) over a shared service registry. Python >= 3.12 required. Uses uv for dependency management (not pip). Before any other work in this repo, enable prek: uv tool install prek && prek install. Hooks are defined in prek.toml. MCP terminology: For nuances around frequently-confused MCP terms (Host vs. Client vs. Server, Tools vs. Resources vs. Prompts, Roots vs. Resources, transports, OAuth pitfalls, etc.), see mcp_server/COMMON_TERMS.md. Consult it…
- Reads credentials
- Deletes or force-pushes
- Installs packages
- Commits and pushes
In this repository28 other files it gives its agents.
- .cursor/rules/agent.mdcCursor rule
- .cursor/rules/coding_style.mdcCursor rule
- .cursor/rules/commit_msg.mdcCursor rule
- .cursor/rules/common.mdcCursor rule
- .cursor/rules/deprecated.mdcCursor rule
- .cursor/rules/github_actions.mdcCursor rule
- .cursor/rules/langfuse.mdcCursor rule
- .cursor/rules/llm.mdcCursor rule
- .cursor/rules/long_run_code.mdcCursor rule
- .cursor/rules/makefile-commands.mdCursor rule
- .cursor/rules/package-management.mdCursor rule
- .cursor/rules/tests.mdcCursor rule
- .cursor/rules/typing.mdcCursor rule
- .cursor/rules/uv.mdcCursor rule
- frontend-design-principlesSkill
- goose-gui-e2eSkill
- local-sandboxSkill
- manage-agent-configSkill
- new-agent-ruleSkill
- onboardingSkill
- push-releaseSkill
- cleanupSkill
- code-qualitySkill
- prek-precommit-hookSkill
- thermo-nuclear-code-quality-reviewSkill
- usageSkill
- waitSkill
- gmail-mcpSkill
The file
We split this CLAUDE.md into the rules it gives an agent
7 rules. About 0 are common, 3 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- could be automatic: a setting could enforce it
- Distinctive: line 172
Use emoji prefixes indicating change type and magnitude (multiple emojis = 5+ files):
- Distinctive: line 185
Use descriptive IDs (runId, taskId)
- Distinctive: line 202
If a tool just returns data, leave it headless.
- Very specific: line 235
When adding a new app, also add an entry to [tool.hatch.build.targets.wheel] force-include in pyproject.toml so the HTML ships in the wheel.
- Very specific: line 269
Never force push: Do not use git push --force or --force-with-lease.
- Very specific: line 270
Pre-commit CI gate: Always run make ci before committing any changes.
- Very specific: line 274
Don't use datetime.utcnow() - use datetime.now(timezone.utc)
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CLAUDE.md
Project Overview
Super-opinionated Python template that ships one codebase, three interfaces (CLI, MCP server, HTTP API) over a shared service registry. Python >= 3.12 required. Uses uv for dependency management (not pip).
Before any other work in this repo, enable prek: uv tool install prek && prek install. Hooks are defined in prek.toml.
MCP terminology: For nuances around frequently-confused MCP terms (Host vs. Client vs. Server, Tools vs. Resources vs. Prompts, Roots vs. Resources, transports, OAuth pitfalls, etc.), see mcp_server/COMMON_TERMS.md. Consult it before naming or designing new MCP-related code.
MCP Apps: an MCP extension for interactive iframe-sandboxed UIs (HTML resources via ui:// URIs) embedded in chat clients, with bidirectional postMessage / JSON-RPC communication. See ext-apps. Not a generic word for "MCP application".
MCP is an actively-evolving spec. Behaviors change frequently (transports, auth, primitives). Don't rely on training-data assumptions for anything MCP-related - always verify against the current spec via a fresh web search before writing or reviewing MCP code.
Common Commands
# Setup
make all # Sync dependencies
# Testing
make test # Run pytest on tests/
make test_fast # Run fast tests (no slow/nondeterministic)
make test_flaky # Repeat fast tests to detect flakiness
make test_slow # Run slow tests only
make test_nondeterministic # Run nondeterministic tests only
make mcp_conformance # MCPJam apps + protocol conformance vs local /mcp (needs node; CI-gated separately)
# Code Quality (run after major changes)
make fmt # Run ruff formatter + JSON formatting
make ruff # Run ruff linter
make vulture # Find dead code
make ty # Run type checker
make lint_links # Check for broken links in markdown files (README, etc.)
make docs_test # Run the docs site's vitest suite (docs/lib/*.test.ts)
make tool_surface_docs_check # Fail if docs/ has drifted from the @service registry
make ci # Run all CI checks (ruff, vulture, ty, import_lint, docs_lint, docs_test, check_deps, file_len_check, blind_except_check, tool_surface_docs_check, lint_immutable_refs)
# Dependencies
uv sync # Install dependencies (not pip install)
uv add <pkg> # Add new dependency
uv run python <file> # Run Python files
uv run pytest path/to/test.py # Run specific test
# Release
# 1. Update version in pyproject.toml
# 2. Tag the commit: git tag -a v0.1.0 -m "Release v0.1.0"
# 3. Push the tag: git push origin v0.1.0 (triggers Release workflow)Architecture
Layering (top calls down, never the reverse):
- Transports:
src/cli/app.py(Typer) ·mcp_server/(FastMCP) ·api_server/(FastAPI) - Services:
services/- pure@service-decorated functions, transport-agnostic - Contracts:
models/- Pydantic input/output schemas shared by all transports - Infra:
common/(config) ·db/(SQLAlchemy + Alembic) ·utils/llm/(DSPY) ·src/utils/(logging, theme, errors)
Top-level layout
src/cli/app.py+src/cli/commands/- Typer CLI (mymcp);src/cli/commands/__init__.pyauto-discoverssrc/cli/commands/*.pyand registers them.mcp_server/- FastMCP server. Primary transport is streamable HTTP, mounted on the FastAPI app at/mcpfromapi_server/server.py(one process, one port, shared auth/CORS). Stdio is legacy/dev-only viamymcp-mcp.mcp_server/server.py:build_mcp_serverpopulates a single FastMCP singleton from the service registry;mount_on+lifespanhandle the streamable-HTTP wiring. Seemcp_server/COMMON_TERMS.md.api_server/- FastAPI HTTP server (auth/,billing/,middleware/,routes/). Hosts the/mcpmount; the primary entrypoint ismymcp-serve.services/-@service(name=, description=, input_model=, output_model=)-decorated pure functions (services/__init__.py:20).common/- pydantic-settings config.global_config.yaml- base;<name>.yaml- split configs loaded as root key<name>production_config.yaml- overlay loaded with high priority whenDEV_ENV=prod
src/- CLI plumbing (src/cli/) + shared utils (logging, theme, errors, output).utils/llm/- DSPY + LiteLLM wrapper with fallback model, Tenacity retries, LangFuse observability.tests/- subclassTestTemplate(tests/test_template.py:14) for per-test config isolation.docs/- Next.js + Fumadocs site; English source indocs/content/en/. Two invariants are enforced, both inmake ci:- The tool reference in
docs/content/docs/mcp/tools.mdxis generated. Everything between the{/* BEGIN generated */}markers is rendered byscripts/gen_tool_docs.pyfromllm_tool_surface()plus the enhancer registry (which supplies the**UI**markers); descriptions are the registry's own, so the page shows what an MCP client is actually told, and the tool count is interpolated rather than asserted. Editorial input is theGROUPStable in that script - a registered tool missing from it is a hard error, not a silent omission. Runmake gen_tool_docsafter adding a@service;make tool_surface_docs_checkand the unconditionaltool-surface-docsprek hook fail on drift. The hook is not path-filtered on purpose: afilesfilter never sees agit rm, and deleting a service is exactly how the page ends up advertising tools that no longer exist. make docs_testruns vitest overdocs/lib/*.test.ts. Pure helpers only - todaymdx-to-markdown.ts, thestring -> stringconverter behind the agent-facing.mdxtwins andllms-full.txt. It is kept out ofsource.tsso it imports nothing:source.tspulls in thefumadocs-mdx:collections/*virtual modules, which need the site's codegen to exist.
- The tool reference in
landing-page/- standalone Astro + Tailwind v4 marketing site, separate fromdocs/and the Python server, with its own Railway deploy (landing-page/railway.toml). Data-driven: all copy lives insrc/config/landing.ts, design tokens insrc/styles/global.css(@theme). Usesbun, never npm. Seelanding-page/README.md..claude/,.agents/,.codex/- Claude/Codex agents and skills kept in sync byscripts/sync_agent_config.py(pre-commit enforced).
Don't add new files at the repo root unless tooling requires it. Nest under an existing folder.
Adding a new feature
- Pydantic models in
models/<feature>.py. - Pure
@servicefunction inservices/<feature>_svc.py. Passmutating=Trueif it has side effects (create/charge/send). - (CLI) Typer command in
src/cli/commands/<feature>.pycalling the service. - (MCP) Nothing -
mcp_server/server.pyauto-registers on import. - (HTTP, optional) Route in
api_server/routes/. - Tests inheriting
TestTemplate.
mutating=True services get REST Idempotency-Key enforcement on their auto-generated API route: the key is required, claimed in the idempotency_keys table, and the response replayed on retries (same key + different payload → 422). Logic in api_server/idempotency.py:execute_idempotent. CLI unaffected. On MCP, mutating also gates the enhancer crash-fallback (mcp_server/_tool_factory.py): a mutating service is never silently re-executed - the fallback reuses the already-completed tool.call() result or propagates the enhancer error.
Code Style
Enforced by ruff - see [tool.ruff] in pyproject.toml. Run make fmt and make ruff.
Error handling
Broad catches like except Exception: and except BaseException: are banned by ruff's BLE001 rule; bare except: is separately banned by E722 (part of the E select). Either narrow the catch to the concrete exception types you expect, or - if the call site is a genuine defensive boundary (middleware, health probe, background task, third-party SDK) - suppress with # noqa: BLE001 and a justification comment on the next line (or trailing the same line) explaining why a broad catch is correct. scripts/check_blind_except_justification.py (wired into make ci and prek) fails the build if any # noqa: BLE001 lacks a justification.
Configuration Pattern
from common import global_config
global_config.example_parent.example_child
global_config.llm_config.default_model
global_config.OPENAI_API_KEY # secrets from .envLLM Inference Pattern
from utils.llm.dspy_inference import DSPYInference
import dspy
class MySignature(dspy.Signature):
input_field: str = dspy.InputField()
output_field: str = dspy.OutputField()
inf_module = DSPYInference(pred_signature=MySignature, observe=True)
result = await inf_module.run(input_field="value")Testing Pattern
from tests.test_template import TestTemplate
from tests.conftest import slow_test, nondeterministic_test
class TestMyFeature(TestTemplate):
def test_something(self):
assert self.config is not None
@slow_test
def test_slow_operation(self):
passLogging
from loguru import logger as log
from src.utils.logging_config import setup_logging
setup_logging()
log.debug("detailed diagnostic information")
log.info("general informational message")
log.warning("warning message for potentially harmful situations")
log.error("error message for error events")Commit Message Convention
Use emoji prefixes indicating change type and magnitude (multiple emojis = 5+ files):
- 🏗️ initial implementation
- 🔨 feature changes
- 🐛 bugfix
- ✨ formatting/linting only
- ✅ feature complete with E2E tests
- ⚙️ config changes
- 💽 DB schema/migrations
Long-Running Code Pattern
Structure as: init() → continue(id) → cleanup(id)
- Keep state serializable
- Use descriptive IDs (runId, taskId)
- Handle rate limits, timeouts, retries at system boundaries
MCP: Headless vs UI
This template supports two MCP tool styles:
- Headless tools (default) - sync wrapper, no
Context, returns the Pydantic output model so FastMCP derivesoutputSchema. The CLI/API/MCP transports share identical behavior. Use this for any tool the LLM should call autonomously without UI affordances. - Enhanced tools (opt-in via
@enhanceinmcp_server/enhancers/) - async wrapper withContext, may elicit user input mid-call, attach images/audio, or render an MCP App (iframe dashboard). MCP-only - never affects CLI/API consumers of the same service. The pure service stays untouched inservices/.
If a tool just returns data, leave it headless. Reach for an enhancer only when the MCP transport needs something the spec offers and other transports don't.
Adding an enhancer
from mcp_server.enhancers import enhance
from mcp_server.enhancers.base import EnhancedTool
@enhance("my_service", fallback="headless")
async def my_enhanced(tool: EnhancedTool[MyInput, MyOutput]) -> MyOutput:
result = tool.call()
if tool.can_elicit:
... # await tool.elicit(...)
if tool.can_show_app:
tool.send_app("ui://mymcp/my_dashboard")
return resultThen add import mcp_server.enhancers.my_service # noqa: F401 to
_register_tools() in mcp_server/server.py.
MCP Apps (iframe dashboards)
Apps live in mcp_server/apps/<name>/:
- React + Vite +
vite-plugin-singlefile(always bun, never npm) dist/mcp-app.htmlis committed so the template works without Nodemake build_appsrebuilds the bundle (developer-only; not part of CI)make dev_hostruns the upstream@modelcontextprotocol/ext-appsbasic-host for manual smoke testing
When adding a new app, also add an entry to [tool.hatch.build.targets.wheel]
force-include in pyproject.toml so the HTML ships in the wheel.
See mcp_server/MCP_UI_ARCHITECTURE.md for design rationale and
mcp_server/MCP_UI_EDGE_CASES.md for the edge-case spec.
MCP testing (two tiers)
- Fast tier (default CI, pytest): unit tests for the enhancer machinery
plus
tests/test_mcp_e2e.py, which runs a full MCP session over the streamable-HTTP mount and asserts the wire format (outputSchema,_meta.ui, CallToolResult assembly). No Node needed. - Conformance tier:
make mcp_conformance(MCPJam protocol + apps suites, CI-gated, needs Node). OAuth conformance is manual - seedocs/content/docs/mcp/oauth.mdx. - App frontends: vitest per app (
bun run testinmcp_server/apps/<name>/, not in CI); each app has anappContract.test.tspinning the realext-appsAppsurface against theMcpAppLikemock type. - Rendered-iframe tier (L4, #37):
make test_apps_e2edrives the real Goose desktop GUI (Electron) as an MCP client and asserts theui://app actually renders + round-trips in a real host - including a click →callServerTool→ re-render scenario. Also wired as a skipped-by-default pytest entry (tests/test_apps_e2e.py, opt in withRUN_APPS_E2E=1) and an opt-in CI workflow (.github/workflows/apps_e2e.yaml,workflow_dispatch+ weekly). Build- rationale:
.agents/skills/goose-gui-e2e/SKILL.md. Heavy (Goose + Electron); not inmake ci.
- rationale:
Subagents
- Folder-size CI failure → spawn subagent
.claude/agents/folder-refactor-advisor.md.
Git Workflow
- Protected Branch:
mainis protected. Do not push directly tomain. Use PRs. - Merge Strategy: Squash and merge.
- Never force push: Do not use
git push --forceor--force-with-lease. If you hit a git issue, stop and ask the user for guidance. - Pre-commit CI gate: Always run
make cibefore committing any changes. Ensure it passes with zero errors. Do not commit ifmake cifails - fix all issues first, then commit.
Deprecated
- Don't use
datetime.utcnow()- usedatetime.now(timezone.utc)
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

