Point imports of mcp.server.fastmcp at the migration guide - #3388

Merged
maxisbey merged 2 commits into
mainfrom
fastmcp-import-tombstone
Aug 25, 2026
Merged

Point imports of mcp.server.fastmcp at the migration guide#3388
maxisbey merged 2 commits into
mainfrom
fastmcp-import-tombstone

Conversation

@maxisbey

@maxisbeymaxisbey commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

v1 code running against mcp 2 fails with a bare ModuleNotFoundError: No module named 'mcp.server.fastmcp', which reads like a broken install. This adds a 16-line src/mcp/server/fastmcp.py whose only statement raises the same exception with a message that says what happened:

ModuleNotFoundError: No module named 'mcp.server.fastmcp'. This is mcp 2.x, where FastMCP was renamed to MCPServer (from mcp.server.mcpserver import MCPServer) and other APIs changed; see the migration guide at https://py.sdk.modelcontextprotocol.io/v2/migration/#fastmcp-renamed-to-mcpserver or pin 'mcp<2' to keep running v1 code.

Not #3189: nothing is aliased or re-exported and no warning is emitted. The old path still fails to import; it just says why.

Motivation and Context

Since 2.0 shipped the bare string has been quoted in roughly a thousand downstream GitHub issues and PRs (one here, #3309), and new v1-shaped code keeps being written. It raises ModuleNotFoundError with name= set, rather than ImportError, because dual-version shims in the wild catch that class specifically or check exc.name; those keep working unchanged. It's a module file rather than a package so one file covers every mcp.server.fastmcp.* path, and it's excluded from the API reference so it stays undocumented and deletable in any release.

How Has This Been Tested?

New tests/server/test_fastmcp.py (message snapshot, .name, nothing left in sys.modules, deep submodule path, v1-first fallback idiom); ./scripts/test, pyright, docs build; and by hand via python -c, mcp run, and a built wheel.

Breaking Changes

None. Exception type and .name are unchanged; only the message text differs, and importlib.util.find_spec("mcp.server.fastmcp") now returns a spec instead of None.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I am assigned to the linked issue (or it is labeled help wanted, or I'm a maintainer)
  • I have disclosed any AI assistance and can explain the change in my own words
  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Deliberately out of scope: the other removed v1 module paths (near-zero reports), from mcp.server import FastMCP and McpError (would need per-symbol __getattr__, cf. c27f95c).

AI Disclaimer

v1 code running against mcp 2 fails with a bare "No module named
'mcp.server.fastmcp'", which reads like a broken install and gives no
hint that the package was renamed in a new major version. Add a plain
module at the old path whose only statement raises ModuleNotFoundError
with a message that keeps the canonical prefix, names the replacement
import, links the migration guide, and mentions pinning mcp<2.
The exception type and its `name` attribute match what a genuinely
missing module produces, so existing `except ImportError`,
`except ModuleNotFoundError`, and `exc.name` fallbacks keep working and
nothing is re-exported or warned about. The module is a file rather
than a package so tools that walk packages do not execute it, and it is
excluded from the generated API reference since it carries no API.
The first-symptom row keeps the verbatim 2.0/2.1 text people search
for while staying true once the message carries a pointer, and the
FastMCP section notes that the old path raises ModuleNotFoundError and
that dual-version import fallbacks continue to work.
@maxisbey
maxisbey marked this pull request as ready for review August 25, 2026 15:37
@github-actions

Copy link
Copy Markdown
Contributor

📚 Documentation preview

Previewhttps://pr-3388.mcp-python-docs.pages.dev
Deploymenthttps://ddc25858.mcp-python-docs.pages.dev
Commit3330346
Triggered by@maxisbey
Updated2026-08-25 15:38:03 UTC

@cubic-dev-aicubic-dev-aiBot 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.

No issues found across 4 files

Re-trigger cubic

@maxisbey
maxisbey merged commit 0921d94 into mainAug 25, 2026
41 checks passed
@maxisbey
maxisbey deleted the fastmcp-import-tombstone branch August 25, 2026 15:39

@claudeclaudeBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Beyond the inline finding, I also checked that the new tombstone doesn't break v1-detection patterns: importlib.util.find_spec("mcp.server.fastmcp") now returns a spec instead of None, but any code that follows that check with an actual import still hits the pointer message, and the dominant try/except-ImportError dual-major guard is unaffected (ModuleNotFoundError subclasses ImportError, and the new tests cover it). Also confirmed the module raising at import leaves no sys.modules residue, and the gen_ref_pages.py exclusion just keeps the tombstone out of the API docs.

Extended reasoning...

The change adds a tombstone module at the removed v1 path mcp/server/fastmcp.py that raises ModuleNotFoundError (with the correct name=) pointing at the migration guide, plus docs and doc-generation exclusions and thorough tests. The one inline finding (the equally common v1 spelling from mcp.server import FastMCP still gets the bare error) is a coverage-scope question worth a maintainer's eye, so I am not approving over it. Separately, I examined whether making mcp.server.fastmcp discoverable again (find_spec returns a spec, pkgutil lists it) could misfire spec-based v1 detection and ruled it out: such detection is unusual for this path, and any subsequent real import still raises the pointer, while the try/except import guard the change explicitly targets keeps working since ModuleNotFoundError subclasses ImportError and the module is never left cached after raising.

"or pin 'mcp<2' to keep running v1 code."
)

raiseModuleNotFoundError(_MESSAGE, name=__name__)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟣 Pre-existing: the migration-pointer tombstone covers only the mcp.server.fastmcp module path, but v1 also re-exported the class from the package itself (v1's mcp/server/__init__.py had from .fastmcp import FastMCP), so the equally common v1 spelling from mcp.server import FastMCP still fails with the bare ImportError: cannot import name 'FastMCP' from 'mcp.server' and never sees the new pointer this PR adds. A module-level __getattr__ in src/mcp/server/__init__.py raising the same guidance for FastMCP (mirroring the _MESSAGE in src/mcp/server/fastmcp.py) would close the gap; docs/migration.md line 675 and the line-20 symptom row also only describe the ModuleNotFoundError path.

Extended reasoning...

A v1 user whose server does from mcp.server import FastMCP (a valid, exported v1 import path) upgrades to a 2.x release containing this change. Instead of the improved message pointing at MCPServer and the migration guide, they still get the uninformative ImportError: cannot import name 'FastMCP' from 'mcp.server' — exactly the confusing experience this PR was written to eliminate — because the tombstone only intercepts imports of the mcp.server.fastmcp module, not the FastMCP attribute of mcp.server.

Verification: pre-existing — src/mcp/server/init.py defines no FastMCP and no module-level __getattr__ (its imports are only CacheHint, ServerRequestContext, NotificationOptions, Server, MCPServer, InitializationOptions), so from mcp.server import FastMCP — a valid v1 spelling, since v1's mcp/server/__init__.py re-exported FastMCP via from .fastmcp import FastMCP and listed it in __all__ — raise

Sign up for freeto 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

@maxisbey
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Point imports of mcp.server.fastmcp at the migration guide - #3388

Merged
maxisbey merged 2 commits into
mainfrom
fastmcp-import-tombstone
Aug 25, 2026
Merged

Point imports of mcp.server.fastmcp at the migration guide#3388
maxisbey merged 2 commits into
mainfrom
fastmcp-import-tombstone

Conversation

@maxisbey

@maxisbeymaxisbey commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

v1 code running against mcp 2 fails with a bare ModuleNotFoundError: No module named 'mcp.server.fastmcp', which reads like a broken install. This adds a 16-line src/mcp/server/fastmcp.py whose only statement raises the same exception with a message that says what happened:

ModuleNotFoundError: No module named 'mcp.server.fastmcp'. This is mcp 2.x, where FastMCP was renamed to MCPServer (from mcp.server.mcpserver import MCPServer) and other APIs changed; see the migration guide at https://py.sdk.modelcontextprotocol.io/v2/migration/#fastmcp-renamed-to-mcpserver or pin 'mcp<2' to keep running v1 code.

Not #3189: nothing is aliased or re-exported and no warning is emitted. The old path still fails to import; it just says why.

Motivation and Context

Since 2.0 shipped the bare string has been quoted in roughly a thousand downstream GitHub issues and PRs (one here, #3309), and new v1-shaped code keeps being written. It raises ModuleNotFoundError with name= set, rather than ImportError, because dual-version shims in the wild catch that class specifically or check exc.name; those keep working unchanged. It's a module file rather than a package so one file covers every mcp.server.fastmcp.* path, and it's excluded from the API reference so it stays undocumented and deletable in any release.

How Has This Been Tested?

New tests/server/test_fastmcp.py (message snapshot, .name, nothing left in sys.modules, deep submodule path, v1-first fallback idiom); ./scripts/test, pyright, docs build; and by hand via python -c, mcp run, and a built wheel.

Breaking Changes

None. Exception type and .name are unchanged; only the message text differs, and importlib.util.find_spec("mcp.server.fastmcp") now returns a spec instead of None.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I am assigned to the linked issue (or it is labeled help wanted, or I'm a maintainer)
  • I have disclosed any AI assistance and can explain the change in my own words
  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Deliberately out of scope: the other removed v1 module paths (near-zero reports), from mcp.server import FastMCP and McpError (would need per-symbol __getattr__, cf. c27f95c).

AI Disclaimer

v1 code running against mcp 2 fails with a bare "No module named
'mcp.server.fastmcp'", which reads like a broken install and gives no
hint that the package was renamed in a new major version. Add a plain
module at the old path whose only statement raises ModuleNotFoundError
with a message that keeps the canonical prefix, names the replacement
import, links the migration guide, and mentions pinning mcp<2.
The exception type and its `name` attribute match what a genuinely
missing module produces, so existing `except ImportError`,
`except ModuleNotFoundError`, and `exc.name` fallbacks keep working and
nothing is re-exported or warned about. The module is a file rather
than a package so tools that walk packages do not execute it, and it is
excluded from the generated API reference since it carries no API.
The first-symptom row keeps the verbatim 2.0/2.1 text people search
for while staying true once the message carries a pointer, and the
FastMCP section notes that the old path raises ModuleNotFoundError and
that dual-version import fallbacks continue to work.
@maxisbey
maxisbey marked this pull request as ready for review August 25, 2026 15:37
@github-actions

Copy link
Copy Markdown
Contributor

📚 Documentation preview

Previewhttps://pr-3388.mcp-python-docs.pages.dev
Deploymenthttps://ddc25858.mcp-python-docs.pages.dev
Commit3330346
Triggered by@maxisbey
Updated2026-08-25 15:38:03 UTC

@cubic-dev-aicubic-dev-aiBot 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.

No issues found across 4 files

Re-trigger cubic

@maxisbey
maxisbey merged commit 0921d94 into mainAug 25, 2026
41 checks passed
@maxisbey
maxisbey deleted the fastmcp-import-tombstone branch August 25, 2026 15:39

@claudeclaudeBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Beyond the inline finding, I also checked that the new tombstone doesn't break v1-detection patterns: importlib.util.find_spec("mcp.server.fastmcp") now returns a spec instead of None, but any code that follows that check with an actual import still hits the pointer message, and the dominant try/except-ImportError dual-major guard is unaffected (ModuleNotFoundError subclasses ImportError, and the new tests cover it). Also confirmed the module raising at import leaves no sys.modules residue, and the gen_ref_pages.py exclusion just keeps the tombstone out of the API docs.

Extended reasoning...

The change adds a tombstone module at the removed v1 path mcp/server/fastmcp.py that raises ModuleNotFoundError (with the correct name=) pointing at the migration guide, plus docs and doc-generation exclusions and thorough tests. The one inline finding (the equally common v1 spelling from mcp.server import FastMCP still gets the bare error) is a coverage-scope question worth a maintainer's eye, so I am not approving over it. Separately, I examined whether making mcp.server.fastmcp discoverable again (find_spec returns a spec, pkgutil lists it) could misfire spec-based v1 detection and ruled it out: such detection is unusual for this path, and any subsequent real import still raises the pointer, while the try/except import guard the change explicitly targets keeps working since ModuleNotFoundError subclasses ImportError and the module is never left cached after raising.

"or pin 'mcp<2' to keep running v1 code."
)

raiseModuleNotFoundError(_MESSAGE, name=__name__)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟣 Pre-existing: the migration-pointer tombstone covers only the mcp.server.fastmcp module path, but v1 also re-exported the class from the package itself (v1's mcp/server/__init__.py had from .fastmcp import FastMCP), so the equally common v1 spelling from mcp.server import FastMCP still fails with the bare ImportError: cannot import name 'FastMCP' from 'mcp.server' and never sees the new pointer this PR adds. A module-level __getattr__ in src/mcp/server/__init__.py raising the same guidance for FastMCP (mirroring the _MESSAGE in src/mcp/server/fastmcp.py) would close the gap; docs/migration.md line 675 and the line-20 symptom row also only describe the ModuleNotFoundError path.

Extended reasoning...

A v1 user whose server does from mcp.server import FastMCP (a valid, exported v1 import path) upgrades to a 2.x release containing this change. Instead of the improved message pointing at MCPServer and the migration guide, they still get the uninformative ImportError: cannot import name 'FastMCP' from 'mcp.server' — exactly the confusing experience this PR was written to eliminate — because the tombstone only intercepts imports of the mcp.server.fastmcp module, not the FastMCP attribute of mcp.server.

Verification: pre-existing — src/mcp/server/init.py defines no FastMCP and no module-level __getattr__ (its imports are only CacheHint, ServerRequestContext, NotificationOptions, Server, MCPServer, InitializationOptions), so from mcp.server import FastMCP — a valid v1 spelling, since v1's mcp/server/__init__.py re-exported FastMCP via from .fastmcp import FastMCP and listed it in __all__ — raise

Sign up for freeto 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

@maxisbey
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Point imports of mcp.server.fastmcp at the migration guide - #3388

Merged
maxisbey merged 2 commits into
mainfrom
fastmcp-import-tombstone
Aug 25, 2026
Merged

Point imports of mcp.server.fastmcp at the migration guide#3388
maxisbey merged 2 commits into
mainfrom
fastmcp-import-tombstone

Conversation

@maxisbey

@maxisbeymaxisbey commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

v1 code running against mcp 2 fails with a bare ModuleNotFoundError: No module named 'mcp.server.fastmcp', which reads like a broken install. This adds a 16-line src/mcp/server/fastmcp.py whose only statement raises the same exception with a message that says what happened:

ModuleNotFoundError: No module named 'mcp.server.fastmcp'. This is mcp 2.x, where FastMCP was renamed to MCPServer (from mcp.server.mcpserver import MCPServer) and other APIs changed; see the migration guide at https://py.sdk.modelcontextprotocol.io/v2/migration/#fastmcp-renamed-to-mcpserver or pin 'mcp<2' to keep running v1 code.

Not #3189: nothing is aliased or re-exported and no warning is emitted. The old path still fails to import; it just says why.

Motivation and Context

Since 2.0 shipped the bare string has been quoted in roughly a thousand downstream GitHub issues and PRs (one here, #3309), and new v1-shaped code keeps being written. It raises ModuleNotFoundError with name= set, rather than ImportError, because dual-version shims in the wild catch that class specifically or check exc.name; those keep working unchanged. It's a module file rather than a package so one file covers every mcp.server.fastmcp.* path, and it's excluded from the API reference so it stays undocumented and deletable in any release.

How Has This Been Tested?

New tests/server/test_fastmcp.py (message snapshot, .name, nothing left in sys.modules, deep submodule path, v1-first fallback idiom); ./scripts/test, pyright, docs build; and by hand via python -c, mcp run, and a built wheel.

Breaking Changes

None. Exception type and .name are unchanged; only the message text differs, and importlib.util.find_spec("mcp.server.fastmcp") now returns a spec instead of None.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I am assigned to the linked issue (or it is labeled help wanted, or I'm a maintainer)
  • I have disclosed any AI assistance and can explain the change in my own words
  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Deliberately out of scope: the other removed v1 module paths (near-zero reports), from mcp.server import FastMCP and McpError (would need per-symbol __getattr__, cf. c27f95c).

AI Disclaimer

v1 code running against mcp 2 fails with a bare "No module named
'mcp.server.fastmcp'", which reads like a broken install and gives no
hint that the package was renamed in a new major version. Add a plain
module at the old path whose only statement raises ModuleNotFoundError
with a message that keeps the canonical prefix, names the replacement
import, links the migration guide, and mentions pinning mcp<2.
The exception type and its `name` attribute match what a genuinely
missing module produces, so existing `except ImportError`,
`except ModuleNotFoundError`, and `exc.name` fallbacks keep working and
nothing is re-exported or warned about. The module is a file rather
than a package so tools that walk packages do not execute it, and it is
excluded from the generated API reference since it carries no API.
The first-symptom row keeps the verbatim 2.0/2.1 text people search
for while staying true once the message carries a pointer, and the
FastMCP section notes that the old path raises ModuleNotFoundError and
that dual-version import fallbacks continue to work.
@maxisbey
maxisbey marked this pull request as ready for review August 25, 2026 15:37
@github-actions

Copy link
Copy Markdown
Contributor

📚 Documentation preview

Previewhttps://pr-3388.mcp-python-docs.pages.dev
Deploymenthttps://ddc25858.mcp-python-docs.pages.dev
Commit3330346
Triggered by@maxisbey
Updated2026-08-25 15:38:03 UTC

@cubic-dev-aicubic-dev-aiBot 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.

No issues found across 4 files

Re-trigger cubic

@maxisbey
maxisbey merged commit 0921d94 into mainAug 25, 2026
41 checks passed
@maxisbey
maxisbey deleted the fastmcp-import-tombstone branch August 25, 2026 15:39

@claudeclaudeBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Beyond the inline finding, I also checked that the new tombstone doesn't break v1-detection patterns: importlib.util.find_spec("mcp.server.fastmcp") now returns a spec instead of None, but any code that follows that check with an actual import still hits the pointer message, and the dominant try/except-ImportError dual-major guard is unaffected (ModuleNotFoundError subclasses ImportError, and the new tests cover it). Also confirmed the module raising at import leaves no sys.modules residue, and the gen_ref_pages.py exclusion just keeps the tombstone out of the API docs.

Extended reasoning...

The change adds a tombstone module at the removed v1 path mcp/server/fastmcp.py that raises ModuleNotFoundError (with the correct name=) pointing at the migration guide, plus docs and doc-generation exclusions and thorough tests. The one inline finding (the equally common v1 spelling from mcp.server import FastMCP still gets the bare error) is a coverage-scope question worth a maintainer's eye, so I am not approving over it. Separately, I examined whether making mcp.server.fastmcp discoverable again (find_spec returns a spec, pkgutil lists it) could misfire spec-based v1 detection and ruled it out: such detection is unusual for this path, and any subsequent real import still raises the pointer, while the try/except import guard the change explicitly targets keeps working since ModuleNotFoundError subclasses ImportError and the module is never left cached after raising.

"or pin 'mcp<2' to keep running v1 code."
)

raiseModuleNotFoundError(_MESSAGE, name=__name__)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟣 Pre-existing: the migration-pointer tombstone covers only the mcp.server.fastmcp module path, but v1 also re-exported the class from the package itself (v1's mcp/server/__init__.py had from .fastmcp import FastMCP), so the equally common v1 spelling from mcp.server import FastMCP still fails with the bare ImportError: cannot import name 'FastMCP' from 'mcp.server' and never sees the new pointer this PR adds. A module-level __getattr__ in src/mcp/server/__init__.py raising the same guidance for FastMCP (mirroring the _MESSAGE in src/mcp/server/fastmcp.py) would close the gap; docs/migration.md line 675 and the line-20 symptom row also only describe the ModuleNotFoundError path.

Extended reasoning...

A v1 user whose server does from mcp.server import FastMCP (a valid, exported v1 import path) upgrades to a 2.x release containing this change. Instead of the improved message pointing at MCPServer and the migration guide, they still get the uninformative ImportError: cannot import name 'FastMCP' from 'mcp.server' — exactly the confusing experience this PR was written to eliminate — because the tombstone only intercepts imports of the mcp.server.fastmcp module, not the FastMCP attribute of mcp.server.

Verification: pre-existing — src/mcp/server/init.py defines no FastMCP and no module-level __getattr__ (its imports are only CacheHint, ServerRequestContext, NotificationOptions, Server, MCPServer, InitializationOptions), so from mcp.server import FastMCP — a valid v1 spelling, since v1's mcp/server/__init__.py re-exported FastMCP via from .fastmcp import FastMCP and listed it in __all__ — raise

Sign up for freeto 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

@maxisbey
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Point imports of mcp.server.fastmcp at the migration guide - #3388

Merged
maxisbey merged 2 commits into
mainfrom
fastmcp-import-tombstone
Aug 25, 2026
Merged

Point imports of mcp.server.fastmcp at the migration guide#3388
maxisbey merged 2 commits into
mainfrom
fastmcp-import-tombstone

Conversation

@maxisbey

@maxisbeymaxisbey commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

v1 code running against mcp 2 fails with a bare ModuleNotFoundError: No module named 'mcp.server.fastmcp', which reads like a broken install. This adds a 16-line src/mcp/server/fastmcp.py whose only statement raises the same exception with a message that says what happened:

ModuleNotFoundError: No module named 'mcp.server.fastmcp'. This is mcp 2.x, where FastMCP was renamed to MCPServer (from mcp.server.mcpserver import MCPServer) and other APIs changed; see the migration guide at https://py.sdk.modelcontextprotocol.io/v2/migration/#fastmcp-renamed-to-mcpserver or pin 'mcp<2' to keep running v1 code.

Not #3189: nothing is aliased or re-exported and no warning is emitted. The old path still fails to import; it just says why.

Motivation and Context

Since 2.0 shipped the bare string has been quoted in roughly a thousand downstream GitHub issues and PRs (one here, #3309), and new v1-shaped code keeps being written. It raises ModuleNotFoundError with name= set, rather than ImportError, because dual-version shims in the wild catch that class specifically or check exc.name; those keep working unchanged. It's a module file rather than a package so one file covers every mcp.server.fastmcp.* path, and it's excluded from the API reference so it stays undocumented and deletable in any release.

How Has This Been Tested?

New tests/server/test_fastmcp.py (message snapshot, .name, nothing left in sys.modules, deep submodule path, v1-first fallback idiom); ./scripts/test, pyright, docs build; and by hand via python -c, mcp run, and a built wheel.

Breaking Changes

None. Exception type and .name are unchanged; only the message text differs, and importlib.util.find_spec("mcp.server.fastmcp") now returns a spec instead of None.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I am assigned to the linked issue (or it is labeled help wanted, or I'm a maintainer)
  • I have disclosed any AI assistance and can explain the change in my own words
  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Deliberately out of scope: the other removed v1 module paths (near-zero reports), from mcp.server import FastMCP and McpError (would need per-symbol __getattr__, cf. c27f95c).

AI Disclaimer

v1 code running against mcp 2 fails with a bare "No module named
'mcp.server.fastmcp'", which reads like a broken install and gives no
hint that the package was renamed in a new major version. Add a plain
module at the old path whose only statement raises ModuleNotFoundError
with a message that keeps the canonical prefix, names the replacement
import, links the migration guide, and mentions pinning mcp<2.
The exception type and its `name` attribute match what a genuinely
missing module produces, so existing `except ImportError`,
`except ModuleNotFoundError`, and `exc.name` fallbacks keep working and
nothing is re-exported or warned about. The module is a file rather
than a package so tools that walk packages do not execute it, and it is
excluded from the generated API reference since it carries no API.
The first-symptom row keeps the verbatim 2.0/2.1 text people search
for while staying true once the message carries a pointer, and the
FastMCP section notes that the old path raises ModuleNotFoundError and
that dual-version import fallbacks continue to work.
@maxisbey
maxisbey marked this pull request as ready for review August 25, 2026 15:37
@github-actions

Copy link
Copy Markdown
Contributor

📚 Documentation preview

Previewhttps://pr-3388.mcp-python-docs.pages.dev
Deploymenthttps://ddc25858.mcp-python-docs.pages.dev
Commit3330346
Triggered by@maxisbey
Updated2026-08-25 15:38:03 UTC

@cubic-dev-aicubic-dev-aiBot 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.

No issues found across 4 files

Re-trigger cubic

@maxisbey
maxisbey merged commit 0921d94 into mainAug 25, 2026
41 checks passed
@maxisbey
maxisbey deleted the fastmcp-import-tombstone branch August 25, 2026 15:39

@claudeclaudeBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Beyond the inline finding, I also checked that the new tombstone doesn't break v1-detection patterns: importlib.util.find_spec("mcp.server.fastmcp") now returns a spec instead of None, but any code that follows that check with an actual import still hits the pointer message, and the dominant try/except-ImportError dual-major guard is unaffected (ModuleNotFoundError subclasses ImportError, and the new tests cover it). Also confirmed the module raising at import leaves no sys.modules residue, and the gen_ref_pages.py exclusion just keeps the tombstone out of the API docs.

Extended reasoning...

The change adds a tombstone module at the removed v1 path mcp/server/fastmcp.py that raises ModuleNotFoundError (with the correct name=) pointing at the migration guide, plus docs and doc-generation exclusions and thorough tests. The one inline finding (the equally common v1 spelling from mcp.server import FastMCP still gets the bare error) is a coverage-scope question worth a maintainer's eye, so I am not approving over it. Separately, I examined whether making mcp.server.fastmcp discoverable again (find_spec returns a spec, pkgutil lists it) could misfire spec-based v1 detection and ruled it out: such detection is unusual for this path, and any subsequent real import still raises the pointer, while the try/except import guard the change explicitly targets keeps working since ModuleNotFoundError subclasses ImportError and the module is never left cached after raising.

"or pin 'mcp<2' to keep running v1 code."
)

raiseModuleNotFoundError(_MESSAGE, name=__name__)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟣 Pre-existing: the migration-pointer tombstone covers only the mcp.server.fastmcp module path, but v1 also re-exported the class from the package itself (v1's mcp/server/__init__.py had from .fastmcp import FastMCP), so the equally common v1 spelling from mcp.server import FastMCP still fails with the bare ImportError: cannot import name 'FastMCP' from 'mcp.server' and never sees the new pointer this PR adds. A module-level __getattr__ in src/mcp/server/__init__.py raising the same guidance for FastMCP (mirroring the _MESSAGE in src/mcp/server/fastmcp.py) would close the gap; docs/migration.md line 675 and the line-20 symptom row also only describe the ModuleNotFoundError path.

Extended reasoning...

A v1 user whose server does from mcp.server import FastMCP (a valid, exported v1 import path) upgrades to a 2.x release containing this change. Instead of the improved message pointing at MCPServer and the migration guide, they still get the uninformative ImportError: cannot import name 'FastMCP' from 'mcp.server' — exactly the confusing experience this PR was written to eliminate — because the tombstone only intercepts imports of the mcp.server.fastmcp module, not the FastMCP attribute of mcp.server.

Verification: pre-existing — src/mcp/server/init.py defines no FastMCP and no module-level __getattr__ (its imports are only CacheHint, ServerRequestContext, NotificationOptions, Server, MCPServer, InitializationOptions), so from mcp.server import FastMCP — a valid v1 spelling, since v1's mcp/server/__init__.py re-exported FastMCP via from .fastmcp import FastMCP and listed it in __all__ — raise

Sign up for freeto 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

@maxisbey
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Point imports of mcp.server.fastmcp at the migration guide - #3388

Merged
maxisbey merged 2 commits into
mainfrom
fastmcp-import-tombstone
Aug 25, 2026
Merged

Point imports of mcp.server.fastmcp at the migration guide#3388
maxisbey merged 2 commits into
mainfrom
fastmcp-import-tombstone

Conversation

@maxisbey

@maxisbeymaxisbey commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

v1 code running against mcp 2 fails with a bare ModuleNotFoundError: No module named 'mcp.server.fastmcp', which reads like a broken install. This adds a 16-line src/mcp/server/fastmcp.py whose only statement raises the same exception with a message that says what happened:

ModuleNotFoundError: No module named 'mcp.server.fastmcp'. This is mcp 2.x, where FastMCP was renamed to MCPServer (from mcp.server.mcpserver import MCPServer) and other APIs changed; see the migration guide at https://py.sdk.modelcontextprotocol.io/v2/migration/#fastmcp-renamed-to-mcpserver or pin 'mcp<2' to keep running v1 code.

Not #3189: nothing is aliased or re-exported and no warning is emitted. The old path still fails to import; it just says why.

Motivation and Context

Since 2.0 shipped the bare string has been quoted in roughly a thousand downstream GitHub issues and PRs (one here, #3309), and new v1-shaped code keeps being written. It raises ModuleNotFoundError with name= set, rather than ImportError, because dual-version shims in the wild catch that class specifically or check exc.name; those keep working unchanged. It's a module file rather than a package so one file covers every mcp.server.fastmcp.* path, and it's excluded from the API reference so it stays undocumented and deletable in any release.

How Has This Been Tested?

New tests/server/test_fastmcp.py (message snapshot, .name, nothing left in sys.modules, deep submodule path, v1-first fallback idiom); ./scripts/test, pyright, docs build; and by hand via python -c, mcp run, and a built wheel.

Breaking Changes

None. Exception type and .name are unchanged; only the message text differs, and importlib.util.find_spec("mcp.server.fastmcp") now returns a spec instead of None.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I am assigned to the linked issue (or it is labeled help wanted, or I'm a maintainer)
  • I have disclosed any AI assistance and can explain the change in my own words
  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Deliberately out of scope: the other removed v1 module paths (near-zero reports), from mcp.server import FastMCP and McpError (would need per-symbol __getattr__, cf. c27f95c).

AI Disclaimer

v1 code running against mcp 2 fails with a bare "No module named
'mcp.server.fastmcp'", which reads like a broken install and gives no
hint that the package was renamed in a new major version. Add a plain
module at the old path whose only statement raises ModuleNotFoundError
with a message that keeps the canonical prefix, names the replacement
import, links the migration guide, and mentions pinning mcp<2.
The exception type and its `name` attribute match what a genuinely
missing module produces, so existing `except ImportError`,
`except ModuleNotFoundError`, and `exc.name` fallbacks keep working and
nothing is re-exported or warned about. The module is a file rather
than a package so tools that walk packages do not execute it, and it is
excluded from the generated API reference since it carries no API.
The first-symptom row keeps the verbatim 2.0/2.1 text people search
for while staying true once the message carries a pointer, and the
FastMCP section notes that the old path raises ModuleNotFoundError and
that dual-version import fallbacks continue to work.
@maxisbey
maxisbey marked this pull request as ready for review August 25, 2026 15:37
@github-actions

Copy link
Copy Markdown
Contributor

📚 Documentation preview

Previewhttps://pr-3388.mcp-python-docs.pages.dev
Deploymenthttps://ddc25858.mcp-python-docs.pages.dev
Commit3330346
Triggered by@maxisbey
Updated2026-08-25 15:38:03 UTC

@cubic-dev-aicubic-dev-aiBot 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.

No issues found across 4 files

Re-trigger cubic

@maxisbey
maxisbey merged commit 0921d94 into mainAug 25, 2026
41 checks passed
@maxisbey
maxisbey deleted the fastmcp-import-tombstone branch August 25, 2026 15:39

@claudeclaudeBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Beyond the inline finding, I also checked that the new tombstone doesn't break v1-detection patterns: importlib.util.find_spec("mcp.server.fastmcp") now returns a spec instead of None, but any code that follows that check with an actual import still hits the pointer message, and the dominant try/except-ImportError dual-major guard is unaffected (ModuleNotFoundError subclasses ImportError, and the new tests cover it). Also confirmed the module raising at import leaves no sys.modules residue, and the gen_ref_pages.py exclusion just keeps the tombstone out of the API docs.

Extended reasoning...

The change adds a tombstone module at the removed v1 path mcp/server/fastmcp.py that raises ModuleNotFoundError (with the correct name=) pointing at the migration guide, plus docs and doc-generation exclusions and thorough tests. The one inline finding (the equally common v1 spelling from mcp.server import FastMCP still gets the bare error) is a coverage-scope question worth a maintainer's eye, so I am not approving over it. Separately, I examined whether making mcp.server.fastmcp discoverable again (find_spec returns a spec, pkgutil lists it) could misfire spec-based v1 detection and ruled it out: such detection is unusual for this path, and any subsequent real import still raises the pointer, while the try/except import guard the change explicitly targets keeps working since ModuleNotFoundError subclasses ImportError and the module is never left cached after raising.

"or pin 'mcp<2' to keep running v1 code."
)

raiseModuleNotFoundError(_MESSAGE, name=__name__)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟣 Pre-existing: the migration-pointer tombstone covers only the mcp.server.fastmcp module path, but v1 also re-exported the class from the package itself (v1's mcp/server/__init__.py had from .fastmcp import FastMCP), so the equally common v1 spelling from mcp.server import FastMCP still fails with the bare ImportError: cannot import name 'FastMCP' from 'mcp.server' and never sees the new pointer this PR adds. A module-level __getattr__ in src/mcp/server/__init__.py raising the same guidance for FastMCP (mirroring the _MESSAGE in src/mcp/server/fastmcp.py) would close the gap; docs/migration.md line 675 and the line-20 symptom row also only describe the ModuleNotFoundError path.

Extended reasoning...

A v1 user whose server does from mcp.server import FastMCP (a valid, exported v1 import path) upgrades to a 2.x release containing this change. Instead of the improved message pointing at MCPServer and the migration guide, they still get the uninformative ImportError: cannot import name 'FastMCP' from 'mcp.server' — exactly the confusing experience this PR was written to eliminate — because the tombstone only intercepts imports of the mcp.server.fastmcp module, not the FastMCP attribute of mcp.server.

Verification: pre-existing — src/mcp/server/init.py defines no FastMCP and no module-level __getattr__ (its imports are only CacheHint, ServerRequestContext, NotificationOptions, Server, MCPServer, InitializationOptions), so from mcp.server import FastMCP — a valid v1 spelling, since v1's mcp/server/__init__.py re-exported FastMCP via from .fastmcp import FastMCP and listed it in __all__ — raise

Sign up for freeto 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

@maxisbey
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Point imports of mcp.server.fastmcp at the migration guide - #3388

Merged
maxisbey merged 2 commits into
mainfrom
fastmcp-import-tombstone
Aug 25, 2026
Merged

Point imports of mcp.server.fastmcp at the migration guide#3388
maxisbey merged 2 commits into
mainfrom
fastmcp-import-tombstone

Conversation

@maxisbey

@maxisbeymaxisbey commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

v1 code running against mcp 2 fails with a bare ModuleNotFoundError: No module named 'mcp.server.fastmcp', which reads like a broken install. This adds a 16-line src/mcp/server/fastmcp.py whose only statement raises the same exception with a message that says what happened:

ModuleNotFoundError: No module named 'mcp.server.fastmcp'. This is mcp 2.x, where FastMCP was renamed to MCPServer (from mcp.server.mcpserver import MCPServer) and other APIs changed; see the migration guide at https://py.sdk.modelcontextprotocol.io/v2/migration/#fastmcp-renamed-to-mcpserver or pin 'mcp<2' to keep running v1 code.

Not #3189: nothing is aliased or re-exported and no warning is emitted. The old path still fails to import; it just says why.

Motivation and Context

Since 2.0 shipped the bare string has been quoted in roughly a thousand downstream GitHub issues and PRs (one here, #3309), and new v1-shaped code keeps being written. It raises ModuleNotFoundError with name= set, rather than ImportError, because dual-version shims in the wild catch that class specifically or check exc.name; those keep working unchanged. It's a module file rather than a package so one file covers every mcp.server.fastmcp.* path, and it's excluded from the API reference so it stays undocumented and deletable in any release.

How Has This Been Tested?

New tests/server/test_fastmcp.py (message snapshot, .name, nothing left in sys.modules, deep submodule path, v1-first fallback idiom); ./scripts/test, pyright, docs build; and by hand via python -c, mcp run, and a built wheel.

Breaking Changes

None. Exception type and .name are unchanged; only the message text differs, and importlib.util.find_spec("mcp.server.fastmcp") now returns a spec instead of None.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I am assigned to the linked issue (or it is labeled help wanted, or I'm a maintainer)
  • I have disclosed any AI assistance and can explain the change in my own words
  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Deliberately out of scope: the other removed v1 module paths (near-zero reports), from mcp.server import FastMCP and McpError (would need per-symbol __getattr__, cf. c27f95c).

AI Disclaimer

v1 code running against mcp 2 fails with a bare "No module named
'mcp.server.fastmcp'", which reads like a broken install and gives no
hint that the package was renamed in a new major version. Add a plain
module at the old path whose only statement raises ModuleNotFoundError
with a message that keeps the canonical prefix, names the replacement
import, links the migration guide, and mentions pinning mcp<2.
The exception type and its `name` attribute match what a genuinely
missing module produces, so existing `except ImportError`,
`except ModuleNotFoundError`, and `exc.name` fallbacks keep working and
nothing is re-exported or warned about. The module is a file rather
than a package so tools that walk packages do not execute it, and it is
excluded from the generated API reference since it carries no API.
The first-symptom row keeps the verbatim 2.0/2.1 text people search
for while staying true once the message carries a pointer, and the
FastMCP section notes that the old path raises ModuleNotFoundError and
that dual-version import fallbacks continue to work.
@maxisbey
maxisbey marked this pull request as ready for review August 25, 2026 15:37
@github-actions

Copy link
Copy Markdown
Contributor

📚 Documentation preview

Previewhttps://pr-3388.mcp-python-docs.pages.dev
Deploymenthttps://ddc25858.mcp-python-docs.pages.dev
Commit3330346
Triggered by@maxisbey
Updated2026-08-25 15:38:03 UTC

@cubic-dev-aicubic-dev-aiBot 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.

No issues found across 4 files

Re-trigger cubic

@maxisbey
maxisbey merged commit 0921d94 into mainAug 25, 2026
41 checks passed
@maxisbey
maxisbey deleted the fastmcp-import-tombstone branch August 25, 2026 15:39

@claudeclaudeBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Beyond the inline finding, I also checked that the new tombstone doesn't break v1-detection patterns: importlib.util.find_spec("mcp.server.fastmcp") now returns a spec instead of None, but any code that follows that check with an actual import still hits the pointer message, and the dominant try/except-ImportError dual-major guard is unaffected (ModuleNotFoundError subclasses ImportError, and the new tests cover it). Also confirmed the module raising at import leaves no sys.modules residue, and the gen_ref_pages.py exclusion just keeps the tombstone out of the API docs.

Extended reasoning...

The change adds a tombstone module at the removed v1 path mcp/server/fastmcp.py that raises ModuleNotFoundError (with the correct name=) pointing at the migration guide, plus docs and doc-generation exclusions and thorough tests. The one inline finding (the equally common v1 spelling from mcp.server import FastMCP still gets the bare error) is a coverage-scope question worth a maintainer's eye, so I am not approving over it. Separately, I examined whether making mcp.server.fastmcp discoverable again (find_spec returns a spec, pkgutil lists it) could misfire spec-based v1 detection and ruled it out: such detection is unusual for this path, and any subsequent real import still raises the pointer, while the try/except import guard the change explicitly targets keeps working since ModuleNotFoundError subclasses ImportError and the module is never left cached after raising.

"or pin 'mcp<2' to keep running v1 code."
)

raiseModuleNotFoundError(_MESSAGE, name=__name__)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟣 Pre-existing: the migration-pointer tombstone covers only the mcp.server.fastmcp module path, but v1 also re-exported the class from the package itself (v1's mcp/server/__init__.py had from .fastmcp import FastMCP), so the equally common v1 spelling from mcp.server import FastMCP still fails with the bare ImportError: cannot import name 'FastMCP' from 'mcp.server' and never sees the new pointer this PR adds. A module-level __getattr__ in src/mcp/server/__init__.py raising the same guidance for FastMCP (mirroring the _MESSAGE in src/mcp/server/fastmcp.py) would close the gap; docs/migration.md line 675 and the line-20 symptom row also only describe the ModuleNotFoundError path.

Extended reasoning...

A v1 user whose server does from mcp.server import FastMCP (a valid, exported v1 import path) upgrades to a 2.x release containing this change. Instead of the improved message pointing at MCPServer and the migration guide, they still get the uninformative ImportError: cannot import name 'FastMCP' from 'mcp.server' — exactly the confusing experience this PR was written to eliminate — because the tombstone only intercepts imports of the mcp.server.fastmcp module, not the FastMCP attribute of mcp.server.

Verification: pre-existing — src/mcp/server/init.py defines no FastMCP and no module-level __getattr__ (its imports are only CacheHint, ServerRequestContext, NotificationOptions, Server, MCPServer, InitializationOptions), so from mcp.server import FastMCP — a valid v1 spelling, since v1's mcp/server/__init__.py re-exported FastMCP via from .fastmcp import FastMCP and listed it in __all__ — raise

Sign up for freeto 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

@maxisbey
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Point imports of mcp.server.fastmcp at the migration guide - #3388

Merged
maxisbey merged 2 commits into
mainfrom
fastmcp-import-tombstone
Aug 25, 2026
Merged

Point imports of mcp.server.fastmcp at the migration guide#3388
maxisbey merged 2 commits into
mainfrom
fastmcp-import-tombstone

Conversation

@maxisbey

@maxisbeymaxisbey commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

v1 code running against mcp 2 fails with a bare ModuleNotFoundError: No module named 'mcp.server.fastmcp', which reads like a broken install. This adds a 16-line src/mcp/server/fastmcp.py whose only statement raises the same exception with a message that says what happened:

ModuleNotFoundError: No module named 'mcp.server.fastmcp'. This is mcp 2.x, where FastMCP was renamed to MCPServer (from mcp.server.mcpserver import MCPServer) and other APIs changed; see the migration guide at https://py.sdk.modelcontextprotocol.io/v2/migration/#fastmcp-renamed-to-mcpserver or pin 'mcp<2' to keep running v1 code.

Not #3189: nothing is aliased or re-exported and no warning is emitted. The old path still fails to import; it just says why.

Motivation and Context

Since 2.0 shipped the bare string has been quoted in roughly a thousand downstream GitHub issues and PRs (one here, #3309), and new v1-shaped code keeps being written. It raises ModuleNotFoundError with name= set, rather than ImportError, because dual-version shims in the wild catch that class specifically or check exc.name; those keep working unchanged. It's a module file rather than a package so one file covers every mcp.server.fastmcp.* path, and it's excluded from the API reference so it stays undocumented and deletable in any release.

How Has This Been Tested?

New tests/server/test_fastmcp.py (message snapshot, .name, nothing left in sys.modules, deep submodule path, v1-first fallback idiom); ./scripts/test, pyright, docs build; and by hand via python -c, mcp run, and a built wheel.

Breaking Changes

None. Exception type and .name are unchanged; only the message text differs, and importlib.util.find_spec("mcp.server.fastmcp") now returns a spec instead of None.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I am assigned to the linked issue (or it is labeled help wanted, or I'm a maintainer)
  • I have disclosed any AI assistance and can explain the change in my own words
  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Deliberately out of scope: the other removed v1 module paths (near-zero reports), from mcp.server import FastMCP and McpError (would need per-symbol __getattr__, cf. c27f95c).

AI Disclaimer

v1 code running against mcp 2 fails with a bare "No module named
'mcp.server.fastmcp'", which reads like a broken install and gives no
hint that the package was renamed in a new major version. Add a plain
module at the old path whose only statement raises ModuleNotFoundError
with a message that keeps the canonical prefix, names the replacement
import, links the migration guide, and mentions pinning mcp<2.
The exception type and its `name` attribute match what a genuinely
missing module produces, so existing `except ImportError`,
`except ModuleNotFoundError`, and `exc.name` fallbacks keep working and
nothing is re-exported or warned about. The module is a file rather
than a package so tools that walk packages do not execute it, and it is
excluded from the generated API reference since it carries no API.
The first-symptom row keeps the verbatim 2.0/2.1 text people search
for while staying true once the message carries a pointer, and the
FastMCP section notes that the old path raises ModuleNotFoundError and
that dual-version import fallbacks continue to work.
@maxisbey
maxisbey marked this pull request as ready for review August 25, 2026 15:37
@github-actions

Copy link
Copy Markdown
Contributor

📚 Documentation preview

Previewhttps://pr-3388.mcp-python-docs.pages.dev
Deploymenthttps://ddc25858.mcp-python-docs.pages.dev
Commit3330346
Triggered by@maxisbey
Updated2026-08-25 15:38:03 UTC

@cubic-dev-aicubic-dev-aiBot 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.

No issues found across 4 files

Re-trigger cubic

@maxisbey
maxisbey merged commit 0921d94 into mainAug 25, 2026
41 checks passed
@maxisbey
maxisbey deleted the fastmcp-import-tombstone branch August 25, 2026 15:39

@claudeclaudeBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Beyond the inline finding, I also checked that the new tombstone doesn't break v1-detection patterns: importlib.util.find_spec("mcp.server.fastmcp") now returns a spec instead of None, but any code that follows that check with an actual import still hits the pointer message, and the dominant try/except-ImportError dual-major guard is unaffected (ModuleNotFoundError subclasses ImportError, and the new tests cover it). Also confirmed the module raising at import leaves no sys.modules residue, and the gen_ref_pages.py exclusion just keeps the tombstone out of the API docs.

Extended reasoning...

The change adds a tombstone module at the removed v1 path mcp/server/fastmcp.py that raises ModuleNotFoundError (with the correct name=) pointing at the migration guide, plus docs and doc-generation exclusions and thorough tests. The one inline finding (the equally common v1 spelling from mcp.server import FastMCP still gets the bare error) is a coverage-scope question worth a maintainer's eye, so I am not approving over it. Separately, I examined whether making mcp.server.fastmcp discoverable again (find_spec returns a spec, pkgutil lists it) could misfire spec-based v1 detection and ruled it out: such detection is unusual for this path, and any subsequent real import still raises the pointer, while the try/except import guard the change explicitly targets keeps working since ModuleNotFoundError subclasses ImportError and the module is never left cached after raising.

"or pin 'mcp<2' to keep running v1 code."
)

raiseModuleNotFoundError(_MESSAGE, name=__name__)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟣 Pre-existing: the migration-pointer tombstone covers only the mcp.server.fastmcp module path, but v1 also re-exported the class from the package itself (v1's mcp/server/__init__.py had from .fastmcp import FastMCP), so the equally common v1 spelling from mcp.server import FastMCP still fails with the bare ImportError: cannot import name 'FastMCP' from 'mcp.server' and never sees the new pointer this PR adds. A module-level __getattr__ in src/mcp/server/__init__.py raising the same guidance for FastMCP (mirroring the _MESSAGE in src/mcp/server/fastmcp.py) would close the gap; docs/migration.md line 675 and the line-20 symptom row also only describe the ModuleNotFoundError path.

Extended reasoning...

A v1 user whose server does from mcp.server import FastMCP (a valid, exported v1 import path) upgrades to a 2.x release containing this change. Instead of the improved message pointing at MCPServer and the migration guide, they still get the uninformative ImportError: cannot import name 'FastMCP' from 'mcp.server' — exactly the confusing experience this PR was written to eliminate — because the tombstone only intercepts imports of the mcp.server.fastmcp module, not the FastMCP attribute of mcp.server.

Verification: pre-existing — src/mcp/server/init.py defines no FastMCP and no module-level __getattr__ (its imports are only CacheHint, ServerRequestContext, NotificationOptions, Server, MCPServer, InitializationOptions), so from mcp.server import FastMCP — a valid v1 spelling, since v1's mcp/server/__init__.py re-exported FastMCP via from .fastmcp import FastMCP and listed it in __all__ — raise

Sign up for freeto 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

@maxisbey
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Point imports of mcp.server.fastmcp at the migration guide - #3388

Merged
maxisbey merged 2 commits into
mainfrom
fastmcp-import-tombstone
Aug 25, 2026
Merged

Point imports of mcp.server.fastmcp at the migration guide#3388
maxisbey merged 2 commits into
mainfrom
fastmcp-import-tombstone

Conversation

@maxisbey

@maxisbeymaxisbey commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

v1 code running against mcp 2 fails with a bare ModuleNotFoundError: No module named 'mcp.server.fastmcp', which reads like a broken install. This adds a 16-line src/mcp/server/fastmcp.py whose only statement raises the same exception with a message that says what happened:

ModuleNotFoundError: No module named 'mcp.server.fastmcp'. This is mcp 2.x, where FastMCP was renamed to MCPServer (from mcp.server.mcpserver import MCPServer) and other APIs changed; see the migration guide at https://py.sdk.modelcontextprotocol.io/v2/migration/#fastmcp-renamed-to-mcpserver or pin 'mcp<2' to keep running v1 code.

Not #3189: nothing is aliased or re-exported and no warning is emitted. The old path still fails to import; it just says why.

Motivation and Context

Since 2.0 shipped the bare string has been quoted in roughly a thousand downstream GitHub issues and PRs (one here, #3309), and new v1-shaped code keeps being written. It raises ModuleNotFoundError with name= set, rather than ImportError, because dual-version shims in the wild catch that class specifically or check exc.name; those keep working unchanged. It's a module file rather than a package so one file covers every mcp.server.fastmcp.* path, and it's excluded from the API reference so it stays undocumented and deletable in any release.

How Has This Been Tested?

New tests/server/test_fastmcp.py (message snapshot, .name, nothing left in sys.modules, deep submodule path, v1-first fallback idiom); ./scripts/test, pyright, docs build; and by hand via python -c, mcp run, and a built wheel.

Breaking Changes

None. Exception type and .name are unchanged; only the message text differs, and importlib.util.find_spec("mcp.server.fastmcp") now returns a spec instead of None.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I am assigned to the linked issue (or it is labeled help wanted, or I'm a maintainer)
  • I have disclosed any AI assistance and can explain the change in my own words
  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Deliberately out of scope: the other removed v1 module paths (near-zero reports), from mcp.server import FastMCP and McpError (would need per-symbol __getattr__, cf. c27f95c).

AI Disclaimer

v1 code running against mcp 2 fails with a bare "No module named
'mcp.server.fastmcp'", which reads like a broken install and gives no
hint that the package was renamed in a new major version. Add a plain
module at the old path whose only statement raises ModuleNotFoundError
with a message that keeps the canonical prefix, names the replacement
import, links the migration guide, and mentions pinning mcp<2.
The exception type and its `name` attribute match what a genuinely
missing module produces, so existing `except ImportError`,
`except ModuleNotFoundError`, and `exc.name` fallbacks keep working and
nothing is re-exported or warned about. The module is a file rather
than a package so tools that walk packages do not execute it, and it is
excluded from the generated API reference since it carries no API.
The first-symptom row keeps the verbatim 2.0/2.1 text people search
for while staying true once the message carries a pointer, and the
FastMCP section notes that the old path raises ModuleNotFoundError and
that dual-version import fallbacks continue to work.
@maxisbey
maxisbey marked this pull request as ready for review August 25, 2026 15:37
@github-actions

Copy link
Copy Markdown
Contributor

📚 Documentation preview

Previewhttps://pr-3388.mcp-python-docs.pages.dev
Deploymenthttps://ddc25858.mcp-python-docs.pages.dev
Commit3330346
Triggered by@maxisbey
Updated2026-08-25 15:38:03 UTC

@cubic-dev-aicubic-dev-aiBot 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.

No issues found across 4 files

Re-trigger cubic

@maxisbey
maxisbey merged commit 0921d94 into mainAug 25, 2026
41 checks passed
@maxisbey
maxisbey deleted the fastmcp-import-tombstone branch August 25, 2026 15:39

@claudeclaudeBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Beyond the inline finding, I also checked that the new tombstone doesn't break v1-detection patterns: importlib.util.find_spec("mcp.server.fastmcp") now returns a spec instead of None, but any code that follows that check with an actual import still hits the pointer message, and the dominant try/except-ImportError dual-major guard is unaffected (ModuleNotFoundError subclasses ImportError, and the new tests cover it). Also confirmed the module raising at import leaves no sys.modules residue, and the gen_ref_pages.py exclusion just keeps the tombstone out of the API docs.

Extended reasoning...

The change adds a tombstone module at the removed v1 path mcp/server/fastmcp.py that raises ModuleNotFoundError (with the correct name=) pointing at the migration guide, plus docs and doc-generation exclusions and thorough tests. The one inline finding (the equally common v1 spelling from mcp.server import FastMCP still gets the bare error) is a coverage-scope question worth a maintainer's eye, so I am not approving over it. Separately, I examined whether making mcp.server.fastmcp discoverable again (find_spec returns a spec, pkgutil lists it) could misfire spec-based v1 detection and ruled it out: such detection is unusual for this path, and any subsequent real import still raises the pointer, while the try/except import guard the change explicitly targets keeps working since ModuleNotFoundError subclasses ImportError and the module is never left cached after raising.

"or pin 'mcp<2' to keep running v1 code."
)

raiseModuleNotFoundError(_MESSAGE, name=__name__)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟣 Pre-existing: the migration-pointer tombstone covers only the mcp.server.fastmcp module path, but v1 also re-exported the class from the package itself (v1's mcp/server/__init__.py had from .fastmcp import FastMCP), so the equally common v1 spelling from mcp.server import FastMCP still fails with the bare ImportError: cannot import name 'FastMCP' from 'mcp.server' and never sees the new pointer this PR adds. A module-level __getattr__ in src/mcp/server/__init__.py raising the same guidance for FastMCP (mirroring the _MESSAGE in src/mcp/server/fastmcp.py) would close the gap; docs/migration.md line 675 and the line-20 symptom row also only describe the ModuleNotFoundError path.

Extended reasoning...

A v1 user whose server does from mcp.server import FastMCP (a valid, exported v1 import path) upgrades to a 2.x release containing this change. Instead of the improved message pointing at MCPServer and the migration guide, they still get the uninformative ImportError: cannot import name 'FastMCP' from 'mcp.server' — exactly the confusing experience this PR was written to eliminate — because the tombstone only intercepts imports of the mcp.server.fastmcp module, not the FastMCP attribute of mcp.server.

Verification: pre-existing — src/mcp/server/init.py defines no FastMCP and no module-level __getattr__ (its imports are only CacheHint, ServerRequestContext, NotificationOptions, Server, MCPServer, InitializationOptions), so from mcp.server import FastMCP — a valid v1 spelling, since v1's mcp/server/__init__.py re-exported FastMCP via from .fastmcp import FastMCP and listed it in __all__ — raise

Sign up for freeto 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

@maxisbey