mcp-meilisearch 1.0.0
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/README.md +108 -0
- package/dist/client.js +146 -0
- package/dist/config.js +20 -0
- package/dist/http.js +2 -0
- package/dist/index.js +2 -0
- package/dist/server.js +383 -0
- package/dist/stdio.js +2 -0
- package/dist/tools/document-tools.js +208 -0
- package/dist/tools/index-tools.js +146 -0
- package/dist/tools/search-tools.js +221 -0
- package/dist/tools/settings-tools.js +309 -0
- package/dist/tools/system-tools.js +210 -0
- package/dist/tools/task-tools.js +190 -0
- package/dist/tools/vector-tools.js +214 -0
- package/dist/utils/api-handler.js +33 -0
- package/dist/utils/error-handler.js +35 -0
- package/package.json +42 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import apiClient from "../utils/api-handler.js";
|
|
3
|
+
import { createErrorResponse } from "../utils/error-handler.js";
|
|
4
|
+
/**
|
|
5
|
+
* Register document management tools with the MCP server
|
|
6
|
+
*
|
|
7
|
+
* @param server - The MCP server instance
|
|
8
|
+
*/
|
|
9
|
+
export const registerDocumentTools = (server) => {
|
|
10
|
+
// Get documents from an index
|
|
11
|
+
server.tool("get-documents", "Get documents from a Meilisearch index", {
|
|
12
|
+
indexUid: z.string().describe("Unique identifier of the index"),
|
|
13
|
+
limit: z
|
|
14
|
+
.number()
|
|
15
|
+
.min(1)
|
|
16
|
+
.max(1000)
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("Maximum number of documents to return (default: 20)"),
|
|
19
|
+
offset: z
|
|
20
|
+
.number()
|
|
21
|
+
.min(0)
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Number of documents to skip (default: 0)"),
|
|
24
|
+
fields: z
|
|
25
|
+
.array(z.string())
|
|
26
|
+
.optional()
|
|
27
|
+
.describe("Fields to return in the documents"),
|
|
28
|
+
filter: z.string().optional().describe("Filter query to apply"),
|
|
29
|
+
}, async ({ indexUid, limit, offset, fields, filter }) => {
|
|
30
|
+
try {
|
|
31
|
+
const response = await apiClient.get(`/indexes/${indexUid}/documents`, {
|
|
32
|
+
params: {
|
|
33
|
+
limit,
|
|
34
|
+
offset,
|
|
35
|
+
fields: fields?.join(","),
|
|
36
|
+
filter,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
content: [
|
|
41
|
+
{ type: "text", text: JSON.stringify(response.data, null, 2) },
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
return createErrorResponse(error);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
// Get a single document by ID
|
|
50
|
+
server.tool("get-document", "Get a document by its ID from a Meilisearch index", {
|
|
51
|
+
indexUid: z.string().describe("Unique identifier of the index"),
|
|
52
|
+
documentId: z.string().describe("ID of the document to retrieve"),
|
|
53
|
+
fields: z
|
|
54
|
+
.array(z.string())
|
|
55
|
+
.optional()
|
|
56
|
+
.describe("Fields to return in the document"),
|
|
57
|
+
}, async ({ indexUid, documentId, fields }) => {
|
|
58
|
+
try {
|
|
59
|
+
const response = await apiClient.get(`/indexes/${indexUid}/documents/${documentId}`, {
|
|
60
|
+
params: {
|
|
61
|
+
fields: fields?.join(","),
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
content: [
|
|
66
|
+
{ type: "text", text: JSON.stringify(response.data, null, 2) },
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
return createErrorResponse(error);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
// Add documents to an index
|
|
75
|
+
server.tool("add-documents", "Add documents to a Meilisearch index", {
|
|
76
|
+
indexUid: z.string().describe("Unique identifier of the index"),
|
|
77
|
+
documents: z.string().describe("JSON array of documents to add"),
|
|
78
|
+
primaryKey: z
|
|
79
|
+
.string()
|
|
80
|
+
.optional()
|
|
81
|
+
.describe("Primary key for the documents"),
|
|
82
|
+
}, async ({ indexUid, documents, primaryKey }) => {
|
|
83
|
+
try {
|
|
84
|
+
// Parse the documents string to ensure it's valid JSON
|
|
85
|
+
const parsedDocuments = JSON.parse(documents);
|
|
86
|
+
// Ensure documents is an array
|
|
87
|
+
if (!Array.isArray(parsedDocuments)) {
|
|
88
|
+
return {
|
|
89
|
+
isError: true,
|
|
90
|
+
content: [{ type: "text", text: "Documents must be a JSON array" }],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const params = {};
|
|
94
|
+
if (primaryKey) {
|
|
95
|
+
params.primaryKey = primaryKey;
|
|
96
|
+
}
|
|
97
|
+
const response = await apiClient.post(`/indexes/${indexUid}/documents`, parsedDocuments, {
|
|
98
|
+
params,
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
content: [
|
|
102
|
+
{ type: "text", text: JSON.stringify(response.data, null, 2) },
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
return createErrorResponse(error);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
// Update documents in an index
|
|
111
|
+
server.tool("update-documents", "Update documents in a Meilisearch index", {
|
|
112
|
+
indexUid: z.string().describe("Unique identifier of the index"),
|
|
113
|
+
documents: z.string().describe("JSON array of documents to update"),
|
|
114
|
+
primaryKey: z
|
|
115
|
+
.string()
|
|
116
|
+
.optional()
|
|
117
|
+
.describe("Primary key for the documents"),
|
|
118
|
+
}, async ({ indexUid, documents, primaryKey }) => {
|
|
119
|
+
try {
|
|
120
|
+
// Parse the documents string to ensure it's valid JSON
|
|
121
|
+
const parsedDocuments = JSON.parse(documents);
|
|
122
|
+
// Ensure documents is an array
|
|
123
|
+
if (!Array.isArray(parsedDocuments)) {
|
|
124
|
+
return {
|
|
125
|
+
isError: true,
|
|
126
|
+
content: [{ type: "text", text: "Documents must be a JSON array" }],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const params = {};
|
|
130
|
+
if (primaryKey) {
|
|
131
|
+
params.primaryKey = primaryKey;
|
|
132
|
+
}
|
|
133
|
+
const response = await apiClient.put(`/indexes/${indexUid}/documents`, parsedDocuments, {
|
|
134
|
+
params,
|
|
135
|
+
});
|
|
136
|
+
return {
|
|
137
|
+
content: [
|
|
138
|
+
{ type: "text", text: JSON.stringify(response.data, null, 2) },
|
|
139
|
+
],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
return createErrorResponse(error);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
// Delete a document by ID
|
|
147
|
+
server.tool("delete-document", "Delete a document by its ID from a Meilisearch index", {
|
|
148
|
+
indexUid: z.string().describe("Unique identifier of the index"),
|
|
149
|
+
documentId: z.string().describe("ID of the document to delete"),
|
|
150
|
+
}, async ({ indexUid, documentId }) => {
|
|
151
|
+
try {
|
|
152
|
+
const response = await apiClient.delete(`/indexes/${indexUid}/documents/${documentId}`);
|
|
153
|
+
return {
|
|
154
|
+
content: [
|
|
155
|
+
{ type: "text", text: JSON.stringify(response.data, null, 2) },
|
|
156
|
+
],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
return createErrorResponse(error);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
// Delete multiple documents by ID
|
|
164
|
+
server.tool("delete-documents", "Delete multiple documents by their IDs from a Meilisearch index", {
|
|
165
|
+
indexUid: z.string().describe("Unique identifier of the index"),
|
|
166
|
+
documentIds: z.string().describe("JSON array of document IDs to delete"),
|
|
167
|
+
}, async ({ indexUid, documentIds }) => {
|
|
168
|
+
try {
|
|
169
|
+
// Parse the document IDs string to ensure it's valid JSON
|
|
170
|
+
const parsedDocumentIds = JSON.parse(documentIds);
|
|
171
|
+
// Ensure document IDs is an array
|
|
172
|
+
if (!Array.isArray(parsedDocumentIds)) {
|
|
173
|
+
return {
|
|
174
|
+
isError: true,
|
|
175
|
+
content: [
|
|
176
|
+
{ type: "text", text: "Document IDs must be a JSON array" },
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const response = await apiClient.post(`/indexes/${indexUid}/documents/delete-batch`, parsedDocumentIds);
|
|
181
|
+
return {
|
|
182
|
+
content: [
|
|
183
|
+
{ type: "text", text: JSON.stringify(response.data, null, 2) },
|
|
184
|
+
],
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
return createErrorResponse(error);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
// Delete all documents in an index
|
|
192
|
+
server.tool("delete-all-documents", "Delete all documents in a Meilisearch index", {
|
|
193
|
+
indexUid: z.string().describe("Unique identifier of the index"),
|
|
194
|
+
}, async ({ indexUid }) => {
|
|
195
|
+
try {
|
|
196
|
+
const response = await apiClient.delete(`/indexes/${indexUid}/documents`);
|
|
197
|
+
return {
|
|
198
|
+
content: [
|
|
199
|
+
{ type: "text", text: JSON.stringify(response.data, null, 2) },
|
|
200
|
+
],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
return createErrorResponse(error);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
export default registerDocumentTools;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import apiClient from "../utils/api-handler.js";
|
|
3
|
+
import { createErrorResponse } from "../utils/error-handler.js";
|
|
4
|
+
/**
|
|
5
|
+
* Register index management tools with the MCP server
|
|
6
|
+
*
|
|
7
|
+
* @param server - The MCP server instance
|
|
8
|
+
*/
|
|
9
|
+
export const registerIndexTools = (server) => {
|
|
10
|
+
// List all indexes
|
|
11
|
+
server.tool("list-indexes", "List all indexes in the Meilisearch instance", {
|
|
12
|
+
limit: z
|
|
13
|
+
.number()
|
|
14
|
+
.min(1)
|
|
15
|
+
.max(100)
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Maximum number of indexes to return"),
|
|
18
|
+
offset: z
|
|
19
|
+
.number()
|
|
20
|
+
.min(0)
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Number of indexes to skip"),
|
|
23
|
+
}, async ({ limit, offset }) => {
|
|
24
|
+
try {
|
|
25
|
+
const response = await apiClient.get("/indexes", {
|
|
26
|
+
params: {
|
|
27
|
+
limit,
|
|
28
|
+
offset,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
content: [
|
|
33
|
+
{ type: "text", text: JSON.stringify(response.data, null, 2) },
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
return createErrorResponse(error);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
// Get index information
|
|
42
|
+
server.tool("get-index", "Get information about a specific Meilisearch index", {
|
|
43
|
+
indexUid: z.string().describe("Unique identifier of the index"),
|
|
44
|
+
}, async ({ indexUid }) => {
|
|
45
|
+
try {
|
|
46
|
+
const response = await apiClient.get(`/indexes/${indexUid}`);
|
|
47
|
+
return {
|
|
48
|
+
content: [
|
|
49
|
+
{ type: "text", text: JSON.stringify(response.data, null, 2) },
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
return createErrorResponse(error);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
// Create a new index
|
|
58
|
+
server.tool("create-index", "Create a new Meilisearch index", {
|
|
59
|
+
indexUid: z.string().describe("Unique identifier for the new index"),
|
|
60
|
+
primaryKey: z.string().optional().describe("Primary key for the index"),
|
|
61
|
+
}, async ({ indexUid, primaryKey }) => {
|
|
62
|
+
try {
|
|
63
|
+
const response = await apiClient.post("/indexes", {
|
|
64
|
+
uid: indexUid,
|
|
65
|
+
primaryKey,
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
content: [
|
|
69
|
+
{ type: "text", text: JSON.stringify(response.data, null, 2) },
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return createErrorResponse(error);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
// Update an index
|
|
78
|
+
server.tool("update-index", "Update a Meilisearch index (currently only supports updating the primary key)", {
|
|
79
|
+
indexUid: z.string().describe("Unique identifier of the index"),
|
|
80
|
+
primaryKey: z.string().describe("New primary key for the index"),
|
|
81
|
+
}, async ({ indexUid, primaryKey }) => {
|
|
82
|
+
try {
|
|
83
|
+
const response = await apiClient.patch(`/indexes/${indexUid}`, {
|
|
84
|
+
primaryKey,
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
content: [
|
|
88
|
+
{ type: "text", text: JSON.stringify(response.data, null, 2) },
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
return createErrorResponse(error);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
// Delete an index
|
|
97
|
+
server.tool("delete-index", "Delete a Meilisearch index", {
|
|
98
|
+
indexUid: z.string().describe("Unique identifier of the index to delete"),
|
|
99
|
+
}, async ({ indexUid }) => {
|
|
100
|
+
try {
|
|
101
|
+
const response = await apiClient.delete(`/indexes/${indexUid}`);
|
|
102
|
+
return {
|
|
103
|
+
content: [
|
|
104
|
+
{ type: "text", text: JSON.stringify(response.data, null, 2) },
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
return createErrorResponse(error);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
// Swap indexes
|
|
113
|
+
server.tool("swap-indexes", "Swap two or more indexes in Meilisearch", {
|
|
114
|
+
indexes: z
|
|
115
|
+
.string()
|
|
116
|
+
.describe('JSON array of index pairs to swap, e.g. [["movies", "movies_new"]]'),
|
|
117
|
+
}, async ({ indexes }) => {
|
|
118
|
+
try {
|
|
119
|
+
// Parse the indexes string to ensure it's valid JSON
|
|
120
|
+
const parsedIndexes = JSON.parse(indexes);
|
|
121
|
+
// Ensure indexes is an array of arrays
|
|
122
|
+
if (!Array.isArray(parsedIndexes) ||
|
|
123
|
+
!parsedIndexes.every((pair) => Array.isArray(pair) && pair.length === 2)) {
|
|
124
|
+
return {
|
|
125
|
+
isError: true,
|
|
126
|
+
content: [
|
|
127
|
+
{
|
|
128
|
+
type: "text",
|
|
129
|
+
text: 'Indexes must be a JSON array of pairs, e.g. [["movies", "movies_new"]]',
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const response = await apiClient.post("/swap-indexes", parsedIndexes);
|
|
135
|
+
return {
|
|
136
|
+
content: [
|
|
137
|
+
{ type: "text", text: JSON.stringify(response.data, null, 2) },
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
return createErrorResponse(error);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
export default registerIndexTools;
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import apiClient from "../utils/api-handler.js";
|
|
3
|
+
import { createErrorResponse } from "../utils/error-handler.js";
|
|
4
|
+
/**
|
|
5
|
+
* Register search tools with the MCP server
|
|
6
|
+
*
|
|
7
|
+
* @param server - The MCP server instance
|
|
8
|
+
*/
|
|
9
|
+
export const registerSearchTools = (server) => {
|
|
10
|
+
// Search in an index
|
|
11
|
+
server.tool("search", "Search for documents in a Meilisearch index", {
|
|
12
|
+
indexUid: z.string().describe("Unique identifier of the index"),
|
|
13
|
+
q: z.string().describe("Search query"),
|
|
14
|
+
limit: z
|
|
15
|
+
.number()
|
|
16
|
+
.min(1)
|
|
17
|
+
.optional()
|
|
18
|
+
.describe("Maximum number of results to return (default: 20)"),
|
|
19
|
+
offset: z
|
|
20
|
+
.number()
|
|
21
|
+
.min(0)
|
|
22
|
+
.optional()
|
|
23
|
+
.describe("Number of results to skip (default: 0)"),
|
|
24
|
+
filter: z.string().optional().describe("Filter query to apply"),
|
|
25
|
+
sort: z
|
|
26
|
+
.array(z.string())
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Attributes to sort by, e.g. ["price:asc"]'),
|
|
29
|
+
facets: z.array(z.string()).optional().describe("Facets to return"),
|
|
30
|
+
attributesToRetrieve: z
|
|
31
|
+
.array(z.string())
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("Attributes to include in results"),
|
|
34
|
+
attributesToCrop: z
|
|
35
|
+
.array(z.string())
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("Attributes to crop"),
|
|
38
|
+
cropLength: z
|
|
39
|
+
.number()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("Length at which to crop cropped attributes"),
|
|
42
|
+
attributesToHighlight: z
|
|
43
|
+
.array(z.string())
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("Attributes to highlight"),
|
|
46
|
+
highlightPreTag: z
|
|
47
|
+
.string()
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("Tag to insert before highlighted text"),
|
|
50
|
+
highlightPostTag: z
|
|
51
|
+
.string()
|
|
52
|
+
.optional()
|
|
53
|
+
.describe("Tag to insert after highlighted text"),
|
|
54
|
+
showMatchesPosition: z
|
|
55
|
+
.boolean()
|
|
56
|
+
.optional()
|
|
57
|
+
.describe("Whether to include match positions in results"),
|
|
58
|
+
matchingStrategy: z
|
|
59
|
+
.string()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe("Matching strategy: 'all' or 'last'"),
|
|
62
|
+
}, async ({ indexUid, q, limit, offset, filter, sort, facets, attributesToRetrieve, attributesToCrop, cropLength, attributesToHighlight, highlightPreTag, highlightPostTag, showMatchesPosition, matchingStrategy, }) => {
|
|
63
|
+
try {
|
|
64
|
+
const response = await apiClient.post(`/indexes/${indexUid}/search`, {
|
|
65
|
+
q,
|
|
66
|
+
limit,
|
|
67
|
+
offset,
|
|
68
|
+
filter,
|
|
69
|
+
sort,
|
|
70
|
+
facets,
|
|
71
|
+
attributesToRetrieve,
|
|
72
|
+
attributesToCrop,
|
|
73
|
+
cropLength,
|
|
74
|
+
attributesToHighlight,
|
|
75
|
+
highlightPreTag,
|
|
76
|
+
highlightPostTag,
|
|
77
|
+
showMatchesPosition,
|
|
78
|
+
matchingStrategy,
|
|
79
|
+
});
|
|
80
|
+
return {
|
|
81
|
+
content: [
|
|
82
|
+
{ type: "text", text: JSON.stringify(response.data, null, 2) },
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
return createErrorResponse(error);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
// Multi-search across multiple indexes
|
|
91
|
+
server.tool("multi-search", "Perform multiple searches in one request", {
|
|
92
|
+
searches: z
|
|
93
|
+
.string()
|
|
94
|
+
.describe("JSON array of search queries, each with indexUid and q fields"),
|
|
95
|
+
}, async ({ searches }) => {
|
|
96
|
+
try {
|
|
97
|
+
// Parse the searches string to ensure it's valid JSON
|
|
98
|
+
const parsedSearches = JSON.parse(searches);
|
|
99
|
+
// Ensure searches is an array
|
|
100
|
+
if (!Array.isArray(parsedSearches)) {
|
|
101
|
+
return {
|
|
102
|
+
isError: true,
|
|
103
|
+
content: [{ type: "text", text: "Searches must be a JSON array" }],
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// Ensure each search has at least indexUid
|
|
107
|
+
for (const search of parsedSearches) {
|
|
108
|
+
if (!search.indexUid) {
|
|
109
|
+
return {
|
|
110
|
+
isError: true,
|
|
111
|
+
content: [
|
|
112
|
+
{
|
|
113
|
+
type: "text",
|
|
114
|
+
text: "Each search must have an indexUid field",
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const response = await apiClient.post("/multi-search", {
|
|
121
|
+
queries: parsedSearches,
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
content: [
|
|
125
|
+
{ type: "text", text: JSON.stringify(response.data, null, 2) },
|
|
126
|
+
],
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
return createErrorResponse(error);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
server.tool("search-across-all-indexes", "Search for a term across all available Meilisearch indexes and return combined results.", {
|
|
134
|
+
q: z.string().describe("Search query"),
|
|
135
|
+
limit: z
|
|
136
|
+
.number()
|
|
137
|
+
.min(1)
|
|
138
|
+
.optional()
|
|
139
|
+
.describe("Maximum number of results to return per index (default: 20)"),
|
|
140
|
+
attributesToRetrieve: z
|
|
141
|
+
.array(z.string())
|
|
142
|
+
.optional()
|
|
143
|
+
.describe("Attributes to include in results"),
|
|
144
|
+
}, async ({ q, limit, attributesToRetrieve }) => {
|
|
145
|
+
try {
|
|
146
|
+
const indexesResponse = await apiClient.get("/indexes", {
|
|
147
|
+
params: { limit: 1000 },
|
|
148
|
+
});
|
|
149
|
+
const indexUids = indexesResponse.data.results.map((index) => index.uid);
|
|
150
|
+
if (!indexUids || indexUids.length === 0) {
|
|
151
|
+
return {
|
|
152
|
+
content: [
|
|
153
|
+
{
|
|
154
|
+
type: "text",
|
|
155
|
+
text: JSON.stringify({ allHits: [], message: "No indexes found in Meilisearch." }, null, 2),
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const searchPromises = indexUids.map(async (uid) => {
|
|
161
|
+
try {
|
|
162
|
+
const searchResult = await apiClient.post(`/indexes/${uid}/search`, {
|
|
163
|
+
q,
|
|
164
|
+
limit,
|
|
165
|
+
attributesToRetrieve,
|
|
166
|
+
});
|
|
167
|
+
return searchResult.data.hits.map((hit) => ({
|
|
168
|
+
indexUid: uid,
|
|
169
|
+
...hit,
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
catch (searchError) {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
const resultsPerIndex = await Promise.all(searchPromises);
|
|
177
|
+
const allHits = resultsPerIndex.flat();
|
|
178
|
+
return {
|
|
179
|
+
content: [
|
|
180
|
+
{ type: "text", text: JSON.stringify({ allHits }, null, 2) },
|
|
181
|
+
],
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
return createErrorResponse(error);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
// Facet search
|
|
189
|
+
server.tool("facet-search", "Search for facet values matching specific criteria", {
|
|
190
|
+
indexUid: z.string().describe("Unique identifier of the index"),
|
|
191
|
+
facetName: z.string().describe("Name of the facet to search"),
|
|
192
|
+
facetQuery: z
|
|
193
|
+
.string()
|
|
194
|
+
.optional()
|
|
195
|
+
.describe("Query to match against facet values"),
|
|
196
|
+
filter: z
|
|
197
|
+
.string()
|
|
198
|
+
.optional()
|
|
199
|
+
.describe("Filter to apply to the base search"),
|
|
200
|
+
}, async ({ indexUid, facetName, facetQuery, filter }) => {
|
|
201
|
+
try {
|
|
202
|
+
const params = {
|
|
203
|
+
facetName,
|
|
204
|
+
};
|
|
205
|
+
if (facetQuery !== undefined)
|
|
206
|
+
params.facetQuery = facetQuery;
|
|
207
|
+
if (filter)
|
|
208
|
+
params.filter = filter;
|
|
209
|
+
const response = await apiClient.post(`/indexes/${indexUid}/facet-search`, params);
|
|
210
|
+
return {
|
|
211
|
+
content: [
|
|
212
|
+
{ type: "text", text: JSON.stringify(response.data, null, 2) },
|
|
213
|
+
],
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
return createErrorResponse(error);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
};
|
|
221
|
+
export default registerSearchTools;
|