-
Notifications
You must be signed in to change notification settings - Fork 217
Feat: VideoDB Director support for MCP Client #162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
omgate234
wants to merge
7
commits into
video-db:main
Choose a base branch
from
omgate234:feat/mcp-server-setup
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
504ab0a
feat: support for MCP Servers inside VideoDB Director
omgate234 8eac93a
feat: sample mcp_servers.json
omgate234 ed91d7e
refactor
omgate234 37027ba
feat: requirments.txt
omgate234 fadba5f
refactor: code review
omgate234 7aeb0c4
feat: handling sse and stdio
omgate234 3a71a70
feat: support SSE MCP Servers
omgate234 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,4 +16,5 @@ venv | |
| package-lock.json | ||
| *.mjs | ||
| site/* | ||
| backend/director/downloads | ||
| backend/director/downloads | ||
| mcp_servers.json | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| import json | ||
| import logging | ||
| import os | ||
| from typing import AsyncGenerator | ||
| from contextlib import AsyncExitStack, asynccontextmanager | ||
| from mcp import ClientSession, StdioServerParameters | ||
| from mcp.client.stdio import stdio_client, get_default_environment | ||
| from mcp.client.sse import sse_client | ||
| from director.agents.base import AgentResponse, AgentStatus | ||
| from director.constants import MCP_SERVER_CONFIG_PATH | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class MCPClient: | ||
| def __init__(self): | ||
| self.config_path = MCP_SERVER_CONFIG_PATH | ||
| self.servers = self.load_servers() | ||
| self.mcp_tools = [] | ||
| self.exit_stack = AsyncExitStack() | ||
|
|
||
| def load_servers(self): | ||
| if not os.path.exists(self.config_path): | ||
| return {} | ||
|
|
||
| with open(self.config_path, 'r') as file: | ||
| return json.load(file).get('mcpServers', {}) | ||
|
|
||
| @asynccontextmanager | ||
| async def create_session( | ||
| self, | ||
| server_name, | ||
| config | ||
| ) -> AsyncGenerator[ClientSession, None]: | ||
| if server_name not in self.servers: | ||
| raise ValueError(f"Server '{server_name}' not found in configuration.") | ||
|
|
||
| if config.get("transport") == "stdio": | ||
| if not config.get("command") or not config.get("args"): | ||
| raise ValueError( | ||
| f"Command and args are required for stdio transport: {server_name}" | ||
| ) | ||
|
|
||
| server_params = StdioServerParameters( | ||
| command=config["command"], | ||
| args=config["args"], | ||
| env={**get_default_environment(), **config.get("env", {})}, | ||
| ) | ||
|
|
||
| logger.info(f"{server_name}: Initializing stdio transport with {server_params}") | ||
|
|
||
| stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params)) | ||
| read_stream, write_stream = stdio_transport | ||
|
|
||
| session = await self.exit_stack.enter_async_context( | ||
| ClientSession(read_stream, write_stream) | ||
| ) | ||
|
|
||
| await session.initialize() | ||
| logger.info(f"{server_name}: Connected to server using stdio transport.") | ||
|
|
||
| try: | ||
| yield session | ||
| finally: | ||
| logger.debug(f"{server_name}: Closing session.") | ||
|
|
||
| elif config.get("transport") == "sse": | ||
| if not config.get("url"): | ||
| raise ValueError(f"URL is required for SSE transport: {server_name}") | ||
|
|
||
| read_stream, write_stream = await self.exit_stack.enter_async_context(sse_client(config["url"])) | ||
|
|
||
| session = await self.exit_stack.enter_async_context( | ||
| ClientSession(read_stream, write_stream) | ||
| ) | ||
|
|
||
| await session.initialize() | ||
| logger.info(f"{server_name}: Connected to server using sse transport.") | ||
| try: | ||
| yield session | ||
| finally: | ||
| logger.debug(f"{server_name}: Closing session.") | ||
|
|
||
| else: | ||
| raise ValueError(f"Unsupported transport: {config.get('transport')}") | ||
|
|
||
| async def close(self) -> None: | ||
| """Closes all managed sessions and releases resources.""" | ||
| await self.exit_stack.aclose() | ||
| logger.info("MCPClient closed all sessions.") | ||
|
|
||
| async def connect_to_server(self, name, config): | ||
| try: | ||
| async with self.create_session(name, config) as session: | ||
| if not session: | ||
| logger.error(f"Failed to connect to server: {name}") | ||
| return [] | ||
|
|
||
| response = await session.list_tools() | ||
| tools = response.tools | ||
| for tool in tools: | ||
| tool.server_name = name | ||
|
|
||
| logger.info(f"Connected to {name} server with {len(tools)} tools.") | ||
| self.mcp_tools = tools | ||
| return tools | ||
| finally: | ||
| await self.close() | ||
|
|
||
ankit-v2-3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| async def initialize_all_servers(self): | ||
| """Initialize all servers asynchronously.""" | ||
| all_tools = [] | ||
| for name, config in self.servers.items(): | ||
| try: | ||
| tools = await self.connect_to_server(name, config) | ||
| all_tools.extend(tools) | ||
| except Exception as e: | ||
| logger.info(f"Could not connect to {name}: {e} \n\n config: {config}") | ||
| logger.info(f"Loaded {len(all_tools)} tools from all servers.") | ||
| return all_tools | ||
|
|
||
ankit-v2-3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| def mcp_tools_to_llm_format(self): | ||
| """Converts MCP tools into an LLM-compatible format.""" | ||
| return [{ | ||
| "name": tool.name, | ||
| "description": tool.description, | ||
| "parameters": tool.inputSchema | ||
| } for tool in self.mcp_tools] | ||
|
|
||
| def is_mcp_tool_call(self, name): | ||
| """Checks if a given tool name exists in the registered MCP tools.""" | ||
| return any(tool.name == name for tool in self.mcp_tools) | ||
|
|
||
| async def call_tool(self, tool_name, tool_args): | ||
| try: | ||
| tool = next((t for t in self.mcp_tools if t.name == tool_name), None) | ||
| if not tool: | ||
| raise ValueError(f"Tool '{tool_name}' not found in MCP tools.") | ||
|
|
||
| config = self.servers.get(tool.server_name) | ||
| if not config: | ||
| raise ValueError(f"Server '{tool.server_name}' not found in config.") | ||
|
|
||
| async with self.create_session(tool.server_name, config) as session: | ||
| if not session: | ||
| raise ValueError(f"Failed to create session for server '{tool.server_name}'.") | ||
|
|
||
| logger.info(f"Calling {tool_name} with args {tool_args}") | ||
| result = await session.call_tool(tool_name, tool_args) | ||
| logger.info(f"Tool call result: {result}") | ||
|
|
||
| return AgentResponse( | ||
| status=AgentStatus.SUCCESS, | ||
| message=f"Tool call successful: {tool_name}", | ||
| data={"content": result.content} | ||
| ) | ||
|
|
||
| except Exception as e: | ||
| logger.error(f"Error calling tool '{tool_name}': {e}") | ||
| return AgentResponse( | ||
| status=AgentStatus.ERROR, | ||
| message=f"Error calling tool '{tool_name}': {e}", | ||
| data={} | ||
| ) | ||
| finally: | ||
| await self.close() | ||
|
|
||
ankit-v2-3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,3 +17,4 @@ replicate==1.0.1 | |
| yt-dlp==2024.10.7 | ||
| videodb==0.2.10 | ||
| slack_sdk==3.33.2 | ||
| mcp==1.4.1 | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.