Skip to content

Commit 14e0897

Browse files
authored
Merge pull request #55 from meilisearch/feature/update-meilisearch-sdk
feat: Update Meilisearch SDK to v0.37.0 and add conversational search support
2 parents f988850 + 8352017 commit 14e0897

File tree

5 files changed

+451
-3
lines changed

5 files changed

+451
-3
lines changed

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
[project]
22
name = "meilisearch-mcp"
3-
version = "0.5.0"
3+
version = "0.6.0"
44
description = "MCP server for Meilisearch"
55
requires-python = ">=3.10"
66
dependencies = [
7-
"meilisearch>=0.36.0",
7+
"meilisearch>=0.37.0",
88
"mcp>=1.12.4",
99
"httpx>=0.28.1",
1010
"pydantic>=2.11.7",

src/meilisearch_mcp/chat.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
from typing import Any, Dict, Iterator, List, Optional
2+
3+
from meilisearch import Client
4+
from meilisearch.errors import MeilisearchApiError
5+
6+
from .logging import MCPLogger
7+
8+
logger = MCPLogger()
9+
10+
11+
class ChatManager:
12+
def __init__(self, client: Client):
13+
self.client = client
14+
15+
async def create_chat_completion(
16+
self,
17+
workspace_uid: str,
18+
messages: List[Dict[str, str]],
19+
model: str = "gpt-3.5-turbo",
20+
stream: bool = True,
21+
) -> str:
22+
try:
23+
logger.info(f"Creating chat completion for workspace: {workspace_uid}")
24+
25+
# The SDK returns an iterator for streaming responses
26+
response_chunks = []
27+
for chunk in self.client.create_chat_completion(
28+
workspace_uid=workspace_uid,
29+
messages=messages,
30+
model=model,
31+
stream=stream,
32+
):
33+
response_chunks.append(chunk)
34+
35+
# Combine all chunks into a complete response
36+
full_response = self._combine_chunks(response_chunks)
37+
logger.info(
38+
f"Chat completion created successfully for workspace: {workspace_uid}"
39+
)
40+
return full_response
41+
42+
except MeilisearchApiError as e:
43+
logger.error(f"Meilisearch API error in create_chat_completion: {e}")
44+
raise
45+
except Exception as e:
46+
logger.error(f"Error in create_chat_completion: {e}")
47+
raise
48+
49+
def _combine_chunks(self, chunks: List[Dict[str, Any]]) -> str:
50+
"""Combine streaming chunks into a single response message."""
51+
content_parts = []
52+
for chunk in chunks:
53+
if "choices" in chunk and chunk["choices"]:
54+
choice = chunk["choices"][0]
55+
if "delta" in choice and "content" in choice["delta"]:
56+
content_parts.append(choice["delta"]["content"])
57+
return "".join(content_parts)
58+
59+
async def get_chat_workspaces(
60+
self, offset: Optional[int] = None, limit: Optional[int] = None
61+
) -> Dict[str, Any]:
62+
try:
63+
logger.info(f"Getting chat workspaces (offset={offset}, limit={limit})")
64+
workspaces = self.client.get_chat_workspaces(offset=offset, limit=limit)
65+
logger.info(
66+
f"Retrieved {len(workspaces.get('results', []))} chat workspaces"
67+
)
68+
return workspaces
69+
except MeilisearchApiError as e:
70+
logger.error(f"Meilisearch API error in get_chat_workspaces: {e}")
71+
raise
72+
except Exception as e:
73+
logger.error(f"Error in get_chat_workspaces: {e}")
74+
raise
75+
76+
async def get_chat_workspace_settings(self, workspace_uid: str) -> Dict[str, Any]:
77+
try:
78+
logger.info(f"Getting settings for chat workspace: {workspace_uid}")
79+
settings = self.client.get_chat_workspace_settings(workspace_uid)
80+
logger.info(f"Retrieved settings for workspace: {workspace_uid}")
81+
return settings
82+
except MeilisearchApiError as e:
83+
logger.error(f"Meilisearch API error in get_chat_workspace_settings: {e}")
84+
raise
85+
except Exception as e:
86+
logger.error(f"Error in get_chat_workspace_settings: {e}")
87+
raise
88+
89+
async def update_chat_workspace_settings(
90+
self, workspace_uid: str, settings: Dict[str, Any]
91+
) -> Dict[str, Any]:
92+
try:
93+
logger.info(f"Updating settings for chat workspace: {workspace_uid}")
94+
updated_settings = self.client.update_chat_workspace_settings(
95+
workspace_uid, settings
96+
)
97+
logger.info(f"Updated settings for workspace: {workspace_uid}")
98+
return updated_settings
99+
except MeilisearchApiError as e:
100+
logger.error(
101+
f"Meilisearch API error in update_chat_workspace_settings: {e}"
102+
)
103+
raise
104+
except Exception as e:
105+
logger.error(f"Error in update_chat_workspace_settings: {e}")
106+
raise

src/meilisearch_mcp/server.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import mcp.server.stdio
1010

1111
from .client import MeilisearchClient
12+
from .chat import ChatManager
1213
from .logging import MCPLogger
1314

1415
logger = MCPLogger()
@@ -47,6 +48,7 @@ def __init__(
4748
self.url = url
4849
self.api_key = api_key
4950
self.meili_client = MeilisearchClient(url, api_key)
51+
self.chat_manager = ChatManager(self.meili_client.client)
5052
self.server = Server("meilisearch")
5153
self._setup_handlers()
5254

@@ -60,6 +62,7 @@ def update_connection(
6062
self.api_key = api_key
6163

6264
self.meili_client = MeilisearchClient(self.url, self.api_key)
65+
self.chat_manager = ChatManager(self.meili_client.client)
6366
self.logger.info("Updated Meilisearch connection settings", url=self.url)
6467

6568
def _setup_handlers(self):
@@ -361,6 +364,99 @@ async def handle_list_tools() -> list[types.Tool]:
361364
"additionalProperties": False,
362365
},
363366
),
367+
types.Tool(
368+
name="create-chat-completion",
369+
description="Create a conversational chat completion using Meilisearch's chat feature",
370+
inputSchema={
371+
"type": "object",
372+
"properties": {
373+
"workspace_uid": {
374+
"type": "string",
375+
"description": "Unique identifier of the chat workspace",
376+
},
377+
"messages": {
378+
"type": "array",
379+
"items": {
380+
"type": "object",
381+
"properties": {
382+
"role": {
383+
"type": "string",
384+
"enum": ["user", "assistant", "system"],
385+
},
386+
"content": {"type": "string"},
387+
},
388+
"required": ["role", "content"],
389+
},
390+
"description": "List of message objects comprising the chat history",
391+
},
392+
"model": {
393+
"type": "string",
394+
"default": "gpt-3.5-turbo",
395+
"description": "The model to use for completion",
396+
},
397+
"stream": {
398+
"type": "boolean",
399+
"default": True,
400+
"description": "Whether to stream the response (currently must be true)",
401+
},
402+
},
403+
"required": ["workspace_uid", "messages"],
404+
"additionalProperties": False,
405+
},
406+
),
407+
types.Tool(
408+
name="get-chat-workspaces",
409+
description="Get list of available chat workspaces",
410+
inputSchema={
411+
"type": "object",
412+
"properties": {
413+
"offset": {
414+
"type": "integer",
415+
"description": "Number of workspaces to skip",
416+
},
417+
"limit": {
418+
"type": "integer",
419+
"description": "Maximum number of workspaces to return",
420+
},
421+
},
422+
"additionalProperties": False,
423+
},
424+
),
425+
types.Tool(
426+
name="get-chat-workspace-settings",
427+
description="Get settings for a specific chat workspace",
428+
inputSchema={
429+
"type": "object",
430+
"properties": {
431+
"workspace_uid": {
432+
"type": "string",
433+
"description": "Unique identifier of the chat workspace",
434+
},
435+
},
436+
"required": ["workspace_uid"],
437+
"additionalProperties": False,
438+
},
439+
),
440+
types.Tool(
441+
name="update-chat-workspace-settings",
442+
description="Update settings for a specific chat workspace",
443+
inputSchema={
444+
"type": "object",
445+
"properties": {
446+
"workspace_uid": {
447+
"type": "string",
448+
"description": "Unique identifier of the chat workspace",
449+
},
450+
"settings": {
451+
"type": "object",
452+
"description": "Settings to update for the workspace",
453+
"additionalProperties": True,
454+
},
455+
},
456+
"required": ["workspace_uid", "settings"],
457+
"additionalProperties": False,
458+
},
459+
),
364460
]
365461

