mcp-use 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mcp-use might be problematic. Click here for more details.

mcp_use/client.py ADDED
@@ -0,0 +1,226 @@
1
+ """
2
+ Client for managing MCP servers and sessions.
3
+
4
+ This module provides a high-level client that manages MCP servers, connectors,
5
+ and sessions from configuration.
6
+ """
7
+
8
+ import json
9
+ from typing import Any
10
+
11
+ from .config import create_connector_from_config, load_config_file
12
+ from .session import MCPSession
13
+ from .tools.converter import ModelProvider
14
+
15
+
16
+ class MCPClient:
17
+ """Client for managing MCP servers and sessions.
18
+
19
+ This class provides a unified interface for working with MCP servers,
20
+ handling configuration, connector creation, and session management.
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ config: str | dict[str, Any] | None = None,
26
+ model_provider: str | ModelProvider = "openai",
27
+ ) -> None:
28
+ """Initialize a new MCP client.
29
+
30
+ Args:
31
+ config: Either a dict containing configuration or a path to a JSON config file.
32
+ If None, an empty configuration is used.
33
+ model_provider: The model provider to use for tool conversion.
34
+ """
35
+ self.model_provider = model_provider
36
+ self.config: dict[str, Any] = {}
37
+ self.sessions: dict[str, MCPSession] = {}
38
+ self.active_session: str | None = None
39
+
40
+ # Load configuration if provided
41
+ if config is not None:
42
+ if isinstance(config, str):
43
+ self.config = load_config_file(config)
44
+ else:
45
+ self.config = config
46
+
47
+ @classmethod
48
+ def from_dict(cls, config: dict[str, Any]) -> "MCPClient":
49
+ """Create a MCPClient from a dictionary.
50
+
51
+ Args:
52
+ config: The configuration dictionary.
53
+ """
54
+ return cls(config=config)
55
+
56
+ @classmethod
57
+ def from_config_file(cls, filepath: str) -> "MCPClient":
58
+ """Create a MCPClient from a configuration file.
59
+
60
+ Args:
61
+ filepath: The path to the configuration file.
62
+ """
63
+ return cls(config=load_config_file(filepath))
64
+
65
+ def add_server(
66
+ self,
67
+ name: str,
68
+ server_config: dict[str, Any],
69
+ ) -> None:
70
+ """Add a server configuration.
71
+
72
+ Args:
73
+ name: The name to identify this server.
74
+ server_config: The server configuration.
75
+ """
76
+ if "mcpServers" not in self.config:
77
+ self.config["mcpServers"] = {}
78
+
79
+ self.config["mcpServers"][name] = server_config
80
+
81
+ def remove_server(self, name: str) -> None:
82
+ """Remove a server configuration.
83
+
84
+ Args:
85
+ name: The name of the server to remove.
86
+ """
87
+ if "mcpServers" in self.config and name in self.config["mcpServers"]:
88
+ del self.config["mcpServers"][name]
89
+
90
+ # If we removed the active session, set active_session to None
91
+ if name == self.active_session:
92
+ self.active_session = None
93
+
94
+ def get_server_names(self) -> list[str]:
95
+ """Get the list of configured server names.
96
+
97
+ Returns:
98
+ List of server names.
99
+ """
100
+ return list(self.config.get("mcpServers", {}).keys())
101
+
102
+ def save_config(self, filepath: str) -> None:
103
+ """Save the current configuration to a file.
104
+
105
+ Args:
106
+ filepath: The path to save the configuration to.
107
+ """
108
+ with open(filepath, "w") as f:
109
+ json.dump(self.config, f, indent=2)
110
+
111
+ async def create_session(
112
+ self,
113
+ server_name: str | None = None,
114
+ auto_initialize: bool = True,
115
+ ) -> MCPSession:
116
+ """Create a session for the specified server.
117
+
118
+ Args:
119
+ server_name: The name of the server to create a session for.
120
+ If None, uses the first available server.
121
+ auto_initialize: Whether to automatically initialize the session.
122
+
123
+ Returns:
124
+ The created MCPSession.
125
+
126
+ Raises:
127
+ ValueError: If no servers are configured or the specified server doesn't exist.
128
+ """
129
+ # Get server config
130
+ servers = self.config.get("mcpServers", {})
131
+ if not servers:
132
+ raise ValueError("No MCP servers defined in config")
133
+
134
+ # If server_name not specified, use the first one
135
+ if not server_name:
136
+ server_name = next(iter(servers.keys()))
137
+
138
+ if server_name not in servers:
139
+ raise ValueError(f"Server '{server_name}' not found in config")
140
+
141
+ server_config = servers[server_name]
142
+ connector = create_connector_from_config(server_config)
143
+
144
+ # Create the session
145
+ session = MCPSession(connector, self.model_provider)
146
+ self.sessions[server_name] = session
147
+
148
+ # Make this the active session
149
+ self.active_session = server_name
150
+
151
+ # Initialize if requested
152
+ if auto_initialize:
153
+ await session.initialize()
154
+
155
+ return session
156
+
157
+ def get_session(self, server_name: str | None = None) -> MCPSession:
158
+ """Get an existing session.
159
+
160
+ Args:
161
+ server_name: The name of the server to get the session for.
162
+ If None, uses the active session.
163
+
164
+ Returns:
165
+ The MCPSession for the specified server.
166
+
167
+ Raises:
168
+ ValueError: If no active session exists or the specified session doesn't exist.
169
+ """
170
+ if server_name is None:
171
+ if self.active_session is None:
172
+ raise ValueError("No active session")
173
+ server_name = self.active_session
174
+
175
+ if server_name not in self.sessions:
176
+ raise ValueError(f"No session exists for server '{server_name}'")
177
+
178
+ return self.sessions[server_name]
179
+
180
+ async def close_session(self, server_name: str | None = None) -> None:
181
+ """Close a session.
182
+
183
+ Args:
184
+ server_name: The name of the server to close the session for.
185
+ If None, uses the active session.
186
+
187
+ Raises:
188
+ ValueError: If no active session exists or the specified session doesn't exist.
189
+ """
190
+ session = self.get_session(server_name)
191
+ await session.disconnect()
192
+
193
+ # Remove the session
194
+ if server_name is None:
195
+ server_name = self.active_session
196
+
197
+ if server_name in self.sessions:
198
+ del self.sessions[server_name]
199
+
200
+ # If we closed the active session, set active_session to None
201
+ if server_name == self.active_session:
202
+ self.active_session = None
203
+
204
+ async def close_all_sessions(self) -> None:
205
+ """Close all active sessions."""
206
+ for server_name in list(self.sessions.keys()):
207
+ await self.close_session(server_name)
208
+
209
+ async def __aenter__(self) -> "MCPClient":
210
+ """Enter the async context manager.
211
+
212
+ Creates a session for the first available server if no sessions exist.
213
+
214
+ Returns:
215
+ The client instance.
216
+ """
217
+ if not self.sessions and self.config.get("mcpServers"):
218
+ await self.create_session()
219
+ return self
220
+
221
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
222
+ """Exit the async context manager.
223
+
224
+ Closes all active sessions.
225
+ """
226
+ await self.close_all_sessions()
mcp_use/config.py ADDED
@@ -0,0 +1,113 @@
1
+ """
2
+ Configuration loader for MCP session.
3
+
4
+ This module provides functionality to load MCP configuration from JSON files.
5
+ """
6
+
7
+ import json
8
+ import os
9
+ import subprocess
10
+ from typing import Any
11
+
12
+ from .connectors import BaseConnector, HttpConnector, StdioConnector, WebSocketConnector
13
+ from .session import MCPSession
14
+ from .tools.converter import ModelProvider
15
+
16
+
17
+ def _execute_command(command: str, args: list[str], env: dict[str, str] | None = None) -> None:
18
+ """Execute a command with given arguments and environment variables.
19
+
20
+ Args:
21
+ command: The command to execute
22
+ args: List of command arguments
23
+ env: Optional environment variables to set
24
+ """
25
+ full_env = os.environ.copy()
26
+ if env:
27
+ full_env.update(env)
28
+
29
+ subprocess.Popen([command] + args, env=full_env)
30
+
31
+
32
+ def load_config_file(filepath: str) -> dict[str, Any]:
33
+ """Load a configuration file.
34
+
35
+ Args:
36
+ filepath: Path to the configuration file
37
+
38
+ Returns:
39
+ The parsed configuration
40
+ """
41
+ with open(filepath) as f:
42
+ return json.load(f)
43
+
44
+
45
+ def create_connector_from_config(server_config: dict[str, Any]) -> BaseConnector:
46
+ """Create a connector based on server configuration.
47
+
48
+ Args:
49
+ server_config: The server configuration section
50
+
51
+ Returns:
52
+ A configured connector instance
53
+ """
54
+ # Stdio connector (command-based)
55
+ if "command" in server_config and "args" in server_config:
56
+ return StdioConnector(
57
+ command=server_config["command"],
58
+ args=server_config["args"],
59
+ env=server_config.get("env", None),
60
+ )
61
+
62
+ # HTTP connector
63
+ elif "url" in server_config:
64
+ return HttpConnector(
65
+ url=server_config["url"],
66
+ headers=server_config.get("headers", None),
67
+ auth=server_config.get("auth", None),
68
+ )
69
+
70
+ # WebSocket connector
71
+ elif "ws_url" in server_config:
72
+ return WebSocketConnector(
73
+ url=server_config["ws_url"],
74
+ headers=server_config.get("headers", None),
75
+ auth=server_config.get("auth", None),
76
+ )
77
+
78
+ raise ValueError("Cannot determine connector type from config")
79
+
80
+
81
+ def create_session_from_config(
82
+ filepath: str,
83
+ server_name: str | None = None,
84
+ model_provider: str | ModelProvider = "openai",
85
+ ) -> MCPSession:
86
+ """Create an MCPSession from a configuration file.
87
+
88
+ Args:
89
+ filepath: Path to the configuration file
90
+ server_name: Name of the server to use from config, uses first if None
91
+ model_provider: Model provider to use for tool conversion
92
+
93
+ Returns:
94
+ Configured MCPSession instance
95
+ """
96
+ config = load_config_file(filepath)
97
+
98
+ # Get server config
99
+ servers = config.get("mcpServers", {})
100
+ if not servers:
101
+ raise ValueError("No MCP servers defined in config")
102
+
103
+ # If server_name not specified, use the first one
104
+ if not server_name:
105
+ server_name = next(iter(servers.keys()))
106
+
107
+ if server_name not in servers:
108
+ raise ValueError(f"Server '{server_name}' not found in config")
109
+
110
+ server_config = servers[server_name]
111
+ connector = create_connector_from_config(server_config)
112
+
113
+ return MCPSession(connector, model_provider)
@@ -0,0 +1,13 @@
1
+ """
2
+ Connectors for various MCP transports.
3
+
4
+ This module provides interfaces for connecting to MCP implementations
5
+ through different transport mechanisms.
6
+ """
7
+
8
+ from .base import BaseConnector
9
+ from .http import HttpConnector
10
+ from .stdio import StdioConnector
11
+ from .websocket import WebSocketConnector
12
+
13
+ __all__ = ["BaseConnector", "StdioConnector", "WebSocketConnector", "HttpConnector"]
@@ -0,0 +1,61 @@
1
+ """
2
+ Base connector for MCP implementations.
3
+
4
+ This module provides the base connector interface that all MCP connectors
5
+ must implement.
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+ from typing import Any
10
+
11
+ from mcp.types import CallToolResult
12
+
13
+ from mcp_use.types import Tool
14
+
15
+
16
+ class BaseConnector(ABC):
17
+ """Base class for MCP connectors.
18
+
19
+ This class defines the interface that all MCP connectors must implement.
20
+ """
21
+
22
+ @abstractmethod
23
+ async def connect(self) -> None:
24
+ """Establish a connection to the MCP implementation."""
25
+ pass
26
+
27
+ @abstractmethod
28
+ async def disconnect(self) -> None:
29
+ """Close the connection to the MCP implementation."""
30
+ pass
31
+
32
+ @abstractmethod
33
+ async def initialize(self) -> dict[str, Any]:
34
+ """Initialize the MCP session and return session information."""
35
+ pass
36
+
37
+ @property
38
+ @abstractmethod
39
+ def tools(self) -> list[Tool]:
40
+ """Get the list of available tools."""
41
+ pass
42
+
43
+ @abstractmethod
44
+ async def call_tool(self, name: str, arguments: dict[str, Any]) -> CallToolResult:
45
+ """Call an MCP tool with the given arguments."""
46
+ pass
47
+
48
+ @abstractmethod
49
+ async def list_resources(self) -> list[dict[str, Any]]:
50
+ """List all available resources from the MCP implementation."""
51
+ pass
52
+
53
+ @abstractmethod
54
+ async def read_resource(self, uri: str) -> tuple[bytes, str]:
55
+ """Read a resource by URI."""
56
+ pass
57
+
58
+ @abstractmethod
59
+ async def request(self, method: str, params: dict[str, Any] | None = None) -> Any:
60
+ """Send a raw request to the MCP implementation."""
61
+ pass
@@ -0,0 +1,126 @@
1
+ """
2
+ HTTP connector for MCP implementations.
3
+
4
+ This module provides a connector for communicating with MCP implementations
5
+ through HTTP APIs.
6
+ """
7
+
8
+ from typing import Any
9
+
10
+ import aiohttp
11
+
12
+ from .base import BaseConnector
13
+
14
+
15
+ class HttpConnector(BaseConnector):
16
+ """Connector for MCP implementations using HTTP transport.
17
+
18
+ This connector uses HTTP requests to communicate with remote MCP implementations.
19
+ """
20
+
21
+ def __init__(
22
+ self, base_url: str, auth_token: str | None = None, headers: dict[str, str] | None = None
23
+ ):
24
+ """Initialize a new HTTP connector.
25
+
26
+ Args:
27
+ base_url: The base URL of the MCP HTTP API.
28
+ auth_token: Optional authentication token.
29
+ headers: Optional additional headers.
30
+ """
31
+ self.base_url = base_url.rstrip("/")
32
+ self.auth_token = auth_token
33
+ self.headers = headers or {}
34
+ if auth_token:
35
+ self.headers["Authorization"] = f"Bearer {auth_token}"
36
+ self.session: aiohttp.ClientSession | None = None
37
+
38
+ async def connect(self) -> None:
39
+ """Establish a connection to the MCP implementation."""
40
+ self.session = aiohttp.ClientSession(headers=self.headers)
41
+
42
+ async def disconnect(self) -> None:
43
+ """Close the connection to the MCP implementation."""
44
+ if self.session:
45
+ await self.session.close()
46
+ self.session = None
47
+
48
+ async def _request(self, method: str, endpoint: str, data: dict[str, Any] | None = None) -> Any:
49
+ """Send an HTTP request to the MCP API.
50
+
51
+ Args:
52
+ method: The HTTP method (GET, POST, etc.).
53
+ endpoint: The API endpoint path.
54
+ data: Optional request data.
55
+
56
+ Returns:
57
+ The parsed JSON response.
58
+ """
59
+ if not self.session:
60
+ raise RuntimeError("HTTP session is not connected")
61
+
62
+ url = f"{self.base_url}/{endpoint.lstrip('/')}"
63
+
64
+ if method.upper() == "GET" and data:
65
+ # For GET requests, convert data to query parameters
66
+ async with self.session.get(url, params=data) as response:
67
+ response.raise_for_status()
68
+ return await response.json()
69
+ else:
70
+ # For other methods, send data as JSON body
71
+ async with self.session.request(method, url, json=data) as response:
72
+ response.raise_for_status()
73
+ return await response.json()
74
+
75
+ async def initialize(self) -> dict[str, Any]:
76
+ """Initialize the MCP session and return session information."""
77
+ return await self._request("POST", "initialize")
78
+
79
+ async def list_tools(self) -> list[dict[str, Any]]:
80
+ """List all available tools from the MCP implementation."""
81
+ result = await self._request("GET", "tools")
82
+ return result.get("tools", [])
83
+
84
+ async def call_tool(self, name: str, arguments: dict[str, Any]) -> Any:
85
+ """Call an MCP tool with the given arguments."""
86
+ return await self._request("POST", f"tools/{name}", arguments)
87
+
88
+ async def list_resources(self) -> list[dict[str, Any]]:
89
+ """List all available resources from the MCP implementation."""
90
+ result = await self._request("GET", "resources")
91
+ return result
92
+
93
+ async def read_resource(self, uri: str) -> tuple[bytes, str]:
94
+ """Read a resource by URI."""
95
+ # For resources, we may need to handle binary data
96
+ if not self.session:
97
+ raise RuntimeError("HTTP session is not connected")
98
+
99
+ url = f"{self.base_url}/resources/read"
100
+
101
+ async with self.session.get(url, params={"uri": uri}) as response:
102
+ response.raise_for_status()
103
+
104
+ # Check if this is a JSON response or binary data
105
+ content_type = response.headers.get("Content-Type", "")
106
+ if "application/json" in content_type:
107
+ data = await response.json()
108
+ content = data.get("content", b"")
109
+ mime_type = data.get("mimeType", "")
110
+
111
+ # If content is base64 encoded, decode it
112
+ if isinstance(content, str):
113
+ import base64
114
+
115
+ content = base64.b64decode(content)
116
+
117
+ return content, mime_type
118
+ else:
119
+ # Assume binary response
120
+ content = await response.read()
121
+ return content, content_type
122
+
123
+ async def request(self, method: str, params: dict[str, Any] | None = None) -> Any:
124
+ """Send a raw request to the MCP implementation."""
125
+ # For custom methods, we'll use the RPC-style endpoint
126
+ return await self._request("POST", "rpc", {"method": method, "params": params or {}})
@@ -0,0 +1,124 @@
1
+ """
2
+ StdIO connector for MCP implementations.
3
+
4
+ This module provides a connector for communicating with MCP implementations
5
+ through the standard input/output streams.
6
+ """
7
+
8
+ from typing import Any
9
+
10
+ from mcp import ClientSession, StdioServerParameters
11
+ from mcp.client.stdio import stdio_client
12
+ from mcp.types import Tool
13
+
14
+ from ..logging import logger
15
+ from .base import BaseConnector
16
+
17
+
18
+ class StdioConnector(BaseConnector):
19
+ """Connector for MCP implementations using stdio transport.
20
+
21
+ This connector uses the stdio transport to communicate with MCP implementations
22
+ that are executed as child processes.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ command: str = "npx",
28
+ args: list[str] | None = None,
29
+ env: dict[str, str] | None = None,
30
+ ):
31
+ """Initialize a new stdio connector.
32
+
33
+ Args:
34
+ command: The command to execute.
35
+ args: Optional command line arguments.
36
+ env: Optional environment variables.
37
+ """
38
+ self.command = command
39
+ self.args = args
40
+ self.env = env
41
+ self.client: ClientSession | None = None
42
+ self._stdio_ctx = None
43
+ self._tools: list[Tool] | None = None
44
+
45
+ async def connect(self) -> None:
46
+ """Establish a connection to the MCP implementation."""
47
+ server_params = StdioServerParameters(command=self.command, args=self.args, env=self.env)
48
+ self._stdio_ctx = stdio_client(server_params)
49
+ read_stream, write_stream = await self._stdio_ctx.__aenter__()
50
+ self.client = ClientSession(read_stream, write_stream, sampling_callback=None)
51
+ await self.client.__aenter__()
52
+
53
+ async def disconnect(self) -> None:
54
+ """Close the connection to the MCP implementation."""
55
+ try:
56
+ if self.client:
57
+ await self.client.__aexit__(None, None, None)
58
+ self.client = None
59
+ if self._stdio_ctx:
60
+ await self._stdio_ctx.__aexit__(None, None, None)
61
+ self._stdio_ctx = None
62
+ except Exception as e:
63
+ logger.warning(f"Warning: Error during stdio connector disconnect: {e}")
64
+ finally:
65
+ # Always clean up references even if there were errors
66
+ self.client = None
67
+ self._stdio_ctx = None
68
+ self._tools = None
69
+
70
+ async def __aenter__(self) -> "StdioConnector":
71
+ """Enter the async context manager."""
72
+ await self.connect()
73
+ return self
74
+
75
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
76
+ """Exit the async context manager."""
77
+ await self.disconnect()
78
+
79
+ async def initialize(self) -> dict[str, Any]:
80
+ """Initialize the MCP session and return session information."""
81
+ if not self.client:
82
+ raise RuntimeError("MCP client is not connected")
83
+
84
+ # Initialize the session
85
+ result = await self.client.initialize()
86
+
87
+ # Get available tools
88
+ tools_result = await self.client.list_tools()
89
+ self._tools = tools_result.tools
90
+
91
+ return result
92
+
93
+ @property
94
+ def tools(self) -> list[Tool]:
95
+ """Get the list of available tools."""
96
+ if not self._tools:
97
+ raise RuntimeError("MCP client is not initialized")
98
+ return self._tools
99
+
100
+ async def call_tool(self, name: str, arguments: dict[str, Any]) -> Any:
101
+ """Call an MCP tool with the given arguments."""
102
+ if not self.client:
103
+ raise RuntimeError("MCP client is not connected")
104
+ return await self.client.call_tool(name, arguments)
105
+
106
+ async def list_resources(self) -> list[dict[str, Any]]:
107
+ """List all available resources from the MCP implementation."""
108
+ if not self.client:
109
+ raise RuntimeError("MCP client is not connected")
110
+ resources = await self.client.list_resources()
111
+ return resources
112
+
113
+ async def read_resource(self, uri: str) -> tuple[bytes, str]:
114
+ """Read a resource by URI."""
115
+ if not self.client:
116
+ raise RuntimeError("MCP client is not connected")
117
+ resource = await self.client.read_resource(uri)
118
+ return resource.content, resource.mimeType
119
+
120
+ async def request(self, method: str, params: dict[str, Any] | None = None) -> Any:
121
+ """Send a raw request to the MCP implementation."""
122
+ if not self.client:
123
+ raise RuntimeError("MCP client is not connected")
124
+ return await self.client.request({"method": method, "params": params or {}})