Skip to content

Commit 0c5b9a9

Browse files
authored
Merge pull request #39 from meilisearch/feature/delete-index-tool-issue-23
Add delete-index MCP tool (resolves #23)
2 parents bbb9e0d + e642ba9 commit 0c5b9a9

File tree

2 files changed

+169
-6
lines changed

2 files changed

+169
-6
lines changed

src/meilisearch_mcp/server.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def json_serializer(obj: Any) -> str:
1919
if isinstance(obj, datetime):
2020
return obj.isoformat()
2121
# Handle Meilisearch model objects by using their __dict__ if available
22-
if hasattr(obj, '__dict__'):
22+
if hasattr(obj, "__dict__"):
2323
return obj.__dict__
2424
return str(obj)
2525

@@ -117,6 +117,15 @@ async def handle_list_tools() -> list[types.Tool]:
117117
description="List all Meilisearch indexes",
118118
inputSchema={"type": "object", "properties": {}},
119119
),
120+
types.Tool(
121+
name="delete-index",
122+
description="Delete a Meilisearch index",
123+
inputSchema={
124+
"type": "object",
125+
"properties": {"uid": {"type": "string"}},
126+
"required": ["uid"],
127+
},
128+
),
120129
types.Tool(
121130
name="get-documents",
122131
description="Get documents from an index",
@@ -353,6 +362,17 @@ async def handle_call_tool(
353362
)
354363
]
355364

365+
elif name == "delete-index":
366+
result = await self.meili_client.indexes.delete_index(
367+
arguments["uid"]
368+
)
369+
return [
370+
types.TextContent(
371+
type="text",
372+
text=f"Successfully deleted index: {arguments['uid']}",
373+
)
374+
]
375+
356376
elif name == "get-documents":
357377
# Use default values to fix None parameter issues (related to issue #17)
358378
offset = arguments.get("offset", 0)
@@ -363,9 +383,13 @@ async def handle_call_tool(
363383
limit,
364384
)
365385
# Convert DocumentsResults object to proper JSON format (fixes issue #16)
366-
formatted_json = json.dumps(documents, indent=2, default=json_serializer)
386+
formatted_json = json.dumps(
387+
documents, indent=2, default=json_serializer
388+
)
367389
return [
368-
types.TextContent(type="text", text=f"Documents:\n{formatted_json}")
390+
types.TextContent(
391+
type="text", text=f"Documents:\n{formatted_json}"
392+
)
369393
]
370394

371395
elif name == "add-documents":

tests/test_mcp_client.py

Lines changed: 142 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ async def test_complete_tool_list(self, mcp_server):
232232
tools = await simulate_list_tools(mcp_server)
233233
tool_names = [tool.name for tool in tools]
234234

