Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
15 changes: 7 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "mcphub"
version = "0.1.7"
version = "0.1.8"
description = "A Python package for managing and integrating Model Context Protocol (MCP) servers with AI frameworks like OpenAI Agents, LangChain, and Autogen"
readme = "README.md"
authors = [
Expand All @@ -21,14 +21,14 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
dependencies = [
"python-dotenv>=1.0.0,<2.0.0",
"build (>=1.2.2.post1,<2.0.0)",
"twine (>=6.1.0,<7.0.0)",
]
dependencies = []
requires-python = "<4.0,>=3.10"

[project.optional-dependencies]
dev = [
"build>=1.2.2.post1,<2.0.0",
"twine>=6.1.0,<7.0.0",
]
openai = [
"openai-agents (>=0.0.9,<0.0.10)",
]
Expand All @@ -54,5 +54,4 @@ mcphub = "mcphub.cli.commands:main"

[tool.poetry.group.dev.dependencies]
pytest = "^8.3.5"
pytest-asyncio = "^0.26.0"

pytest-asyncio = "^0.26.0"
21 changes: 21 additions & 0 deletions src/mcphub/adapters/autogen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
try:
from typing import List

from autogen_ext.tools.mcp import StdioMcpToolAdapter

from .base import MCPBaseAdapter

class MCPAutogenAdapter(MCPBaseAdapter):
async def create_adapters(self, mcp_name: str) -> List[StdioMcpToolAdapter]:
server_params = self.get_server_params(mcp_name)
async with self.create_session(mcp_name) as session:
tools = await session.list_tools()
return [
await StdioMcpToolAdapter.from_server_params(server_params, tool.name)
for tool in tools.tools
]

except ImportError:
class MCPAutogenAdapter: # type: ignore
def __init__(self, *args, **kwargs):
raise ImportError("Autogen dependencies not found. Install with: pip install mcphub[autogen]")
44 changes: 44 additions & 0 deletions src/mcphub/adapters/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from abc import ABC
from contextlib import asynccontextmanager
from typing import AsyncGenerator, List, Tuple, Any

from mcp import ClientSession, StdioServerParameters, Tool
from mcp.client.stdio import stdio_client
from ..mcp_servers.params import MCPServersParams, MCPServerConfig
from ..mcp_servers.exceptions import ServerConfigNotFoundError

class MCPBaseAdapter(ABC):
def __init__(self, servers_params: MCPServersParams):
self.servers_params = servers_params

def get_server_config(self, mcp_name: str) -> MCPServerConfig:
"""Get server configuration or raise error if not found"""
server_config = self.servers_params.retrieve_server_params(mcp_name)
if not server_config:
raise ServerConfigNotFoundError(f"Server configuration not found for '{mcp_name}'")
return server_config

def get_server_params(self, mcp_name: str) -> StdioServerParameters:
"""Convert server config to StdioServerParameters"""
server_config = self.get_server_config(mcp_name)
return StdioServerParameters(
command=server_config.command,
args=server_config.args,
env=server_config.env,
cwd=server_config.cwd
)

async def get_tools(self, mcp_name: str) -> List[Tool]:
"""Get tools from the server"""
async with self.create_session(mcp_name) as session:
tools = await session.list_tools()
return tools.tools

@asynccontextmanager
async def create_session(self, mcp_name: str) -> AsyncGenerator[ClientSession, None]:
"""Create and initialize a client session for the given MCP server"""
server_params = self.get_server_params(mcp_name)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
yield session
19 changes: 19 additions & 0 deletions src/mcphub/adapters/langchain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@


try:
from typing import List

from langchain_core.tools import BaseTool
from langchain_mcp_adapters.tools import load_mcp_tools

from .base import MCPBaseAdapter

class MCPLangChainAdapter(MCPBaseAdapter):
async def create_tools(self, mcp_name: str) -> List[BaseTool]:
async with self.create_session(mcp_name) as session:
return await load_mcp_tools(session)

except ImportError:
class MCPLangChainAdapter: # type: ignore
def __init__(self, *args, **kwargs):
raise ImportError("LangChain dependencies not found. Install with: pip install mcphub[langchain]")
21 changes: 21 additions & 0 deletions src/mcphub/adapters/openai.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
try:
from agents.mcp import MCPServerStdio, MCPServerStdioParams
from .base import MCPBaseAdapter

class MCPOpenAIAgentsAdapter(MCPBaseAdapter):
def create_server(self, mcp_name: str, cache_tools_list: bool = True) -> MCPServerStdio:
server_config = self.get_server_config(mcp_name)
server_params = MCPServerStdioParams(
command=server_config.command,
args=server_config.args,
env=server_config.env,
cwd=server_config.cwd
)
return MCPServerStdio(
params=server_params,
cache_tools_list=cache_tools_list
)
except ImportError:
class MCPOpenAIAgentsAdapter: # type: ignore
def __init__(self, *args, **kwargs):
raise ImportError("OpenAI Agents dependencies not found. Install with: pip install mcphub[openai]")
1 change: 1 addition & 0 deletions src/mcphub/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# CLI package for MCPHub
162 changes: 162 additions & 0 deletions src/mcphub/cli/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
"""CLI commands for mcphub."""
import argparse
import json
import sys
from pathlib import Path
from typing import Dict, Any, List, Optional

from .utils import (
load_config,
save_config,
DEFAULT_CONFIG,
get_config_path,
add_server_config,
remove_server_config,
list_available_servers,
list_configured_servers
)

def init_command(args):
"""Initialize a new .mcphub.json configuration file in the current directory."""
config_path = get_config_path()
if config_path.exists():
print(f"Configuration file already exists at: {config_path}")
return

save_config(DEFAULT_CONFIG)
print(f"Created new configuration file at: {config_path}")

def add_command(args):
"""Add a preconfigured MCP server to the local config."""
server_name = args.mcp_name
non_interactive = args.non_interactive if hasattr(args, 'non_interactive') else False

success, missing_env_vars = add_server_config(server_name, interactive=not non_interactive)

if not success:
print(f"Error: MCP server '{server_name}' not found in preconfigured servers")
# Show available options
print("\nAvailable preconfigured servers:")
available_servers = list_available_servers()
for name in available_servers:
print(f"- {name}")
sys.exit(1)

print(f"Added configuration for '{server_name}' to .mcphub.json")

# Notify about missing environment variables
if missing_env_vars:
print("\nWarning: The following environment variables are required but not set:")
for var in missing_env_vars:
print(f"- {var}")
print("\nYou can either:")
print("1. Set them in your environment before using this server")
print("2. Run 'mcphub add-env' to add them to your configuration")
print("3. Edit .mcphub.json manually to set the values")

def remove_command(args):
"""Remove an MCP server configuration from the local config."""
server_name = args.mcp_name
if remove_server_config(server_name):
print(f"Removed configuration for '{server_name}' from .mcphub.json")
else:
print(f"Error: MCP server '{server_name}' not found in current configuration")
# Show what's currently configured
configured = list_configured_servers()
if configured:
print("\nCurrently configured servers:")
for name in configured:
print(f"- {name}")
sys.exit(1)

def list_command(args):
"""List all configured and available MCP servers."""
show_all = args.all if hasattr(args, 'all') else False

configured = list_configured_servers()
print("Configured MCP servers:")
if configured:
for name in configured:
print(f"- {name}")
else:
print(" No servers configured in local .mcphub.json")

if show_all:
available = list_available_servers()
print("\nAvailable preconfigured MCP servers:")
if available:
for name in available:
print(f"- {name}")
else:
print(" No preconfigured servers available")

def parse_args(args=None):
"""Parse command line arguments."""
parser = argparse.ArgumentParser(
description="MCPHub CLI tool for managing MCP server configurations"
)

subparsers = parser.add_subparsers(dest="command", help="Commands")

# Init command
init_parser = subparsers.add_parser(
"init",
help="Create a new .mcphub.json file in the current directory"
)

# Add command
add_parser = subparsers.add_parser(
"add",
help="Add a preconfigured MCP server to your local config"
)
add_parser.add_argument(
"mcp_name",
help="Name of the preconfigured MCP server to add"
)
add_parser.add_argument(
"-n", "--non-interactive",
action="store_true",
help="Don't prompt for environment variables"
)

# Remove command
remove_parser = subparsers.add_parser(
"remove",
help="Remove an MCP server from your local config"
)
remove_parser.add_argument(
"mcp_name",
help="Name of the MCP server to remove"
)

# List command
list_parser = subparsers.add_parser(
"list",
help="List configured MCP servers"
)
list_parser.add_argument(
"-a", "--all",
action="store_true",
help="Show all available preconfigured servers"
)

return parser.parse_args(args)

def main():
"""Main entry point for the CLI."""
args = parse_args()

if args.command == "init":
init_command(args)
elif args.command == "add":
add_command(args)
elif args.command == "remove":
remove_command(args)
elif args.command == "list":
list_command(args)
else:
# Show help if no command is provided
parse_args(["-h"])

if __name__ == "__main__":
main()
Loading