@@ -6,6 +6,10 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
66import type { IndexState } from "../core/types.js" ;
77import type { IndexStoreReader } from "../stores/types.js" ;
88import type { Source } from "../sources/types.js" ;
9+ import {
10+ ListToolsRequestSchema ,
11+ CallToolRequestSchema ,
12+ } from "@modelcontextprotocol/sdk/types.js" ;
913
1014// Try to import SDK-dependent modules
1115let createMCPServer : typeof import ( "./mcp-server.js" ) . createMCPServer ;
@@ -134,3 +138,209 @@ describe.skipIf(sdkLoadError !== null)("MCP Server Unit Tests", () => {
134138 } ) ;
135139} ) ;
136140
141+ // Tests for list_indexes tool and discovery vs fixed mode
142+ describe . skipIf ( sdkLoadError !== null || ! hasApiCredentials ) (
143+ "list_indexes tool and discovery mode" ,
144+ ( ) => {
145+ describe ( "list_indexes tool" , ( ) => {
146+ it ( "returns available indexes with metadata" , async ( ) => {
147+ const mockState = createMockState ( ) ;
148+ const store = createMockStore ( mockState ) ;
149+
150+ const server = await createMCPServer ( {
151+ store,
152+ indexNames : [ "test-key" ] ,
153+ } ) ;
154+
155+ // Get the ListToolsRequestSchema handler
156+ const listToolsHandler = ( server as any ) . requestHandlers . get (
157+ ListToolsRequestSchema
158+ ) ;
159+ expect ( listToolsHandler ) . toBeDefined ( ) ;
160+
161+ // Call the handler to get tools
162+ const result = await listToolsHandler ( ) ;
163+ const listIndexesTool = result . tools . find (
164+ ( t : any ) => t . name === "list_indexes"
165+ ) ;
166+ expect ( listIndexesTool ) . toBeDefined ( ) ;
167+ expect ( listIndexesTool . description ) . toContain ( "available indexes" ) ;
168+ } ) ;
169+
170+ it ( "returns 'No indexes available' message when empty in discovery mode" , async ( ) => {
171+ const store = createMockStore ( null ) ;
172+ // Mock store.list() to return empty array for discovery mode
173+ store . list = vi . fn ( ) . mockResolvedValue ( [ ] ) ;
174+
175+ const server = await createMCPServer ( {
176+ store,
177+ indexNames : [ ] ,
178+ discovery : true ,
179+ } ) ;
180+
181+ // Get the CallToolRequestSchema handler
182+ const callToolHandler = ( server as any ) . requestHandlers . get (
183+ CallToolRequestSchema
184+ ) ;
185+ expect ( callToolHandler ) . toBeDefined ( ) ;
186+
187+ // Call list_indexes
188+ const result = await callToolHandler ( {
189+ params : {
190+ name : "list_indexes" ,
191+ arguments : { } ,
192+ } ,
193+ } ) ;
194+
195+ expect ( result . content [ 0 ] . text ) . toContain ( "No indexes available" ) ;
196+ } ) ;
197+
198+ it ( "handles errors gracefully and returns isError: true" , async ( ) => {
199+ const store = createMockStore ( createMockState ( ) ) ;
200+ // Mock store.list() to throw an error
201+ store . list = vi . fn ( ) . mockRejectedValue ( new Error ( "Store error" ) ) ;
202+
203+ const server = await createMCPServer ( {
204+ store,
205+ indexNames : [ "test-key" ] ,
206+ discovery : true ,
207+ } ) ;
208+
209+ // Get the CallToolRequestSchema handler
210+ const callToolHandler = ( server as any ) . requestHandlers . get (
211+ CallToolRequestSchema
212+ ) ;
213+
214+ // Call list_indexes
215+ const result = await callToolHandler ( {
216+ params : {
217+ name : "list_indexes" ,
218+ arguments : { } ,
219+ } ,
220+ } ) ;
221+
222+ expect ( result . isError ) . toBe ( true ) ;
223+ expect ( result . content [ 0 ] . text ) . toContain ( "Error listing indexes" ) ;
224+ } ) ;
225+ } ) ;
226+
227+ describe ( "discovery vs fixed mode" , ( ) => {
228+ it ( "fixed mode includes enum in tool schemas for index_name" , async ( ) => {
229+ const mockState = createMockState ( ) ;
230+ const store = createMockStore ( mockState ) ;
231+
232+ const server = await createMCPServer ( {
233+ store,
234+ indexNames : [ "test-key" ] ,
235+ discovery : false , // Fixed mode
236+ } ) ;
237+
238+ // Get the ListToolsRequestSchema handler
239+ const listToolsHandler = ( server as any ) . requestHandlers . get (
240+ ListToolsRequestSchema
241+ ) ;
242+ const result = await listToolsHandler ( ) ;
243+
244+ // Check search tool has enum
245+ const searchTool = result . tools . find ( ( t : any ) => t . name === "search" ) ;
246+ expect ( searchTool . inputSchema . properties . index_name . enum ) . toBeDefined ( ) ;
247+ expect ( searchTool . inputSchema . properties . index_name . enum ) . toContain (
248+ "test-key"
249+ ) ;
250+
251+ // Check list_files tool has enum (if present)
252+ const listFilesTool = result . tools . find (
253+ ( t : any ) => t . name === "list_files"
254+ ) ;
255+ if ( listFilesTool ) {
256+ expect ( listFilesTool . inputSchema . properties . index_name . enum ) . toBeDefined ( ) ;
257+ expect ( listFilesTool . inputSchema . properties . index_name . enum ) . toContain (
258+ "test-key"
259+ ) ;
260+ }
261+
262+ // Check read_file tool has enum (if present)
263+ const readFileTool = result . tools . find ( ( t : any ) => t . name === "read_file" ) ;
264+ if ( readFileTool ) {
265+ expect ( readFileTool . inputSchema . properties . index_name . enum ) . toBeDefined ( ) ;
266+ expect ( readFileTool . inputSchema . properties . index_name . enum ) . toContain (
267+ "test-key"
268+ ) ;
269+ }
270+ } ) ;
271+
272+ it ( "discovery mode does NOT include enum in tool schemas" , async ( ) => {
273+ const mockState = createMockState ( ) ;
274+ const store = createMockStore ( mockState ) ;
275+
276+ const server = await createMCPServer ( {
277+ store,
278+ indexNames : [ "test-key" ] ,
279+ discovery : true , // Discovery mode
280+ } ) ;
281+
282+ // Get the ListToolsRequestSchema handler
283+ const listToolsHandler = ( server as any ) . requestHandlers . get (
284+ ListToolsRequestSchema
285+ ) ;
286+ const result = await listToolsHandler ( ) ;
287+
288+ // Check search tool does NOT have enum
289+ const searchTool = result . tools . find ( ( t : any ) => t . name === "search" ) ;
290+ expect ( searchTool . inputSchema . properties . index_name . enum ) . toBeUndefined ( ) ;
291+
292+ // Check list_files tool does NOT have enum (if present)
293+ const listFilesTool = result . tools . find (
294+ ( t : any ) => t . name === "list_files"
295+ ) ;
296+ if ( listFilesTool ) {
297+ expect ( listFilesTool . inputSchema . properties . index_name . enum ) . toBeUndefined ( ) ;
298+ }
299+
300+ // Check read_file tool does NOT have enum (if present)
301+ const readFileTool = result . tools . find ( ( t : any ) => t . name === "read_file" ) ;
302+ if ( readFileTool ) {
303+ expect ( readFileTool . inputSchema . properties . index_name . enum ) . toBeUndefined ( ) ;
304+ }
305+ } ) ;
306+
307+ it ( "list_indexes tool is available in both fixed and discovery modes" , async ( ) => {
308+ const mockState = createMockState ( ) ;
309+ const store = createMockStore ( mockState ) ;
310+
311+ // Test fixed mode
312+ const fixedServer = await createMCPServer ( {
313+ store,
314+ indexNames : [ "test-key" ] ,
315+ discovery : false ,
316+ } ) ;
317+
318+ const fixedListToolsHandler = ( fixedServer as any ) . requestHandlers . get (
319+ ListToolsRequestSchema
320+ ) ;
321+ const fixedResult = await fixedListToolsHandler ( ) ;
322+ const fixedListIndexesTool = fixedResult . tools . find (
323+ ( t : any ) => t . name === "list_indexes"
324+ ) ;
325+ expect ( fixedListIndexesTool ) . toBeDefined ( ) ;
326+
327+ // Test discovery mode
328+ const discoveryServer = await createMCPServer ( {
329+ store,
330+ indexNames : [ "test-key" ] ,
331+ discovery : true ,
332+ } ) ;
333+
334+ const discoveryListToolsHandler = ( discoveryServer as any ) . requestHandlers . get (
335+ ListToolsRequestSchema
336+ ) ;
337+ const discoveryResult = await discoveryListToolsHandler ( ) ;
338+ const discoveryListIndexesTool = discoveryResult . tools . find (
339+ ( t : any ) => t . name === "list_indexes"
340+ ) ;
341+ expect ( discoveryListIndexesTool ) . toBeDefined ( ) ;
342+ } ) ;
343+ } ) ;
344+ }
345+ ) ;
346+
0 commit comments