Skip to content

Commit 0aaaa13

Browse files
authored
Fix/list collection return empty check test (#33)
* Fix: Update test_list_collections parameter types from None to integers * Use | None = None instead of = None
1 parent 50ec12f commit 0aaaa13

File tree

2 files changed

+72
-42
lines changed

2 files changed

+72
-42
lines changed

src/chroma_mcp/server.py

Lines changed: 70 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Dict, List, Optional, TypedDict
1+
from typing import Dict, List, TypedDict, Union
22
from enum import Enum
33
import chromadb
44
from mcp.server.fastmcp import FastMCP
@@ -145,8 +145,8 @@ def get_chroma_client(args=None):
145145

146146
@mcp.tool()
147147
async def chroma_list_collections(
148-
limit: Optional[int] = None,
149-
offset: Optional[int] = None
148+
limit: int | None = None,
149+
offset: int | None = None
150150
) -> List[str]:
151151
"""List all collection names in the Chroma database with pagination support.
152152
@@ -155,12 +155,15 @@ async def chroma_list_collections(
155155
offset: Optional number of collections to skip before returning results
156156
157157
Returns:
158-
List of collection names
158+
List of collection names or ["__NO_COLLECTIONS_FOUND__"] if database is empty
159159
"""
160160
client = get_chroma_client()
161161
try:
162162
colls = client.list_collections(limit=limit, offset=offset)
163-
# iterate over colls and output the names
163+
# Safe handling: If colls is None or empty, return a special marker
164+
if not colls:
165+
return ["__NO_COLLECTIONS_FOUND__"]
166+
# Otherwise iterate to get collection names
164167
return [coll.name for coll in colls]
165168

166169
except Exception as e:
@@ -177,16 +180,16 @@ async def chroma_list_collections(
177180
@mcp.tool()
178181
async def chroma_create_collection(
179182
collection_name: str,
180-
embedding_function_name: Optional[str] = "default",
181-
metadata: Optional[Dict] = None,
182-
space: Optional[str] = None,
183-
ef_construction: Optional[int] = None,
184-
ef_search: Optional[int] = None,
185-
max_neighbors: Optional[int] = None,
186-
num_threads: Optional[int] = None,
187-
batch_size: Optional[int] = None,
188-
sync_threshold: Optional[int] = None,
189-
resize_factor: Optional[float] = None,
183+
embedding_function_name: str = "default",
184+
metadata: Dict | None = None,
185+
space: str | None = None,
186+
ef_construction: int | None = None,
187+
ef_search: int | None = None,
188+
max_neighbors: int | None = None,
189+
num_threads: int | None = None,
190+
batch_size: int | None = None,
191+
sync_threshold: int | None = None,
192+
resize_factor: float | None = None,
190193
) -> str:
191194
"""Create a new Chroma collection with configurable HNSW parameters.
192195
@@ -305,13 +308,13 @@ async def chroma_get_collection_count(collection_name: str) -> int:
305308
@mcp.tool()
306309
async def chroma_modify_collection(
307310
collection_name: str,
308-
new_name: Optional[str] = None,
309-
new_metadata: Optional[Dict] = None,
310-
ef_search: Optional[int] = None,
311-
num_threads: Optional[int] = None,
312-
batch_size: Optional[int] = None,
313-
sync_threshold: Optional[int] = None,
314-
resize_factor: Optional[float] = None,
311+
new_name: str | None = None,
312+
new_metadata: Dict | None = None,
313+
ef_search: int | None = None,
314+
num_threads: int | None = None,
315+
batch_size: int | None = None,
316+
sync_threshold: int | None = None,
317+
resize_factor: float | None = None,
315318
) -> str:
316319
"""Modify a Chroma collection's name or metadata.
317320
@@ -377,35 +380,62 @@ async def chroma_delete_collection(collection_name: str) -> str:
377380
async def chroma_add_documents(
378381
collection_name: str,
379382
documents: List[str],
380-
metadatas: Optional[List[Dict]] = None,
381-
ids: Optional[List[str]] = None
383+
ids: List[str],
384+
metadatas: List[Dict] | None = None
382385
) -> str:
383386
"""Add documents to a Chroma collection.
384387
385388
Args:
386389
collection_name: Name of the collection to add documents to
387390
documents: List of text documents to add
391+
ids: List of IDs for the documents (required)
388392
metadatas: Optional list of metadata dictionaries for each document
389-
ids: Optional list of IDs for the documents
390393
"""
391394
if not documents:
392395
raise ValueError("The 'documents' list cannot be empty.")
396+
397+
if not ids:
398+
raise ValueError("The 'ids' list is required and cannot be empty.")
399+
400+
# Check if there are empty strings in the ids list
401+
if any(not id.strip() for id in ids):
402+
raise ValueError("IDs cannot be empty strings.")
403+
404+
if len(ids) != len(documents):
405+
raise ValueError(f"Number of ids ({len(ids)}) must match number of documents ({len(documents)}).")
393406

394407
client = get_chroma_client()
395408
try:
396409
collection = client.get_or_create_collection(collection_name)
397410

398-
# Generate sequential IDs if none provided
399-
if ids is None:
400-
ids = [str(i) for i in range(len(documents))]
411+
# Check for duplicate IDs
412+
existing_ids = collection.get(include=[])["ids"]
413+
duplicate_ids = [id for id in ids if id in existing_ids]
414+
415+
if duplicate_ids:
416+
raise ValueError(
417+
f"The following IDs already exist in collection '{collection_name}': {duplicate_ids}. "
418+
f"Use 'chroma_update_documents' to update existing documents."
419+
)
401420

402-
collection.add(
421+
result = collection.add(
403422
documents=documents,
404423
metadatas=metadatas,
405424
ids=ids
406425
)
407426

408-
return f"Successfully added {len(documents)} documents to collection {collection_name}"
427+
# Check the return value
428+
if result and isinstance(result, dict):
429+
# If the return value is a dictionary, it may contain success information
430+
if 'success' in result and not result['success']:
431+
raise Exception(f"Failed to add documents: {result.get('error', 'Unknown error')}")
432+
433+
# If the return value contains the actual number added
434+
if 'count' in result:
435+
return f"Successfully added {result['count']} documents to collection {collection_name}"
436+
437+
# Default return
438+
return f"Successfully added {len(documents)} documents to collection {collection_name}, result is {result}"
409439
except Exception as e:
410440
raise Exception(f"Failed to add documents to collection '{collection_name}': {str(e)}") from e
411441

@@ -414,8 +444,8 @@ async def chroma_query_documents(
414444
collection_name: str,
415445
query_texts: List[str],
416446
n_results: int = 5,
417-
where: Optional[Dict] = None,
418-
where_document: Optional[Dict] = None,
447+
where: Dict | None = None,
448+
where_document: Dict | None = None,
419449
include: List[str] = ["documents", "metadatas", "distances"]
420450
) -> Dict:
421451
"""Query documents from a Chroma collection with advanced filtering.
@@ -452,12 +482,12 @@ async def chroma_query_documents(
452482
@mcp.tool()
453483
async def chroma_get_documents(
454484
collection_name: str,
455-
ids: Optional[List[str]] = None,
456-
where: Optional[Dict] = None,
457-
where_document: Optional[Dict] = None,
485+
ids: List[str] | None = None,
486+
where: Dict | None = None,
487+
where_document: Dict | None = None,
458488
include: List[str] = ["documents", "metadatas"],
459-
limit: Optional[int] = None,
460-
offset: Optional[int] = None
489+
limit: int | None = None,
490+
offset: int | None = None
461491
) -> Dict:
462492
"""Get documents from a Chroma collection with optional filtering.
463493
@@ -496,9 +526,9 @@ async def chroma_get_documents(
496526
async def chroma_update_documents(
497527
collection_name: str,
498528
ids: List[str],
499-
embeddings: Optional[List[List[float]]] = None,
500-
metadatas: Optional[List[Dict]] = None,
501-
documents: Optional[List[str]] = None
529+
embeddings: List[List[float]] | None = None,
530+
metadatas: List[Dict] | None = None,
531+
documents: List[str] | None = None
502532
) -> str:
503533
"""Update documents in a Chroma collection.
504534

tests/test_server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def test_get_chroma_client_ephemeral():
3636
@pytest.mark.asyncio
3737
async def test_list_collections():
3838
# Test list_collections tool
39-
result = await mcp.call_tool("chroma_list_collections", {"limit": None, "offset": None})
39+
result = await mcp.call_tool("chroma_list_collections", {"limit": 50, "offset": 0})
4040
assert isinstance(result, list)
4141

4242
@pytest.mark.asyncio
@@ -538,7 +538,7 @@ async def test_list_collections_success():
538538
await mcp.call_tool("chroma_create_collection", {"collection_name": collection_name})
539539

540540
# List collections
541-
result = await mcp.call_tool("chroma_list_collections", {"limit": None, "offset": None})
541+
result = await mcp.call_tool("chroma_list_collections", {"limit": 50, "offset": 0})
542542
assert isinstance(result, list)
543543
assert any(collection_name in item.text for item in result)
544544

0 commit comments

Comments
 (0)