Skip to content

chore: upgrade fastmcp to 3.4.2#29

Open
mskry wants to merge 1 commit into
mainfrom
chore/fastmcp-3-4-2
Open

chore: upgrade fastmcp to 3.4.2#29
mskry wants to merge 1 commit into
mainfrom
chore/fastmcp-3-4-2

Conversation

@mskry

@mskry mskry commented Jun 20, 2026

Copy link
Copy Markdown
Member

Summary

  • upgrade FastMCP from 3.2.3 to 3.4.2
  • migrate server/tool registration to the FastMCP API exposed by 3.4.2
  • update tests for the new HTTP app/session behavior and media return handling

Verification

  • python -m pytest
  • python -m ruff check . --fix

Summary by CodeRabbit

  • Chores

    • Updated fastmcp dependency from version 3.2.3 to 3.4.2.
  • Improvements

    • Refactored internal tool and resource registration to use a unified, centralized architecture.
    • Enhanced PDF diagram delivery with improved resource embedding.

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Upgrades fastmcp from 3.2.3 to 3.4.2 and introduces a fastmcp_compat.py module that centralizes re-exports and adds a binary_file() helper wrapping bytes into EmbeddedResource. All per-module FastMCP instances and @tool decorators are removed from tool modules; tool/resource/prompt registration moves to explicit calls in server.py. PDF delivery switches from File to EmbeddedResource.

Changes

FastMCP 3.4.2 upgrade and server re-wiring

Layer / File(s) Summary
fastmcp version bump and compat module
pyproject.toml, src/diagrams_mcp/fastmcp_compat.py
fastmcp bumped from 3.2.3 to 3.4.2; new fastmcp_compat.py re-exports FastMCP, Image, ToolError from fastmcp and adds binary_file() to wrap bytes into an EmbeddedResource with a base64-encoded BlobResourceContents and file:/// URI.
PDF binary delivery via EmbeddedResource
src/diagrams_mcp/image_store.py, src/diagrams_mcp/tools/plantuml.py, src/diagrams_mcp/tools/render.py
deliver_image return type changes from File to EmbeddedResource and adds a fmt == "pdf" branch returning binary_file(...). render_plantuml and render_diagram return types updated to Image | EmbeddedResource | str; all three modules switch to compat imports.
Tool modules stripped of per-module FastMCP instances
src/diagrams_mcp/tools/discovery.py, src/diagrams_mcp/tools/equivalence.py, src/diagrams_mcp/tools/mermaid.py
Module-level FastMCP("...") instances and @<module>.tool(...) decorators removed from discovery, equivalence, and mermaid tool modules; functions left as plain callables with updated imports from diagrams_mcp.fastmcp_compat.
Server centralized tool/resource/prompt registration
src/diagrams_mcp/server.py, src/diagrams_mcp/prompts.py, src/diagrams_mcp/resources.py, src/diagrams_mcp/renderer.py, src/diagrams_mcp/sandbox.py
server.py removes rate-limiting middleware and mcp.mount() sub-app wiring, replacing them with explicit mcp.add_tool(), mcp.resource(), and mcp.prompt() registrations. Adds create_test_http_app(). Remaining modules switch FastMCP/ToolError imports to diagrams_mcp.fastmcp_compat.
Tests updated for compat imports and EmbeddedResource assertions
tests/test_*.py
All test files update ToolError/Image imports to diagrams_mcp.fastmcp_compat. test_mermaid.py asserts PDF results are EmbeddedResource. test_server.py replaces mcp.http_app() with create_test_http_app(). test_sandbox.py loosens CPU-limit assertion to accept timeout or generic failure.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • ByteOverDev/diagrams-mcp#16: Directly overlaps on image_store.py's deliver_image function and PDF handling/return typing changes.
  • ByteOverDev/diagrams-mcp#28: Touches the same deliver_image/PDF EmbeddedResource delivery code paths in image_store.py.
  • ByteOverDev/diagrams-mcp#11: Directly conflicts — adds the equivalence = FastMCP(...) instance and @equivalence.tool(...) decorators that this PR removes from tools/equivalence.py.

Poem

🐇 Hop hop, the compat shim is here,
No more direct fastmcp imports to fear!
binary_file wraps bytes with base64 care,
And add_tool wires tools with declarative flair.
The server's now tidy, the PDF embedded right —
This bunny ships clean code into the night! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: upgrading FastMCP from 3.2.3 to 3.4.2, which is the primary focus and driver of all subsequent refactoring throughout the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 93.33% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/fastmcp-3-4-2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/diagrams_mcp/tools/plantuml.py (1)

7-7: Align ToolError import with coding guideline for tool functions.

