Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/user_guide/how_to_guides/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Install the MCP extra:
pip install redisvl[mcp]
```

The extra installs FastMCP 2.13.0 or later, below 4. If you pin FastMCP yourself, stay within that range, because the server does not work with older releases.

If your vectorizer needs a provider extra, install that too:

```bash
Expand Down
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ mcp = [
# stateless redesign of the protocol core, so a major bump could change both
# the negotiated version and how tools advertise their input schemas.
# Supporting that revision is its own piece of work.
"fastmcp>=2.0.0,<4",
# The floor is the oldest release RedisVL works with, not a security floor:
# earlier 2.x releases lack fastmcp.server.auth, fail to import with
# pydantic 2.13, reject the base_url the JWT verifier is built with, or
# (2.12.1 to 2.12.5) leave SSE clients hanging while they list tools.
"fastmcp>=2.13.0,<4",
"pydantic-settings>=2.0,<3",
]
mistralai = ["mistralai>=1.0.0,<2"]
Expand Down
10 changes: 7 additions & 3 deletions tests/integration/test_mcp/test_profiles.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from importlib.metadata import version
from pathlib import Path

import pytest
Expand Down Expand Up @@ -258,10 +259,13 @@ async def test_profile_advertises_only_its_exposed_arguments(started_server):
# a fake, since the schema is derived by the library from the signature.
assert set(schema["properties"]) == {"query", "limit", "filter"}
# A set, not a list: `required` ordering is a pydantic implementation detail,
# and only its membership is behavior. `fastmcp` is pinned `>=2.0.0` with no
# upper bound, so a minor bump must not break this on presentation alone.
# and only its membership is behavior. `fastmcp` is allowed anywhere in
# `>=2.13.0,<4`, so a minor bump must not break this on presentation alone.
assert set(schema["required"]) == {"query"}
assert schema["additionalProperties"] is False
# FastMCP 2.x leaves `additionalProperties` out of the schema but still
# refuses extra arguments at call time; only 3.x publishes the refusal.
if int(version("fastmcp").split(".")[0]) >= 3:
assert schema["additionalProperties"] is False

# A raw string filter has no safe composition with a locked filter, so the
# object form is the only shape offered. Optional[dict] is encoded as an
Expand Down
4 changes: 3 additions & 1 deletion tests/unit/test_mcp/test_profiles_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1043,7 +1043,9 @@ async def record_search(server, **kwargs):
async with fastmcp.Client(mcp) as client:
(tool,) = await client.list_tools()
assert "category" not in tool.inputSchema["properties"]
assert tool.inputSchema["additionalProperties"] is False
# FastMCP 2.x omits the key; the refused call below holds either way.
if int(fastmcp.__version__.split(".")[0]) >= 3:
assert tool.inputSchema["additionalProperties"] is False

refused = await client.call_tool(
tool.name, {"query": "jam", "category": "victim"}, raise_on_error=False
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading