@moorchehai/mcp 1.3.1 → 1.3.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/README.md +296 -296
- package/package.json +69 -69
- package/src/server/config/api.js +27 -20
- package/src/server/tools/data-tools.js +27 -10
- package/src/server/tools/namespace-tools.js +2 -2
- package/src/server/tools/search-tools.js +275 -273
- package/src/server/utils/prompts.js +4 -4
- package/src/server/utils/resources.js +5 -5
|
@@ -1,274 +1,276 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { makeApiRequest, API_ENDPOINTS } from '../config/api.js';
|
|
3
|
-
|
|
4
|
-
// Search tool
|
|
5
|
-
export const searchTool = {
|
|
6
|
-
name: "search",
|
|
7
|
-
description: "Search for data in a namespace using semantic search or vector similarity. This tool provides powerful search capabilities across your namespaces, supporting both text-based semantic search and vector-based similarity search. For text search, you can use natural language queries to find relevant documents based on meaning rather than just keywords. For vector search, you can find similar content by comparing vector embeddings. The tool supports advanced features like result filtering, similarity thresholds, metadata filters, keyword filters, and kiosk mode for production environments. This is ideal for building intelligent search interfaces, recommendation systems, or content discovery features.\n\nFiltering Capabilities:\n- Metadata Filters: Use #key:value format (e.g., #category:tech, #priority:high)\n- Keyword Filters: Use #keyword format (e.g., #important, #urgent)\n- Filters only apply to text search and metadata must be manually uploaded with documents",
|
|
8
|
-
parameters: {
|
|
9
|
-
namespaces: z.array(z.string().min(1)).min(1).describe("Namespaces to search in. Provide an array of namespace names where you want to search for content. You can search across multiple namespaces simultaneously. All namespaces must be accessible with your API key."),
|
|
10
|
-
query: z.union([z.string().min(1), z.array(z.number())]).describe("Search query. For text search: provide a natural language query string (e.g., 'tell me about the company?', 'how to configure authentication?'). For vector search: provide an array of numbers representing a vector embedding (e.g., [0.1, 0.2, 0.3, ..., 0.768]). The query type will be automatically detected based on the input format. DO NOT USE QUOTES IN THE QUERY FOR VECTOR SEARCH.\n\nFiltering: For text search, you can include filters in your query:\n- Metadata filters: #category:tech #priority:high\n- Keyword filters: #important #urgent\n- Combine both: 'serverless benefits #category:tech #important'"),
|
|
11
|
-
query_type: z.enum(['text', 'vector']).optional().describe("Type of query to perform. 'text' for semantic search using natural language queries. 'vector' for similarity search using vector embeddings. If not specified, the type will be automatically detected based on the query format (string for text, array for vector)."),
|
|
12
|
-
top_k: z.number().int().positive().optional().describe("Number of top results to return. Controls how many search results are returned, with higher values providing more comprehensive results. Default is 10. Use lower values (3-5) for focused results, higher values (10-20) for broader exploration."),
|
|
13
|
-
threshold: z.number().min(0).max(1).optional().describe("Similarity threshold for results. A value between 0 and 1 that filters results based on similarity score. Higher values (0.7-0.9) return only highly similar results, lower values (0.3-0.5) return more comprehensive results. Required when kiosk_mode is true."),
|
|
14
|
-
kiosk_mode: z.boolean().optional().describe("Kiosk mode for restricted search. When true, search is restricted to specific namespaces with threshold filtering, providing more controlled results suitable for production environments. When false, search across all specified namespaces without strict filtering."),
|
|
15
|
-
},
|
|
16
|
-
handler: async ({ namespaces, query, query_type, top_k = 10, threshold, kiosk_mode = false }) => {
|
|
17
|
-
try {
|
|
18
|
-
// Determine query type if not explicitly provided
|
|
19
|
-
let finalQueryType = query_type;
|
|
20
|
-
let finalQuery = query;
|
|
21
|
-
|
|
22
|
-
if (!finalQueryType) {
|
|
23
|
-
if (typeof query === 'string') {
|
|
24
|
-
// Check if it's a string representation of a vector array
|
|
25
|
-
if (query.startsWith('[') && query.endsWith(']')) {
|
|
26
|
-
try {
|
|
27
|
-
const parsedArray = JSON.parse(query);
|
|
28
|
-
if (Array.isArray(parsedArray) && parsedArray.every(item => typeof item === 'number')) {
|
|
29
|
-
finalQuery = parsedArray;
|
|
30
|
-
finalQueryType = 'vector';
|
|
31
|
-
} else {
|
|
32
|
-
finalQueryType = 'text';
|
|
33
|
-
}
|
|
34
|
-
} catch (e) {
|
|
35
|
-
finalQueryType = 'text';
|
|
36
|
-
}
|
|
37
|
-
} else {
|
|
38
|
-
finalQueryType = 'text';
|
|
39
|
-
}
|
|
40
|
-
} else if (Array.isArray(query) && query.every(item => typeof item === 'number')) {
|
|
41
|
-
finalQueryType = 'vector';
|
|
42
|
-
} else {
|
|
43
|
-
return {
|
|
44
|
-
content: [
|
|
45
|
-
{
|
|
46
|
-
type: "text",
|
|
47
|
-
text: 'Error: Unable to determine query type. Please specify query_type parameter or provide a valid string (for text) or number array (for vector).',
|
|
48
|
-
},
|
|
49
|
-
],
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Handle vector query type with string input
|
|
55
|
-
if (finalQueryType === 'vector' && typeof query === 'string') {
|
|
56
|
-
try {
|
|
57
|
-
const parsedArray = JSON.parse(query);
|
|
58
|
-
if (Array.isArray(parsedArray) && parsedArray.every(item => typeof item === 'number')) {
|
|
59
|
-
finalQuery = parsedArray;
|
|
60
|
-
} else {
|
|
61
|
-
return {
|
|
62
|
-
content: [
|
|
63
|
-
{
|
|
64
|
-
type: "text",
|
|
65
|
-
text: 'Error: Vector query type requires an array of numbers',
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
} catch (e) {
|
|
71
|
-
return {
|
|
72
|
-
content: [
|
|
73
|
-
{
|
|
74
|
-
type: "text",
|
|
75
|
-
text: 'Error: Vector query type requires an array of numbers',
|
|
76
|
-
},
|
|
77
|
-
],
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Validate query format matches query type
|
|
83
|
-
if (finalQueryType === 'text' && typeof finalQuery !== 'string') {
|
|
84
|
-
return {
|
|
85
|
-
content: [
|
|
86
|
-
{
|
|
87
|
-
type: "text",
|
|
88
|
-
text: 'Error: Text query type requires a string query. Example: "your search text here"',
|
|
89
|
-
},
|
|
90
|
-
],
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
if (finalQueryType === 'vector' && (!Array.isArray(finalQuery) || !finalQuery.every(item => typeof item === 'number'))) {
|
|
94
|
-
return {
|
|
95
|
-
content: [
|
|
96
|
-
{
|
|
97
|
-
type: "text",
|
|
98
|
-
text: 'Error: Vector query type requires an array of numbers. Example: [0.1, 0.2, 0.3, 0.4, 0.5] for 5-dimensional namespace',
|
|
99
|
-
},
|
|
100
|
-
],
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const requestBody = {
|
|
105
|
-
namespaces,
|
|
106
|
-
query: finalQuery,
|
|
107
|
-
top_k,
|
|
108
|
-
kiosk_mode,
|
|
109
|
-
};
|
|
110
|
-
if (threshold !== undefined) {
|
|
111
|
-
requestBody.threshold = threshold;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const data = await makeApiRequest('POST', API_ENDPOINTS.search, requestBody);
|
|
115
|
-
|
|
116
|
-
if (!data.results || data.results.length === 0) {
|
|
117
|
-
return {
|
|
118
|
-
content: [
|
|
119
|
-
{
|
|
120
|
-
type: "text",
|
|
121
|
-
text: `No results found for query: "${query}"`,
|
|
122
|
-
},
|
|
123
|
-
],
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const formattedResults = data.results.map((result, index) =>
|
|
128
|
-
[
|
|
129
|
-
`Result ${index + 1}:`,
|
|
130
|
-
`ID: ${result.id || 'N/A'}`,
|
|
131
|
-
`Text: ${result.text || result.content || 'N/A'}`,
|
|
132
|
-
`Score: ${result.score || 'N/A'}`,
|
|
133
|
-
`Label: ${result.label || 'N/A'}`,
|
|
134
|
-
`Metadata: ${JSON.stringify(result.metadata || {}, null, 2)}`,
|
|
135
|
-
"---",
|
|
136
|
-
].join("\n")
|
|
137
|
-
);
|
|
138
|
-
|
|
139
|
-
let searchText = `Search results for "${query}" in namespaces [${namespaces.join(', ')}]:\n\n${formattedResults.join("\n")}\n\nTotal results: ${data.total || data.results.length}`;
|
|
140
|
-
|
|
141
|
-
// Add execution time and performance metrics if available
|
|
142
|
-
if (data.execution_time) {
|
|
143
|
-
searchText += `\n\nExecution time: ${data.execution_time}s`;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (data.timings) {
|
|
147
|
-
searchText += `\n\nPerformance breakdown:\n${Object.entries(data.timings)
|
|
148
|
-
.filter(([key]) => key !== 'total')
|
|
149
|
-
.map(([key, value]) => ` ${key}: ${value}s`)
|
|
150
|
-
.join('\n')}`;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (data.optimization_info) {
|
|
154
|
-
searchText += `\n\nOptimization: ${data.optimization_info.fetch_strategy}`;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return {
|
|
158
|
-
content: [
|
|
159
|
-
{
|
|
160
|
-
type: "text",
|
|
161
|
-
text: searchText,
|
|
162
|
-
},
|
|
163
|
-
],
|
|
164
|
-
};
|
|
165
|
-
} catch (error) {
|
|
166
|
-
return {
|
|
167
|
-
content: [
|
|
168
|
-
{
|
|
169
|
-
type: "text",
|
|
170
|
-
text: `Error searching: ${error.message}`,
|
|
171
|
-
},
|
|
172
|
-
],
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
// Answer tool
|
|
179
|
-
export const answerTool = {
|
|
180
|
-
name: "answer",
|
|
181
|
-
description: "Get AI-generated answers based on data in a namespace using text queries. This tool provides intelligent, context-aware responses by searching through your stored text documents and generating comprehensive answers using advanced language models. Supports two modes: Search Mode (with namespace) and Direct AI Mode (empty namespace).",
|
|
182
|
-
parameters: {
|
|
183
|
-
namespace: z.string().describe("Namespace to answer questions from. For Search Mode: provide a text namespace containing documents to search for context. For Direct AI Mode: provide empty string \"\" to make direct AI model calls without searching your data."),
|
|
184
|
-
query: z.string().min(1).describe("Text query for AI answer generation. Provide a natural language question or prompt that you want the AI to answer. The AI will search through your namespace content and generate a comprehensive response based on the relevant information found."),
|
|
185
|
-
top_k: z.number().int().positive().optional().describe("Number of top results to return. Controls how many relevant documents the AI considers when generating an answer. Default is
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
requestBody.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { makeApiRequest, API_ENDPOINTS } from '../config/api.js';
|
|
3
|
+
|
|
4
|
+
// Search tool
|
|
5
|
+
export const searchTool = {
|
|
6
|
+
name: "search",
|
|
7
|
+
description: "Search for data in a namespace using semantic search or vector similarity. This tool provides powerful search capabilities across your namespaces, supporting both text-based semantic search and vector-based similarity search. For text search, you can use natural language queries to find relevant documents based on meaning rather than just keywords. For vector search, you can find similar content by comparing vector embeddings. The tool supports advanced features like result filtering, similarity thresholds, metadata filters, keyword filters, and kiosk mode for production environments. This is ideal for building intelligent search interfaces, recommendation systems, or content discovery features.\n\nFiltering Capabilities:\n- Metadata Filters: Use #key:value format (e.g., #category:tech, #priority:high)\n- Keyword Filters: Use #keyword format (e.g., #important, #urgent)\n- Filters only apply to text search and metadata must be manually uploaded with documents",
|
|
8
|
+
parameters: {
|
|
9
|
+
namespaces: z.array(z.string().min(1)).min(1).describe("Namespaces to search in. Provide an array of namespace names where you want to search for content. You can search across multiple namespaces simultaneously. All namespaces must be accessible with your API key."),
|
|
10
|
+
query: z.union([z.string().min(1), z.array(z.number())]).describe("Search query. For text search: provide a natural language query string (e.g., 'tell me about the company?', 'how to configure authentication?'). For vector search: provide an array of numbers representing a vector embedding (e.g., [0.1, 0.2, 0.3, ..., 0.768]). The query type will be automatically detected based on the input format. DO NOT USE QUOTES IN THE QUERY FOR VECTOR SEARCH.\n\nFiltering: For text search, you can include filters in your query:\n- Metadata filters: #category:tech #priority:high\n- Keyword filters: #important #urgent\n- Combine both: 'serverless benefits #category:tech #important'"),
|
|
11
|
+
query_type: z.enum(['text', 'vector']).optional().describe("Type of query to perform. 'text' for semantic search using natural language queries. 'vector' for similarity search using vector embeddings. If not specified, the type will be automatically detected based on the query format (string for text, array for vector)."),
|
|
12
|
+
top_k: z.number().int().positive().optional().describe("Number of top results to return. Controls how many search results are returned, with higher values providing more comprehensive results. Default is 10. Use lower values (3-5) for focused results, higher values (10-20) for broader exploration."),
|
|
13
|
+
threshold: z.number().min(0).max(1).optional().describe("Similarity threshold for results. A value between 0 and 1 that filters results based on similarity score. Higher values (0.7-0.9) return only highly similar results, lower values (0.3-0.5) return more comprehensive results. Required when kiosk_mode is true."),
|
|
14
|
+
kiosk_mode: z.boolean().optional().describe("Kiosk mode for restricted search. When true, search is restricted to specific namespaces with threshold filtering, providing more controlled results suitable for production environments. When false, search across all specified namespaces without strict filtering."),
|
|
15
|
+
},
|
|
16
|
+
handler: async ({ namespaces, query, query_type, top_k = 10, threshold, kiosk_mode = false }) => {
|
|
17
|
+
try {
|
|
18
|
+
// Determine query type if not explicitly provided
|
|
19
|
+
let finalQueryType = query_type;
|
|
20
|
+
let finalQuery = query;
|
|
21
|
+
|
|
22
|
+
if (!finalQueryType) {
|
|
23
|
+
if (typeof query === 'string') {
|
|
24
|
+
// Check if it's a string representation of a vector array
|
|
25
|
+
if (query.startsWith('[') && query.endsWith(']')) {
|
|
26
|
+
try {
|
|
27
|
+
const parsedArray = JSON.parse(query);
|
|
28
|
+
if (Array.isArray(parsedArray) && parsedArray.every(item => typeof item === 'number')) {
|
|
29
|
+
finalQuery = parsedArray;
|
|
30
|
+
finalQueryType = 'vector';
|
|
31
|
+
} else {
|
|
32
|
+
finalQueryType = 'text';
|
|
33
|
+
}
|
|
34
|
+
} catch (e) {
|
|
35
|
+
finalQueryType = 'text';
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
finalQueryType = 'text';
|
|
39
|
+
}
|
|
40
|
+
} else if (Array.isArray(query) && query.every(item => typeof item === 'number')) {
|
|
41
|
+
finalQueryType = 'vector';
|
|
42
|
+
} else {
|
|
43
|
+
return {
|
|
44
|
+
content: [
|
|
45
|
+
{
|
|
46
|
+
type: "text",
|
|
47
|
+
text: 'Error: Unable to determine query type. Please specify query_type parameter or provide a valid string (for text) or number array (for vector).',
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Handle vector query type with string input
|
|
55
|
+
if (finalQueryType === 'vector' && typeof query === 'string') {
|
|
56
|
+
try {
|
|
57
|
+
const parsedArray = JSON.parse(query);
|
|
58
|
+
if (Array.isArray(parsedArray) && parsedArray.every(item => typeof item === 'number')) {
|
|
59
|
+
finalQuery = parsedArray;
|
|
60
|
+
} else {
|
|
61
|
+
return {
|
|
62
|
+
content: [
|
|
63
|
+
{
|
|
64
|
+
type: "text",
|
|
65
|
+
text: 'Error: Vector query type requires an array of numbers',
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
} catch (e) {
|
|
71
|
+
return {
|
|
72
|
+
content: [
|
|
73
|
+
{
|
|
74
|
+
type: "text",
|
|
75
|
+
text: 'Error: Vector query type requires an array of numbers',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Validate query format matches query type
|
|
83
|
+
if (finalQueryType === 'text' && typeof finalQuery !== 'string') {
|
|
84
|
+
return {
|
|
85
|
+
content: [
|
|
86
|
+
{
|
|
87
|
+
type: "text",
|
|
88
|
+
text: 'Error: Text query type requires a string query. Example: "your search text here"',
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (finalQueryType === 'vector' && (!Array.isArray(finalQuery) || !finalQuery.every(item => typeof item === 'number'))) {
|
|
94
|
+
return {
|
|
95
|
+
content: [
|
|
96
|
+
{
|
|
97
|
+
type: "text",
|
|
98
|
+
text: 'Error: Vector query type requires an array of numbers. Example: [0.1, 0.2, 0.3, 0.4, 0.5] for 5-dimensional namespace',
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const requestBody = {
|
|
105
|
+
namespaces,
|
|
106
|
+
query: finalQuery,
|
|
107
|
+
top_k,
|
|
108
|
+
kiosk_mode,
|
|
109
|
+
};
|
|
110
|
+
if (threshold !== undefined) {
|
|
111
|
+
requestBody.threshold = threshold;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const data = await makeApiRequest('POST', API_ENDPOINTS.search, requestBody);
|
|
115
|
+
|
|
116
|
+
if (!data.results || data.results.length === 0) {
|
|
117
|
+
return {
|
|
118
|
+
content: [
|
|
119
|
+
{
|
|
120
|
+
type: "text",
|
|
121
|
+
text: `No results found for query: "${query}"`,
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const formattedResults = data.results.map((result, index) =>
|
|
128
|
+
[
|
|
129
|
+
`Result ${index + 1}:`,
|
|
130
|
+
`ID: ${result.id || 'N/A'}`,
|
|
131
|
+
`Text: ${result.text || result.content || 'N/A'}`,
|
|
132
|
+
`Score: ${result.score || 'N/A'}`,
|
|
133
|
+
`Label: ${result.label || 'N/A'}`,
|
|
134
|
+
`Metadata: ${JSON.stringify(result.metadata || {}, null, 2)}`,
|
|
135
|
+
"---",
|
|
136
|
+
].join("\n")
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
let searchText = `Search results for "${query}" in namespaces [${namespaces.join(', ')}]:\n\n${formattedResults.join("\n")}\n\nTotal results: ${data.total || data.results.length}`;
|
|
140
|
+
|
|
141
|
+
// Add execution time and performance metrics if available
|
|
142
|
+
if (data.execution_time) {
|
|
143
|
+
searchText += `\n\nExecution time: ${data.execution_time}s`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (data.timings) {
|
|
147
|
+
searchText += `\n\nPerformance breakdown:\n${Object.entries(data.timings)
|
|
148
|
+
.filter(([key]) => key !== 'total')
|
|
149
|
+
.map(([key, value]) => ` ${key}: ${value}s`)
|
|
150
|
+
.join('\n')}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (data.optimization_info) {
|
|
154
|
+
searchText += `\n\nOptimization: ${data.optimization_info.fetch_strategy}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
content: [
|
|
159
|
+
{
|
|
160
|
+
type: "text",
|
|
161
|
+
text: searchText,
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
};
|
|
165
|
+
} catch (error) {
|
|
166
|
+
return {
|
|
167
|
+
content: [
|
|
168
|
+
{
|
|
169
|
+
type: "text",
|
|
170
|
+
text: `Error searching: ${error.message}`,
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Answer tool
|
|
179
|
+
export const answerTool = {
|
|
180
|
+
name: "answer",
|
|
181
|
+
description: "Get AI-generated answers based on data in a namespace using text queries. This tool provides intelligent, context-aware responses by searching through your stored text documents and generating comprehensive answers using advanced language models. Supports two modes: Search Mode (with namespace) and Direct AI Mode (empty namespace).",
|
|
182
|
+
parameters: {
|
|
183
|
+
namespace: z.string().describe("Namespace to answer questions from. For Search Mode: provide a text namespace containing documents to search for context. For Direct AI Mode: provide empty string \"\" to make direct AI model calls without searching your data."),
|
|
184
|
+
query: z.string().min(1).describe("Text query for AI answer generation. Provide a natural language question or prompt that you want the AI to answer. The AI will search through your namespace content and generate a comprehensive response based on the relevant information found."),
|
|
185
|
+
top_k: z.number().int().positive().optional().describe("Number of top results to return. Controls how many relevant documents the AI considers when generating an answer. Default is 10. Use lower values (3-5) for focused answers, higher values (8-10) for comprehensive responses that consider more context."),
|
|
186
|
+
type: z.enum(['text']).optional().describe("Search type for answer generation. Supported value is 'text'."),
|
|
187
|
+
threshold: z.number().min(0).max(1).optional().describe("Similarity threshold for results. A value between 0 and 1 that filters documents based on relevance before generating the answer. Higher values (0.7-0.9) ensure only highly relevant content is used, lower values (0.3-0.5) include more context. Required when kiosk_mode is true."),
|
|
188
|
+
kiosk_mode: z.boolean().optional().describe("Kiosk mode for restricted search. When true, search is restricted to specific namespaces with threshold filtering, providing more controlled and focused answers suitable for production environments."),
|
|
189
|
+
ai_model: z.string().optional().describe("AI model to use for answer generation. Different models may have different capabilities, response styles, and performance characteristics. Supported AI models include: 'anthropic.claude-3-7-sonnet-20250219-v1:0' (Claude 3.7 Sonnet), 'anthropic.claude-sonnet-4-20250514-v1:0' (Claude Sonnet 4), 'meta.llama4-maverick-17b-instruct-v1:0' (Llama 4 Maverick), 'meta.llama3-3-70b-instruct-v1:0' (Llama 3.3 70B), 'deepseek.r1-v1:0' (DeepSeek R1). If not specified, defaults to Claude 3.7 Sonnet."),
|
|
190
|
+
chat_history: z.array(z.object({
|
|
191
|
+
role: z.string().describe("Role of the message in the conversation. Use 'user' for user messages and 'assistant' for AI responses. This helps maintain conversation context and allows the AI to reference previous exchanges."),
|
|
192
|
+
content: z.string()
|
|
193
|
+
})).optional().describe("Chat history for AI answer generation. Provide previous conversation context to help the AI maintain continuity and reference earlier parts of the conversation. This enables more coherent multi-turn conversations."),
|
|
194
|
+
header_prompt: z.string().optional().describe("Header prompt for AI answer generation. Custom instructions that define the AI's role, style, and behavior. Use this to create specialized assistants (e.g., technical support, friendly helper, formal advisor) or set specific guidelines for response generation."),
|
|
195
|
+
footer_prompt: z.string().optional().describe("Footer prompt for AI answer generation. Additional instructions that are applied after the main response generation. Useful for formatting requirements, citation styles, or specific response patterns that should be consistently applied."),
|
|
196
|
+
temperature: z.number().min(0).max(2.0).optional().describe("Temperature for AI answer generation. Controls the creativity and randomness of responses. Lower values (0.1-0.3) produce more focused, deterministic answers. Higher values (0.7-1.0) produce more creative, varied responses. Default is 0.7."),
|
|
197
|
+
},
|
|
198
|
+
handler: async ({ namespace, query, top_k = 10, type = 'text', threshold, kiosk_mode = false, ai_model, chat_history = [], header_prompt, footer_prompt, temperature = 0.7 }) => {
|
|
199
|
+
try {
|
|
200
|
+
// Determine if this is Direct AI Mode (empty namespace) or Search Mode (with namespace)
|
|
201
|
+
const isDirectAIMode = namespace === "";
|
|
202
|
+
|
|
203
|
+
const requestBody = {
|
|
204
|
+
namespace,
|
|
205
|
+
query,
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
if (isDirectAIMode) {
|
|
209
|
+
// Direct AI Mode: Only allow basic AI fields
|
|
210
|
+
if (ai_model) {
|
|
211
|
+
requestBody.ai_model = ai_model;
|
|
212
|
+
}
|
|
213
|
+
if (chat_history && chat_history.length > 0) {
|
|
214
|
+
requestBody.chat_history = chat_history;
|
|
215
|
+
}
|
|
216
|
+
if (header_prompt) {
|
|
217
|
+
requestBody.header_prompt = header_prompt;
|
|
218
|
+
}
|
|
219
|
+
if (footer_prompt) {
|
|
220
|
+
requestBody.footer_prompt = footer_prompt;
|
|
221
|
+
}
|
|
222
|
+
if (temperature !== undefined) {
|
|
223
|
+
requestBody.temperature = temperature;
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
// Search Mode: Allow all fields including search parameters
|
|
227
|
+
requestBody.top_k = top_k;
|
|
228
|
+
requestBody.type = type;
|
|
229
|
+
requestBody.kiosk_mode = kiosk_mode;
|
|
230
|
+
|
|
231
|
+
if (threshold !== undefined) {
|
|
232
|
+
requestBody.threshold = threshold;
|
|
233
|
+
}
|
|
234
|
+
if (ai_model) {
|
|
235
|
+
requestBody.ai_model = ai_model;
|
|
236
|
+
}
|
|
237
|
+
if (chat_history && chat_history.length > 0) {
|
|
238
|
+
requestBody.chat_history = chat_history;
|
|
239
|
+
}
|
|
240
|
+
if (header_prompt) {
|
|
241
|
+
requestBody.header_prompt = header_prompt;
|
|
242
|
+
}
|
|
243
|
+
if (footer_prompt) {
|
|
244
|
+
requestBody.footer_prompt = footer_prompt;
|
|
245
|
+
}
|
|
246
|
+
if (temperature !== undefined) {
|
|
247
|
+
requestBody.temperature = temperature;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const data = await makeApiRequest('POST', API_ENDPOINTS.answer, requestBody);
|
|
252
|
+
|
|
253
|
+
const mode = isDirectAIMode ? "Direct AI Mode" : "Search Mode";
|
|
254
|
+
const namespaceInfo = isDirectAIMode ? "no namespace (direct AI call)" : `namespace "${namespace}"`;
|
|
255
|
+
const resultText = `AI Answer (${mode}) for "${query}" using ${namespaceInfo}:\n\n${data.answer || data.response || JSON.stringify(data, null, 2)}`;
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
content: [
|
|
259
|
+
{
|
|
260
|
+
type: "text",
|
|
261
|
+
text: resultText,
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
};
|
|
265
|
+
} catch (error) {
|
|
266
|
+
return {
|
|
267
|
+
content: [
|
|
268
|
+
{
|
|
269
|
+
type: "text",
|
|
270
|
+
text: `Error getting AI answer: ${error.message}`,
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
},
|
|
274
276
|
};
|
|
@@ -43,7 +43,7 @@ export const searchOptimizationPrompt = {
|
|
|
43
43
|
\`\`\`bash
|
|
44
44
|
curl -X POST "https://api.moorcheh.ai/v1/search" \\
|
|
45
45
|
-H "Content-Type: application/json" \\
|
|
46
|
-
-H "x-api-
|
|
46
|
+
-H "x-api-key: your-api-key-here" \\
|
|
47
47
|
-d '{
|
|
48
48
|
"query": [0.1, 0.2, 0.3, ..., 0.768],
|
|
49
49
|
"namespaces": ["vector-embeddings"],
|
|
@@ -333,11 +333,11 @@ Include previous conversation context to:
|
|
|
333
333
|
{
|
|
334
334
|
"namespace": "your-namespace",
|
|
335
335
|
"query": "How do I configure authentication?",
|
|
336
|
-
"
|
|
337
|
-
"
|
|
336
|
+
"header_prompt": "You are a technical assistant...",
|
|
337
|
+
"footer_prompt": "Always include code examples...",
|
|
338
338
|
"temperature": 0.5,
|
|
339
339
|
"top_k": 5,
|
|
340
|
-
"
|
|
340
|
+
"chat_history": [
|
|
341
341
|
{"role": "user", "content": "Previous question..."},
|
|
342
342
|
{"role": "assistant", "content": "Previous answer..."}
|
|
343
343
|
]
|
|
@@ -22,8 +22,8 @@ export const namespaceDetailsResource = {
|
|
|
22
22
|
mimeType: "application/json",
|
|
23
23
|
handler: async (uri) => {
|
|
24
24
|
try {
|
|
25
|
-
const
|
|
26
|
-
const data = await makeApiRequest('GET', `${API_ENDPOINTS.namespaces}/${
|
|
25
|
+
const namespace_name = uri.split('/').pop();
|
|
26
|
+
const data = await makeApiRequest('GET', `${API_ENDPOINTS.namespaces}/${namespace_name}`);
|
|
27
27
|
return JSON.stringify(data, null, 2);
|
|
28
28
|
} catch (error) {
|
|
29
29
|
return JSON.stringify({ error: error.message }, null, 2);
|
|
@@ -345,11 +345,11 @@ Include previous conversation context to:
|
|
|
345
345
|
{
|
|
346
346
|
"namespace": "your-namespace",
|
|
347
347
|
"query": "How do I configure authentication?",
|
|
348
|
-
"
|
|
349
|
-
"
|
|
348
|
+
"header_prompt": "You are a technical assistant...",
|
|
349
|
+
"footer_prompt": "Always include code examples...",
|
|
350
350
|
"temperature": 0.5,
|
|
351
351
|
"top_k": 5,
|
|
352
|
-
"
|
|
352
|
+
"chat_history": [
|
|
353
353
|
{"role": "user", "content": "Previous question..."},
|
|
354
354
|
{"role": "assistant", "content": "Previous answer..."}
|
|
355
355
|
]
|