Line 7 imports ToolError via diagrams_mcp.fastmcp_compat, but the coding guideline requires fastmcp.exceptions.ToolError for error handling in tool functions. While the compat layer works, all tool modules in this package currently use this pattern (mermaid.py, equivalence.py, discovery.py, render.py). Consider refactoring to import directly from fastmcp.exceptions to align with the specified guideline.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/diagrams_mcp/tools/plantuml.py` at line 7, The import statement on line 7
currently imports ToolError from diagrams_mcp.fastmcp_compat, but the coding
guideline requires importing ToolError directly from fastmcp.exceptions to align
with the pattern used in other tool modules (mermaid.py, equivalence.py,
discovery.py, render.py). Refactor the import statement to separate the imports:
keep Image imported from diagrams_mcp.fastmcp_compat but import ToolError
directly from fastmcp.exceptions instead.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/diagrams_mcp/image_store.py`:
- Around line 333-334: The PDF inline path using binary_file() at line 333-334
is unreachable because the condition at line 294 forces download_link = True for
all non-PNG formats, causing execution to return early at line 318. To fix this,
modify the condition at line 294 that sets download_link = True to exclude PDF
format (or conditionally skip the early return at line 318 for PDF), so that PDF
requests can proceed to the binary_file() call instead of taking the forced
download path.

In `@src/diagrams_mcp/server.py`:
- Around line 57-83: The expensive rendering tools render_diagram,
render_mermaid, and render_plantuml are registered with mcp.add_tool() without
any rate limiting protection, leaving them vulnerable to saturation and abuse.
Add server-side throttling by implementing a middleware-based rate limiting
layer or configuring an upstream policy to restrict request frequency for these
resource-intensive operations. Ensure the throttling mechanism is enforced
before these tools execute to prevent abuse and maintain system stability.

In `@tests/test_sandbox.py`:
- Line 260: The assertion on line 260 performs inconsistent case handling where
"timed out" is checked against msg.lower() but "Diagram code failed" is checked
against the original msg without case normalization. To fix this, normalize the
second alternative by changing the check to also use msg.lower(), so both "timed
out" and "diagram code failed" (in lowercase) are checked against the lowercased
message string. This ensures the assertion handles all message variations
consistently regardless of capitalization.

---

Nitpick comments:
In `@src/diagrams_mcp/tools/plantuml.py`:
- Line 7: The import statement on line 7 currently imports ToolError from
diagrams_mcp.fastmcp_compat, but the coding guideline requires importing
ToolError directly from fastmcp.exceptions to align with the pattern used in
other tool modules (mermaid.py, equivalence.py, discovery.py, render.py).
Refactor the import statement to separate the imports: keep Image imported from
diagrams_mcp.fastmcp_compat but import ToolError directly from
fastmcp.exceptions instead.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c9920bfa-22bc-4649-8963-297bd902ff24

📥 Commits

Reviewing files that changed from the base of the PR and between 2c54b4f and 78f3fd9.

📒 Files selected for processing (22)
  • pyproject.toml
  • src/diagrams_mcp/fastmcp_compat.py
  • src/diagrams_mcp/image_store.py
  • src/diagrams_mcp/prompts.py
  • src/diagrams_mcp/renderer.py
  • src/diagrams_mcp/resources.py
  • src/diagrams_mcp/sandbox.py
  • src/diagrams_mcp/server.py
  • src/diagrams_mcp/tools/discovery.py
  • src/diagrams_mcp/tools/equivalence.py
  • src/diagrams_mcp/tools/mermaid.py
  • src/diagrams_mcp/tools/plantuml.py
  • src/diagrams_mcp/tools/render.py
  • tests/test_discovery.py
  • tests/test_equivalence.py
  • tests/test_image_store.py
  • tests/test_mermaid.py
  • tests/test_plantuml.py
  • tests/test_render.py
  • tests/test_renderer.py
  • tests/test_sandbox.py
  • tests/test_server.py

Comment on lines +333 to +334
if fmt == "pdf":
return binary_file(data, f"{filename}.pdf", _FORMAT_MAP[fmt]["mime"])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

PDF inline path is unreachable due earlier forced download mode.

At Line 294, any non-PNG format sets download_link = True, so PDF requests return at Line 318 and never hit the new binary_file(...) branch. This breaks the new EmbeddedResource contract for PDF responses.

Suggested fix
-    if fmt != "png":
+    if fmt == "svg":
         download_link = True
