qdrant-mcp-server 0.1.0__py3-none-any.whl
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.
- qdrant_mcp_server/__init__.py +20 -0
- qdrant_mcp_server/__main__.py +47 -0
- qdrant_mcp_server/py.typed +0 -0
- qdrant_mcp_server/server.py +607 -0
- qdrant_mcp_server-0.1.0.dist-info/METADATA +302 -0
- qdrant_mcp_server-0.1.0.dist-info/RECORD +10 -0
- qdrant_mcp_server-0.1.0.dist-info/WHEEL +5 -0
- qdrant_mcp_server-0.1.0.dist-info/entry_points.txt +2 -0
- qdrant_mcp_server-0.1.0.dist-info/licenses/LICENSE +21 -0
- qdrant_mcp_server-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Qdrant MCP Server
|
|
3
|
+
|
|
4
|
+
A Model Context Protocol (MCP) server for Qdrant vector database
|
|
5
|
+
with semantic search capabilities.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
__author__ = "fiyen"
|
|
10
|
+
__email__ = "623320480@qq.com"
|
|
11
|
+
|
|
12
|
+
from .server import create_server, QdrantMCPServer
|
|
13
|
+
from .__main__ import main, run
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"create_server",
|
|
17
|
+
"QdrantMCPServer",
|
|
18
|
+
"main",
|
|
19
|
+
"run",
|
|
20
|
+
]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Qdrant MCP Server 主入口点
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import logging
|
|
8
|
+
|
|
9
|
+
from mcp.server.models import InitializationOptions
|
|
10
|
+
from mcp.server.lowlevel import NotificationOptions
|
|
11
|
+
import mcp.server.stdio
|
|
12
|
+
|
|
13
|
+
from .server import create_server
|
|
14
|
+
|
|
15
|
+
logging.basicConfig(level=logging.INFO)
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def main():
|
|
20
|
+
"""主函数"""
|
|
21
|
+
server, qdrant_server = create_server()
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
|
|
25
|
+
await server.run(
|
|
26
|
+
read_stream,
|
|
27
|
+
write_stream,
|
|
28
|
+
InitializationOptions(
|
|
29
|
+
server_name="qdrant-mcp",
|
|
30
|
+
server_version="0.1.0",
|
|
31
|
+
capabilities=server.get_capabilities(
|
|
32
|
+
notification_options=NotificationOptions(),
|
|
33
|
+
experimental_capabilities={},
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
)
|
|
37
|
+
finally:
|
|
38
|
+
await qdrant_server.close()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def run():
|
|
42
|
+
"""命令行入口点"""
|
|
43
|
+
asyncio.run(main())
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
run()
|
|
File without changes
|
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Qdrant MCP Server - 标准实现
|
|
4
|
+
|
|
5
|
+
基于Qdrant向量数据库的MCP(Model Context Protocol)工具包
|
|
6
|
+
提供知识记录、搜索、更新和删除功能
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
import uuid
|
|
14
|
+
from typing import Any, Dict, List, Optional, Union
|
|
15
|
+
|
|
16
|
+
import httpx
|
|
17
|
+
from sentence_transformers import SentenceTransformer
|
|
18
|
+
from dotenv import load_dotenv
|
|
19
|
+
|
|
20
|
+
from mcp.server.lowlevel import NotificationOptions, Server
|
|
21
|
+
from mcp.server.models import InitializationOptions
|
|
22
|
+
import mcp.server.stdio
|
|
23
|
+
import mcp.types as types
|
|
24
|
+
|
|
25
|
+
# 加载环境变量
|
|
26
|
+
load_dotenv()
|
|
27
|
+
|
|
28
|
+
# 配置日志
|
|
29
|
+
logging.basicConfig(level=logging.INFO)
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
class QdrantMCPServer:
|
|
33
|
+
"""Qdrant MCP服务器类"""
|
|
34
|
+
|
|
35
|
+
def __init__(self):
|
|
36
|
+
self.qdrant_url = os.getenv("QDRANT_URL", "http://localhost:6333")
|
|
37
|
+
self.qdrant_token = os.getenv("QDRANT_API_KEY") or os.getenv("QDRANT_TOKEN")
|
|
38
|
+
self.collection_name = os.getenv("COLLECTION_NAME", "default_collection")
|
|
39
|
+
self.embedding_model_name = os.getenv("EMBEDDING_MODEL", "sentence-transformers/all-MiniLM-L6-v2")
|
|
40
|
+
self.jina_token = os.getenv("JINA_TOKEN")
|
|
41
|
+
|
|
42
|
+
# 从环境变量读取向量名称配置
|
|
43
|
+
self.vector_name = os.getenv("VECTOR_NAME")
|
|
44
|
+
|
|
45
|
+
# HTTP客户端将在需要时延迟初始化
|
|
46
|
+
self.http_client = None
|
|
47
|
+
self._initialized = False
|
|
48
|
+
|
|
49
|
+
# 判断使用哪种嵌入方式(兼容多种别名)
|
|
50
|
+
normalized_name = str(self.embedding_model_name).strip().lower().replace("_", "-")
|
|
51
|
+
self.use_jina_embedding = normalized_name in {"jina-embeddings-v3", "jina-embedding-v3"}
|
|
52
|
+
|
|
53
|
+
if self.use_jina_embedding:
|
|
54
|
+
# 使用Jina在线嵌入
|
|
55
|
+
if not self.jina_token:
|
|
56
|
+
raise ValueError("使用JINA在线嵌入时必须设置JINA_TOKEN环境变量")
|
|
57
|
+
self.embedding_model = None
|
|
58
|
+
# 如果没有指定向量名称,使用默认的Jina向量名称
|
|
59
|
+
if not self.vector_name:
|
|
60
|
+
self.vector_name = "jina-embeddings-v3"
|
|
61
|
+
logger.info(f"已配置Jina在线嵌入模型: {normalized_name} → 使用向量名称: {self.vector_name}")
|
|
62
|
+
else:
|
|
63
|
+
# 使用本地sentence-transformers模型
|
|
64
|
+
try:
|
|
65
|
+
self.embedding_model = SentenceTransformer(self.embedding_model_name)
|
|
66
|
+
# 如果没有指定向量名称,使用默认的本地模型向量名称
|
|
67
|
+
if not self.vector_name:
|
|
68
|
+
self.vector_name = "fast-all-minilm-l6-v2"
|
|
69
|
+
logger.info(f"已加载本地嵌入模型: {self.embedding_model_name},向量名称: {self.vector_name}")
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.error(f"加载嵌入模型失败: {e}")
|
|
72
|
+
raise
|
|
73
|
+
|
|
74
|
+
logger.info(f"Qdrant MCP服务器配置完成,连接到: {self.qdrant_url}")
|
|
75
|
+
|
|
76
|
+
async def _ensure_http_client(self):
|
|
77
|
+
"""确保HTTP客户端已初始化"""
|
|
78
|
+
if self.http_client is None or self.http_client.is_closed:
|
|
79
|
+
headers = {"Content-Type": "application/json"}
|
|
80
|
+
if self.qdrant_token:
|
|
81
|
+
headers["Authorization"] = f"Bearer {self.qdrant_token}"
|
|
82
|
+
|
|
83
|
+
self.http_client = httpx.AsyncClient(
|
|
84
|
+
base_url=self.qdrant_url,
|
|
85
|
+
headers=headers,
|
|
86
|
+
timeout=30.0
|
|
87
|
+
)
|
|
88
|
+
self._initialized = True
|
|
89
|
+
logger.info("HTTP客户端已初始化")
|
|
90
|
+
|
|
91
|
+
async def _make_request(self, method: str, url: str, **kwargs) -> httpx.Response:
|
|
92
|
+
"""发送HTTP请求"""
|
|
93
|
+
await self._ensure_http_client()
|
|
94
|
+
|
|
95
|
+
if method.upper() == "GET":
|
|
96
|
+
return await self.http_client.get(url, **kwargs)
|
|
97
|
+
elif method.upper() == "POST":
|
|
98
|
+
return await self.http_client.post(url, **kwargs)
|
|
99
|
+
elif method.upper() == "PUT":
|
|
100
|
+
return await self.http_client.put(url, **kwargs)
|
|
101
|
+
elif method.upper() == "DELETE":
|
|
102
|
+
return await self.http_client.delete(url, **kwargs)
|
|
103
|
+
else:
|
|
104
|
+
raise ValueError(f"不支持的HTTP方法: {method}")
|
|
105
|
+
|
|
106
|
+
async def generate_embedding(self, text: str) -> List[float]:
|
|
107
|
+
"""生成文本嵌入向量"""
|
|
108
|
+
try:
|
|
109
|
+
if self.use_jina_embedding:
|
|
110
|
+
return await self._generate_jina_embedding(text)
|
|
111
|
+
else:
|
|
112
|
+
embedding = self.embedding_model.encode(text)
|
|
113
|
+
return embedding.tolist()
|
|
114
|
+
except Exception as e:
|
|
115
|
+
logger.error(f"生成嵌入向量失败: {e}")
|
|
116
|
+
raise
|
|
117
|
+
|
|
118
|
+
async def _generate_jina_embedding(self, text: str) -> List[float]:
|
|
119
|
+
"""使用Jina API生成嵌入向量"""
|
|
120
|
+
try:
|
|
121
|
+
headers = {
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
"Authorization": f"Bearer {self.jina_token}"
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
payload = {
|
|
127
|
+
"model": "jina-embeddings-v3",
|
|
128
|
+
"task": "text-matching", # 用于语义相似度匹配
|
|
129
|
+
"input": [text],
|
|
130
|
+
"normalized": True, # L2标准化
|
|
131
|
+
"embedding_type": "float"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async with httpx.AsyncClient() as client:
|
|
135
|
+
response = await client.post(
|
|
136
|
+
"https://api.jina.ai/v1/embeddings",
|
|
137
|
+
headers=headers,
|
|
138
|
+
json=payload,
|
|
139
|
+
timeout=30.0
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
if response.status_code == 200:
|
|
143
|
+
result = response.json()
|
|
144
|
+
embedding = result["data"][0]["embedding"]
|
|
145
|
+
logger.debug(f"Jina API返回嵌入向量,维度: {len(embedding)}")
|
|
146
|
+
return embedding
|
|
147
|
+
else:
|
|
148
|
+
raise Exception(f"Jina API请求失败: {response.status_code} - {response.text}")
|
|
149
|
+
|
|
150
|
+
except Exception as e:
|
|
151
|
+
logger.error(f"Jina嵌入生成失败: {e}")
|
|
152
|
+
raise
|
|
153
|
+
|
|
154
|
+
async def ensure_collection_exists(self) -> bool:
|
|
155
|
+
"""确保集合存在,如果不存在则创建"""
|
|
156
|
+
try:
|
|
157
|
+
# 检查集合是否存在
|
|
158
|
+
response = await self._make_request("GET", f"/collections/{self.collection_name}")
|
|
159
|
+
if response.status_code == 200:
|
|
160
|
+
logger.info(f"集合 {self.collection_name} 已存在")
|
|
161
|
+
return True
|
|
162
|
+
|
|
163
|
+
# 创建集合,使用配置的向量名称和正确的向量维度
|
|
164
|
+
vector_size = 1024 if self.use_jina_embedding else 384
|
|
165
|
+
collection_config = {
|
|
166
|
+
"vectors": {
|
|
167
|
+
self.vector_name: {
|
|
168
|
+
"size": vector_size,
|
|
169
|
+
"distance": "Cosine"
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
logger.info(f"创建集合 {self.collection_name},向量名称: {self.vector_name},向量维度: {vector_size}")
|
|
175
|
+
|
|
176
|
+
response = await self._make_request(
|
|
177
|
+
"PUT",
|
|
178
|
+
f"/collections/{self.collection_name}",
|
|
179
|
+
json=collection_config
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
if response.status_code in [200, 201]:
|
|
183
|
+
logger.info(f"成功创建集合 {self.collection_name}")
|
|
184
|
+
return True
|
|
185
|
+
else:
|
|
186
|
+
logger.error(f"创建集合失败: {response.status_code} - {response.text}")
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
except Exception as e:
|
|
190
|
+
logger.error(f"确保集合存在时出错: {e}")
|
|
191
|
+
return False
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
async def record_knowledge(self, document: str, metadata: Optional[Dict[str, Any]] = None, point_id: Optional[Union[str, int]] = None) -> Dict[str, Any]:
|
|
195
|
+
"""记录知识到Qdrant,使用document+metadata格式"""
|
|
196
|
+
try:
|
|
197
|
+
# 确保集合存在
|
|
198
|
+
if not await self.ensure_collection_exists():
|
|
199
|
+
raise Exception("无法创建或访问集合")
|
|
200
|
+
|
|
201
|
+
# 生成嵌入向量
|
|
202
|
+
vector = await self.generate_embedding(document)
|
|
203
|
+
|
|
204
|
+
# 准备点数据
|
|
205
|
+
if point_id is None:
|
|
206
|
+
point_id = str(uuid.uuid4())
|
|
207
|
+
|
|
208
|
+
# 使用document+metadata格式
|
|
209
|
+
payload = {
|
|
210
|
+
"document": document
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
# 如果有metadata,添加到payload中
|
|
214
|
+
if metadata:
|
|
215
|
+
payload["metadata"] = metadata
|
|
216
|
+
|
|
217
|
+
point_data = {
|
|
218
|
+
"points": [{
|
|
219
|
+
"id": point_id,
|
|
220
|
+
"vector": {
|
|
221
|
+
self.vector_name: vector # 使用配置的向量名称
|
|
222
|
+
},
|
|
223
|
+
"payload": payload
|
|
224
|
+
}]
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
# 上传到Qdrant
|
|
228
|
+
response = await self._make_request(
|
|
229
|
+
"PUT",
|
|
230
|
+
f"/collections/{self.collection_name}/points",
|
|
231
|
+
json=point_data
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
if response.status_code in [200, 201]:
|
|
235
|
+
result = response.json()
|
|
236
|
+
return {
|
|
237
|
+
"success": True,
|
|
238
|
+
"point_id": point_id,
|
|
239
|
+
"operation_id": result.get("result", {}).get("operation_id"),
|
|
240
|
+
"message": "知识记录成功"
|
|
241
|
+
}
|
|
242
|
+
else:
|
|
243
|
+
raise Exception(f"记录失败: {response.status_code} - {response.text}")
|
|
244
|
+
|
|
245
|
+
except Exception as e:
|
|
246
|
+
logger.error(f"记录知识失败: {e}")
|
|
247
|
+
return {
|
|
248
|
+
"success": False,
|
|
249
|
+
"error": str(e)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async def search_knowledge(self, query: str, limit: int = 5, score_threshold: float = 0.0, filter_conditions: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
253
|
+
"""搜索相关知识"""
|
|
254
|
+
try:
|
|
255
|
+
# 确保集合存在
|
|
256
|
+
if not await self.ensure_collection_exists():
|
|
257
|
+
raise Exception("无法访问集合")
|
|
258
|
+
|
|
259
|
+
# 生成查询向量
|
|
260
|
+
query_vector = await self.generate_embedding(query)
|
|
261
|
+
|
|
262
|
+
# 准备搜索请求,使用配置的向量名称
|
|
263
|
+
search_data = {
|
|
264
|
+
"vector": {
|
|
265
|
+
"name": self.vector_name, # 使用配置的向量名称
|
|
266
|
+
"vector": query_vector
|
|
267
|
+
},
|
|
268
|
+
"limit": limit,
|
|
269
|
+
"score_threshold": score_threshold,
|
|
270
|
+
"with_payload": True,
|
|
271
|
+
"with_vector": False
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
# 添加过滤条件
|
|
275
|
+
if filter_conditions:
|
|
276
|
+
search_data["filter"] = filter_conditions
|
|
277
|
+
|
|
278
|
+
# 执行搜索
|
|
279
|
+
response = await self._make_request(
|
|
280
|
+
"POST",
|
|
281
|
+
f"/collections/{self.collection_name}/points/search",
|
|
282
|
+
json=search_data
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
if response.status_code == 200:
|
|
286
|
+
result = response.json()
|
|
287
|
+
search_results = result.get("result", [])
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
"success": True,
|
|
291
|
+
"query": query,
|
|
292
|
+
"results": [
|
|
293
|
+
{
|
|
294
|
+
"id": item["id"],
|
|
295
|
+
"score": item["score"],
|
|
296
|
+
"document": item["payload"].get("document", item["payload"].get("text", "")), # 兼容新旧格式
|
|
297
|
+
"metadata": item["payload"].get("metadata", {k: v for k, v in item["payload"].items() if k not in ["document", "text"]})
|
|
298
|
+
}
|
|
299
|
+
for item in search_results
|
|
300
|
+
],
|
|
301
|
+
"total_found": len(search_results)
|
|
302
|
+
}
|
|
303
|
+
else:
|
|
304
|
+
raise Exception(f"搜索失败: {response.status_code} - {response.text}")
|
|
305
|
+
|
|
306
|
+
except Exception as e:
|
|
307
|
+
logger.error(f"搜索知识失败: {e}")
|
|
308
|
+
return {
|
|
309
|
+
"success": False,
|
|
310
|
+
"error": str(e)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async def update_knowledge(self, point_id: Union[str, int], document: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
314
|
+
"""更新已存储的知识条目,使用document+metadata格式"""
|
|
315
|
+
try:
|
|
316
|
+
# 确保集合存在
|
|
317
|
+
if not await self.ensure_collection_exists():
|
|
318
|
+
raise Exception("无法访问集合")
|
|
319
|
+
|
|
320
|
+
# 获取现有点数据
|
|
321
|
+
response = await self._make_request(
|
|
322
|
+
"GET",
|
|
323
|
+
f"/collections/{self.collection_name}/points/{point_id}"
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
if response.status_code != 200:
|
|
327
|
+
raise Exception(f"找不到ID为 {point_id} 的知识条目")
|
|
328
|
+
|
|
329
|
+
existing_data = response.json()["result"]
|
|
330
|
+
existing_payload = existing_data.get("payload", {})
|
|
331
|
+
|
|
332
|
+
# 准备更新数据,使用document+metadata格式
|
|
333
|
+
update_payload = {}
|
|
334
|
+
|
|
335
|
+
# 更新文档内容和向量(如果提供了新文档)
|
|
336
|
+
if document is not None:
|
|
337
|
+
update_payload["document"] = document
|
|
338
|
+
vector = {
|
|
339
|
+
self.vector_name: await self.generate_embedding(document) # 使用配置的向量名称
|
|
340
|
+
}
|
|
341
|
+
else:
|
|
342
|
+
# 保持原有文档内容
|
|
343
|
+
update_payload["document"] = existing_payload.get("document", existing_payload.get("text", ""))
|
|
344
|
+
vector = existing_data.get("vector")
|
|
345
|
+
|
|
346
|
+
# 更新元数据
|
|
347
|
+
if metadata:
|
|
348
|
+
update_payload["metadata"] = metadata
|
|
349
|
+
else:
|
|
350
|
+
# 保持原有元数据
|
|
351
|
+
update_payload["metadata"] = existing_payload.get("metadata", {})
|
|
352
|
+
|
|
353
|
+
# 执行更新
|
|
354
|
+
point_data = {
|
|
355
|
+
"points": [{
|
|
356
|
+
"id": point_id,
|
|
357
|
+
"vector": vector,
|
|
358
|
+
"payload": update_payload
|
|
359
|
+
}]
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
response = await self._make_request(
|
|
363
|
+
"PUT",
|
|
364
|
+
f"/collections/{self.collection_name}/points",
|
|
365
|
+
json=point_data
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
if response.status_code in [200, 201]:
|
|
369
|
+
result = response.json()
|
|
370
|
+
return {
|
|
371
|
+
"success": True,
|
|
372
|
+
"point_id": point_id,
|
|
373
|
+
"operation_id": result.get("result", {}).get("operation_id"),
|
|
374
|
+
"message": "知识更新成功"
|
|
375
|
+
}
|
|
376
|
+
else:
|
|
377
|
+
raise Exception(f"更新失败: {response.status_code} - {response.text}")
|
|
378
|
+
|
|
379
|
+
except Exception as e:
|
|
380
|
+
logger.error(f"更新知识失败: {e}")
|
|
381
|
+
return {
|
|
382
|
+
"success": False,
|
|
383
|
+
"error": str(e)
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async def delete_knowledge(self, point_ids: Union[str, int, List[Union[str, int]]], filter_conditions: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
387
|
+
"""删除知识条目"""
|
|
388
|
+
try:
|
|
389
|
+
# 确保集合存在
|
|
390
|
+
if not await self.ensure_collection_exists():
|
|
391
|
+
raise Exception("无法访问集合")
|
|
392
|
+
|
|
393
|
+
# 准备删除请求
|
|
394
|
+
delete_data = {}
|
|
395
|
+
|
|
396
|
+
if point_ids is not None:
|
|
397
|
+
# 按ID删除
|
|
398
|
+
if not isinstance(point_ids, list):
|
|
399
|
+
point_ids = [point_ids]
|
|
400
|
+
delete_data["points"] = point_ids
|
|
401
|
+
|
|
402
|
+
if filter_conditions:
|
|
403
|
+
# 按条件删除
|
|
404
|
+
delete_data["filter"] = filter_conditions
|
|
405
|
+
|
|
406
|
+
if not delete_data:
|
|
407
|
+
raise Exception("必须提供point_ids或filter_conditions")
|
|
408
|
+
|
|
409
|
+
# 执行删除
|
|
410
|
+
response = await self._make_request(
|
|
411
|
+
"POST",
|
|
412
|
+
f"/collections/{self.collection_name}/points/delete",
|
|
413
|
+
json=delete_data
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
if response.status_code == 200:
|
|
417
|
+
result = response.json()
|
|
418
|
+
return {
|
|
419
|
+
"success": True,
|
|
420
|
+
"operation_id": result.get("result", {}).get("operation_id"),
|
|
421
|
+
"message": "知识删除成功"
|
|
422
|
+
}
|
|
423
|
+
else:
|
|
424
|
+
raise Exception(f"删除失败: {response.status_code} - {response.text}")
|
|
425
|
+
|
|
426
|
+
except Exception as e:
|
|
427
|
+
logger.error(f"删除知识失败: {e}")
|
|
428
|
+
return {
|
|
429
|
+
"success": False,
|
|
430
|
+
"error": str(e)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async def close(self):
|
|
434
|
+
"""优雅关闭HTTP客户端"""
|
|
435
|
+
try:
|
|
436
|
+
if self.http_client and not self.http_client.is_closed:
|
|
437
|
+
await self.http_client.aclose()
|
|
438
|
+
logger.info("HTTP客户端已关闭")
|
|
439
|
+
except Exception as e:
|
|
440
|
+
logger.warning(f"关闭HTTP客户端时出错: {e}")
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
def create_server() -> Server:
|
|
444
|
+
"""创建并配置MCP服务器"""
|
|
445
|
+
# 创建全局实例
|
|
446
|
+
qdrant_server = QdrantMCPServer()
|
|
447
|
+
|
|
448
|
+
# 创建MCP服务器
|
|
449
|
+
server = Server("qdrant-mcp")
|
|
450
|
+
|
|
451
|
+
@server.list_tools()
|
|
452
|
+
async def handle_list_tools() -> list[types.Tool]:
|
|
453
|
+
"""列出可用的工具"""
|
|
454
|
+
return [
|
|
455
|
+
types.Tool(
|
|
456
|
+
name="qdrant-record",
|
|
457
|
+
description="将文档信息转换为向量并存储到Qdrant集合中,使用document+metadata格式",
|
|
458
|
+
inputSchema={
|
|
459
|
+
"type": "object",
|
|
460
|
+
"properties": {
|
|
461
|
+
"document": {
|
|
462
|
+
"type": "string",
|
|
463
|
+
"description": "要存储的文档内容"
|
|
464
|
+
},
|
|
465
|
+
"metadata": {
|
|
466
|
+
"type": "object",
|
|
467
|
+
"description": "可选的元数据信息",
|
|
468
|
+
"additionalProperties": True
|
|
469
|
+
},
|
|
470
|
+
"point_id": {
|
|
471
|
+
"type": ["string", "integer", "null"],
|
|
472
|
+
"description": "可选的点ID,如果不提供将自动生成UUID"
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
"required": ["document"]
|
|
476
|
+
}
|
|
477
|
+
),
|
|
478
|
+
types.Tool(
|
|
479
|
+
name="qdrant-search",
|
|
480
|
+
description="基于语义相似度搜索相关知识条目",
|
|
481
|
+
inputSchema={
|
|
482
|
+
"type": "object",
|
|
483
|
+
"properties": {
|
|
484
|
+
"query": {
|
|
485
|
+
"type": "string",
|
|
486
|
+
"description": "搜索查询文本"
|
|
487
|
+
},
|
|
488
|
+
"limit": {
|
|
489
|
+
"type": "integer",
|
|
490
|
+
"description": "返回结果数量限制",
|
|
491
|
+
"default": 5,
|
|
492
|
+
"minimum": 1,
|
|
493
|
+
"maximum": 100
|
|
494
|
+
},
|
|
495
|
+
"score_threshold": {
|
|
496
|
+
"type": "number",
|
|
497
|
+
"description": "相似度分数阈值",
|
|
498
|
+
"default": 0.0,
|
|
499
|
+
"minimum": 0.0,
|
|
500
|
+
"maximum": 1.0
|
|
501
|
+
},
|
|
502
|
+
"filter_conditions": {
|
|
503
|
+
"type": "object",
|
|
504
|
+
"description": "可选的过滤条件",
|
|
505
|
+
"additionalProperties": True
|
|
506
|
+
}
|
|
507
|
+
},
|
|
508
|
+
"required": ["query"]
|
|
509
|
+
}
|
|
510
|
+
),
|
|
511
|
+
types.Tool(
|
|
512
|
+
name="qdrant-update",
|
|
513
|
+
description="修改已存储的知识条目内容或元数据,使用document+metadata格式",
|
|
514
|
+
inputSchema={
|
|
515
|
+
"type": "object",
|
|
516
|
+
"properties": {
|
|
517
|
+
"point_id": {
|
|
518
|
+
"type": ["string", "integer"],
|
|
519
|
+
"description": "要更新的点ID"
|
|
520
|
+
},
|
|
521
|
+
"document": {
|
|
522
|
+
"type": "string",
|
|
523
|
+
"description": "新的文档内容(可选)"
|
|
524
|
+
},
|
|
525
|
+
"metadata": {
|
|
526
|
+
"type": "object",
|
|
527
|
+
"description": "要更新的元数据",
|
|
528
|
+
"additionalProperties": True
|
|
529
|
+
}
|
|
530
|
+
},
|
|
531
|
+
"required": ["point_id"]
|
|
532
|
+
}
|
|
533
|
+
),
|
|
534
|
+
types.Tool(
|
|
535
|
+
name="qdrant-delete",
|
|
536
|
+
description="删除指定的知识条目",
|
|
537
|
+
inputSchema={
|
|
538
|
+
"type": "object",
|
|
539
|
+
"properties": {
|
|
540
|
+
"point_ids": {
|
|
541
|
+
"type": ["string", "integer", "array"],
|
|
542
|
+
"description": "要删除的点ID或ID列表",
|
|
543
|
+
"items": {
|
|
544
|
+
"type": ["string", "integer"]
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
"filter_conditions": {
|
|
548
|
+
"type": "object",
|
|
549
|
+
"description": "按条件删除的过滤器",
|
|
550
|
+
"additionalProperties": True
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
)
|
|
555
|
+
]
|
|
556
|
+
|
|
557
|
+
@server.call_tool()
|
|
558
|
+
async def handle_call_tool(name: str, arguments: Dict[str, Any]) -> list[types.TextContent]:
|
|
559
|
+
"""处理工具调用"""
|
|
560
|
+
try:
|
|
561
|
+
if name == "qdrant-record":
|
|
562
|
+
result = await qdrant_server.record_knowledge(
|
|
563
|
+
document=arguments["document"],
|
|
564
|
+
metadata=arguments.get("metadata"),
|
|
565
|
+
point_id=arguments.get("point_id")
|
|
566
|
+
)
|
|
567
|
+
elif name == "qdrant-search":
|
|
568
|
+
result = await qdrant_server.search_knowledge(
|
|
569
|
+
query=arguments["query"],
|
|
570
|
+
limit=arguments.get("limit", 5),
|
|
571
|
+
score_threshold=arguments.get("score_threshold", 0.0),
|
|
572
|
+
filter_conditions=arguments.get("filter_conditions")
|
|
573
|
+
)
|
|
574
|
+
elif name == "qdrant-update":
|
|
575
|
+
result = await qdrant_server.update_knowledge(
|
|
576
|
+
point_id=arguments["point_id"],
|
|
577
|
+
document=arguments.get("document"),
|
|
578
|
+
metadata=arguments.get("metadata")
|
|
579
|
+
)
|
|
580
|
+
elif name == "qdrant-delete":
|
|
581
|
+
result = await qdrant_server.delete_knowledge(
|
|
582
|
+
point_ids=arguments.get("point_ids"),
|
|
583
|
+
filter_conditions=arguments.get("filter_conditions")
|
|
584
|
+
)
|
|
585
|
+
else:
|
|
586
|
+
raise ValueError(f"未知工具: {name}")
|
|
587
|
+
|
|
588
|
+
return [
|
|
589
|
+
types.TextContent(
|
|
590
|
+
type="text",
|
|
591
|
+
text=json.dumps(result, ensure_ascii=False, indent=2)
|
|
592
|
+
)
|
|
593
|
+
]
|
|
594
|
+
|
|
595
|
+
except Exception as e:
|
|
596
|
+
logger.error(f"工具调用失败 {name}: {e}")
|
|
597
|
+
return [
|
|
598
|
+
types.TextContent(
|
|
599
|
+
type="text",
|
|
600
|
+
text=json.dumps({
|
|
601
|
+
"success": False,
|
|
602
|
+
"error": str(e)
|
|
603
|
+
}, ensure_ascii=False, indent=2)
|
|
604
|
+
)
|
|
605
|
+
]
|
|
606
|
+
|
|
607
|
+
return server, qdrant_server
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qdrant-mcp-server
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Model Context Protocol (MCP) server for Qdrant vector database with semantic search capabilities
|
|
5
|
+
Author-email: fiyen <623320480@qq.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/fiyen/qdrant-mcp-server
|
|
8
|
+
Project-URL: Repository, https://github.com/fiyen/qdrant-mcp-server
|
|
9
|
+
Project-URL: Documentation, https://github.com/fiyen/qdrant-mcp-server#readme
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/fiyen/qdrant-mcp-server/issues
|
|
11
|
+
Keywords: mcp,qdrant,vector-database,semantic-search,embeddings,ai
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: mcp>=1.0.0
|
|
25
|
+
Requires-Dist: httpx>=0.27.0
|
|
26
|
+
Requires-Dist: sentence-transformers>=2.2.0
|
|
27
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
31
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# Qdrant MCP Server
|
|
36
|
+
|
|
37
|
+
[](https://badge.fury.io/py/qdrant-mcp-server)
|
|
38
|
+
[](https://www.python.org/downloads/)
|
|
39
|
+
[](https://opensource.org/licenses/MIT)
|
|
40
|
+
|
|
41
|
+
一个基于 [Qdrant](https://qdrant.tech/) 向量数据库的 [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) 服务器,提供强大的语义搜索和知识管理功能。
|
|
42
|
+
|
|
43
|
+
## ✨ 特性
|
|
44
|
+
|
|
45
|
+
- 🚀 **即插即用**: 使用 `uvx` 一键运行,无需复杂配置
|
|
46
|
+
- 🔍 **语义搜索**: 基于向量相似度的智能搜索
|
|
47
|
+
- 🧠 **多种嵌入模型**: 支持本地 Sentence-Transformers 和在线 Jina AI
|
|
48
|
+
- 📝 **完整 CRUD**: 创建、读取、更新、删除知识条目
|
|
49
|
+
- 🔧 **灵活配置**: 通过环境变量自定义各项参数
|
|
50
|
+
- 🌐 **标准协议**: 完全符合 MCP 规范
|
|
51
|
+
|
|
52
|
+
## 📦 安装
|
|
53
|
+
|
|
54
|
+
### 使用 uvx (推荐)
|
|
55
|
+
|
|
56
|
+
最简单的方式是使用 `uvx` 直接运行:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
uvx qdrant-mcp-server
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 使用 uv
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
uv pip install qdrant-mcp-server
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 使用 pip
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install qdrant-mcp-server
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 🚀 快速开始
|
|
75
|
+
|
|
76
|
+
### 1. 配置环境变量
|
|
77
|
+
|
|
78
|
+
创建 `.env` 文件或设置环境变量:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Qdrant 连接配置
|
|
82
|
+
QDRANT_URL=https://your-qdrant-instance.com:443
|
|
83
|
+
QDRANT_API_KEY=your-api-key-here
|
|
84
|
+
COLLECTION_NAME=my_knowledge_base
|
|
85
|
+
|
|
86
|
+
# 嵌入模型配置 (二选一)
|
|
87
|
+
# 选项1: 使用本地模型 (默认)
|
|
88
|
+
EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
|
|
89
|
+
VECTOR_NAME=fast-all-minilm-l6-v2
|
|
90
|
+
|
|
91
|
+
# 选项2: 使用 Jina AI 在线模型
|
|
92
|
+
# EMBEDDING_MODEL=jina-embeddings-v3
|
|
93
|
+
# JINA_TOKEN=your-jina-api-key
|
|
94
|
+
# VECTOR_NAME=jina-embeddings-v3
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 2. 在 MCP 客户端中配置
|
|
98
|
+
|
|
99
|
+
#### Claude Desktop 配置
|
|
100
|
+
|
|
101
|
+
编辑 `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"mcpServers": {
|
|
106
|
+
"qdrant": {
|
|
107
|
+
"command": "uvx",
|
|
108
|
+
"args": ["qdrant-mcp-server"],
|
|
109
|
+
"env": {
|
|
110
|
+
"QDRANT_URL": "https://your-instance.com:443",
|
|
111
|
+
"QDRANT_API_KEY": "your-api-key",
|
|
112
|
+
"COLLECTION_NAME": "knowledge_base"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### Roo Code / OpenCode 配置
|
|
120
|
+
|
|
121
|
+
编辑配置文件 (如 `~/.config/opencode/opencode.json`):
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"mcp": {
|
|
126
|
+
"qdrant": {
|
|
127
|
+
"type": "local",
|
|
128
|
+
"command": ["uvx", "qdrant-mcp-server"],
|
|
129
|
+
"environment": {
|
|
130
|
+
"QDRANT_URL": "https://your-instance.com:443",
|
|
131
|
+
"QDRANT_API_KEY": "your-api-key",
|
|
132
|
+
"COLLECTION_NAME": "knowledge_base"
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 🛠️ 可用工具
|
|
140
|
+
|
|
141
|
+
### qdrant-record
|
|
142
|
+
|
|
143
|
+
将文档存储到 Qdrant 向量数据库。
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
# 参数
|
|
147
|
+
{
|
|
148
|
+
"document": "要存储的文档内容", # 必需
|
|
149
|
+
"metadata": { # 可选
|
|
150
|
+
"source": "example.pdf",
|
|
151
|
+
"author": "John Doe",
|
|
152
|
+
"date": "2024-01-01"
|
|
153
|
+
},
|
|
154
|
+
"point_id": "custom-id-123" # 可选,不提供则自动生成
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### qdrant-search
|
|
159
|
+
|
|
160
|
+
基于语义相似度搜索知识。
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
# 参数
|
|
164
|
+
{
|
|
165
|
+
"query": "搜索关键词", # 必需
|
|
166
|
+
"limit": 5, # 可选,默认 5
|
|
167
|
+
"score_threshold": 0.7, # 可选,默认 0.0
|
|
168
|
+
"filter_conditions": { # 可选
|
|
169
|
+
"must": [
|
|
170
|
+
{
|
|
171
|
+
"key": "metadata.source",
|
|
172
|
+
"match": {"value": "example.pdf"}
|
|
173
|
+
}
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### qdrant-update
|
|
180
|
+
|
|
181
|
+
更新已存储的知识条目。
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
# 参数
|
|
185
|
+
{
|
|
186
|
+
"point_id": "existing-id", # 必需
|
|
187
|
+
"document": "新的文档内容", # 可选
|
|
188
|
+
"metadata": { # 可选
|
|
189
|
+
"updated": "2024-01-02"
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### qdrant-delete
|
|
195
|
+
|
|
196
|
+
删除知识条目。
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
# 按 ID 删除
|
|
200
|
+
{
|
|
201
|
+
"point_ids": "single-id" # 或 ["id1", "id2"]
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
# 按条件删除
|
|
205
|
+
{
|
|
206
|
+
"filter_conditions": {
|
|
207
|
+
"must": [
|
|
208
|
+
{
|
|
209
|
+
"key": "metadata.source",
|
|
210
|
+
"match": {"value": "old-doc.pdf"}
|
|
211
|
+
}
|
|
212
|
+
]
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## 🔧 配置选项
|
|
218
|
+
|
|
219
|
+
| 环境变量 | 默认值 | 说明 |
|
|
220
|
+
|---------|--------|------|
|
|
221
|
+
| `QDRANT_URL` | `http://localhost:6333` | Qdrant 服务器地址 |
|
|
222
|
+
| `QDRANT_API_KEY` | - | Qdrant API 密钥 (云服务需要) |
|
|
223
|
+
| `COLLECTION_NAME` | `default_collection` | 集合名称 |
|
|
224
|
+
| `EMBEDDING_MODEL` | `sentence-transformers/all-MiniLM-L6-v2` | 嵌入模型 |
|
|
225
|
+
| `VECTOR_NAME` | 自动检测 | 向量字段名称 |
|
|
226
|
+
| `JINA_TOKEN` | - | Jina AI API 密钥 (使用 Jina 时需要) |
|
|
227
|
+
|
|
228
|
+
## 🌟 使用示例
|
|
229
|
+
|
|
230
|
+
### 1. 存储知识
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
请将这段内容存储到知识库:
|
|
234
|
+
"人工智能是计算机科学的一个分支,致力于创建能够执行通常需要人类智能的任务的系统。"
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 2. 搜索知识
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
在知识库中搜索关于"机器学习"的内容
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### 3. 更新知识
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
更新 ID 为 "doc-123" 的文档,添加元数据 {"verified": true}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## 🏗️ 开发
|
|
250
|
+
|
|
251
|
+
### 从源码安装
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
git clone https://github.com/fiyen/qdrant-mcp-server.git
|
|
255
|
+
cd qdrant-mcp-server
|
|
256
|
+
uv pip install -e ".[dev]"
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### 运行测试
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
pytest
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### 代码格式化
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
black src/
|
|
269
|
+
ruff check src/
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## 📚 文档
|
|
273
|
+
|
|
274
|
+
- [MCP 协议文档](https://modelcontextprotocol.io/)
|
|
275
|
+
- [Qdrant 文档](https://qdrant.tech/documentation/)
|
|
276
|
+
- [Sentence Transformers](https://www.sbert.net/)
|
|
277
|
+
- [Jina AI Embeddings](https://jina.ai/embeddings/)
|
|
278
|
+
|
|
279
|
+
## 🤝 贡献
|
|
280
|
+
|
|
281
|
+
欢迎提交 Issue 和 Pull Request!
|
|
282
|
+
|
|
283
|
+
## 📄 许可证
|
|
284
|
+
|
|
285
|
+
MIT License - 详见 [LICENSE](LICENSE) 文件
|
|
286
|
+
|
|
287
|
+
## 🙏 致谢
|
|
288
|
+
|
|
289
|
+
- [Qdrant](https://qdrant.tech/) - 高性能向量数据库
|
|
290
|
+
- [Anthropic](https://www.anthropic.com/) - MCP 协议
|
|
291
|
+
- [Sentence Transformers](https://www.sbert.net/) - 嵌入模型
|
|
292
|
+
- [Jina AI](https://jina.ai/) - 在线嵌入服务
|
|
293
|
+
|
|
294
|
+
## 📧 联系
|
|
295
|
+
|
|
296
|
+
- 作者: fiyen
|
|
297
|
+
- Email: 623320480@qq.com
|
|
298
|
+
- GitHub: [@fiyen](https://github.com/fiyen)
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
⭐ 如果这个项目对您有帮助,请给个 Star!
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
qdrant_mcp_server/__init__.py,sha256=RVBdaMflB321UmTaRQyP8_wLKBY4S_m63j7U8b-aqWM,369
|
|
2
|
+
qdrant_mcp_server/__main__.py,sha256=DRqZCAzfAnZ7Po6WjLRLmt23X98vC5gHkN9s6CCg-hA,1171
|
|
3
|
+
qdrant_mcp_server/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
qdrant_mcp_server/server.py,sha256=SKn0WxNcXWELmJEKjS1C9TGk_0sMCsLJdvkN9Xe4pSw,24239
|
|
5
|
+
qdrant_mcp_server-0.1.0.dist-info/licenses/LICENSE,sha256=qAepqbBtrJD-C4ZO6lxaAxM7MKmfJKMxcwraV79MV9Q,1083
|
|
6
|
+
qdrant_mcp_server-0.1.0.dist-info/METADATA,sha256=ovS1FWcuQN80GB9ngWFNA8KQJewySWceLWkFRJI0lPw,7604
|
|
7
|
+
qdrant_mcp_server-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
8
|
+
qdrant_mcp_server-0.1.0.dist-info/entry_points.txt,sha256=3DhxwrVvCbb1tW0JkwAukF1KdafC8QxLQBVwa3-Jn_I,69
|
|
9
|
+
qdrant_mcp_server-0.1.0.dist-info/top_level.txt,sha256=dF-2lRto5n9Ve4e_aP4ft7OjAyCg2mKVM9iAnXalkyQ,18
|
|
10
|
+
qdrant_mcp_server-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 fiyen
|
|
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 @@
|
|
|
1
|
+
qdrant_mcp_server
|