loci-memory 0.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) [2026] [Daniel Roque Gonçalves de Almeida]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: loci-memory
3
+ Version: 0.0.2
4
+ Summary: Chroma based MCP Server - Provide memory capabilities using Vector Database for LLM Applications
5
+ License-Expression: MIT
6
+ Keywords: chroma,mcp,vector-database,llm,memory
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
12
+ Requires-Python: >=3.12
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: chromadb>=1.0.16
16
+ Requires-Dist: fastmcp>=3.2.3
17
+ Requires-Dist: typing-extensions>=4.13.1
18
+ Dynamic: license-file
19
+
20
+ ## Features
21
+
22
+ - **Flexible Client Types**
23
+ - Ephemeral (in-memory) for testing and development
24
+ - Persistent for file-based storage
25
+
26
+ - **Collection Management**
27
+ - Create, modify, and delete collections
28
+ - List all collections with pagination support
29
+ - Get collection information and statistics
30
+ - Configure HNSW parameters for optimized vector search
31
+ - Select embedding functions when creating collections
32
+
33
+ - **Document Operations**
34
+ - Add documents with optional metadata and custom IDs
35
+ - Query documents using semantic search
36
+ - Advanced filtering using metadata and document content
37
+ - Retrieve documents by IDs or filters
38
+ - Full text search capabilities
39
+
40
+ ### Supported Tools
41
+
42
+ - `chroma_list_collections` - List all collections with pagination support
43
+ - `chroma_create_collection` - Create a new collection with optional HNSW configuration
44
+ - `chroma_peek_collection` - View a sample of documents in a collection
45
+ - `chroma_get_collection_info` - Get detailed information about a collection
46
+ - `chroma_get_collection_count` - Get the number of documents in a collection
47
+ - `chroma_modify_collection` - Update a collection's name or metadata
48
+ - `chroma_delete_collection` - Delete a collection
49
+ - `chroma_add_documents` - Add documents with optional metadata and custom IDs
50
+ - `chroma_query_documents` - Query documents using semantic search with advanced filtering
51
+ - `chroma_get_documents` - Retrieve documents by IDs or filters with pagination
52
+ - `chroma_update_documents` - Update existing documents' content, metadata, or embeddings
53
+ - `chroma_delete_documents` - Delete specific documents from a collection
54
+
55
+ ### Embedding Functions
56
+ Chroma MCP supports several embedding functions: `default`, `cohere`, `openai`, `jina`, `voyageai`, and `roboflow`.
57
+
58
+ The embedding functions utilize Chroma's collection configuration, which persists the selected embedding function of a collection for retrieval. Once a collection is created using the collection configuration, on retrieval for future queries and inserts, the same embedding function will be used, without needing to specify the embedding function again. Embedding function persistance was added in v1.0.0 of Chroma, so if you created a collection using version <=0.6.3, this feature is not supported.
59
+
60
+ When accessing embedding functions that utilize external APIs, please be sure to add the environment variable for the API key with the correct format, found in [Embedding Function Environment Variables](#embedding-function-environment-variables)
61
+
62
+ ## Usage with LM Studio
63
+
64
+ Add the following to your `mcp.json` file:
65
+
66
+ ```json
67
+ "chroma": {
68
+ "command": "uvx",
69
+ "args": [
70
+ "loci-memory",
71
+ "--client-type",
72
+ "persistent",
73
+ "--data-dir",
74
+ "/full/path/to/your/data/directory"
75
+ ]
76
+ }
77
+
78
+ [GitHub-flavored Markdown](https://guides.github.com/features/mastering-markdown/)
@@ -0,0 +1,59 @@
1
+ ## Features
2
+
3
+ - **Flexible Client Types**
4
+ - Ephemeral (in-memory) for testing and development
5
+ - Persistent for file-based storage
6
+
7
+ - **Collection Management**
8
+ - Create, modify, and delete collections
9
+ - List all collections with pagination support
10
+ - Get collection information and statistics
11
+ - Configure HNSW parameters for optimized vector search
12
+ - Select embedding functions when creating collections
13
+
14
+ - **Document Operations**
15
+ - Add documents with optional metadata and custom IDs
16
+ - Query documents using semantic search
17
+ - Advanced filtering using metadata and document content
18
+ - Retrieve documents by IDs or filters
19
+ - Full text search capabilities
20
+
21
+ ### Supported Tools
22
+
23
+ - `chroma_list_collections` - List all collections with pagination support
24
+ - `chroma_create_collection` - Create a new collection with optional HNSW configuration
25
+ - `chroma_peek_collection` - View a sample of documents in a collection
26
+ - `chroma_get_collection_info` - Get detailed information about a collection
27
+ - `chroma_get_collection_count` - Get the number of documents in a collection
28
+ - `chroma_modify_collection` - Update a collection's name or metadata
29
+ - `chroma_delete_collection` - Delete a collection
30
+ - `chroma_add_documents` - Add documents with optional metadata and custom IDs
31
+ - `chroma_query_documents` - Query documents using semantic search with advanced filtering
32
+ - `chroma_get_documents` - Retrieve documents by IDs or filters with pagination
33
+ - `chroma_update_documents` - Update existing documents' content, metadata, or embeddings
34
+ - `chroma_delete_documents` - Delete specific documents from a collection
35
+
36
+ ### Embedding Functions
37
+ Chroma MCP supports several embedding functions: `default`, `cohere`, `openai`, `jina`, `voyageai`, and `roboflow`.
38
+
39
+ The embedding functions utilize Chroma's collection configuration, which persists the selected embedding function of a collection for retrieval. Once a collection is created using the collection configuration, on retrieval for future queries and inserts, the same embedding function will be used, without needing to specify the embedding function again. Embedding function persistance was added in v1.0.0 of Chroma, so if you created a collection using version <=0.6.3, this feature is not supported.
40
+
41
+ When accessing embedding functions that utilize external APIs, please be sure to add the environment variable for the API key with the correct format, found in [Embedding Function Environment Variables](#embedding-function-environment-variables)
42
+
43
+ ## Usage with LM Studio
44
+
45
+ Add the following to your `mcp.json` file:
46
+
47
+ ```json
48
+ "chroma": {
49
+ "command": "uvx",
50
+ "args": [
51
+ "loci-memory",
52
+ "--client-type",
53
+ "persistent",
54
+ "--data-dir",
55
+ "/full/path/to/your/data/directory"
56
+ ]
57
+ }
58
+
59
+ [GitHub-flavored Markdown](https://guides.github.com/features/mastering-markdown/)
@@ -0,0 +1,23 @@
1
+ [project]
2
+ name = "loci-memory"
3
+ version = "0.0.2"
4
+ description = "Chroma based MCP Server - Provide memory capabilities using Vector Database for LLM Applications"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ license = "MIT"
8
+
9
+ keywords = ["chroma", "mcp", "vector-database", "llm", "memory"]
10
+
11
+ classifiers = [
12
+ "Development Status :: 4 - Beta",
13
+ "Intended Audience :: Developers",
14
+ "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Topic :: Software Development :: Libraries :: Python Modules"
17
+ ]
18
+
19
+ dependencies = [
20
+ "chromadb>=1.0.16",
21
+ "fastmcp>=3.2.3",
22
+ "typing-extensions>=4.13.1",
23
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,599 @@
1
+ # loci-memory.py
2
+ from typing import Dict, List, TypedDict, Union
3
+ from mcp.server.fastmcp import FastMCP # Framework for building MCP Applications
4
+ import chromadb
5
+ import os # Python os Module
6
+ import argparse # Argument parser
7
+
8
+ from chromadb.api.collection_configuration import (
9
+ CreateCollectionConfiguration
10
+ )
11
+ from chromadb.api import EmbeddingFunction
12
+ from chromadb.utils.embedding_functions import (
13
+ DefaultEmbeddingFunction,
14
+ CohereEmbeddingFunction,
15
+ OpenAIEmbeddingFunction,
16
+ JinaEmbeddingFunction,
17
+ VoyageAIEmbeddingFunction,
18
+ RoboflowEmbeddingFunction,
19
+ )
20
+
21
+ # Create an MCP server
22
+ mcp = FastMCP(
23
+ "LOCI",
24
+ instructions="Provides memory capabilities to an LLM. Start with get_summary() for an overview.")
25
+
26
+ # Global variables
27
+ _chroma_client = None
28
+
29
+ #
30
+ # Create Parser
31
+ #
32
+ def create_parser():
33
+ """Create and return the argument parser."""
34
+ parser = argparse.ArgumentParser(description='FastMCP server for Chroma DB')
35
+ parser.add_argument('--client-type',
36
+ choices=['persistent', 'ephemeral'],
37
+ default=os.getenv('CHROMA_CLIENT_TYPE', 'ephemeral'),
38
+ help='Type of Chroma client to use')
39
+ parser.add_argument('--data-dir',
40
+ default=os.getenv('CHROMA_DATA_DIR'),
41
+ help='Directory for persistent client data (only used with persistent client)')
42
+ return parser
43
+
44
+ #
45
+ # Get Chroma Client
46
+ #
47
+ def get_chroma_client(args=None):
48
+ """Get or create the global Chroma client instance."""
49
+ global _chroma_client
50
+ if _chroma_client is None:
51
+ if args is None:
52
+ # Create parser and parse args if not provided
53
+ parser = create_parser()
54
+ args = parser.parse_args()
55
+
56
+ if args.client_type == 'persistent':
57
+ if not args.data_dir:
58
+ raise ValueError("Data directory must be provided via --data-dir flag when using persistent client")
59
+ _chroma_client = chromadb.PersistentClient(path=args.data_dir)
60
+ else: # ephemeral
61
+ _chroma_client = chromadb.EphemeralClient()
62
+
63
+ return _chroma_client
64
+
65
+
66
+ @mcp.tool()
67
+ async def chroma_list_collections(
68
+ limit: int | None = None,
69
+ offset: int | None = None
70
+ ) -> List[str]:
71
+ """List all collection names in the Chroma database with pagination support.
72
+
73
+ Args:
74
+ limit: Optional maximum number of collections to return
75
+ offset: Optional number of collections to skip before returning results
76
+
77
+ Returns:
78
+ List of collection names or ["__NO_COLLECTIONS_FOUND__"] if database is empty
79
+ """
80
+ client = get_chroma_client()
81
+ try:
82
+ colls = client.list_collections(limit=limit, offset=offset)
83
+ # Safe handling: If colls is None or empty, return a special marker
84
+ if not colls:
85
+ return ["__NO_COLLECTIONS_FOUND__"]
86
+ # Otherwise iterate to get collection names
87
+ return [coll.name for coll in colls]
88
+
89
+ except Exception as e:
90
+ raise Exception(f"Failed to list collections: {str(e)}") from e
91
+
92
+ #
93
+ # Declare a variable with common embedding functions
94
+ #
95
+ mcp_embedding_functions: Dict[str, EmbeddingFunction] = {
96
+ "default": DefaultEmbeddingFunction,
97
+ "cohere": CohereEmbeddingFunction,
98
+ "openai": OpenAIEmbeddingFunction,
99
+ "jina": JinaEmbeddingFunction,
100
+ "voyageai": VoyageAIEmbeddingFunction,
101
+ "roboflow": RoboflowEmbeddingFunction,
102
+ }
103
+
104
+ @mcp.tool()
105
+ async def chroma_create_collection(
106
+ collection_name: str,
107
+ embedding_function_name: str = "default",
108
+ metadata: Dict | None = None,
109
+ ) -> str:
110
+ """Create a new Chroma collection with configurable HNSW parameters.
111
+
112
+ Args:
113
+ collection_name: Name of the collection to create
114
+ embedding_function_name: Name of the embedding function to use. Options: 'default', 'cohere', 'openai', 'jina', 'voyageai', 'ollama', 'roboflow'
115
+ metadata: Optional metadata dict to add to the collection
116
+ """
117
+ client = get_chroma_client()
118
+
119
+ embedding_function = mcp_embedding_functions[embedding_function_name]
120
+
121
+ configuration=CreateCollectionConfiguration(
122
+ embedding_function=embedding_function()
123
+ )
124
+
125
+ try:
126
+ client.create_collection(
127
+ name=collection_name,
128
+ configuration=configuration,
129
+ metadata=metadata
130
+ )
131
+ config_msg = f" with configuration: {configuration}"
132
+ return f"Successfully created collection {collection_name}{config_msg}"
133
+ except Exception as e:
134
+ raise Exception(f"Failed to create collection '{collection_name}': {str(e)}") from e
135
+
136
+ @mcp.tool()
137
+ async def chroma_peek_collection(
138
+ collection_name: str,
139
+ limit: int = 5
140
+ ) -> Dict:
141
+ """Peek at documents in a Chroma collection.
142
+
143
+ Args:
144
+ collection_name: Name of the collection to peek into
145
+ limit: Number of documents to peek at
146
+ """
147
+ client = get_chroma_client()
148
+ try:
149
+ collection = client.get_collection(collection_name)
150
+ results = collection.peek(limit=limit)
151
+ return results
152
+ except Exception as e:
153
+ raise Exception(f"Failed to peek collection '{collection_name}': {str(e)}") from e
154
+
155
+ @mcp.tool()
156
+ async def chroma_get_collection_info(collection_name: str) -> Dict:
157
+ """Get information about a Chroma collection.
158
+
159
+ Args:
160
+ collection_name: Name of the collection to get info about
161
+ """
162
+ client = get_chroma_client()
163
+ try:
164
+ collection = client.get_collection(collection_name)
165
+
166
+ # Get collection count
167
+ count = collection.count()
168
+
169
+ # Peek at a few documents
170
+ peek_results = collection.peek(limit=3)
171
+
172
+ return {
173
+ "name": collection_name,
174
+ "count": count,
175
+ "sample_documents": peek_results
176
+ }
177
+ except Exception as e:
178
+ raise Exception(f"Failed to get collection info for '{collection_name}': {str(e)}") from e
179
+
180
+ @mcp.tool()
181
+ async def chroma_get_collection_count(collection_name: str) -> int:
182
+ """Get the number of documents in a Chroma collection.
183
+
184
+ Args:
185
+ collection_name: Name of the collection to count
186
+ """
187
+ client = get_chroma_client()
188
+ try:
189
+ collection = client.get_collection(collection_name)
190
+ return collection.count()
191
+ except Exception as e:
192
+ raise Exception(f"Failed to get collection count for '{collection_name}': {str(e)}") from e
193
+
194
+ @mcp.tool()
195
+ async def chroma_modify_collection(
196
+ collection_name: str,
197
+ new_name: str | None = None,
198
+ new_metadata: Dict | None = None,
199
+ ) -> str:
200
+ """Modify a Chroma collection's name or metadata.
201
+
202
+ Args:
203
+ collection_name: Name of the collection to modify
204
+ new_name: Optional new name for the collection
205
+ new_metadata: Optional new metadata for the collection
206
+ """
207
+ client = get_chroma_client()
208
+ try:
209
+ collection = client.get_collection(collection_name)
210
+ collection.modify(name=new_name, metadata=new_metadata)
211
+
212
+ modified_aspects = []
213
+ if new_name:
214
+ modified_aspects.append("name")
215
+ if new_metadata:
216
+ modified_aspects.append("metadata")
217
+
218
+ return f"Successfully modified collection {collection_name}: updated {' and '.join(modified_aspects)}"
219
+ except Exception as e:
220
+ raise Exception(f"Failed to modify collection '{collection_name}': {str(e)}") from e
221
+
222
+ @mcp.tool()
223
+ async def chroma_fork_collection(
224
+ collection_name: str,
225
+ new_collection_name: str,
226
+ ) -> str:
227
+ """Fork a Chroma collection.
228
+
229
+ Args:
230
+ collection_name: Name of the collection to fork
231
+ new_collection_name: Name of the new collection to create
232
+ metadata: Optional metadata dict to add to the new collection
233
+ """
234
+ client = get_chroma_client()
235
+ try:
236
+ collection = client.get_collection(collection_name)
237
+ collection.fork(new_collection_name)
238
+ return f"Successfully forked collection {collection_name} to {new_collection_name}"
239
+ except Exception as e:
240
+ raise Exception(f"Failed to fork collection '{collection_name}': {str(e)}") from e
241
+
242
+ @mcp.tool()
243
+ async def chroma_delete_collection(collection_name: str) -> str:
244
+ """Delete a Chroma collection.
245
+
246
+ Args:
247
+ collection_name: Name of the collection to delete
248
+ """
249
+ client = get_chroma_client()
250
+ try:
251
+ client.delete_collection(collection_name)
252
+ return f"Successfully deleted collection {collection_name}"
253
+ except Exception as e:
254
+ raise Exception(f"Failed to delete collection '{collection_name}': {str(e)}") from e
255
+
256
+ ##### Document Tools #####
257
+ @mcp.tool()
258
+ async def chroma_add_documents(
259
+ collection_name: str,
260
+ documents: List[str],
261
+ ids: List[str],
262
+ metadatas: List[Dict] | None = None
263
+ ) -> str:
264
+ """Add documents to a Chroma collection.
265
+
266
+ Args:
267
+ collection_name: Name of the collection to add documents to
268
+ documents: List of text documents to add
269
+ ids: List of IDs for the documents (required)
270
+ metadatas: Optional list of metadata dictionaries for each document
271
+ """
272
+ if not documents:
273
+ raise ValueError("The 'documents' list cannot be empty.")
274
+
275
+ if not ids:
276
+ raise ValueError("The 'ids' list is required and cannot be empty.")
277
+
278
+ # Check if there are empty strings in the ids list
279
+ if any(not id.strip() for id in ids):
280
+ raise ValueError("IDs cannot be empty strings.")
281
+
282
+ if len(ids) != len(documents):
283
+ raise ValueError(f"Number of ids ({len(ids)}) must match number of documents ({len(documents)}).")
284
+
285
+ client = get_chroma_client()
286
+ try:
287
+ collection = client.get_or_create_collection(collection_name)
288
+
289
+ # Check for duplicate IDs
290
+ existing_ids = collection.get(include=[])["ids"]
291
+ duplicate_ids = [id for id in ids if id in existing_ids]
292
+
293
+ if duplicate_ids:
294
+ raise ValueError(
295
+ f"The following IDs already exist in collection '{collection_name}': {duplicate_ids}. "
296
+ f"Use 'chroma_update_documents' to update existing documents."
297
+ )
298
+
299
+ result = collection.add(
300
+ documents=documents,
301
+ metadatas=metadatas,
302
+ ids=ids
303
+ )
304
+
305
+ # Check the return value
306
+ if result and isinstance(result, dict):
307
+ # If the return value is a dictionary, it may contain success information
308
+ if 'success' in result and not result['success']:
309
+ raise Exception(f"Failed to add documents: {result.get('error', 'Unknown error')}")
310
+
311
+ # If the return value contains the actual number added
312
+ if 'count' in result:
313
+ return f"Successfully added {result['count']} documents to collection {collection_name}"
314
+
315
+ # Default return
316
+ return f"Successfully added {len(documents)} documents to collection {collection_name}, result is {result}"
317
+ except Exception as e:
318
+ raise Exception(f"Failed to add documents to collection '{collection_name}': {str(e)}") from e
319
+
320
+ @mcp.tool()
321
+ async def chroma_query_documents(
322
+ collection_name: str,
323
+ query_texts: List[str],
324
+ n_results: int = 5,
325
+ where: Dict | None = None,
326
+ where_document: Dict | None = None,
327
+ include: List[str] = ["documents", "metadatas", "distances"]
328
+ ) -> Dict:
329
+ """Query documents from a Chroma collection with advanced filtering.
330
+
331
+ Args:
332
+ collection_name: Name of the collection to query
333
+ query_texts: List of query texts to search for
334
+ n_results: Number of results to return per query
335
+ where: Optional metadata filters using Chroma's query operators
336
+ Examples:
337
+ - Simple equality: {"metadata_field": "value"}
338
+ - Comparison: {"metadata_field": {"$gt": 5}}
339
+ - Logical AND: {"$and": [{"field1": {"$eq": "value1"}}, {"field2": {"$gt": 5}}]}
340
+ - Logical OR: {"$or": [{"field1": {"$eq": "value1"}}, {"field1": {"$eq": "value2"}}]}
341
+ where_document: Optional document content filters
342
+ Examples:
343
+ - Contains: {"$contains": "value"}
344
+ - Not contains: {"$not_contains": "value"}
345
+ - Regex: {"$regex": "[a-z]+"}
346
+ - Not regex: {"$not_regex": "[a-z]+"}
347
+ - Logical AND: {"$and": [{"$contains": "value1"}, {"$not_regex": "[a-z]+"}]}
348
+ - Logical OR: {"$or": [{"$regex": "[a-z]+"}, {"$not_contains": "value2"}]}
349
+ include: List of what to include in response. By default, this will include documents, metadatas, and distances.
350
+ """
351
+ if not query_texts:
352
+ raise ValueError("The 'query_texts' list cannot be empty.")
353
+
354
+ client = get_chroma_client()
355
+ try:
356
+ collection = client.get_collection(collection_name)
357
+ return collection.query(
358
+ query_texts=query_texts,
359
+ n_results=n_results,
360
+ where=where,
361
+ where_document=where_document,
362
+ include=include
363
+ )
364
+ except Exception as e:
365
+ raise Exception(f"Failed to query documents from collection '{collection_name}': {str(e)}") from e
366
+
367
+ @mcp.tool()
368
+ async def chroma_get_documents(
369
+ collection_name: str,
370
+ ids: List[str] | None = None,
371
+ where: Dict | None = None,
372
+ where_document: Dict | None = None,
373
+ include: List[str] = ["documents", "metadatas"],
374
+ limit: int | None = None,
375
+ offset: int | None = None
376
+ ) -> Dict:
377
+ """Get documents from a Chroma collection with optional filtering.
378
+
379
+ Args:
380
+ collection_name: Name of the collection to get documents from
381
+ ids: Optional list of document IDs to retrieve
382
+ where: Optional metadata filters using Chroma's query operators
383
+ Examples:
384
+ - Simple equality: {"metadata_field": "value"}
385
+ - Comparison: {"metadata_field": {"$gt": 5}}
386
+ - Logical AND: {"$and": [{"field1": {"$eq": "value1"}}, {"field2": {"$gt": 5}}]}
387
+ - Logical OR: {"$or": [{"field1": {"$eq": "value1"}}, {"field1": {"$eq": "value2"}}]}
388
+ where_document: Optional document content filters
389
+ Examples:
390
+ - Contains: {"$contains": "value"}
391
+ - Not contains: {"$not_contains": "value"}
392
+ - Regex: {"$regex": "[a-z]+"}
393
+ - Not regex: {"$not_regex": "[a-z]+"}
394
+ - Logical AND: {"$and": [{"$contains": "value1"}, {"$not_regex": "[a-z]+"}]}
395
+ - Logical OR: {"$or": [{"$regex": "[a-z]+"}, {"$not_contains": "value2"}]}
396
+ include: List of what to include in response. By default, this will include documents, and metadatas.
397
+ limit: Optional maximum number of documents to return
398
+ offset: Optional number of documents to skip before returning results
399
+
400
+ Returns:
401
+ Dictionary containing the matching documents, their IDs, and requested includes
402
+ """
403
+ client = get_chroma_client()
404
+ try:
405
+ collection = client.get_collection(collection_name)
406
+ return collection.get(
407
+ ids=ids,
408
+ where=where,
409
+ where_document=where_document,
410
+ include=include,
411
+ limit=limit,
412
+ offset=offset
413
+ )
414
+ except Exception as e:
415
+ raise Exception(f"Failed to get documents from collection '{collection_name}': {str(e)}") from e
416
+
417
+ @mcp.tool()
418
+ async def chroma_update_documents(
419
+ collection_name: str,
420
+ ids: List[str],
421
+ embeddings: List[List[float]] | None = None,
422
+ metadatas: List[Dict] | None = None,
423
+ documents: List[str] | None = None
424
+ ) -> str:
425
+ """Update documents in a Chroma collection.
426
+
427
+ Args:
428
+ collection_name: Name of the collection to update documents in
429
+ ids: List of document IDs to update (required)
430
+ embeddings: Optional list of new embeddings for the documents.
431
+ Must match length of ids if provided.
432
+ metadatas: Optional list of new metadata dictionaries for the documents.
433
+ Must match length of ids if provided.
434
+ documents: Optional list of new text documents.
435
+ Must match length of ids if provided.
436
+
437
+ Returns:
438
+ A confirmation message indicating the number of documents updated.
439
+
440
+ Raises:
441
+ ValueError: If 'ids' is empty or if none of 'embeddings', 'metadatas',
442
+ or 'documents' are provided, or if the length of provided
443
+ update lists does not match the length of 'ids'.
444
+ Exception: If the collection does not exist or if the update operation fails.
445
+ """
446
+ if not ids:
447
+ raise ValueError("The 'ids' list cannot be empty.")
448
+
449
+ if embeddings is None and metadatas is None and documents is None:
450
+ raise ValueError(
451
+ "At least one of 'embeddings', 'metadatas', or 'documents' "
452
+ "must be provided for update."
453
+ )
454
+
455
+ # Ensure provided lists match the length of ids if they are not None
456
+ if embeddings is not None and len(embeddings) != len(ids):
457
+ raise ValueError("Length of 'embeddings' list must match length of 'ids' list.")
458
+ if metadatas is not None and len(metadatas) != len(ids):
459
+ raise ValueError("Length of 'metadatas' list must match length of 'ids' list.")
460
+ if documents is not None and len(documents) != len(ids):
461
+ raise ValueError("Length of 'documents' list must match length of 'ids' list.")
462
+
463
+
464
+ client = get_chroma_client()
465
+ try:
466
+ collection = client.get_collection(collection_name)
467
+ except Exception as e:
468
+ raise Exception(
469
+ f"Failed to get collection '{collection_name}': {str(e)}"
470
+ ) from e
471
+
472
+ # Prepare arguments for update, excluding None values at the top level
473
+ update_args = {
474
+ "ids": ids,
475
+ "embeddings": embeddings,
476
+ "metadatas": metadatas,
477
+ "documents": documents,
478
+ }
479
+ kwargs = {k: v for k, v in update_args.items() if v is not None}
480
+
481
+ try:
482
+ collection.update(**kwargs)
483
+ return (
484
+ f"Successfully processed update request for {len(ids)} documents in "
485
+ f"collection '{collection_name}'. Note: Non-existent IDs are ignored by ChromaDB."
486
+ )
487
+ except Exception as e:
488
+ raise Exception(
489
+ f"Failed to update documents in collection '{collection_name}': {str(e)}"
490
+ ) from e
491
+
492
+ @mcp.tool()
493
+ async def chroma_delete_documents(
494
+ collection_name: str,
495
+ ids: List[str]
496
+ ) -> str:
497
+ """Delete documents from a Chroma collection.
498
+
499
+ Args:
500
+ collection_name: Name of the collection to delete documents from
501
+ ids: List of document IDs to delete
502
+
503
+ Returns:
504
+ A confirmation message indicating the number of documents deleted.
505
+
506
+ Raises:
507
+ ValueError: If 'ids' is empty
508
+ Exception: If the collection does not exist or if the delete operation fails.
509
+ """
510
+ if not ids:
511
+ raise ValueError("The 'ids' list cannot be empty.")
512
+
513
+ client = get_chroma_client()
514
+ try:
515
+ collection = client.get_collection(collection_name)
516
+ except Exception as e:
517
+ raise Exception(
518
+ f"Failed to get collection '{collection_name}': {str(e)}"
519
+ ) from e
520
+
521
+ try:
522
+ collection.delete(ids=ids)
523
+ return (
524
+ f"Successfully deleted {len(ids)} documents from "
525
+ f"collection '{collection_name}'. Note: Non-existent IDs are ignored by ChromaDB."
526
+ )
527
+ except Exception as e:
528
+ raise Exception(
529
+ f"Failed to delete documents from collection '{collection_name}': {str(e)}"
530
+ ) from e
531
+
532
+ def validate_thought_data(input_data: Dict) -> Dict:
533
+ """Validate thought data structure."""
534
+ if not input_data.get("sessionId"):
535
+ raise ValueError("Invalid sessionId: must be provided")
536
+ if not input_data.get("thought") or not isinstance(input_data.get("thought"), str):
537
+ raise ValueError("Invalid thought: must be a string")
538
+ if not input_data.get("thoughtNumber") or not isinstance(input_data.get("thoughtNumber"), int):
539
+ raise ValueError("Invalid thoughtNumber: must be a number")
540
+ if not input_data.get("totalThoughts") or not isinstance(input_data.get("totalThoughts"), int):
541
+ raise ValueError("Invalid totalThoughts: must be a number")
542
+ if not isinstance(input_data.get("nextThoughtNeeded"), bool):
543
+ raise ValueError("Invalid nextThoughtNeeded: must be a boolean")
544
+
545
+ return {
546
+ "sessionId": input_data.get("sessionId"),
547
+ "thought": input_data.get("thought"),
548
+ "thoughtNumber": input_data.get("thoughtNumber"),
549
+ "totalThoughts": input_data.get("totalThoughts"),
550
+ "nextThoughtNeeded": input_data.get("nextThoughtNeeded"),
551
+ "isRevision": input_data.get("isRevision"),
552
+ "revisesThought": input_data.get("revisesThought"),
553
+ "branchFromThought": input_data.get("branchFromThought"),
554
+ "branchId": input_data.get("branchId"),
555
+ "needsMoreThoughts": input_data.get("needsMoreThoughts"),
556
+ }
557
+
558
+ def main():
559
+ """Entry point for the Chroma MCP server."""
560
+ parser = create_parser()
561
+ args = parser.parse_args()
562
+
563
+ # Initialize client with parsed args
564
+ try:
565
+ get_chroma_client(args)
566
+ print("Successfully initialized Chroma client")
567
+ except Exception as e:
568
+ print(f"Failed to initialize Chroma client: {str(e)}")
569
+ raise
570
+
571
+ # Initialize and run the server
572
+ print("Starting MCP server")
573
+ mcp.run(transport='stdio')
574
+
575
+ #
576
+ # Declare the tool set
577
+ #
578
+ """
579
+ @mcp.tool(
580
+ name="find_products", # Custom tool name for the LLM
581
+ description="Search the product catalog with optional category filtering.", # Custom description
582
+ tags={"catalog", "search"}, # Optional tags for organization/filtering
583
+ meta={"version": "1.2", "author": "product-team"} # Custom metadata
584
+ )
585
+ def test_test(
586
+ query: str,
587
+ max_results: int = 10, # Optional - has default value
588
+ category: str | None = None # Optional - can be None
589
+ ) -> list[dict]:
590
+ "" "Internal function description (ignored if description is provided above)." ""
591
+ # Implementation...
592
+ print(f"Searching for '{query}' in category '{category}'")
593
+ return [{"id": 2, "name": "Another Product"}]
594
+ """
595
+
596
+ if __name__ == "__main__":
597
+ mcp.run()
598
+ #mcp.run(transport="http", port=8000) # Run Web Version
599
+ #main()
@@ -0,0 +1,78 @@
1
+ Metadata-Version: 2.4
2
+ Name: loci-memory
3
+ Version: 0.0.2
4
+ Summary: Chroma based MCP Server - Provide memory capabilities using Vector Database for LLM Applications
5
+ License-Expression: MIT
6
+ Keywords: chroma,mcp,vector-database,llm,memory
7
+ Classifier: Development Status :: 4 - Beta
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
12
+ Requires-Python: >=3.12
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: chromadb>=1.0.16
16
+ Requires-Dist: fastmcp>=3.2.3
17
+ Requires-Dist: typing-extensions>=4.13.1
18
+ Dynamic: license-file
19
+
20
+ ## Features
21
+
22
+ - **Flexible Client Types**
23
+ - Ephemeral (in-memory) for testing and development
24
+ - Persistent for file-based storage
25
+
26
+ - **Collection Management**
27
+ - Create, modify, and delete collections
28
+ - List all collections with pagination support
29
+ - Get collection information and statistics
30
+ - Configure HNSW parameters for optimized vector search
31
+ - Select embedding functions when creating collections
32
+
33
+ - **Document Operations**
34
+ - Add documents with optional metadata and custom IDs
35
+ - Query documents using semantic search
36
+ - Advanced filtering using metadata and document content
37
+ - Retrieve documents by IDs or filters
38
+ - Full text search capabilities
39
+
40
+ ### Supported Tools
41
+
42
+ - `chroma_list_collections` - List all collections with pagination support
43
+ - `chroma_create_collection` - Create a new collection with optional HNSW configuration
44
+ - `chroma_peek_collection` - View a sample of documents in a collection
45
+ - `chroma_get_collection_info` - Get detailed information about a collection
46
+ - `chroma_get_collection_count` - Get the number of documents in a collection
47
+ - `chroma_modify_collection` - Update a collection's name or metadata
48
+ - `chroma_delete_collection` - Delete a collection
49
+ - `chroma_add_documents` - Add documents with optional metadata and custom IDs
50
+ - `chroma_query_documents` - Query documents using semantic search with advanced filtering
51
+ - `chroma_get_documents` - Retrieve documents by IDs or filters with pagination
52
+ - `chroma_update_documents` - Update existing documents' content, metadata, or embeddings
53
+ - `chroma_delete_documents` - Delete specific documents from a collection
54
+
55
+ ### Embedding Functions
56
+ Chroma MCP supports several embedding functions: `default`, `cohere`, `openai`, `jina`, `voyageai`, and `roboflow`.
57
+
58
+ The embedding functions utilize Chroma's collection configuration, which persists the selected embedding function of a collection for retrieval. Once a collection is created using the collection configuration, on retrieval for future queries and inserts, the same embedding function will be used, without needing to specify the embedding function again. Embedding function persistance was added in v1.0.0 of Chroma, so if you created a collection using version <=0.6.3, this feature is not supported.
59
+
60
+ When accessing embedding functions that utilize external APIs, please be sure to add the environment variable for the API key with the correct format, found in [Embedding Function Environment Variables](#embedding-function-environment-variables)
61
+
62
+ ## Usage with LM Studio
63
+
64
+ Add the following to your `mcp.json` file:
65
+
66
+ ```json
67
+ "chroma": {
68
+ "command": "uvx",
69
+ "args": [
70
+ "loci-memory",
71
+ "--client-type",
72
+ "persistent",
73
+ "--data-dir",
74
+ "/full/path/to/your/data/directory"
75
+ ]
76
+ }
77
+
78
+ [GitHub-flavored Markdown](https://guides.github.com/features/mastering-markdown/)
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/loci-memory/__init__.py
5
+ src/loci-memory/loci-memory.py
6
+ src/loci_memory.egg-info/PKG-INFO
7
+ src/loci_memory.egg-info/SOURCES.txt
8
+ src/loci_memory.egg-info/dependency_links.txt
9
+ src/loci_memory.egg-info/requires.txt
10
+ src/loci_memory.egg-info/top_level.txt
@@ -0,0 +1,3 @@
1
+ chromadb>=1.0.16
2
+ fastmcp>=3.2.3
3
+ typing-extensions>=4.13.1
@@ -0,0 +1 @@
1
+ loci-memory