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/__init__.py +30 -0
- mcp_use/agents/__init__.py +12 -0
- mcp_use/agents/base.py +63 -0
- mcp_use/agents/langchain_agent.py +241 -0
- mcp_use/agents/mcpagent.py +149 -0
- mcp_use/client.py +226 -0
- mcp_use/config.py +113 -0
- mcp_use/connectors/__init__.py +13 -0
- mcp_use/connectors/base.py +61 -0
- mcp_use/connectors/http.py +126 -0
- mcp_use/connectors/stdio.py +124 -0
- mcp_use/connectors/websocket.py +142 -0
- mcp_use/logging.py +96 -0
- mcp_use/session.py +168 -0
- mcp_use/tools/__init__.py +11 -0
- mcp_use/tools/converter.py +108 -0
- mcp_use/tools/formats.py +181 -0
- mcp_use/types.py +33 -0
- mcp_use-0.1.0.dist-info/METADATA +287 -0
- mcp_use-0.1.0.dist-info/RECORD +23 -0
- mcp_use-0.1.0.dist-info/WHEEL +5 -0
- mcp_use-0.1.0.dist-info/licenses/LICENSE +21 -0
- mcp_use-0.1.0.dist-info/top_level.txt +1 -0
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 {}})
|