235-
# Complete list of expected tools (21 total)
235+
# Complete list of expected tools (22 total)
236236
expected_tools = [
237237
"get-connection-settings",
238238
"update-connection-settings",
@@ -241,6 +241,7 @@ async def test_complete_tool_list(self, mcp_server):
241241
"get-stats",
242242
"create-index",
243243
"list-indexes",
244+
"delete-index",
244245
"get-documents",
245246
"add-documents",
246247
"get-settings",
@@ -272,7 +273,13 @@ async def test_tool_categorization(self, mcp_server):
272273
t
273274
for t in tools
274275
if any(
275-
word in t.name for word in ["index", "create-index", "list-indexes"]
276+
word in t.name
277+
for word in [
278+
"index",
279+
"create-index",
280+
"list-indexes",
281+
"delete-index",
282+
]
276283
)
277284
],
278285
"document": [t for t in tools if "document" in t.name],
@@ -292,7 +299,7 @@ async def test_tool_categorization(self, mcp_server):
292299
# Verify minimum expected tools per category
293300
expected_counts = {
294301
"connection": 2,
295-
"index": 2,
302+
"index": 3,
296303
"document": 2,
297304
"search": 1,
298305
"task": 2,
@@ -471,3 +478,135 @@ async def test_get_documents_default_values_applied(self, mcp_server):
471478
# Both should work and return similar results
472479
assert_text_content_response(result_no_params)
473480
assert_text_content_response(result_with_defaults)
481+
482+
483+
class TestIssue23DeleteIndexTool:
484+
"""Test for issue #23 - Add delete-index MCP tool functionality"""
485+
486+
async def test_delete_index_tool_discovery(self, mcp_server):
487+
"""Test that delete-index tool is discoverable by MCP clients (issue #23)"""
488+
tools = await simulate_list_tools(mcp_server)
489+
tool_names = [tool.name for tool in tools]
490+
491+
assert "delete-index" in tool_names
492+
493+
# Find the delete-index tool and verify its schema
494+
delete_tool = next(tool for tool in tools if tool.name == "delete-index")
495+
assert delete_tool.description == "Delete a Meilisearch index"
496+
assert delete_tool.inputSchema["type"] == "object"
497+
assert "uid" in delete_tool.inputSchema["required"]
498+
assert "uid" in delete_tool.inputSchema["properties"]
499+
assert delete_tool.inputSchema["properties"]["uid"]["type"] == "string"
500+
501+
async def test_delete_index_successful_deletion(self, mcp_server):
502+
"""Test successful index deletion through MCP client (issue #23)"""
503+
test_index = generate_unique_index_name("test_delete_success")
504+
505+
# Create index first
506+
await simulate_mcp_call(mcp_server, "create-index", {"uid": test_index})
507+
await wait_for_indexing()
508+
509+
# Verify index exists by listing indexes
510+
list_result = await simulate_mcp_call(mcp_server, "list-indexes")
511+
list_text = assert_text_content_response(list_result)
512+
assert test_index in list_text
513+
514+
# Delete the index
515+
result = await simulate_mcp_call(
516+
mcp_server, "delete-index", {"uid": test_index}
517+
)
518+
response_text = assert_text_content_response(
519+
result, "Successfully deleted index:"
520+
)
521+
assert test_index in response_text
522+
523+
# Verify index no longer exists by listing indexes
524+
await wait_for_indexing()
525+
list_result_after = await simulate_mcp_call(mcp_server, "list-indexes")
526+
list_text_after = assert_text_content_response(list_result_after)
527+
assert test_index not in list_text_after
528+
529+
async def test_delete_index_with_documents(self, mcp_server):
530+
"""Test deleting index that contains documents (issue #23)"""
531+
test_index = generate_unique_index_name("test_delete_with_docs")
532+
test_documents = [
533+
{"id": 1, "title": "Test Document 1", "content": "Content 1"},
534+
{"id": 2, "title": "Test Document 2", "content": "Content 2"},
535+
]
536+
537+
# Create index and add documents
538+
await create_test_index_with_documents(mcp_server, test_index, test_documents)
539+
540+
# Verify documents exist
541+
docs_result = await simulate_mcp_call(
542+
mcp_server, "get-documents", {"indexUid": test_index}
543+
)
544+
docs_text = assert_text_content_response(docs_result, "Documents:")
545+
assert "Test Document 1" in docs_text
546+
547+
# Delete the index (should also delete all documents)
548+
result = await simulate_mcp_call(
549+
mcp_server, "delete-index", {"uid": test_index}
550+
)
551+
response_text = assert_text_content_response(
552+
result, "Successfully deleted index:"
553+
)
554+
assert test_index in response_text
555+
556+
# Verify index and documents are gone
557+
await wait_for_indexing()
558+
list_result = await simulate_mcp_call(mcp_server, "list-indexes")
559+
list_text = assert_text_content_response(list_result)
560+
assert test_index not in list_text
561+
562+
async def test_delete_nonexistent_index_behavior(self, mcp_server):
563+
"""Test behavior when deleting non-existent index (issue #23)"""
564+
nonexistent_index = generate_unique_index_name("nonexistent")
565+
566+
# Try to delete non-existent index
567+
# Note: Meilisearch allows deleting non-existent indexes without error
568+
result = await simulate_mcp_call(
569+
mcp_server, "delete-index", {"uid": nonexistent_index}
570+
)
571+
response_text = assert_text_content_response(
572+
result, "Successfully deleted index:"
573+
)
574+
assert nonexistent_index in response_text
575+
576+
async def test_delete_index_input_validation(self, mcp_server):
577+
"""Test input validation for delete-index tool (issue #23)"""
578+
# Test missing uid parameter
579+
result = await simulate_mcp_call(mcp_server, "delete-index", {})
580+
response_text = assert_text_content_response(result, "Error:")
581+
assert "Error:" in response_text
582+
583+
async def test_delete_index_integration_workflow(self, mcp_server):
584+
"""Test complete workflow: create -> add docs -> search -> delete (issue #23)"""
585+
test_index = generate_unique_index_name("test_delete_workflow")
586+
test_documents = [
587+
{"id": 1, "title": "Workflow Document", "content": "Testing workflow"},
588+
]
589+
590+
# Create index and add documents
591+
await create_test_index_with_documents(mcp_server, test_index, test_documents)
592+
593+
# Search to verify functionality
594+
search_result = await simulate_mcp_call(
595+
mcp_server, "search", {"query": "workflow", "indexUid": test_index}
596+
)
597+
search_text = assert_text_content_response(search_result)
598+
assert "Workflow Document" in search_text
599+
600+
# Delete the index
601+
delete_result = await simulate_mcp_call(
602+
mcp_server, "delete-index", {"uid": test_index}
603+
)
604+
assert_text_content_response(delete_result, "Successfully deleted index:")
605+
606+
# Verify search no longer works on deleted index
607+
await wait_for_indexing()
608+
search_after_delete = await simulate_mcp_call(
609+
mcp_server, "search", {"query": "workflow", "indexUid": test_index}
610+
)
611+
search_after_text = assert_text_content_response(search_after_delete, "Error:")
612+
assert "Error:" in search_after_text

0 commit comments

Comments
 (0)