mcp-use 0.0.3__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,222 @@
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
+
14
+
15
+ class MCPClient:
16
+ """Client for managing MCP servers and sessions.
17
+
18
+ This class provides a unified interface for working with MCP servers,
19
+ handling configuration, connector creation, and session management.
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ config: str | dict[str, Any] | None = None,
25
+ ) -> None:
26
+ """Initialize a new MCP client.
27
+
28
+ Args:
29
+ config: Either a dict containing configuration or a path to a JSON config file.
30
+ If None, an empty configuration is used.
31
+ """
32
+ self.config: dict[str, Any] = {}
33
+ self.sessions: dict[str, MCPSession] = {}
34
+ self.active_session: str | None = None
35
+
36
+ # Load configuration if provided
37
+ if config is not None:
38
+ if isinstance(config, str):
39
+ self.config = load_config_file(config)
40
+ else:
41
+ self.config = config
42
+
43
+ @classmethod
44
+ def from_dict(cls, config: dict[str, Any]) -> "MCPClient":
45
+ """Create a MCPClient from a dictionary.
46
+
47
+ Args:
48
+ config: The configuration dictionary.
49
+ """
50
+ return cls(config=config)
51
+
52
+ @classmethod
53
+ def from_config_file(cls, filepath: str) -> "MCPClient":
54
+ """Create a MCPClient from a configuration file.
55
+
56
+ Args:
57
+ filepath: The path to the configuration file.
58
+ """
59
+ return cls(config=load_config_file(filepath))
60
+
61
+ def add_server(
62
+ self,
63
+ name: str,
64
+ server_config: dict[str, Any],
65
+ ) -> None:
66
+ """Add a server configuration.
67
+
68
+ Args:
69
+ name: The name to identify this server.
70
+ server_config: The server configuration.
71
+ """
72
+ if "mcpServers" not in self.config:
73
+ self.config["mcpServers"] = {}
74
+
75
+ self.config["mcpServers"][name] = server_config
76
+
77
+ def remove_server(self, name: str) -> None:
78
+ """Remove a server configuration.
79
+
80
+ Args:
81
+ name: The name of the server to remove.
82
+ """
83
+ if "mcpServers" in self.config and name in self.config["mcpServers"]:
84
+ del self.config["mcpServers"][name]
85
+
86
+ # If we removed the active session, set active_session to None
87
+ if name == self.active_session:
88
+ self.active_session = None
89
+
90
+ def get_server_names(self) -> list[str]:
91
+ """Get the list of configured server names.
92
+
93
+ Returns:
94
+ List of server names.
95
+ """
96
+ return list(self.config.get("mcpServers", {}).keys())
97
+
98
+ def save_config(self, filepath: str) -> None:
99
+ """Save the current configuration to a file.
100
+
101
+ Args:
102
+ filepath: The path to save the configuration to.
103
+ """
104
+ with open(filepath, "w") as f:
105
+ json.dump(self.config, f, indent=2)
106
+
107
+ async def create_session(
108
+ self,
109
+ server_name: str | None = None,
110
+ auto_initialize: bool = True,
111
+ ) -> MCPSession:
112
+ """Create a session for the specified server.
113
+
114
+ Args:
115
+ server_name: The name of the server to create a session for.
116
+ If None, uses the first available server.
117
+ auto_initialize: Whether to automatically initialize the session.
118
+
119
+ Returns:
120
+ The created MCPSession.
121
+
122
+ Raises:
123
+ ValueError: If no servers are configured or the specified server doesn't exist.
124
+ """
125
+ # Get server config
126
+ servers = self.config.get("mcpServers", {})
127
+ if not servers:
128
+ raise ValueError("No MCP servers defined in config")
129
+
130
+ # If server_name not specified, use the first one
131
+ if not server_name:
132
+ server_name = next(iter(servers.keys()))
133
+
134
+ if server_name not in servers:
135
+ raise ValueError(f"Server '{server_name}' not found in config")
136
+
137
+ server_config = servers[server_name]
138
+ connector = create_connector_from_config(server_config)
139
+
140
+ # Create the session
141
+ session = MCPSession(connector)
142
+ self.sessions[server_name] = session
143
+
144
+ # Make this the active session
145
+ self.active_session = server_name
146
+
147
+ # Initialize if requested
148
+ if auto_initialize:
149
+ await session.initialize()
150
+
151
+ return session
152
+
153
+ def get_session(self, server_name: str | None = None) -> MCPSession:
154
+ """Get an existing session.
155
+
156
+ Args:
157
+ server_name: The name of the server to get the session for.
158
+ If None, uses the active session.
159
+
160
+ Returns:
161
+ The MCPSession for the specified server.
162
+
163
+ Raises:
164
+ ValueError: If no active session exists or the specified session doesn't exist.
165
+ """
166
+ if server_name is None:
167
+ if self.active_session is None:
168
+ raise ValueError("No active session")
169
+ server_name = self.active_session
170
+
171
+ if server_name not in self.sessions:
172
+ raise ValueError(f"No session exists for server '{server_name}'")
173
+
174
+ return self.sessions[server_name]
175
+
176
+ async def close_session(self, server_name: str | None = None) -> None:
177
+ """Close a session.
178
+
179
+ Args:
180
+ server_name: The name of the server to close the session for.
181
+ If None, uses the active session.
182
+
183
+ Raises:
184
+ ValueError: If no active session exists or the specified session doesn't exist.
185
+ """
186
+ session = self.get_session(server_name)
187
+ await session.disconnect()
188
+
189
+ # Remove the session
190
+ if server_name is None:
191
+ server_name = self.active_session
192
+
193
+ if server_name in self.sessions:
194
+ del self.sessions[server_name]
195
+
196
+ # If we closed the active session, set active_session to None
197
+ if server_name == self.active_session:
198
+ self.active_session = None
199
+
200
+ async def close_all_sessions(self) -> None:
201
+ """Close all active sessions."""
202
+ for server_name in list(self.sessions.keys()):
203
+ await self.close_session(server_name)
204
+
205
+ async def __aenter__(self) -> "MCPClient":
206
+ """Enter the async context manager.
207
+
208
+ Creates a session for the first available server if no sessions exist.
209
+
210
+ Returns:
211
+ The client instance.
212
+ """
213
+ if not self.sessions and self.config.get("mcpServers"):
214
+ await self.create_session()
215
+ return self
216
+
217
+ async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
218
+ """Exit the async context manager.
219
+
220
+ Closes all active sessions.
221
+ """
222
+ await self.close_all_sessions()
mcp_use/config.py ADDED
@@ -0,0 +1,93 @@
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
+ from typing import Any
9
+
10
+ from .connectors import BaseConnector, HttpConnector, StdioConnector, WebSocketConnector
11
+ from .session import MCPSession
12
+
13
+
14
+ def load_config_file(filepath: str) -> dict[str, Any]:
15
+ """Load a configuration file.
16
+
17
+ Args:
18
+ filepath: Path to the configuration file
19
+
20
+ Returns:
21
+ The parsed configuration
22
+ """
23
+ with open(filepath) as f:
24
+ return json.load(f)
25
+
26
+
27
+ def create_connector_from_config(server_config: dict[str, Any]) -> BaseConnector:
28
+ """Create a connector based on server configuration.
29
+
30
+ Args:
31
+ server_config: The server configuration section
32
+
33
+ Returns:
34
+ A configured connector instance
35
+ """
36
+ # Stdio connector (command-based)
37
+ if "command" in server_config and "args" in server_config:
38
+ return StdioConnector(
39
+ command=server_config["command"],
40
+ args=server_config["args"],
41
+ env=server_config.get("env", None),
42
+ )
43
+
44
+ # HTTP connector
45
+ elif "url" in server_config:
46
+ return HttpConnector(
47
+ url=server_config["url"],
48
+ headers=server_config.get("headers", None),
49
+ auth=server_config.get("auth", None),
50
+ )
51
+
52
+ # WebSocket connector
53
+ elif "ws_url" in server_config:
54
+ return WebSocketConnector(
55
+ url=server_config["ws_url"],
56
+ headers=server_config.get("headers", None),
57
+ auth=server_config.get("auth", None),
58
+ )
59
+
60
+ raise ValueError("Cannot determine connector type from config")
61
+
62
+
63
+ def create_session_from_config(
64
+ filepath: str,
65
+ server_name: str | None = None,
66
+ ) -> MCPSession:
67
+ """Create an MCPSession from a configuration file.
68
+
69
+ Args:
70
+ filepath: Path to the configuration file
71
+ server_name: Name of the server to use from config, uses first if None
72
+
73
+ Returns:
74
+ Configured MCPSession instance
75
+ """
76
+ config = load_config_file(filepath)
77
+
78
+ # Get server config
79
+ servers = config.get("mcpServers", {})
80
+ if not servers:
81
+ raise ValueError("No MCP servers defined in config")
82
+
83
+ # If server_name not specified, use the first one
84
+ if not server_name:
85
+ server_name = next(iter(servers.keys()))
86
+
87
+ if server_name not in servers:
88
+ raise ValueError(f"Server '{server_name}' not found in config")
89
+
90
+ server_config = servers[server_name]
91
+ connector = create_connector_from_config(server_config)
92
+
93
+ return MCPSession(connector)
@@ -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,59 @@
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, Tool
12
+
13
+
14
+ class BaseConnector(ABC):
15
+ """Base class for MCP connectors.
16
+
17
+ This class defines the interface that all MCP connectors must implement.
18
+ """
19
+
20
+ @abstractmethod
21
+ async def connect(self) -> None:
22
+ """Establish a connection to the MCP implementation."""
23
+ pass
24
+
25
+ @abstractmethod
26
+ async def disconnect(self) -> None:
27
+ """Close the connection to the MCP implementation."""
28
+ pass
29
+
30
+ @abstractmethod
31
+ async def initialize(self) -> dict[str, Any]:
32
+ """Initialize the MCP session and return session information."""
33
+ pass
34
+
35
+ @property
36
+ @abstractmethod
37
+ def tools(self) -> list[Tool]:
38
+ """Get the list of available tools."""
39
+ pass
40
+
41
+ @abstractmethod
42
+ async def call_tool(self, name: str, arguments: dict[str, Any]) -> CallToolResult:
43
+ """Call an MCP tool with the given arguments."""
44
+ pass
45
+
46
+ @abstractmethod
47
+ async def list_resources(self) -> list[dict[str, Any]]:
48
+ """List all available resources from the MCP implementation."""
49
+ pass
50
+
51
+ @abstractmethod
52
+ async def read_resource(self, uri: str) -> tuple[bytes, str]:
53
+ """Read a resource by URI."""
54
+ pass
55
+
56
+ @abstractmethod
57
+ async def request(self, method: str, params: dict[str, Any] | None = None) -> Any:
58
+ """Send a raw request to the MCP implementation."""
59
+ 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 {}})