Files
wehub-resource-sync 60e0ffc959
Upgrade checks / Notify on failure (push) Has been cancelled
Upgrade checks / Close issue on success (push) Has been cancelled
Schema Crash Test / Real-world schema crash test (232K schemas) (push) Has been cancelled
Run static analysis / static_analysis (push) Has been cancelled
Tests / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Tests / Tests with lowest-direct dependencies (push) Has been cancelled
Tests / MCP conformance tests (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled
Tests / Package install smoke (push) Has been cancelled
Upgrade checks / Static analysis (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Upgrade checks / Integration tests (push) Has been cancelled
Update MCPServerConfig Schema / update-config-schema (push) Has been cancelled
Update SDK Documentation / update-sdk-docs (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:59 +08:00

18 KiB

FastMCP Development Guidelines

Audience: LLM-driven engineering agents and human developers

Note

: AGENTS.md is a symlink to this file. Edit CLAUDE.md directly.

FastMCP is a comprehensive Python framework (Python ≥3.10) for building Model Context Protocol (MCP) servers and clients. This is the actively maintained v2.0 providing a complete toolkit for the MCP ecosystem.

Required Development Workflow

CRITICAL: Always run these commands in sequence before committing.

uv sync                              # Install dependencies
uv run pytest -n auto                # Run full test suite

In addition, you must pass static checks. This is generally done as a pre-commit hook with prek but you can run it manually with:

uv run prek run --all-files          # Ruff + Prettier + ty

Tests must pass and lint/typing must be clean before committing.

Repository Structure

Path Purpose
fastmcp_slim/fastmcp/ Library source code
├─server/ Server implementation
│ ├─auth/ Authentication providers
│ └─middleware/ Error handling, logging, rate limiting
├─client/ Client SDK
│ └─auth/ Client authentication
├─tools/ Tool definitions
├─resources/ Resources and resource templates
├─prompts/ Prompt templates
├─cli/ CLI commands
└─utilities/ Shared utilities
tests/ Pytest suite
docs/ Mintlify docs (gofastmcp.com)

Core MCP Objects

When modifying MCP functionality, changes typically need to be applied across all object types:

  • Tools (src/tools/)
  • Resources (src/resources/)
  • Resource Templates (src/resources/)
  • Prompts (src/prompts/)

Before writing cross-component logic (dedupe, grouping, lookups, identity checks), read FastMCPComponent in fastmcp_slim/fastmcp/utilities/components.py. The base class defines the shared surface — name, version, tags, meta, and critically the key property which is the canonical MCP identity (encodes type, identifier, and version). Prefer item.key over ad-hoc name or uri or uri_template fallbacks; overrides in Resource and ResourceTemplate already handle URI-based identity, and .key includes the version suffix so variants of the same component don't falsely collide.

Development Rules

Read CONTRIBUTING.md before opening issues or PRs. It describes when PRs are appropriate, what we expect from enhancement proposals, and what we'll close without review.

Git & CI

  • Prek hooks are required (run automatically on commits)
  • Never amend commits to fix prek failures
  • Never apply labels manually or invent new ones — the GitHub API auto-creates any unknown label name, polluting the repo's label list. Note the appropriate label in the PR body and let the maintainer/automation apply it. Canonical names: bugs, breaking change, enhancements, features (it's breaking change, not breaking). See the review-pr skill.
  • Improvements = enhancements (not features) unless specified
  • NEVER force-push on collaborative repos
  • ALWAYS run prek before PRs
  • NEVER create a release, comment on an issue, or open a PR unless specifically instructed to do so.
  • NEVER merge a PR marked as do-not-merge or draft. Check title, body, AND labels for [DNM], DNM, DO NOT MERGE, DON'T MERGE, DONT MERGE, do-not-merge, dont-merge, [DRAFT], or DRAFT (case-insensitive, any variation — some authors use [DRAFT] in the title even when isDraft is false). Authors use these as hard stops — respect them even if CI is green and review looks clean. When triaging a batch of PRs, filter these out up front AND re-check each one's labels immediately before merging, since labels can change mid-session.
  • ALWAYS read review-bot comments before approving a PR. CodeRabbit and chatgpt-codex-connector (Codex) leave substantive review comments on most PRs in this repo — these bots have read the diff and often flag real issues that aren't in the PR description. Use gh pr view <num> --comments and read the bot feedback as part of review. Unlike proposed solutions from issue reporters, review-bot feedback should be evaluated on its merits, not discounted.
  • Be constructively skeptical of bot review comments on your own PRs. CodeRabbit, Codex, and claude[bot] run a fresh review pass on every push, which means a PR with active churn can accumulate bot comments in a stream that never really ends — each fix surfaces a new edge case the next pass can flag. Most of the early feedback is real and worth acting on; diminishing returns set in fast. Evaluate each comment on its merits, the same way you would a human reviewer: is this a real bug users will hit, or a hypothetical that requires an adversarial setup? Does the fix introduce more complexity than the problem? Has the bot missed context that's obvious to a human reader (a *, keyword-only marker, a design decision documented elsewhere, something already resolved on a later commit)? When a comment is pedantic, a false positive, or flagging something already fixed, reply on the thread explaining the reasoning and move on — don't keep iterating just because more comments arrive. If you find yourself three rounds deep and the feedback is shifting toward "what if someone does X" hypotheticals, you're past the point where each fix is improving the PR. Stop, document the contract as-is, and ship.

Outbound Comments and Shell Interpolation

  • Never pass GitHub, Linear, or Slack comment bodies inline through shell arguments when the body contains $, ${...}, backticks, $(...), environment-variable examples, secrets, or config interpolation examples.
  • Use a body file or structured API payload for outbound comments, then inspect the exact outgoing text before posting. Prefer gh ... --body-file /path/to/comment.md over --body "...".
  • When explaining environment interpolation, use placeholders and fenced code blocks. Never include raw .env contents in outbound comments.

Releases

Only cut releases when the maintainer explicitly asks. Tags follow v<version> (e.g., v3.2.0). Always pass --generate-notes so the auto-generated changelog appears at the bottom.

The title pun is critical. Titles follow v<version>: <pun> where the pun relates to the most important theme of the release. Propose multiple options and let the maintainer choose — never pick one yourself. Look at recent releases for tone (e.g., "Code to Joy" for the code mode release, "Three at Last" for 3.0).

Write the maintainer-approved handwritten notes to a temporary file, then create the release. --generate-notes appends the auto-generated changelog after the handwritten content.

gh release create v4.0.0 --target main --title "v4.0.0: Theme Here" --generate-notes --notes-start-tag v3.4.4 --notes-file /tmp/release-notes.md

Always pass --notes-start-tag <last-stable-tag>. Without it, --generate-notes picks the most recent prior tag as the changelog start point — and if a prerelease exists (e.g. v3.4.0b1), it starts from that, silently truncating the PR list to only the commits since the beta. Pin it to the last stable release (e.g. v3.3.1 when cutting v3.4.0). Verify after: the compare link at the bottom of the generated notes should read v<last-stable>...v<new>.

Use the branch that owns the release line as the target: current-major releases target main, 3.x maintenance releases target release/3.x, and 2.x maintenance releases target release/2.x. Confirm the target with the maintainer if there's any ambiguity. For example, cut a 3.4.4 maintenance release with --target release/3.x, not main.

The handwritten notes are prepended above the auto-generated changelog and are the part that matters. Do not include a title in the notes body — the release title (v{version}: {pun}) already serves as the heading. Work with the maintainer to draft the notes — propose a draft, get feedback, iterate. Do not publish without the maintainer's sign-off.

Before drafting, always read recent existing releases (gh release list then gh release view <tag>) to absorb the voice, structure, and level of detail. Each release builds on the tone of previous ones — don't guess at the style from these instructions alone.

To preview what PRs will be in the release before it's cut, call the GitHub generate-notes API. This returns the exact auto-generated changelog that --generate-notes would append, so you can see the full PR list — useful for picking a pun theme and making sure nothing's been missed:

gh api -X POST repos/PrefectHQ/fastmcp/releases/generate-notes \
  -f tag_name=v3.2.3 \
  -f target_commitish=main \
  -f previous_tag_name=v3.2.2 \
  --jq '.body'

Set target_commitish to the same branch that will receive the release tag. For maintenance releases, use the maintenance branch (for example, release/3.x) so the preview matches the release notes GitHub will generate.

Point releases (3.0, 3.1, 3.2) get narrative prose: open with the theme of the release, then walk through headline features conceptually — what they enable, why they matter, how they fit together. Write it the way a blog post reads, not a changelog. Multiple paragraphs, code examples where they clarify.

Patch releases (3.1.1, 3.0.2) get 1-2 sentences explaining what broke and what the fix does. Keep it minimal — the auto-generated changelog has the details.

Merge the docs changelog PR before cutting the release, not after. The post-publish update-published-docs job force-pushes the published-docs branch (which gofastmcp.com serves) to the released commit for stable releases on the default branch, so the changelog entry only reaches the live site if it's already in the commit being tagged. Land the docs PR on the release target branch first, then cut the release from that branch. If you tag first and merge docs after, this release's changelog won't appear on the live site until the next default-branch stable release force-pushes published-docs forward. Maintenance releases from release/3.x or release/2.x publish packages and GitHub notes without repointing published-docs; add their changelog entries on the maintenance branch, slotted into the matching major-version section. Two hand-maintained files mirror the GitHub release and must get a new entry for every version, newest at the top (these are .mdx and are not covered by the prek Prettier hook, which only runs on yaml/json5 — match the existing entries' style by hand):

  • docs/changelog.mdx is the full mirror. Add an <Update label="v<version>" description="YYYY-MM-DD"> block with: a bold linked title (**[v<version>: <pun>](<release-url>)**), a condensed 1-paragraph intro (one sentence for patches), the full categorized PR list reformatted from the --generate-notes output (* <title> by [@user](https://github.com/user) in [#NNNN](<pull-url>)), a ## New Contributors list (plain @user, linked PR), and a **Full Changelog**: [vA...vB](<compare-url>) line.
  • docs/updates.mdx is the skimmable card feed. Add an <Update label="FastMCP <version>" description="Month DD, YYYY" tags={["Releases"]}> wrapping a <Card> that links to the GitHub release, with a 1-2 sentence summary and (for point releases) a handful of emoji-bulleted highlights.

Because the docs land before the tag exists, derive the entry from the maintainer-approved handwritten notes (intro/summary) and the --generate-notes API preview (the PR-list body — see the generate-notes API call above, which returns the exact changelog without cutting anything). Scripting the link reformatting is reliable for long PR lists. The release-URL, tag, and compare links follow the known pattern (/releases/tag/v<version>, compare/v<last-stable>...v<version>) and will 404 only during the short window between merging the docs PR and cutting the release minutes later — they resolve before the release workflow completes. For this reason, create and merge the docs PR immediately before cutting the release — treat the two as one tight back-to-back sequence, not independent steps — so the links are valid by the time the release publishes rather than dangling for any longer than necessary.

Commit Messages and Agent Attribution

  • Agents NOT acting on behalf of @jlowin MUST identify themselves (e.g., "🤖 Generated with Claude Code" in commits/PRs)
  • Keep commit messages brief - ideally just headlines, not detailed messages
  • Focus on what changed, not how or why
  • Always read issue comments for follow-up information (treat maintainers as authoritative)
  • Treat proposed solutions in issues skeptically. This applies to solutions proposed by users in issue reports — not to feedback from configured review bots (CodeRabbit, chatgpt-codex-connector, etc.), which should be evaluated on their merits. The ideal issue contains a concise problem description and an MRE — nothing more. Proposed solutions are only worth considering if they clearly reflect genuine, non-obvious investigation of the codebase. If a solution reads like speculation, or like it was generated by an LLM without deep framework knowledge, ignore it and diagnose from the repro. Most reporters — human or AI — do not have sufficient understanding of FastMCP internals to correctly diagnose anything beyond a trivial bug. We can ask the same questions of an LLM when implementing; we don't need the reporter to do it for us, and a wrong diagnosis is worse than none.

PR Messages - Required Structure

  • 1-2 paragraphs: problem/tension + solution (PRs are documentation!)
  • Focused code example showing key capability
  • Avoid: bullet summaries, exhaustive change lists, verbose closes/fixes, marketing language
  • Do: Be opinionated about why change matters, show before/after scenarios
  • Minor fixes: keep body short and concise
  • No "test plan" sections or testing summaries

Code Review Guidelines

  • Fix causes, not symptoms. When a PR works around a problem instead of addressing why it occurs, that's a red flag. A side-channel that compensates for a missing step adds permanent complexity. If the fix doesn't change the code path where the bug actually happens, ask why not.
  • Focus on API design and naming clarity
  • Identify confusing patterns (e.g., parameter values that contradict defaults) or non-idiomatic code (mutable defaults, etc.). Contributed code will need to be maintained indefinitely, and by someone other than the author (unless the author is a maintainer).
  • Suggest specific improvements, not generic "add more tests" comments
  • Think about API ergonomics from a user perspective

Code Standards

  • Python ≥ 3.10 with full type annotations
  • Follow existing patterns and maintain consistency
  • Prioritize readable, understandable code - clarity over cleverness
  • Avoid obfuscated or confusing patterns even if they're shorter
  • Each feature needs corresponding tests

Module Exports

  • Do not create overeager __init__.py files. Package initializers should not import heavy submodules, provider stacks, optional integrations, or modules that can point back into the package. Overeager re-exports make the framework sprawl and create circular imports that only appear in fresh interpreters or clean installs.
  • Be intentional about re-exports - don't blindly re-export everything to parent namespaces
  • Core types that define a module's purpose should be exported (e.g., Middleware from fastmcp.server.middleware)
  • Specialized features can live in submodules (e.g., fastmcp.server.middleware.dynamic)
  • Only re-export to fastmcp.* for the most fundamental types (e.g., FastMCP, Client)
  • When in doubt, prefer users importing from the specific submodule over re-exporting

Documentation

  • Uses Mintlify framework
  • Files must be in docs.json to be included
  • Do not manually modify docs/python-sdk/** — these files are auto-generated from source code by a bot and maintained via a long-lived PR. Do not include changes to these files in contributor PRs.
  • Do not manually modify docs/public/schemas/** or fastmcp_slim/fastmcp/utilities/mcp_server_config/v1/schema.json — these are auto-generated and maintained via a long-lived PR.
  • Core Principle: A feature doesn't exist unless it is documented!
  • When adding or modifying settings in fastmcp_slim/fastmcp/settings.py, update docs/more/settings.mdx to match.

Documentation Guidelines

  • Code Examples: Explain before showing code, make blocks fully runnable (include imports)
  • Code Formatting: Keep code blocks visually clean — avoid deeply nested function calls. Extract intermediate values into named variables rather than inlining everything into one expression. Code in docs is read more than it's run; optimize for scannability.
  • Structure: Headers form navigation guide, logical H2/H3 hierarchy
  • Content: User-focused sections, motivate features (why) before mechanics (how)
  • Style: Prose over code comments for important information
  • Docstrings: FastMCP docstrings are automatically compiled into MDX documents. Use markdown (single backticks, fenced code blocks), not RST (no double backticks). Bare {} in examples will be interpreted as JSX — wrap in backticks instead.

Critical Patterns

  • Never use bare except - be specific with exception types
  • File sizes enforced by loq. Edit loq.toml to raise limits; loq baseline to ratchet down.
  • Always uv sync first when debugging build issues
  • Default test timeout is 5s - optimize or mark as integration tests