Skip to content

Commit 244139b

Browse files
authored
migrate to fastmcp 2 (#57)
* migrate to fastmcp 2 * format * ignore mypy being unreasonable * fmt * upd readme
1 parent ade0753 commit 244139b

File tree

7 files changed

+212
-62
lines changed

7 files changed

+212
-62
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ Supported transport protocols:
9797

9898
- `stdio` (default): Standard input/output transport, might only be used by local MCP clients
9999
- `sse`: Server-Sent Events transport, perfect for remote clients
100+
- `streamable-http`: Streamable HTTP transport, perfect for remote clients, more recent than SSE
100101

101102
The default transport is `stdio` if not specified.
102103

@@ -275,7 +276,7 @@ The MCP server can be run in development mode using the `mcp dev` command. This
275276
inspector in your browser.
276277

277278
```shell
278-
COLLECTION_NAME=mcp-dev mcp dev src/mcp_server_qdrant/server.py
279+
COLLECTION_NAME=mcp-dev fastmcp dev src/mcp_server_qdrant/server.py
279280
```
280281

281282
### Using with VS Code
@@ -467,7 +468,7 @@ your browser to use the inspector.
467468
468469
```shell
469470
QDRANT_URL=":memory:" COLLECTION_NAME="test" \
470-
mcp dev src/mcp_server_qdrant/server.py
471+
fastmcp dev src/mcp_server_qdrant/server.py
471472
```
472473
473474
Once started, open your browser to http://localhost:5173 to access the inspector interface.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ readme = "README.md"
66
requires-python = ">=3.10"
77
license = "Apache-2.0"
88
dependencies = [
9-
"mcp[cli]>=1.3.0",
109
"fastembed>=0.6.0",
1110
"qdrant-client>=1.12.0",
1211
"pydantic>=2.10.6",
12+
"fastmcp>=2.5.1",
1313
]
1414

1515
[build-system]

src/mcp_server_qdrant/common/__init__.py

Whitespace-only changes.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import inspect
2+
from functools import wraps
3+
from typing import Callable
4+
5+
6+
def make_partial_function(original_func: Callable, fixed_values: dict) -> Callable:
7+
sig = inspect.signature(original_func)
8+
9+
@wraps(original_func)
10+
def wrapper(*args, **kwargs):
11+
# Start with fixed values
12+
bound_args = dict(fixed_values)
13+
14+
# Bind positional/keyword args from caller
15+
for name, value in zip(remaining_params, args):
16+
bound_args[name] = value
17+
bound_args.update(kwargs)
18+
19+
return original_func(**bound_args)
20+
21+
# Only keep parameters NOT in fixed_values
22+
remaining_params = [name for name in sig.parameters if name not in fixed_values]
23+
new_params = [sig.parameters[name] for name in remaining_params]
24+
25+
# Set the new __signature__ for introspection
26+
wrapper.__signature__ = sig.replace(parameters=new_params) # type:ignore
27+
28+
return wrapper

src/mcp_server_qdrant/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def main():
1212
parser = argparse.ArgumentParser(description="mcp-server-qdrant")
1313
parser.add_argument(
1414
"--transport",
15-
choices=["stdio", "sse"],
15+
choices=["stdio", "sse", "streamable-http"],
1616
default="stdio",
1717
)
1818
args = parser.parse_args()

src/mcp_server_qdrant/mcp_server.py

Lines changed: 21 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import json
22
import logging
3-
from typing import Any, List
3+
from typing import Any, List, Optional
44

5-
from mcp.server.fastmcp import Context, FastMCP
5+
from fastmcp import Context, FastMCP
66

7+
from mcp_server_qdrant.common.func_tools import make_partial_function
78
from mcp_server_qdrant.embeddings.factory import create_embedding_provider
89
from mcp_server_qdrant.qdrant import Entry, Metadata, QdrantConnector
910
from mcp_server_qdrant.settings import (
@@ -67,7 +68,7 @@ async def store(
6768
# The `metadata` parameter is defined as non-optional, but it can be None.
6869
# If we set it to be optional, some of the MCP clients, like Cursor, cannot
6970
# handle the optional parameter correctly.
70-
metadata: Metadata = None, # type: ignore
71+
metadata: Optional[Metadata] = None, # type: ignore
7172
) -> str:
7273
"""
7374
Store some information in Qdrant.
@@ -87,16 +88,6 @@ async def store(
8788
return f"Remembered: {information} in collection {collection_name}"
8889
return f"Remembered: {information}"
8990

90-
async def store_with_default_collection(
91-
ctx: Context,
92-
information: str,
93-
metadata: Metadata = None, # type: ignore
94-
) -> str:
95-
assert self.qdrant_settings.collection_name is not None
96-
return await store(
97-
ctx, information, self.qdrant_settings.collection_name, metadata
98-
)
99-
10091
async def find(
10192
ctx: Context,
10293
query: str,
@@ -130,40 +121,27 @@ async def find(
130121
content.append(self.format_entry(entry))
131122
return content
132123

133-
async def find_with_default_collection(
134-
ctx: Context,
135-
query: str,
136-
) -> List[str]:
137-
assert self.qdrant_settings.collection_name is not None
138-
return await find(ctx, query, self.qdrant_settings.collection_name)
139-
140-
# Register the tools depending on the configuration
124+
find_foo = find
125+
store_foo = store
141126

142127
if self.qdrant_settings.collection_name:
143-
self.add_tool(
144-
find_with_default_collection,
145-
name="qdrant-find",
146-
description=self.tool_settings.tool_find_description,
128+
find_foo = make_partial_function(
129+
find_foo, {"collection_name": self.qdrant_settings.collection_name}
147130
)
148-
else:
149-
self.add_tool(
150-
find,
151-
name="qdrant-find",
152-
description=self.tool_settings.tool_find_description,
131+
store_foo = make_partial_function(
132+
store_foo, {"collection_name": self.qdrant_settings.collection_name}
153133
)
154134

135+
self.add_tool(
136+
find_foo,
137+
name="qdrant-find",
138+
description=self.tool_settings.tool_find_description,
139+
)
140+
155141
if not self.qdrant_settings.read_only:
156142
# Those methods can modify the database
157-
158-
if self.qdrant_settings.collection_name:
159-
self.add_tool(
160-
store_with_default_collection,
161-
name="qdrant-store",
162-
description=self.tool_settings.tool_store_description,
163-
)
164-
else:
165-
self.add_tool(
166-
store,
167-
name="qdrant-store",
168-
description=self.tool_settings.tool_store_description,
169-
)
143+
self.add_tool(
144+
store_foo,
145+
name="qdrant-store",
146+
description=self.tool_settings.tool_store_description,
147+
)

0 commit comments

Comments
 (0)