Skip to content

Commit 2efdc9a

Browse files
authored
docs: document PlexServer Qdrant client (#44)
1 parent c0dd009 commit 2efdc9a

File tree

5 files changed

+31
-21
lines changed

5 files changed

+31
-21
lines changed

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
`SPARSE_MODEL` environment variables or the corresponding CLI options.
1212
- Hybrid search uses Qdrant's built-in `FusionQuery` with reciprocal rank fusion
1313
to combine dense and sparse results before optional cross-encoder reranking.
14+
- Qdrant client initialization moved into `PlexServer` to centralize state and
15+
simplify testing.
1416

1517
## User Queries
1618
The project should handle natural-language searches and recommendations such as:

mcp_plex/server.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,21 @@
5050
if _QDRANT_URL is None and _QDRANT_HOST is None:
5151
_QDRANT_URL = ":memory:"
5252

53-
# Instantiate global client
54-
_client = AsyncQdrantClient(
55-
location=_QDRANT_URL,
56-
api_key=_QDRANT_API_KEY,
57-
host=_QDRANT_HOST,
58-
port=_QDRANT_PORT,
59-
grpc_port=_QDRANT_GRPC_PORT,
60-
prefer_grpc=_QDRANT_PREFER_GRPC,
61-
https=_QDRANT_HTTPS,
62-
)
53+
54+
class PlexServer(FastMCP):
55+
"""FastMCP server with an attached Qdrant client."""
56+
57+
def __init__(self) -> None: # noqa: D401 - short description inherited
58+
super().__init__()
59+
self.qdrant_client = AsyncQdrantClient(
60+
location=_QDRANT_URL,
61+
api_key=_QDRANT_API_KEY,
62+
host=_QDRANT_HOST,
63+
port=_QDRANT_PORT,
64+
grpc_port=_QDRANT_GRPC_PORT,
65+
prefer_grpc=_QDRANT_PREFER_GRPC,
66+
https=_QDRANT_HTTPS,
67+
)
6368

6469
_USE_RERANKER = os.getenv("USE_RERANKER", "1") == "1"
6570
_reranker = None
@@ -69,7 +74,7 @@
6974
except Exception:
7075
_reranker = None
7176

72-
server = FastMCP()
77+
server = PlexServer()
7378

7479

7580
_CACHE_SIZE = 128
@@ -98,7 +103,9 @@ async def _find_records(identifier: str, limit: int = 5) -> list[models.Record]:
98103
# First, try direct ID lookup
99104
try:
100105
record_id: Any = int(identifier) if identifier.isdigit() else identifier
101-
recs = await _client.retrieve("media-items", ids=[record_id], with_payload=True)
106+
recs = await server.qdrant_client.retrieve(
107+
"media-items", ids=[record_id], with_payload=True
108+
)
102109
if recs:
103110
return recs
104111
except Exception:
@@ -119,7 +126,7 @@ async def _find_records(identifier: str, limit: int = 5) -> list[models.Record]:
119126
models.FieldCondition(key="title", match=models.MatchText(text=identifier))
120127
)
121128
flt = models.Filter(should=should)
122-
points, _ = await _client.scroll(
129+
points, _ = await server.qdrant_client.scroll(
123130
collection_name="media-items",
124131
limit=limit,
125132
scroll_filter=flt,
@@ -199,7 +206,7 @@ async def search_media(
199206
limit=candidate_limit,
200207
),
201208
]
202-
res = await _client.query_points(
209+
res = await server.qdrant_client.query_points(
203210
collection_name="media-items",
204211
query=models.FusionQuery(fusion=models.Fusion.RRF),
205212
prefetch=prefetch,
@@ -274,7 +281,7 @@ async def recommend_media(
274281
record = records[0]
275282
if record is None:
276283
return []
277-
recs = await _client.recommend(
284+
recs = await server.qdrant_client.recommend(
278285
collection_name="media-items",
279286
positive=[record.id],
280287
limit=limit,
@@ -307,7 +314,7 @@ async def new_movies(
307314
)
308315
]
309316
)
310-
res = await _client.query_points(
317+
res = await server.qdrant_client.query_points(
311318
collection_name="media-items",
312319
query=query,
313320
query_filter=flt,
@@ -340,7 +347,7 @@ async def new_shows(
340347
)
341348
]
342349
)
343-
res = await _client.query_points(
350+
res = await server.qdrant_client.query_points(
344351
collection_name="media-items",
345352
query=query,
346353
query_filter=flt,
@@ -393,7 +400,7 @@ async def actor_movies(
393400
query = models.OrderByQuery(
394401
order_by=models.OrderBy(key="year", direction=models.Direction.DESC)
395402
)
396-
res = await _client.query_points(
403+
res = await server.qdrant_client.query_points(
397404
collection_name="media-items",
398405
query=query,
399406
query_filter=flt,

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "mcp-plex"
7-
version = "0.26.9"
7+
version = "0.26.10"
88

99
description = "Plex-Oriented Model Context Protocol Server"
1010
requires-python = ">=3.11,<3.13"

tests/test_server.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,14 @@ def __init__(self, *args, **kwargs):
5454
monkeypatch.setenv("QDRANT_GRPC_PORT", "5678")
5555
monkeypatch.setenv("QDRANT_PREFER_GRPC", "1")
5656
monkeypatch.setenv("QDRANT_HTTPS", "1")
57-
importlib.reload(importlib.import_module("mcp_plex.server"))
57+
module = importlib.reload(importlib.import_module("mcp_plex.server"))
5858

5959
assert captured["host"] == "example.com"
6060
assert captured["port"] == 1234
6161
assert captured["grpc_port"] == 5678
6262
assert captured["prefer_grpc"] is True
6363
assert captured["https"] is True
64+
assert hasattr(module.server, "qdrant_client")
6465

6566

6667
def test_server_tools(monkeypatch):

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)