mcp-use 1.3.12__py3-none-any.whl → 1.4.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 +1 -1
- mcp_use/adapters/.deprecated +0 -0
- mcp_use/adapters/__init__.py +18 -7
- mcp_use/adapters/base.py +12 -185
- mcp_use/adapters/langchain_adapter.py +12 -219
- mcp_use/agents/adapters/__init__.py +17 -0
- mcp_use/agents/adapters/anthropic.py +93 -0
- mcp_use/agents/adapters/base.py +316 -0
- mcp_use/agents/adapters/google.py +103 -0
- mcp_use/agents/adapters/langchain_adapter.py +212 -0
- mcp_use/agents/adapters/openai.py +111 -0
- mcp_use/agents/base.py +1 -1
- mcp_use/agents/managers/__init__.py +19 -0
- mcp_use/agents/managers/base.py +36 -0
- mcp_use/agents/managers/server_manager.py +131 -0
- mcp_use/agents/managers/tools/__init__.py +15 -0
- mcp_use/agents/managers/tools/base_tool.py +19 -0
- mcp_use/agents/managers/tools/connect_server.py +69 -0
- mcp_use/agents/managers/tools/disconnect_server.py +43 -0
- mcp_use/agents/managers/tools/get_active_server.py +29 -0
- mcp_use/agents/managers/tools/list_servers_tool.py +53 -0
- mcp_use/agents/managers/tools/search_tools.py +328 -0
- mcp_use/agents/mcpagent.py +386 -485
- mcp_use/agents/prompts/system_prompt_builder.py +1 -1
- mcp_use/agents/remote.py +15 -2
- mcp_use/auth/.deprecated +0 -0
- mcp_use/auth/__init__.py +19 -4
- mcp_use/auth/bearer.py +11 -12
- mcp_use/auth/oauth.py +11 -620
- mcp_use/auth/oauth_callback.py +16 -207
- mcp_use/client/__init__.py +1 -0
- mcp_use/client/auth/__init__.py +6 -0
- mcp_use/client/auth/bearer.py +23 -0
- mcp_use/client/auth/oauth.py +629 -0
- mcp_use/client/auth/oauth_callback.py +215 -0
- mcp_use/client/client.py +356 -0
- mcp_use/client/config.py +106 -0
- mcp_use/client/connectors/__init__.py +20 -0
- mcp_use/client/connectors/base.py +470 -0
- mcp_use/client/connectors/http.py +304 -0
- mcp_use/client/connectors/sandbox.py +332 -0
- mcp_use/client/connectors/stdio.py +109 -0
- mcp_use/client/connectors/utils.py +13 -0
- mcp_use/client/connectors/websocket.py +257 -0
- mcp_use/client/exceptions.py +31 -0
- mcp_use/client/middleware/__init__.py +50 -0
- mcp_use/client/middleware/logging.py +31 -0
- mcp_use/client/middleware/metrics.py +314 -0
- mcp_use/client/middleware/middleware.py +266 -0
- mcp_use/client/session.py +162 -0
- mcp_use/client/task_managers/__init__.py +20 -0
- mcp_use/client/task_managers/base.py +145 -0
- mcp_use/client/task_managers/sse.py +84 -0
- mcp_use/client/task_managers/stdio.py +69 -0
- mcp_use/client/task_managers/streamable_http.py +86 -0
- mcp_use/client/task_managers/websocket.py +68 -0
- mcp_use/client.py +12 -344
- mcp_use/config.py +20 -97
- mcp_use/connectors/.deprecated +0 -0
- mcp_use/connectors/__init__.py +46 -20
- mcp_use/connectors/base.py +12 -455
- mcp_use/connectors/http.py +13 -300
- mcp_use/connectors/sandbox.py +13 -306
- mcp_use/connectors/stdio.py +13 -104
- mcp_use/connectors/utils.py +15 -8
- mcp_use/connectors/websocket.py +13 -252
- mcp_use/exceptions.py +33 -18
- mcp_use/logging.py +1 -1
- mcp_use/managers/.deprecated +0 -0
- mcp_use/managers/__init__.py +56 -17
- mcp_use/managers/base.py +13 -31
- mcp_use/managers/server_manager.py +13 -119
- mcp_use/managers/tools/__init__.py +45 -15
- mcp_use/managers/tools/base_tool.py +5 -16
- mcp_use/managers/tools/connect_server.py +5 -67
- mcp_use/managers/tools/disconnect_server.py +5 -41
- mcp_use/managers/tools/get_active_server.py +5 -26
- mcp_use/managers/tools/list_servers_tool.py +5 -51
- mcp_use/managers/tools/search_tools.py +17 -321
- mcp_use/middleware/.deprecated +0 -0
- mcp_use/middleware/__init__.py +89 -50
- mcp_use/middleware/logging.py +14 -26
- mcp_use/middleware/metrics.py +30 -303
- mcp_use/middleware/middleware.py +39 -246
- mcp_use/session.py +13 -149
- mcp_use/task_managers/.deprecated +0 -0
- mcp_use/task_managers/__init__.py +48 -20
- mcp_use/task_managers/base.py +13 -140
- mcp_use/task_managers/sse.py +13 -79
- mcp_use/task_managers/stdio.py +13 -64
- mcp_use/task_managers/streamable_http.py +15 -81
- mcp_use/task_managers/websocket.py +13 -63
- mcp_use/telemetry/events.py +58 -0
- mcp_use/telemetry/telemetry.py +71 -1
- mcp_use/telemetry/utils.py +1 -1
- mcp_use/types/.deprecated +0 -0
- mcp_use/types/sandbox.py +13 -18
- {mcp_use-1.3.12.dist-info → mcp_use-1.4.0.dist-info}/METADATA +68 -43
- mcp_use-1.4.0.dist-info/RECORD +111 -0
- mcp_use/cli.py +0 -581
- mcp_use-1.3.12.dist-info/RECORD +0 -64
- mcp_use-1.3.12.dist-info/licenses/LICENSE +0 -21
- /mcp_use/{observability → agents/observability}/__init__.py +0 -0
- /mcp_use/{observability → agents/observability}/callbacks_manager.py +0 -0
- /mcp_use/{observability → agents/observability}/laminar.py +0 -0
- /mcp_use/{observability → agents/observability}/langfuse.py +0 -0
- {mcp_use-1.3.12.dist-info → mcp_use-1.4.0.dist-info}/WHEEL +0 -0
- {mcp_use-1.3.12.dist-info → mcp_use-1.4.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""OAuth callback server implementation."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import html
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
import anyio
|
|
8
|
+
import uvicorn
|
|
9
|
+
from starlette.applications import Starlette
|
|
10
|
+
from starlette.requests import Request
|
|
11
|
+
from starlette.responses import HTMLResponse
|
|
12
|
+
from starlette.routing import Route
|
|
13
|
+
|
|
14
|
+
from mcp_use.logging import logger
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class CallbackResponse:
|
|
19
|
+
"""Response data from OAuth callback."""
|
|
20
|
+
|
|
21
|
+
code: str | None = None # Authorization code (success)
|
|
22
|
+
state: str | None = None # CSRF protection token
|
|
23
|
+
error: str | None = None # Errors code (if failed)
|
|
24
|
+
error_description: str | None = None
|
|
25
|
+
error_uri: str | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class OAuthCallbackServer:
|
|
29
|
+
"""Local server to handle OAuth callback."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, port: int):
|
|
32
|
+
"""Initialize the callback server.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
port: Port to listen on.
|
|
36
|
+
"""
|
|
37
|
+
self.port = port
|
|
38
|
+
self.redirect_uri: str | None = None
|
|
39
|
+
# Thread safe way to pass callback data to the main OAuth flow
|
|
40
|
+
self.response_queue: asyncio.Queue[CallbackResponse] = asyncio.Queue(maxsize=1)
|
|
41
|
+
self.server: uvicorn.Server | None = None
|
|
42
|
+
self._shutdown_event = anyio.Event()
|
|
43
|
+
|
|
44
|
+
async def start(self) -> str:
|
|
45
|
+
"""Start the callback server and return the redirect URI."""
|
|
46
|
+
app = self._create_app()
|
|
47
|
+
|
|
48
|
+
# Create the server
|
|
49
|
+
config = uvicorn.Config(
|
|
50
|
+
app,
|
|
51
|
+
host="127.0.0.1",
|
|
52
|
+
port=self.port,
|
|
53
|
+
log_level="error", # Suppress uvicorn logs
|
|
54
|
+
)
|
|
55
|
+
self.server = uvicorn.Server(config)
|
|
56
|
+
|
|
57
|
+
# Start server in background
|
|
58
|
+
self._server_task = asyncio.create_task(self.server.serve())
|
|
59
|
+
|
|
60
|
+
# Wait a moment for server to start
|
|
61
|
+
await asyncio.sleep(0.1)
|
|
62
|
+
|
|
63
|
+
self.redirect_uri = f"http://localhost:{self.port}/callback"
|
|
64
|
+
return self.redirect_uri
|
|
65
|
+
|
|
66
|
+
async def wait_for_code(self, timeout: float = 300) -> CallbackResponse:
|
|
67
|
+
"""Wait for the OAuth callback with a timeout (default 5 minutes)."""
|
|
68
|
+
try:
|
|
69
|
+
response = await asyncio.wait_for(self.response_queue.get(), timeout=timeout)
|
|
70
|
+
return response
|
|
71
|
+
except TimeoutError:
|
|
72
|
+
raise TimeoutError(f"OAuth callback not received within {timeout} seconds") from None
|
|
73
|
+
finally:
|
|
74
|
+
await self.shutdown()
|
|
75
|
+
|
|
76
|
+
async def shutdown(self):
|
|
77
|
+
"""Shutdown the callback server."""
|
|
78
|
+
self._shutdown_event.set()
|
|
79
|
+
if self.server:
|
|
80
|
+
self.server.should_exit = True
|
|
81
|
+
if hasattr(self, "_server_task"):
|
|
82
|
+
try:
|
|
83
|
+
await asyncio.wait_for(self._server_task, timeout=5.0)
|
|
84
|
+
except TimeoutError:
|
|
85
|
+
self._server_task.cancel()
|
|
86
|
+
|
|
87
|
+
def _create_app(self) -> Starlette:
|
|
88
|
+
"""Create the Starlette application."""
|
|
89
|
+
|
|
90
|
+
async def callback(request: Request) -> HTMLResponse:
|
|
91
|
+
"""Handle the OAuth callback."""
|
|
92
|
+
params = request.query_params
|
|
93
|
+
|
|
94
|
+
# Extract OAuth parameters
|
|
95
|
+
response = CallbackResponse(
|
|
96
|
+
code=params.get("code"),
|
|
97
|
+
state=params.get("state"),
|
|
98
|
+
error=params.get("error"),
|
|
99
|
+
error_description=params.get("error_description"),
|
|
100
|
+
error_uri=params.get("error_uri"),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Log the callback response
|
|
104
|
+
logger.debug(
|
|
105
|
+
f"OAuth callback received: error={response.error}, error_description={response.error_description}"
|
|
106
|
+
)
|
|
107
|
+
if response.code:
|
|
108
|
+
logger.debug("OAuth callback received authorization code")
|
|
109
|
+
else:
|
|
110
|
+
logger.error(f"OAuth callback error: {response.error} - {response.error_description}")
|
|
111
|
+
|
|
112
|
+
# Put response in queue
|
|
113
|
+
try:
|
|
114
|
+
self.response_queue.put_nowait(response)
|
|
115
|
+
except asyncio.QueueFull:
|
|
116
|
+
pass # Ignore if queue is already full
|
|
117
|
+
|
|
118
|
+
# Return success page
|
|
119
|
+
if response.code:
|
|
120
|
+
html = self._success_html()
|
|
121
|
+
else:
|
|
122
|
+
html = self._error_html(response.error, response.error_description)
|
|
123
|
+
|
|
124
|
+
return HTMLResponse(content=html)
|
|
125
|
+
|
|
126
|
+
routes = [Route("/callback", callback)]
|
|
127
|
+
return Starlette(routes=routes)
|
|
128
|
+
|
|
129
|
+
def _success_html(self) -> str:
|
|
130
|
+
"""HTML response for successful authorization."""
|
|
131
|
+
return """
|
|
132
|
+
<!DOCTYPE html>
|
|
133
|
+
<html>
|
|
134
|
+
<head>
|
|
135
|
+
<title>Authorization Successful</title>
|
|
136
|
+
<style>
|
|
137
|
+
body {
|
|
138
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
139
|
+
display: flex;
|
|
140
|
+
justify-content: center;
|
|
141
|
+
align-items: center;
|
|
142
|
+
height: 100vh;
|
|
143
|
+
margin: 0;
|
|
144
|
+
background-color: #f5f5f5;
|
|
145
|
+
}
|
|
146
|
+
.container {
|
|
147
|
+
text-align: center;
|
|
148
|
+
padding: 2rem;
|
|
149
|
+
background: white;
|
|
150
|
+
border-radius: 8px;
|
|
151
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
152
|
+
}
|
|
153
|
+
h1 { color: #22c55e; margin-bottom: 0.5rem; }
|
|
154
|
+
p { color: #666; margin-top: 0.5rem; }
|
|
155
|
+
.icon { font-size: 48px; margin-bottom: 1rem; }
|
|
156
|
+
</style>
|
|
157
|
+
</head>
|
|
158
|
+
<body>
|
|
159
|
+
<div class="container">
|
|
160
|
+
<div class="icon">✅</div>
|
|
161
|
+
<h1>Authorization Successful!</h1>
|
|
162
|
+
<p>You can now close this window and return to your application.</p>
|
|
163
|
+
</div>
|
|
164
|
+
<script>
|
|
165
|
+
// Auto-close after 3 seconds
|
|
166
|
+
setTimeout(() => window.close(), 3000);
|
|
167
|
+
</script>
|
|
168
|
+
</body>
|
|
169
|
+
</html>
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
def _error_html(self, error: str | None, description: str | None) -> str:
|
|
173
|
+
"""HTML response for authorization error."""
|
|
174
|
+
error_msg = html.escape(error or "Unknown error")
|
|
175
|
+
desc_msg = html.escape(description or "Authorization was not completed successfully.")
|
|
176
|
+
|
|
177
|
+
return f"""
|
|
178
|
+
<!DOCTYPE html>
|
|
179
|
+
<html>
|
|
180
|
+
<head>
|
|
181
|
+
<title>Authorization Error</title>
|
|
182
|
+
<style>
|
|
183
|
+
body {{
|
|
184
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
185
|
+
display: flex;
|
|
186
|
+
justify-content: center;
|
|
187
|
+
align-items: center;
|
|
188
|
+
height: 100vh;
|
|
189
|
+
margin: 0;
|
|
190
|
+
background-color: #f5f5f5;
|
|
191
|
+
}}
|
|
192
|
+
.container {{
|
|
193
|
+
text-align: center;
|
|
194
|
+
padding: 2rem;
|
|
195
|
+
background: white;
|
|
196
|
+
border-radius: 8px;
|
|
197
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
198
|
+
max-width: 500px;
|
|
199
|
+
}}
|
|
200
|
+
h1 {{ color: #ef4444; margin-bottom: 0.5rem; }}
|
|
201
|
+
.error {{ color: #dc2626; font-weight: 600; margin: 1rem 0; }}
|
|
202
|
+
.description {{ color: #666; margin-top: 0.5rem; }}
|
|
203
|
+
.icon {{ font-size: 48px; margin-bottom: 1rem; }}
|
|
204
|
+
</style>
|
|
205
|
+
</head>
|
|
206
|
+
<body>
|
|
207
|
+
<div class="container">
|
|
208
|
+
<div class="icon">❌</div>
|
|
209
|
+
<h1>Authorization Error</h1>
|
|
210
|
+
<p class="error">{error_msg}</p>
|
|
211
|
+
<p class="description">{desc_msg}</p>
|
|
212
|
+
</div>
|
|
213
|
+
</body>
|
|
214
|
+
</html>
|
|
215
|
+
"""
|
mcp_use/client/client.py
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
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
|
+
import warnings
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from mcp.client.session import ElicitationFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
|
|
13
|
+
|
|
14
|
+
from mcp_use.client.config import create_connector_from_config, load_config_file
|
|
15
|
+
from mcp_use.client.connectors.sandbox import SandboxOptions
|
|
16
|
+
from mcp_use.client.middleware import Middleware, default_logging_middleware
|
|
17
|
+
from mcp_use.client.session import MCPSession
|
|
18
|
+
from mcp_use.logging import logger
|
|
19
|
+
from mcp_use.telemetry.telemetry import telemetry
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MCPClient:
|
|
23
|
+
"""Client for managing MCP servers and sessions.
|
|
24
|
+
|
|
25
|
+
This class provides a unified interface for working with MCP servers,
|
|
26
|
+
handling configuration, connector creation, and session management.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
config: str | dict[str, Any] | None = None,
|
|
32
|
+
allowed_servers: list[str] | None = None,
|
|
33
|
+
sandbox: bool = False,
|
|
34
|
+
sandbox_options: SandboxOptions | None = None,
|
|
35
|
+
sampling_callback: SamplingFnT | None = None,
|
|
36
|
+
elicitation_callback: ElicitationFnT | None = None,
|
|
37
|
+
message_handler: MessageHandlerFnT | None = None,
|
|
38
|
+
logging_callback: LoggingFnT | None = None,
|
|
39
|
+
middleware: list[Middleware] | None = None,
|
|
40
|
+
) -> None:
|
|
41
|
+
"""Initialize a new MCP client.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
config: Either a dict containing configuration or a path to a JSON config file.
|
|
45
|
+
If None, an empty configuration is used.
|
|
46
|
+
sandbox: Whether to use sandboxed execution mode for running MCP servers.
|
|
47
|
+
sandbox_options: Optional sandbox configuration options.
|
|
48
|
+
sampling_callback: Optional sampling callback function.
|
|
49
|
+
"""
|
|
50
|
+
self.config: dict[str, Any] = {}
|
|
51
|
+
self.allowed_servers: list[str] = allowed_servers
|
|
52
|
+
self.sandbox = sandbox
|
|
53
|
+
self.sandbox_options = sandbox_options
|
|
54
|
+
self.sessions: dict[str, MCPSession] = {}
|
|
55
|
+
self.active_sessions: list[str] = []
|
|
56
|
+
self.sampling_callback = sampling_callback
|
|
57
|
+
self.elicitation_callback = elicitation_callback
|
|
58
|
+
self.message_handler = message_handler
|
|
59
|
+
self.logging_callback = logging_callback
|
|
60
|
+
# Add default logging middleware if no middleware provided, or prepend it to existing middleware
|
|
61
|
+
default_middleware = [default_logging_middleware]
|
|
62
|
+
if middleware:
|
|
63
|
+
self.middleware = default_middleware + middleware
|
|
64
|
+
else:
|
|
65
|
+
self.middleware = default_middleware
|
|
66
|
+
# Load configuration if provided
|
|
67
|
+
if config is not None:
|
|
68
|
+
if isinstance(config, str):
|
|
69
|
+
self.config = load_config_file(config)
|
|
70
|
+
else:
|
|
71
|
+
self.config = config
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def from_dict(
|
|
75
|
+
cls,
|
|
76
|
+
config: dict[str, Any],
|
|
77
|
+
sandbox: bool = False,
|
|
78
|
+
sandbox_options: SandboxOptions | None = None,
|
|
79
|
+
sampling_callback: SamplingFnT | None = None,
|
|
80
|
+
elicitation_callback: ElicitationFnT | None = None,
|
|
81
|
+
message_handler: MessageHandlerFnT | None = None,
|
|
82
|
+
logging_callback: LoggingFnT | None = None,
|
|
83
|
+
) -> "MCPClient":
|
|
84
|
+
"""Create a MCPClient from a dictionary.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
config: The configuration dictionary.
|
|
88
|
+
sandbox: Whether to use sandboxed execution mode for running MCP servers.
|
|
89
|
+
sandbox_options: Optional sandbox configuration options.
|
|
90
|
+
sampling_callback: Optional sampling callback function.
|
|
91
|
+
elicitation_callback: Optional elicitation callback function.
|
|
92
|
+
"""
|
|
93
|
+
return cls(
|
|
94
|
+
config=config,
|
|
95
|
+
sandbox=sandbox,
|
|
96
|
+
sandbox_options=sandbox_options,
|
|
97
|
+
sampling_callback=sampling_callback,
|
|
98
|
+
elicitation_callback=elicitation_callback,
|
|
99
|
+
message_handler=message_handler,
|
|
100
|
+
logging_callback=logging_callback,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def from_config_file(
|
|
105
|
+
cls,
|
|
106
|
+
filepath: str,
|
|
107
|
+
sandbox: bool = False,
|
|
108
|
+
sandbox_options: SandboxOptions | None = None,
|
|
109
|
+
sampling_callback: SamplingFnT | None = None,
|
|
110
|
+
elicitation_callback: ElicitationFnT | None = None,
|
|
111
|
+
message_handler: MessageHandlerFnT | None = None,
|
|
112
|
+
logging_callback: LoggingFnT | None = None,
|
|
113
|
+
) -> "MCPClient":
|
|
114
|
+
"""Create a MCPClient from a configuration file.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
filepath: The path to the configuration file.
|
|
118
|
+
sandbox: Whether to use sandboxed execution mode for running MCP servers.
|
|
119
|
+
sandbox_options: Optional sandbox configuration options.
|
|
120
|
+
sampling_callback: Optional sampling callback function.
|
|
121
|
+
elicitation_callback: Optional elicitation callback function.
|
|
122
|
+
"""
|
|
123
|
+
return cls(
|
|
124
|
+
config=load_config_file(filepath),
|
|
125
|
+
sandbox=sandbox,
|
|
126
|
+
sandbox_options=sandbox_options,
|
|
127
|
+
sampling_callback=sampling_callback,
|
|
128
|
+
elicitation_callback=elicitation_callback,
|
|
129
|
+
message_handler=message_handler,
|
|
130
|
+
logging_callback=logging_callback,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
@telemetry("client_add_server")
|
|
134
|
+
def add_server(
|
|
135
|
+
self,
|
|
136
|
+
name: str,
|
|
137
|
+
server_config: dict[str, Any],
|
|
138
|
+
) -> None:
|
|
139
|
+
"""Add a server configuration.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
name: The name to identify this server.
|
|
143
|
+
server_config: The server configuration.
|
|
144
|
+
"""
|
|
145
|
+
if "mcpServers" not in self.config:
|
|
146
|
+
self.config["mcpServers"] = {}
|
|
147
|
+
|
|
148
|
+
self.config["mcpServers"][name] = server_config
|
|
149
|
+
|
|
150
|
+
@telemetry("client_remove_server")
|
|
151
|
+
def remove_server(self, name: str) -> None:
|
|
152
|
+
"""Remove a server configuration.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
name: The name of the server to remove.
|
|
156
|
+
"""
|
|
157
|
+
if "mcpServers" in self.config and name in self.config["mcpServers"]:
|
|
158
|
+
del self.config["mcpServers"][name]
|
|
159
|
+
|
|
160
|
+
# If we removed an active session, remove it from active_sessions
|
|
161
|
+
if name in self.active_sessions:
|
|
162
|
+
self.active_sessions.remove(name)
|
|
163
|
+
|
|
164
|
+
def add_middleware(self, middleware: Middleware) -> None:
|
|
165
|
+
"""Add a middleware.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
middleware: The middleware to add
|
|
169
|
+
"""
|
|
170
|
+
if len(self.sessions) == 0 and middleware not in self.middleware:
|
|
171
|
+
self.middleware.append(middleware)
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
if middleware not in self.middleware:
|
|
175
|
+
self.middleware.append(middleware)
|
|
176
|
+
for session in self.sessions.values():
|
|
177
|
+
session.connector.middleware_manager.add_middleware(middleware)
|
|
178
|
+
|
|
179
|
+
def get_server_names(self) -> list[str]:
|
|
180
|
+
"""Get the list of configured server names.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
List of server names.
|
|
184
|
+
"""
|
|
185
|
+
return list(self.config.get("mcpServers", {}).keys())
|
|
186
|
+
|
|
187
|
+
def save_config(self, filepath: str) -> None:
|
|
188
|
+
"""Save the current configuration to a file.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
filepath: The path to save the configuration to.
|
|
192
|
+
"""
|
|
193
|
+
with open(filepath, "w") as f:
|
|
194
|
+
json.dump(self.config, f, indent=2)
|
|
195
|
+
|
|
196
|
+
@telemetry("client_create_session")
|
|
197
|
+
async def create_session(self, server_name: str, auto_initialize: bool = True) -> MCPSession:
|
|
198
|
+
"""Create a session for the specified server.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
server_name: The name of the server to create a session for.
|
|
202
|
+
auto_initialize: Whether to automatically initialize the session.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
The created MCPSession.
|
|
206
|
+
|
|
207
|
+
Raises:
|
|
208
|
+
ValueError: If the specified server doesn't exist.
|
|
209
|
+
"""
|
|
210
|
+
# Get server config
|
|
211
|
+
servers = self.config.get("mcpServers", {})
|
|
212
|
+
if not servers:
|
|
213
|
+
warnings.warn("No MCP servers defined in config", UserWarning, stacklevel=2)
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
if server_name not in servers:
|
|
217
|
+
raise ValueError(f"Server '{server_name}' not found in config")
|
|
218
|
+
|
|
219
|
+
server_config = servers[server_name]
|
|
220
|
+
|
|
221
|
+
# Create connector with options and client-level auth
|
|
222
|
+
connector = create_connector_from_config(
|
|
223
|
+
server_config,
|
|
224
|
+
sandbox=self.sandbox,
|
|
225
|
+
sandbox_options=self.sandbox_options,
|
|
226
|
+
sampling_callback=self.sampling_callback,
|
|
227
|
+
elicitation_callback=self.elicitation_callback,
|
|
228
|
+
message_handler=self.message_handler,
|
|
229
|
+
logging_callback=self.logging_callback,
|
|
230
|
+
middleware=self.middleware,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
# Create the session
|
|
234
|
+
session = MCPSession(connector)
|
|
235
|
+
if auto_initialize:
|
|
236
|
+
await session.initialize()
|
|
237
|
+
self.sessions[server_name] = session
|
|
238
|
+
|
|
239
|
+
# Add to active sessions
|
|
240
|
+
if server_name not in self.active_sessions:
|
|
241
|
+
self.active_sessions.append(server_name)
|
|
242
|
+
|
|
243
|
+
return session
|
|
244
|
+
|
|
245
|
+
@telemetry("client_create_all_sessions")
|
|
246
|
+
async def create_all_sessions(
|
|
247
|
+
self,
|
|
248
|
+
auto_initialize: bool = True,
|
|
249
|
+
) -> dict[str, MCPSession]:
|
|
250
|
+
"""Create sessions for all configured servers.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
auto_initialize: Whether to automatically initialize the sessions.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Dictionary mapping server names to their MCPSession instances.
|
|
257
|
+
|
|
258
|
+
Warns:
|
|
259
|
+
UserWarning: If no servers are configured.
|
|
260
|
+
"""
|
|
261
|
+
# Get server config
|
|
262
|
+
servers = self.config.get("mcpServers", {})
|
|
263
|
+
if not servers:
|
|
264
|
+
warnings.warn("No MCP servers defined in config", UserWarning, stacklevel=2)
|
|
265
|
+
return {}
|
|
266
|
+
|
|
267
|
+
# Create sessions only for allowed servers if applicable else create for all servers
|
|
268
|
+
for name in servers:
|
|
269
|
+
if self.allowed_servers is None or name in self.allowed_servers:
|
|
270
|
+
await self.create_session(name, auto_initialize)
|
|
271
|
+
|
|
272
|
+
return self.sessions
|
|
273
|
+
|
|
274
|
+
def get_session(self, server_name: str) -> MCPSession:
|
|
275
|
+
"""Get an existing session.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
server_name: The name of the server to get the session for.
|
|
279
|
+
If None, uses the first active session.
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
The MCPSession for the specified server.
|
|
283
|
+
|
|
284
|
+
Raises:
|
|
285
|
+
ValueError: If no active sessions exist or the specified session doesn't exist.
|
|
286
|
+
"""
|
|
287
|
+
if server_name not in self.sessions:
|
|
288
|
+
raise ValueError(f"No session exists for server '{server_name}'")
|
|
289
|
+
|
|
290
|
+
return self.sessions[server_name]
|
|
291
|
+
|
|
292
|
+
def get_all_active_sessions(self) -> dict[str, MCPSession]:
|
|
293
|
+
"""Get all active sessions.
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
Dictionary mapping server names to their MCPSession instances.
|
|
297
|
+
"""
|
|
298
|
+
return {name: self.sessions[name] for name in self.active_sessions if name in self.sessions}
|
|
299
|
+
|
|
300
|
+
@telemetry("client_close_session")
|
|
301
|
+
async def close_session(self, server_name: str) -> None:
|
|
302
|
+
"""Close a session.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
server_name: The name of the server to close the session for.
|
|
306
|
+
If None, uses the first active session.
|
|
307
|
+
|
|
308
|
+
Raises:
|
|
309
|
+
ValueError: If no active sessions exist or the specified session doesn't exist.
|
|
310
|
+
"""
|
|
311
|
+
# Check if the session exists
|
|
312
|
+
if server_name not in self.sessions:
|
|
313
|
+
logger.warning(f"No session exists for server '{server_name}', nothing to close")
|
|
314
|
+
return
|
|
315
|
+
|
|
316
|
+
# Get the session
|
|
317
|
+
session = self.sessions[server_name]
|
|
318
|
+
|
|
319
|
+
try:
|
|
320
|
+
# Disconnect from the session
|
|
321
|
+
logger.debug(f"Closing session for server '{server_name}'")
|
|
322
|
+
await session.disconnect()
|
|
323
|
+
except Exception as e:
|
|
324
|
+
logger.error(f"Error closing session for server '{server_name}': {e}")
|
|
325
|
+
finally:
|
|
326
|
+
# Remove the session regardless of whether disconnect succeeded
|
|
327
|
+
del self.sessions[server_name]
|
|
328
|
+
|
|
329
|
+
# Remove from active_sessions
|
|
330
|
+
if server_name in self.active_sessions:
|
|
331
|
+
self.active_sessions.remove(server_name)
|
|
332
|
+
|
|
333
|
+
@telemetry("client_close_all_sessions")
|
|
334
|
+
async def close_all_sessions(self) -> None:
|
|
335
|
+
"""Close all active sessions.
|
|
336
|
+
|
|
337
|
+
This method ensures all sessions are closed even if some fail.
|
|
338
|
+
"""
|
|
339
|
+
# Get a list of all session names first to avoid modification during iteration
|
|
340
|
+
server_names = list(self.sessions.keys())
|
|
341
|
+
errors = []
|
|
342
|
+
|
|
343
|
+
for server_name in server_names:
|
|
344
|
+
try:
|
|
345
|
+
logger.debug(f"Closing session for server '{server_name}'")
|
|
346
|
+
await self.close_session(server_name)
|
|
347
|
+
except Exception as e:
|
|
348
|
+
error_msg = f"Failed to close session for server '{server_name}': {e}"
|
|
349
|
+
logger.error(error_msg)
|
|
350
|
+
errors.append(error_msg)
|
|
351
|
+
|
|
352
|
+
# Log summary if there were errors
|
|
353
|
+
if errors:
|
|
354
|
+
logger.error(f"Encountered {len(errors)} errors while closing sessions")
|
|
355
|
+
else:
|
|
356
|
+
logger.debug("All sessions closed successfully")
|
mcp_use/client/config.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
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 mcp.client.session import ElicitationFnT, LoggingFnT, MessageHandlerFnT, SamplingFnT
|
|
11
|
+
|
|
12
|
+
from mcp_use.client.connectors.base import BaseConnector
|
|
13
|
+
from mcp_use.client.connectors.http import HttpConnector
|
|
14
|
+
from mcp_use.client.connectors.sandbox import SandboxConnector, SandboxOptions
|
|
15
|
+
from mcp_use.client.connectors.stdio import StdioConnector
|
|
16
|
+
from mcp_use.client.connectors.utils import is_stdio_server
|
|
17
|
+
from mcp_use.client.connectors.websocket import WebSocketConnector
|
|
18
|
+
from mcp_use.client.middleware import Middleware
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load_config_file(filepath: str) -> dict[str, Any]:
|
|
22
|
+
"""Load a configuration file.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
filepath: Path to the configuration file
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
The parsed configuration
|
|
29
|
+
"""
|
|
30
|
+
with open(filepath) as f:
|
|
31
|
+
return json.load(f)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def create_connector_from_config(
|
|
35
|
+
server_config: dict[str, Any],
|
|
36
|
+
sandbox: bool = False,
|
|
37
|
+
sandbox_options: SandboxOptions | None = None,
|
|
38
|
+
sampling_callback: SamplingFnT | None = None,
|
|
39
|
+
elicitation_callback: ElicitationFnT | None = None,
|
|
40
|
+
message_handler: MessageHandlerFnT | None = None,
|
|
41
|
+
logging_callback: LoggingFnT | None = None,
|
|
42
|
+
middleware: list[Middleware] | None = None,
|
|
43
|
+
) -> BaseConnector:
|
|
44
|
+
"""Create a connector based on server configuration.
|
|
45
|
+
This function can be called with just the server_config parameter:
|
|
46
|
+
create_connector_from_config(server_config)
|
|
47
|
+
Args:
|
|
48
|
+
server_config: The server configuration section
|
|
49
|
+
sandbox: Whether to use sandboxed execution mode for running MCP servers.
|
|
50
|
+
sandbox_options: Optional sandbox configuration options.
|
|
51
|
+
sampling_callback: Optional sampling callback function.
|
|
52
|
+
Returns:
|
|
53
|
+
A configured connector instance
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
# Stdio connector (command-based)
|
|
57
|
+
if is_stdio_server(server_config) and not sandbox:
|
|
58
|
+
return StdioConnector(
|
|
59
|
+
command=server_config["command"],
|
|
60
|
+
args=server_config["args"],
|
|
61
|
+
env=server_config.get("env", None),
|
|
62
|
+
sampling_callback=sampling_callback,
|
|
63
|
+
elicitation_callback=elicitation_callback,
|
|
64
|
+
message_handler=message_handler,
|
|
65
|
+
logging_callback=logging_callback,
|
|
66
|
+
middleware=middleware,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Sandboxed connector
|
|
70
|
+
elif is_stdio_server(server_config) and sandbox:
|
|
71
|
+
return SandboxConnector(
|
|
72
|
+
command=server_config["command"],
|
|
73
|
+
args=server_config["args"],
|
|
74
|
+
env=server_config.get("env", None),
|
|
75
|
+
e2b_options=sandbox_options,
|
|
76
|
+
sampling_callback=sampling_callback,
|
|
77
|
+
elicitation_callback=elicitation_callback,
|
|
78
|
+
message_handler=message_handler,
|
|
79
|
+
logging_callback=logging_callback,
|
|
80
|
+
middleware=middleware,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# HTTP connector
|
|
84
|
+
elif "url" in server_config:
|
|
85
|
+
return HttpConnector(
|
|
86
|
+
base_url=server_config["url"],
|
|
87
|
+
headers=server_config.get("headers", None),
|
|
88
|
+
auth=server_config.get("auth", {}),
|
|
89
|
+
timeout=server_config.get("timeout", 5),
|
|
90
|
+
sse_read_timeout=server_config.get("sse_read_timeout", 60 * 5),
|
|
91
|
+
sampling_callback=sampling_callback,
|
|
92
|
+
elicitation_callback=elicitation_callback,
|
|
93
|
+
message_handler=message_handler,
|
|
94
|
+
logging_callback=logging_callback,
|
|
95
|
+
middleware=middleware,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# WebSocket connector
|
|
99
|
+
elif "ws_url" in server_config:
|
|
100
|
+
return WebSocketConnector(
|
|
101
|
+
url=server_config["ws_url"],
|
|
102
|
+
headers=server_config.get("headers", None),
|
|
103
|
+
auth=server_config.get("auth", {}),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
raise ValueError("Cannot determine connector type from config")
|
|
@@ -0,0 +1,20 @@
|
|
|
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 # noqa: F401
|
|
9
|
+
from .http import HttpConnector # noqa: F401
|
|
10
|
+
from .sandbox import SandboxConnector # noqa: F401
|
|
11
|
+
from .stdio import StdioConnector # noqa: F401
|
|
12
|
+
from .websocket import WebSocketConnector # noqa: F401
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"BaseConnector",
|
|
16
|
+
"StdioConnector",
|
|
17
|
+
"HttpConnector",
|
|
18
|
+
"WebSocketConnector",
|
|
19
|
+
"SandboxConnector",
|
|
20
|
+
]
|