-    elif len(data) > ANTHROPIC_INLINE_IMAGE_MAX_BYTES:
+    elif fmt == "png" and len(data) > ANTHROPIC_INLINE_IMAGE_MAX_BYTES:
         download_link = True
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/diagrams_mcp/image_store.py` around lines 333 - 334, The PDF inline path
using binary_file() at line 333-334 is unreachable because the condition at line
294 forces download_link = True for all non-PNG formats, causing execution to
return early at line 318. To fix this, modify the condition at line 294 that
sets download_link = True to exclude PDF format (or conditionally skip the early
return at line 318 for PDF), so that PDF requests can proceed to the
binary_file() call instead of taking the forced download path.

Comment on lines +57 to +83
mcp.add_tool(render_diagram, annotations={"readOnlyHint": True}, structured_output=False)
mcp.add_tool(list_providers, annotations={"readOnlyHint": True, "idempotentHint": True})
mcp.add_tool(list_services, annotations={"readOnlyHint": True, "idempotentHint": True})
mcp.add_tool(list_nodes, annotations={"readOnlyHint": True, "idempotentHint": True})
mcp.add_tool(search_nodes, annotations={"readOnlyHint": True, "idempotentHint": True})
mcp.add_tool(find_equivalent, annotations={"readOnlyHint": True, "idempotentHint": True})
mcp.add_tool(list_categories, annotations={"readOnlyHint": True, "idempotentHint": True})
mcp.add_tool(render_mermaid, annotations={"readOnlyHint": True}, structured_output=False)
mcp.add_tool(render_plantuml, annotations={"readOnlyHint": True}, structured_output=False)

mcp.resource("diagrams://reference/diagram", mime_type="text/markdown")(diagram_reference)
mcp.resource("diagrams://reference/edge", mime_type="text/markdown")(edge_reference)
mcp.resource("diagrams://reference/cluster", mime_type="text/markdown")(cluster_reference)
mcp.resource("diagrams://reference/mermaid", mime_type="text/markdown")(mermaid_reference)
mcp.resource("diagrams://reference/plantuml", mime_type="text/markdown")(plantuml_reference)

mcp.mount(render)
mcp.mount(discovery)
mcp.mount(references)
mcp.mount(mermaid)
mcp.mount(plantuml)
mcp.mount(equivalence)
mcp.mount(prompts_app)
mcp.prompt(description="Guide the user through building a cloud architecture diagram")(
architecture_diagram
)
mcp.prompt(description="Guide creation of a sequence or flow diagram")(sequence_flow)
mcp.prompt(description="Walk through multi-cloud service comparison")(compare_providers)
mcp.prompt(
description=(
"Minimal-friction path: describe what to visualize and the best engine is"
" picked automatically"
)
)(quick_sketch)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Reintroduce request throttling after the rewiring.

The previous middleware/mount-based rate limiting was removed, and this registration path does not add a replacement. That leaves expensive render tools (render_diagram, render_mermaid, render_plantuml) unthrottled and increases saturation/abuse risk. Please add server-side throttling (middleware or enforced upstream policy) before release.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/diagrams_mcp/server.py` around lines 57 - 83, The expensive rendering
tools render_diagram, render_mermaid, and render_plantuml are registered with
mcp.add_tool() without any rate limiting protection, leaving them vulnerable to
saturation and abuse. Add server-side throttling by implementing a
middleware-based rate limiting layer or configuring an upstream policy to
restrict request frequency for these resource-intensive operations. Ensure the
throttling mechanism is enforced before these tools execute to prevent abuse and
maintain system stability.

Comment thread tests/test_sandbox.py
msg = str(exc_info.value)
assert "Diagram code failed" in msg, f"Expected CPU-limit kill, got: {msg}"
assert "timed out" not in msg.lower(), f"Got wall-clock timeout instead of CPU limit: {msg}"
assert "timed out" in msg.lower() or "Diagram code failed" in msg, msg

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize both alternatives to lowercase in the assertion.

Line 260 mixes case-normalized and case-sensitive checks, which makes this test fail on harmless capitalization changes.

Proposed fix
-    msg = str(exc_info.value)
-    assert "timed out" in msg.lower() or "Diagram code failed" in msg, msg
+    msg = str(exc_info.value)
+    lowered = msg.lower()
+    assert "timed out" in lowered or "diagram code failed" in lowered, msg
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_sandbox.py` at line 260, The assertion on line 260 performs
inconsistent case handling where "timed out" is checked against msg.lower() but
"Diagram code failed" is checked against the original msg without case
normalization. To fix this, normalize the second alternative by changing the
check to also use msg.lower(), so both "timed out" and "diagram code failed" (in
lowercase) are checked against the lowercased message string. This ensures the
assertion handles all message variations consistently regardless of
capitalization.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant