Conversation
| authors = [{ name = "Keboola", email = "devel@keboola.com" }] | ||
| dependencies = [ | ||
| "fastmcp == 2.14.5", | ||
| "fastmcp==3.2.0", |
There was a problem hiding this comment.
🔴 This PR bumps fastmcp from 2.14.5 to 3.2.0, but fastmcp 3.x removed the get_tools() method (which returned dict[str, Tool]) in favor of list_tools() (returning Sequence[Tool]). Two call sites are broken: cli.py:216 will raise AttributeError on HTTP server startup, and generate_tool_docs.py:182 will break the required check-tools-docs CI tox environment. Both locations need to replace get_tools() with list_tools() and remove the .values() calls.
Extended reasoning...
What the bug is and how it manifests
FastMCP 3.x is a major version bump with breaking API changes. The get_tools() method, which previously existed on FastMCP instances and returned a dict[str, Tool], has been removed entirely and replaced by list_tools() which returns Sequence[Tool]. This PR only updates pyproject.toml and uv.lock but makes zero changes to application source code.
The specific code paths that trigger it
Location 1 - src/keboola_mcp_server/cli.py:216:
app.state.mcp_tools_input_schema = {
tool.name: tool.parameters for tool in (await mcp_server.get_tools()).values()
}This runs during HTTP server lifespan startup (streamable-http, SSE, http-compat transports). The mcp_server.get_tools() call raises AttributeError before any requests can be served.
Location 2 - src/keboola_mcp_server/generate_tool_docs.py:182:
tools = await mcp.get_tools()
...
doc_gen = ToolDocumentationGenerator(list(tools.values()), categories)Called by the check-tools-docs tox environment, a required CI check. This also raises AttributeError.
Why existing code does not prevent it
There is no compatibility shim, no try/except, and no version guard. The code assumes get_tools() exists and returns a dict. Verifiers confirmed by inspecting the fastmcp 3.2.0 source that get_tools() does not exist anywhere. Additionally, even fixing the method name to list_tools() would not be enough: calling .values() on the returned Sequence[Tool] would still raise AttributeError.
Impact
- HTTP server modes are completely broken: Any deployment using
--transport streamable-http,sse, orhttp-compatwill fail to start after this bump. - CI pipeline breaks: The
check-tools-docstox environment fails on every run, blocking future PRs.
How to fix it
Replace get_tools() with list_tools() and drop .values() at both call sites:
cli.py:
app.state.mcp_tools_input_schema = {
tool.name: tool.parameters for tool in await mcp_server.list_tools()
}generate_tool_docs.py:
tools = await mcp.list_tools()
...
doc_gen = ToolDocumentationGenerator(list(tools), categories)Step-by-step proof
- Merge this PR, installing fastmcp==3.2.0.
- Start the server with
--transport streamable-http. - During lifespan startup,
_lifespan()incli.pyreaches line 216:(await mcp_server.get_tools()).values(). - Python calls
mcp_server.get_tools()-FastMCPin 3.2.0 has no such attribute. AttributeError: 'FastMCP' object has no attribute 'get_tools'is raised.- Server startup fails; the HTTP server never becomes ready.
- Separately,
tox -e check-tools-docshitsawait mcp.get_tools()atgenerate_tool_docs.py:182and also raisesAttributeError, failing CI.
2279619 to
7328bbf
Compare
| authors = [{ name = "Keboola", email = "devel@keboola.com" }] | ||
| dependencies = [ | ||
| "fastmcp == 2.14.5", | ||
| "fastmcp==3.2.0", |
There was a problem hiding this comment.
🔴 FastMCP 3.x changed get_tool() to return Tool | None instead of raising NotFoundError, but both ToolsFilteringMiddleware.on_call_tool() in mcp.py:373 and AuthorizationMiddleware.on_call_tool() in authorization.py:126 immediately access attributes on the return value without a None guard. If a client calls a non-existent or session-disabled tool, both locations will raise AttributeError instead of returning a proper MCP error — add a None check at each call site and raise a ToolError with an appropriate message.
Extended reasoning...
What the bug is and how it manifests
In fastmcp 2.x, FastMCP.get_tool() raised NotFoundError when a tool was not found, so callers could assume the return value was always a non-None Tool object. FastMCP 3.x explicitly changed the signature to async def get_tool(self, name: str, ...) -> Tool | None, returning None when the tool is not found, disabled, or hidden by a session-level transform. The application code was never updated to handle this new return value contract.
The specific code paths that trigger it
Location 1 — mcp.py:373 (ToolsFilteringMiddleware.on_call_tool):
tool = await context.fastmcp_context.fastmcp.get_tool(context.message.name)
if token_role == 'readonly':
if not is_read_only_tool(tool): # line 379 — accesses tool.annotations; AttributeError if tool is None
is_read_only_tool(tool) at mcp.py:47 checks tool.annotations, which immediately raises AttributeError when tool is None. Lines 387, 394, 403 also access tool.name without a None guard.
Location 2 — authorization.py:126 (AuthorizationMiddleware.on_call_tool):
tool = await context.fastmcp_context.fastmcp.get_tool(tool_name)
if not self._is_tool_authorized(tool, allowed_tools, disallowed_tools, read_only_mode):
_is_tool_authorized at authorization.py:90 accesses tool.name directly, raising the same AttributeError if tool is None.
Why existing code does not prevent it
The middleware on_call_tool runs as part of the fastmcp middleware chain, which is invoked before fastmcp's own internal tool lookup. In fastmcp 3.x's call_tool flow (server.py:1113-1131), the middleware is called first (the run_middleware=True path), and the core tool-existence check only happens in the inner run_middleware=False path. So the middleware fires and calls get_tool() on its own initiative before any framework guard can reject the unknown tool name.
What the impact would be
Any client that sends a tools/call request for a tool name that does not exist or has been disabled by a session-level transform will receive an unhandled AttributeError crash in the middleware layer rather than a clean MCP error response. Both the filtering middleware (role-based read-only enforcement) and the authorization middleware (allow/disallow-list enforcement) are affected, meaning both security-relevant middleware layers can crash before doing their job.
How to fix it
Add a None guard immediately after each get_tool() call and raise a ToolError for the not-found case:
tool = await context.fastmcp_context.fastmcp.get_tool(context.message.name)
if tool is None:
raise ToolError(f'Tool not found.')
Step-by-step proof
- Merge this PR (fastmcp 3.2.0 installed).
- A client sends tools/call with name="nonexistent_tool".
- FastMCP invokes the middleware chain first (run_middleware=True path).
- ToolsFilteringMiddleware.on_call_tool (mcp.py:368) is entered.
- get_tool("nonexistent_tool") is called; fastmcp 3.x returns None (no tool with that name exists).
- is_read_only_tool(None) is called (mcp.py:379); inside, tool.annotations raises AttributeError: NoneType object has no attribute annotations.
- The exception propagates up uncaught — the client receives an error response, but not a clean MCP ToolError, and both the read-only check and the feature-flag checks are silently skipped.
| authors = [{ name = "Keboola", email = "devel@keboola.com" }] | ||
| dependencies = [ | ||
| "fastmcp == 2.14.5", | ||
| "fastmcp==3.2.0", |
There was a problem hiding this comment.
🟡 This PR does not bump the version in pyproject.toml as required by CLAUDE.md, which states 'Every PR must bump pyproject.toml version before merging.' The version remains at 1.51.0; a patch bump to 1.51.1 (or minor bump to 1.52.0) is needed before this can be merged.
Extended reasoning...
What the issue is
CLAUDE.md contains an explicit project convention: 'Every PR must bump pyproject.toml version before merging.' This PR changes two files — pyproject.toml (fastmcp dependency from 2.14.5 to 3.2.0) and uv.lock — but leaves version = "1.51.0" at line 8 of pyproject.toml unchanged.
The specific location
pyproject.toml line 8 contains version = "1.51.0" and must be incremented before the PR is eligible to merge, per the documented project rule.
Why existing code does not prevent it
There is no automated CI check that enforces the version bump requirement. The rule is documented only in CLAUDE.md as a human-enforced convention, so automated pipelines will not catch it, making manual review the only gate.
Impact
The version in pyproject.toml is used to tag releases. Skipping the bump means the resulting release conflates changes from multiple PRs under one version string, undermining changelogs, dependency pinning by downstream consumers, and release traceability.
How to fix
Increment the patch version: version = "1.51.1". Then run uv lock to regenerate uv.lock with the updated project metadata. A minor bump (1.52.0) would also be acceptable given the major fastmcp dependency upgrade.
Step-by-step proof
- Read CLAUDE.md which explicitly states: Every PR must bump pyproject.toml version before merging.
- Inspect the PR diff for pyproject.toml: only the fastmcp dependency line changed (2.14.5 to 3.2.0). The version field on line 8 is absent from the diff.
- Confirm current value: version = "1.51.0" unchanged from the base branch.
- Conclusion: the CLAUDE.md requirement is unmet. The PR cannot be merged as-is without violating the project documented versioning convention.
…sts 2.33.0 Consolidates 4 Dependabot PRs (#452 #447 #443 #440) into one: - fastmcp 2.14.5 → 3.2.0 (breaking: get_tools() removed → _list_tools()) - cryptography ~= 46.0 (patch 46.0.6) - pygments 2.19.2 → 2.20.0 (indirect) - requests 2.32.5 → 2.33.0 (indirect) fastmcp 3.x migration: - Replace get_tools() (dict) with _list_tools() (list) in cli.py, generate_tool_docs.py, tests - Filter FastMCPDeprecationWarning for serializer in test_json_logging - Regenerate TOOLS.md (fastmcp 3.x adds "additionalProperties": false to all tool schemas) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
f1f9753 to
a3e2503
Compare
ad0858d to
8aaa064
Compare
Bumps [fastmcp](https://github.com/PrefectHQ/fastmcp) from 2.14.5 to 3.2.0. - [Release notes](https://github.com/PrefectHQ/fastmcp/releases) - [Changelog](https://github.com/PrefectHQ/fastmcp/blob/main/docs/changelog.mdx) - [Commits](PrefectHQ/fastmcp@v2.14.5...v3.2.0) --- updated-dependencies: - dependency-name: fastmcp dependency-version: 3.2.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com>
8aaa064 to
787da45
Compare
Bumps fastmcp from 2.14.5 to 3.2.0.
Release notes
Sourced from fastmcp's releases.
... (truncated)
Changelog
Sourced from fastmcp's changelog.
... (truncated)
Commits
665514eAdd forward_resource flag to OAuthProxy (#3711)f189d1fBump pydantic-monty to 0.0.9 (#3707)6faa2d6Remove hardcoded prefab-ui version from pinning warnings (#3708)dd8816cchore: Update SDK documentation (#3701)d274959docs: note that custom routes are unauthenticated (#3706)4a54be2Add examples gallery page (#3705)961dd50Add interactive map example with geocoding (#3702)f01d0c5Add quiz example app, fix dev server empty string args (#3700)85b7efdchore: Update SDK documentation (#3694)27abe3cAdd sales dashboard and live system monitor examples, bump prefab-ui to 0.17 ...