@mhalder/qdrant-mcp-server 2.1.0 → 2.1.2
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.
- package/CHANGELOG.md +12 -0
- package/CONTRIBUTING.md +14 -2
- package/README.md +3 -2
- package/build/code/indexer.d.ts +5 -0
- package/build/code/indexer.d.ts.map +1 -1
- package/build/code/indexer.js +116 -14
- package/build/code/indexer.js.map +1 -1
- package/build/code/types.d.ts +4 -0
- package/build/code/types.d.ts.map +1 -1
- package/build/index.js +28 -831
- package/build/index.js.map +1 -1
- package/build/prompts/register.d.ts +10 -0
- package/build/prompts/register.d.ts.map +1 -0
- package/build/prompts/register.js +50 -0
- package/build/prompts/register.js.map +1 -0
- package/build/resources/index.d.ts +10 -0
- package/build/resources/index.d.ts.map +1 -0
- package/build/resources/index.js +60 -0
- package/build/resources/index.js.map +1 -0
- package/build/tools/code.d.ts +10 -0
- package/build/tools/code.d.ts.map +1 -0
- package/build/tools/code.js +132 -0
- package/build/tools/code.js.map +1 -0
- package/build/tools/collection.d.ts +12 -0
- package/build/tools/collection.d.ts.map +1 -0
- package/build/tools/collection.js +59 -0
- package/build/tools/collection.js.map +1 -0
- package/build/tools/document.d.ts +12 -0
- package/build/tools/document.d.ts.map +1 -0
- package/build/tools/document.js +84 -0
- package/build/tools/document.js.map +1 -0
- package/build/tools/index.d.ts +18 -0
- package/build/tools/index.d.ts.map +1 -0
- package/build/tools/index.js +30 -0
- package/build/tools/index.js.map +1 -0
- package/build/tools/schemas.d.ts +75 -0
- package/build/tools/schemas.d.ts.map +1 -0
- package/build/tools/schemas.js +114 -0
- package/build/tools/schemas.js.map +1 -0
- package/build/tools/search.d.ts +12 -0
- package/build/tools/search.d.ts.map +1 -0
- package/build/tools/search.js +79 -0
- package/build/tools/search.js.map +1 -0
- package/examples/code-search/README.md +19 -4
- package/package.json +1 -1
- package/src/code/indexer.ts +186 -38
- package/src/code/types.ts +5 -0
- package/src/index.ts +26 -983
- package/src/prompts/register.ts +71 -0
- package/src/resources/index.ts +79 -0
- package/src/tools/code.ts +195 -0
- package/src/tools/collection.ts +100 -0
- package/src/tools/document.ts +113 -0
- package/src/tools/index.ts +48 -0
- package/src/tools/schemas.ts +130 -0
- package/src/tools/search.ts +122 -0
- package/tests/code/indexer.test.ts +412 -74
- package/tests/code/integration.test.ts +239 -54
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt registration module for McpServer
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { renderTemplate, validateArguments } from "./template.js";
|
|
8
|
+
import type { PromptArgument, PromptsConfig } from "./types.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Build a Zod schema object for prompt arguments
|
|
12
|
+
*/
|
|
13
|
+
function buildArgsSchema(args: PromptArgument[]): Record<string, z.ZodTypeAny> {
|
|
14
|
+
const schema: Record<string, z.ZodTypeAny> = {};
|
|
15
|
+
|
|
16
|
+
for (const arg of args) {
|
|
17
|
+
const fieldSchema = z.string().describe(arg.description);
|
|
18
|
+
schema[arg.name] = arg.required ? fieldSchema : fieldSchema.optional();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return schema;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Register all prompts from configuration on the server
|
|
26
|
+
*/
|
|
27
|
+
export function registerAllPrompts(
|
|
28
|
+
server: McpServer,
|
|
29
|
+
config: PromptsConfig | null,
|
|
30
|
+
): void {
|
|
31
|
+
if (!config) {
|
|
32
|
+
return; // No prompts = no prompts capability
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const prompt of config.prompts) {
|
|
36
|
+
const argsSchema = buildArgsSchema(prompt.arguments);
|
|
37
|
+
|
|
38
|
+
server.registerPrompt(
|
|
39
|
+
prompt.name,
|
|
40
|
+
{
|
|
41
|
+
title: prompt.name,
|
|
42
|
+
description: prompt.description,
|
|
43
|
+
argsSchema,
|
|
44
|
+
},
|
|
45
|
+
(args) => {
|
|
46
|
+
// Validate arguments
|
|
47
|
+
const argsRecord = (args || {}) as Record<string, string>;
|
|
48
|
+
validateArguments(argsRecord, prompt.arguments);
|
|
49
|
+
|
|
50
|
+
// Render template
|
|
51
|
+
const rendered = renderTemplate(
|
|
52
|
+
prompt.template,
|
|
53
|
+
argsRecord,
|
|
54
|
+
prompt.arguments,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
messages: [
|
|
59
|
+
{
|
|
60
|
+
role: "user",
|
|
61
|
+
content: {
|
|
62
|
+
type: "text",
|
|
63
|
+
text: rendered.text,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resource registration module
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
McpServer,
|
|
7
|
+
ResourceTemplate,
|
|
8
|
+
} from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
import type { QdrantManager } from "../qdrant/client.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Register all MCP resources on the server
|
|
13
|
+
*/
|
|
14
|
+
export function registerAllResources(
|
|
15
|
+
server: McpServer,
|
|
16
|
+
qdrant: QdrantManager,
|
|
17
|
+
): void {
|
|
18
|
+
// Static resource: list all collections
|
|
19
|
+
server.registerResource(
|
|
20
|
+
"collections",
|
|
21
|
+
"qdrant://collections",
|
|
22
|
+
{
|
|
23
|
+
title: "All Collections",
|
|
24
|
+
description: "List of all vector collections in Qdrant",
|
|
25
|
+
mimeType: "application/json",
|
|
26
|
+
},
|
|
27
|
+
async (uri) => {
|
|
28
|
+
const collections = await qdrant.listCollections();
|
|
29
|
+
return {
|
|
30
|
+
contents: [
|
|
31
|
+
{
|
|
32
|
+
uri: uri.href,
|
|
33
|
+
mimeType: "application/json",
|
|
34
|
+
text: JSON.stringify(collections, null, 2),
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Dynamic resource: individual collection info
|
|
42
|
+
server.registerResource(
|
|
43
|
+
"collection-info",
|
|
44
|
+
new ResourceTemplate("qdrant://collection/{name}", {
|
|
45
|
+
list: async () => {
|
|
46
|
+
const collections = await qdrant.listCollections();
|
|
47
|
+
return {
|
|
48
|
+
resources: collections.map((name) => ({
|
|
49
|
+
uri: `qdrant://collection/${name}`,
|
|
50
|
+
name: `Collection: ${name}`,
|
|
51
|
+
description: `Details and statistics for collection "${name}"`,
|
|
52
|
+
mimeType: "application/json",
|
|
53
|
+
})),
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
{
|
|
58
|
+
title: "Collection Details",
|
|
59
|
+
description: "Detailed information about a specific collection",
|
|
60
|
+
mimeType: "application/json",
|
|
61
|
+
},
|
|
62
|
+
async (uri, params) => {
|
|
63
|
+
const name = params.name;
|
|
64
|
+
if (typeof name !== "string" || !name) {
|
|
65
|
+
throw new Error("Invalid collection name parameter");
|
|
66
|
+
}
|
|
67
|
+
const info = await qdrant.getCollectionInfo(name);
|
|
68
|
+
return {
|
|
69
|
+
contents: [
|
|
70
|
+
{
|
|
71
|
+
uri: uri.href,
|
|
72
|
+
mimeType: "application/json",
|
|
73
|
+
text: JSON.stringify(info, null, 2),
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code indexing tools registration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import type { CodeIndexer } from "../code/indexer.js";
|
|
7
|
+
import * as schemas from "./schemas.js";
|
|
8
|
+
|
|
9
|
+
export interface CodeToolDependencies {
|
|
10
|
+
codeIndexer: CodeIndexer;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function registerCodeTools(
|
|
14
|
+
server: McpServer,
|
|
15
|
+
deps: CodeToolDependencies,
|
|
16
|
+
): void {
|
|
17
|
+
const { codeIndexer } = deps;
|
|
18
|
+
|
|
19
|
+
// index_codebase
|
|
20
|
+
server.registerTool(
|
|
21
|
+
"index_codebase",
|
|
22
|
+
{
|
|
23
|
+
title: "Index Codebase",
|
|
24
|
+
description:
|
|
25
|
+
"Index a codebase for semantic code search. Automatically discovers files, chunks code intelligently using AST-aware parsing, and stores in vector database. Respects .gitignore and other ignore files.",
|
|
26
|
+
inputSchema: schemas.IndexCodebaseSchema,
|
|
27
|
+
},
|
|
28
|
+
async ({ path, forceReindex, extensions, ignorePatterns }) => {
|
|
29
|
+
const stats = await codeIndexer.indexCodebase(
|
|
30
|
+
path,
|
|
31
|
+
{ forceReindex, extensions, ignorePatterns },
|
|
32
|
+
(progress) => {
|
|
33
|
+
// Progress callback - could send progress updates via SSE in future
|
|
34
|
+
console.error(
|
|
35
|
+
`[${progress.phase}] ${progress.percentage}% - ${progress.message}`,
|
|
36
|
+
);
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
let statusMessage = `Indexed ${stats.filesIndexed}/${stats.filesScanned} files (${stats.chunksCreated} chunks) in ${(stats.durationMs / 1000).toFixed(1)}s`;
|
|
41
|
+
|
|
42
|
+
if (stats.status === "partial") {
|
|
43
|
+
statusMessage += `\n\nWarnings:\n${stats.errors?.join("\n")}`;
|
|
44
|
+
} else if (stats.status === "failed") {
|
|
45
|
+
statusMessage = `Indexing failed:\n${stats.errors?.join("\n")}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
content: [{ type: "text", text: statusMessage }],
|
|
50
|
+
isError: stats.status === "failed",
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// search_code
|
|
56
|
+
server.registerTool(
|
|
57
|
+
"search_code",
|
|
58
|
+
{
|
|
59
|
+
title: "Search Code",
|
|
60
|
+
description:
|
|
61
|
+
"Search indexed codebase using natural language queries. Returns semantically relevant code chunks with file paths and line numbers.",
|
|
62
|
+
inputSchema: schemas.SearchCodeSchema,
|
|
63
|
+
},
|
|
64
|
+
async ({ path, query, limit, fileTypes, pathPattern }) => {
|
|
65
|
+
const results = await codeIndexer.searchCode(path, query, {
|
|
66
|
+
limit,
|
|
67
|
+
fileTypes,
|
|
68
|
+
pathPattern,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (results.length === 0) {
|
|
72
|
+
return {
|
|
73
|
+
content: [
|
|
74
|
+
{ type: "text", text: `No results found for query: "${query}"` },
|
|
75
|
+
],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Format results with file references
|
|
80
|
+
const formattedResults = results
|
|
81
|
+
.map(
|
|
82
|
+
(r, idx) =>
|
|
83
|
+
`\n--- Result ${idx + 1} (score: ${r.score.toFixed(3)}) ---\n` +
|
|
84
|
+
`File: ${r.filePath}:${r.startLine}-${r.endLine}\n` +
|
|
85
|
+
`Language: ${r.language}\n\n` +
|
|
86
|
+
`${r.content}\n`,
|
|
87
|
+
)
|
|
88
|
+
.join("\n");
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
content: [
|
|
92
|
+
{
|
|
93
|
+
type: "text",
|
|
94
|
+
text: `Found ${results.length} result(s):\n${formattedResults}`,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// reindex_changes
|
|
102
|
+
server.registerTool(
|
|
103
|
+
"reindex_changes",
|
|
104
|
+
{
|
|
105
|
+
title: "Reindex Changes",
|
|
106
|
+
description:
|
|
107
|
+
"Incrementally re-index only changed files. Detects added, modified, and deleted files since last index. Requires previous indexing with index_codebase.",
|
|
108
|
+
inputSchema: schemas.ReindexChangesSchema,
|
|
109
|
+
},
|
|
110
|
+
async ({ path }) => {
|
|
111
|
+
const stats = await codeIndexer.reindexChanges(path, (progress) => {
|
|
112
|
+
console.error(
|
|
113
|
+
`[${progress.phase}] ${progress.percentage}% - ${progress.message}`,
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
let message = `Incremental re-index complete:\n`;
|
|
118
|
+
message += `- Files added: ${stats.filesAdded}\n`;
|
|
119
|
+
message += `- Files modified: ${stats.filesModified}\n`;
|
|
120
|
+
message += `- Files deleted: ${stats.filesDeleted}\n`;
|
|
121
|
+
message += `- Chunks added: ${stats.chunksAdded}\n`;
|
|
122
|
+
message += `- Duration: ${(stats.durationMs / 1000).toFixed(1)}s`;
|
|
123
|
+
|
|
124
|
+
if (
|
|
125
|
+
stats.filesAdded === 0 &&
|
|
126
|
+
stats.filesModified === 0 &&
|
|
127
|
+
stats.filesDeleted === 0
|
|
128
|
+
) {
|
|
129
|
+
message = `No changes detected. Codebase is up to date.`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
content: [{ type: "text", text: message }],
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// get_index_status
|
|
139
|
+
server.registerTool(
|
|
140
|
+
"get_index_status",
|
|
141
|
+
{
|
|
142
|
+
title: "Get Index Status",
|
|
143
|
+
description: "Get indexing status and statistics for a codebase.",
|
|
144
|
+
inputSchema: schemas.GetIndexStatusSchema,
|
|
145
|
+
},
|
|
146
|
+
async ({ path }) => {
|
|
147
|
+
const status = await codeIndexer.getIndexStatus(path);
|
|
148
|
+
|
|
149
|
+
if (status.status === "not_indexed") {
|
|
150
|
+
return {
|
|
151
|
+
content: [
|
|
152
|
+
{
|
|
153
|
+
type: "text",
|
|
154
|
+
text: `Codebase at "${path}" is not indexed. Use index_codebase to index it first.`,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (status.status === "indexing") {
|
|
161
|
+
return {
|
|
162
|
+
content: [
|
|
163
|
+
{
|
|
164
|
+
type: "text",
|
|
165
|
+
text: `Codebase at "${path}" is currently being indexed. ${status.chunksCount || 0} chunks processed so far.`,
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
content: [{ type: "text", text: JSON.stringify(status, null, 2) }],
|
|
173
|
+
};
|
|
174
|
+
},
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// clear_index
|
|
178
|
+
server.registerTool(
|
|
179
|
+
"clear_index",
|
|
180
|
+
{
|
|
181
|
+
title: "Clear Index",
|
|
182
|
+
description:
|
|
183
|
+
"Delete all indexed data for a codebase. This is irreversible and will remove the entire collection.",
|
|
184
|
+
inputSchema: schemas.ClearIndexSchema,
|
|
185
|
+
},
|
|
186
|
+
async ({ path }) => {
|
|
187
|
+
await codeIndexer.clearIndex(path);
|
|
188
|
+
return {
|
|
189
|
+
content: [
|
|
190
|
+
{ type: "text", text: `Index cleared for codebase at "${path}".` },
|
|
191
|
+
],
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collection management tools registration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import type { EmbeddingProvider } from "../embeddings/base.js";
|
|
7
|
+
import type { QdrantManager } from "../qdrant/client.js";
|
|
8
|
+
import * as schemas from "./schemas.js";
|
|
9
|
+
|
|
10
|
+
export interface CollectionToolDependencies {
|
|
11
|
+
qdrant: QdrantManager;
|
|
12
|
+
embeddings: EmbeddingProvider;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function registerCollectionTools(
|
|
16
|
+
server: McpServer,
|
|
17
|
+
deps: CollectionToolDependencies,
|
|
18
|
+
): void {
|
|
19
|
+
const { qdrant, embeddings } = deps;
|
|
20
|
+
|
|
21
|
+
// create_collection
|
|
22
|
+
server.registerTool(
|
|
23
|
+
"create_collection",
|
|
24
|
+
{
|
|
25
|
+
title: "Create Collection",
|
|
26
|
+
description:
|
|
27
|
+
"Create a new vector collection in Qdrant. The collection will be configured with the embedding provider's dimensions automatically. Set enableHybrid to true to enable hybrid search combining semantic and keyword search.",
|
|
28
|
+
inputSchema: schemas.CreateCollectionSchema,
|
|
29
|
+
},
|
|
30
|
+
async ({ name, distance, enableHybrid }) => {
|
|
31
|
+
const vectorSize = embeddings.getDimensions();
|
|
32
|
+
await qdrant.createCollection(
|
|
33
|
+
name,
|
|
34
|
+
vectorSize,
|
|
35
|
+
distance,
|
|
36
|
+
enableHybrid || false,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
let message = `Collection "${name}" created successfully with ${vectorSize} dimensions and ${distance || "Cosine"} distance metric.`;
|
|
40
|
+
if (enableHybrid) {
|
|
41
|
+
message += " Hybrid search is enabled for this collection.";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
content: [{ type: "text", text: message }],
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// list_collections
|
|
51
|
+
server.registerTool(
|
|
52
|
+
"list_collections",
|
|
53
|
+
{
|
|
54
|
+
title: "List Collections",
|
|
55
|
+
description: "List all available collections in Qdrant.",
|
|
56
|
+
inputSchema: {},
|
|
57
|
+
},
|
|
58
|
+
async () => {
|
|
59
|
+
const collections = await qdrant.listCollections();
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: "text", text: JSON.stringify(collections, null, 2) }],
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// get_collection_info
|
|
67
|
+
server.registerTool(
|
|
68
|
+
"get_collection_info",
|
|
69
|
+
{
|
|
70
|
+
title: "Get Collection Info",
|
|
71
|
+
description:
|
|
72
|
+
"Get detailed information about a collection including vector size, point count, and distance metric.",
|
|
73
|
+
inputSchema: schemas.GetCollectionInfoSchema,
|
|
74
|
+
},
|
|
75
|
+
async ({ name }) => {
|
|
76
|
+
const info = await qdrant.getCollectionInfo(name);
|
|
77
|
+
return {
|
|
78
|
+
content: [{ type: "text", text: JSON.stringify(info, null, 2) }],
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// delete_collection
|
|
84
|
+
server.registerTool(
|
|
85
|
+
"delete_collection",
|
|
86
|
+
{
|
|
87
|
+
title: "Delete Collection",
|
|
88
|
+
description: "Delete a collection and all its documents.",
|
|
89
|
+
inputSchema: schemas.DeleteCollectionSchema,
|
|
90
|
+
},
|
|
91
|
+
async ({ name }) => {
|
|
92
|
+
await qdrant.deleteCollection(name);
|
|
93
|
+
return {
|
|
94
|
+
content: [
|
|
95
|
+
{ type: "text", text: `Collection "${name}" deleted successfully.` },
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document operation tools registration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import type { EmbeddingProvider } from "../embeddings/base.js";
|
|
7
|
+
import { BM25SparseVectorGenerator } from "../embeddings/sparse.js";
|
|
8
|
+
import type { QdrantManager } from "../qdrant/client.js";
|
|
9
|
+
import * as schemas from "./schemas.js";
|
|
10
|
+
|
|
11
|
+
export interface DocumentToolDependencies {
|
|
12
|
+
qdrant: QdrantManager;
|
|
13
|
+
embeddings: EmbeddingProvider;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function registerDocumentTools(
|
|
17
|
+
server: McpServer,
|
|
18
|
+
deps: DocumentToolDependencies,
|
|
19
|
+
): void {
|
|
20
|
+
const { qdrant, embeddings } = deps;
|
|
21
|
+
|
|
22
|
+
// add_documents
|
|
23
|
+
server.registerTool(
|
|
24
|
+
"add_documents",
|
|
25
|
+
{
|
|
26
|
+
title: "Add Documents",
|
|
27
|
+
description:
|
|
28
|
+
"Add documents to a collection. Documents will be automatically embedded using the configured embedding provider.",
|
|
29
|
+
inputSchema: schemas.AddDocumentsSchema,
|
|
30
|
+
},
|
|
31
|
+
async ({ collection, documents }) => {
|
|
32
|
+
// Check if collection exists and get info
|
|
33
|
+
const exists = await qdrant.collectionExists(collection);
|
|
34
|
+
if (!exists) {
|
|
35
|
+
return {
|
|
36
|
+
content: [
|
|
37
|
+
{
|
|
38
|
+
type: "text",
|
|
39
|
+
text: `Error: Collection "${collection}" does not exist. Create it first using create_collection.`,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
isError: true,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const collectionInfo = await qdrant.getCollectionInfo(collection);
|
|
47
|
+
|
|
48
|
+
// Generate embeddings for all documents
|
|
49
|
+
const texts = documents.map((doc) => doc.text);
|
|
50
|
+
const embeddingResults = await embeddings.embedBatch(texts);
|
|
51
|
+
|
|
52
|
+
// If hybrid search is enabled, generate sparse vectors and use appropriate method
|
|
53
|
+
if (collectionInfo.hybridEnabled) {
|
|
54
|
+
const sparseGenerator = new BM25SparseVectorGenerator();
|
|
55
|
+
|
|
56
|
+
// Prepare points with both dense and sparse vectors
|
|
57
|
+
const points = documents.map((doc, index) => ({
|
|
58
|
+
id: doc.id,
|
|
59
|
+
vector: embeddingResults[index].embedding,
|
|
60
|
+
sparseVector: sparseGenerator.generate(doc.text),
|
|
61
|
+
payload: {
|
|
62
|
+
text: doc.text,
|
|
63
|
+
...doc.metadata,
|
|
64
|
+
},
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
await qdrant.addPointsWithSparse(collection, points);
|
|
68
|
+
} else {
|
|
69
|
+
// Standard dense-only vectors
|
|
70
|
+
const points = documents.map((doc, index) => ({
|
|
71
|
+
id: doc.id,
|
|
72
|
+
vector: embeddingResults[index].embedding,
|
|
73
|
+
payload: {
|
|
74
|
+
text: doc.text,
|
|
75
|
+
...doc.metadata,
|
|
76
|
+
},
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
await qdrant.addPoints(collection, points);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
content: [
|
|
84
|
+
{
|
|
85
|
+
type: "text",
|
|
86
|
+
text: `Successfully added ${documents.length} document(s) to collection "${collection}".`,
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// delete_documents
|
|
94
|
+
server.registerTool(
|
|
95
|
+
"delete_documents",
|
|
96
|
+
{
|
|
97
|
+
title: "Delete Documents",
|
|
98
|
+
description: "Delete specific documents from a collection by their IDs.",
|
|
99
|
+
inputSchema: schemas.DeleteDocumentsSchema,
|
|
100
|
+
},
|
|
101
|
+
async ({ collection, ids }) => {
|
|
102
|
+
await qdrant.deletePoints(collection, ids);
|
|
103
|
+
return {
|
|
104
|
+
content: [
|
|
105
|
+
{
|
|
106
|
+
type: "text",
|
|
107
|
+
text: `Successfully deleted ${ids.length} document(s) from collection "${collection}".`,
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool registration orchestrator
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import type { CodeIndexer } from "../code/indexer.js";
|
|
7
|
+
import type { EmbeddingProvider } from "../embeddings/base.js";
|
|
8
|
+
import type { QdrantManager } from "../qdrant/client.js";
|
|
9
|
+
import { registerCodeTools } from "./code.js";
|
|
10
|
+
import { registerCollectionTools } from "./collection.js";
|
|
11
|
+
import { registerDocumentTools } from "./document.js";
|
|
12
|
+
import { registerSearchTools } from "./search.js";
|
|
13
|
+
|
|
14
|
+
export interface ToolDependencies {
|
|
15
|
+
qdrant: QdrantManager;
|
|
16
|
+
embeddings: EmbeddingProvider;
|
|
17
|
+
codeIndexer: CodeIndexer;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Register all MCP tools on the server
|
|
22
|
+
*/
|
|
23
|
+
export function registerAllTools(
|
|
24
|
+
server: McpServer,
|
|
25
|
+
deps: ToolDependencies,
|
|
26
|
+
): void {
|
|
27
|
+
registerCollectionTools(server, {
|
|
28
|
+
qdrant: deps.qdrant,
|
|
29
|
+
embeddings: deps.embeddings,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
registerDocumentTools(server, {
|
|
33
|
+
qdrant: deps.qdrant,
|
|
34
|
+
embeddings: deps.embeddings,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
registerSearchTools(server, {
|
|
38
|
+
qdrant: deps.qdrant,
|
|
39
|
+
embeddings: deps.embeddings,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
registerCodeTools(server, {
|
|
43
|
+
codeIndexer: deps.codeIndexer,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Re-export schemas for external use
|
|
48
|
+
export * from "./schemas.js";
|