366462
@self.server.call_tool()
@@ -611,6 +707,66 @@ async def handle_call_tool(
611707
)
612708
]
613709

710+
elif name == "create-chat-completion":
711+
response = await self.chat_manager.create_chat_completion(
712+
workspace_uid=arguments["workspace_uid"],
713+
messages=arguments["messages"],
714+
model=arguments.get("model", "gpt-3.5-turbo"),
715+
stream=arguments.get("stream", True),
716+
)
717+
return [
718+
types.TextContent(
719+
type="text",
720+
text=f"Chat completion response:\n{response}",
721+
)
722+
]
723+
724+
elif name == "get-chat-workspaces":
725+
workspaces = await self.chat_manager.get_chat_workspaces(
726+
offset=arguments.get("offset") if arguments else None,
727+
limit=arguments.get("limit") if arguments else None,
728+
)
729+
formatted_json = json.dumps(
730+
workspaces, indent=2, default=json_serializer
731+
)
732+
return [
733+
types.TextContent(
734+
type="text",
735+
text=f"Chat workspaces:\n{formatted_json}",
736+
)
737+
]
738+
739+
elif name == "get-chat-workspace-settings":
740+
settings = await self.chat_manager.get_chat_workspace_settings(
741+
workspace_uid=arguments["workspace_uid"]
742+
)
743+
formatted_json = json.dumps(
744+
settings, indent=2, default=json_serializer
745+
)
746+
return [
747+
types.TextContent(
748+
type="text",
749+
text=f"Workspace settings for '{arguments['workspace_uid']}':\n{formatted_json}",
750+
)
751+
]
752+
753+
elif name == "update-chat-workspace-settings":
754+
updated_settings = (
755+
await self.chat_manager.update_chat_workspace_settings(
756+
workspace_uid=arguments["workspace_uid"],
757+
settings=arguments["settings"],
758+
)
759+
)
760+
formatted_json = json.dumps(
761+
updated_settings, indent=2, default=json_serializer
762+
)
763+
return [
764+
types.TextContent(
765+
type="text",
766+
text=f"Updated workspace settings for '{arguments['workspace_uid']}':\n{formatted_json}",
767+
)
768+
]
769+
614770
raise ValueError(f"Unknown tool: {name}")
615771

616772
except Exception as e:

0 commit comments

Comments
 (0)