mcp-use 1.3.9__py3-none-any.whl → 1.3.11__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.

@@ -0,0 +1,214 @@
1
+ """OAuth callback server implementation."""
2
+
3
+ import asyncio
4
+ from dataclasses import dataclass
5
+
6
+ import anyio
7
+ import uvicorn
8
+ from starlette.applications import Starlette
9
+ from starlette.requests import Request
10
+ from starlette.responses import HTMLResponse
11
+ from starlette.routing import Route
12
+
13
+ from ..logging import logger
14
+
15
+
16
+ @dataclass
17
+ class CallbackResponse:
18
+ """Response data from OAuth callback."""
19
+
20
+ code: str | None = None # Authorization code (success)
21
+ state: str | None = None # CSRF protection token
22
+ error: str | None = None # Errors code (if failed)
23
+ error_description: str | None = None
24
+ error_uri: str | None = None
25
+
26
+
27
+ class OAuthCallbackServer:
28
+ """Local server to handle OAuth callback."""
29
+
30
+ def __init__(self, port: int):
31
+ """Initialize the callback server.
32
+
33
+ Args:
34
+ port: Port to listen on.
35
+ """
36
+ self.port = port
37
+ self.redirect_uri: str | None = None
38
+ # Thread safe way to pass callback data to the main OAuth flow
39
+ self.response_queue: asyncio.Queue[CallbackResponse] = asyncio.Queue(maxsize=1)
40
+ self.server: uvicorn.Server | None = None
41
+ self._shutdown_event = anyio.Event()
42
+
43
+ async def start(self) -> str:
44
+ """Start the callback server and return the redirect URI."""
45
+ app = self._create_app()
46
+
47
+ # Create the server
48
+ config = uvicorn.Config(
49
+ app,
50
+ host="127.0.0.1",
51
+ port=self.port,
52
+ log_level="error", # Suppress uvicorn logs
53
+ )
54
+ self.server = uvicorn.Server(config)
55
+
56
+ # Start server in background
57
+ self._server_task = asyncio.create_task(self.server.serve())
58
+
59
+ # Wait a moment for server to start
60
+ await asyncio.sleep(0.1)
61
+
62
+ self.redirect_uri = f"http://localhost:{self.port}/callback"
63
+ return self.redirect_uri
64
+
65
+ async def wait_for_code(self, timeout: float = 300) -> CallbackResponse:
66
+ """Wait for the OAuth callback with a timeout (default 5 minutes)."""
67
+ try:
68
+ response = await asyncio.wait_for(self.response_queue.get(), timeout=timeout)
69
+ return response
70
+ except TimeoutError:
71
+ raise TimeoutError(f"OAuth callback not received within {timeout} seconds") from None
72
+ finally:
73
+ await self.shutdown()
74
+
75
+ async def shutdown(self):
76
+ """Shutdown the callback server."""
77
+ self._shutdown_event.set()
78
+ if self.server:
79
+ self.server.should_exit = True
80
+ if hasattr(self, "_server_task"):
81
+ try:
82
+ await asyncio.wait_for(self._server_task, timeout=5.0)
83
+ except TimeoutError:
84
+ self._server_task.cancel()
85
+
86
+ def _create_app(self) -> Starlette:
87
+ """Create the Starlette application."""
88
+
89
+ async def callback(request: Request) -> HTMLResponse:
90
+ """Handle the OAuth callback."""
91
+ params = request.query_params
92
+
93
+ # Extract OAuth parameters
94
+ response = CallbackResponse(
95
+ code=params.get("code"),
96
+ state=params.get("state"),
97
+ error=params.get("error"),
98
+ error_description=params.get("error_description"),
99
+ error_uri=params.get("error_uri"),
100
+ )
101
+
102
+ # Log the callback response
103
+ logger.debug(
104
+ f"OAuth callback received: error={response.error}, error_description={response.error_description}"
105
+ )
106
+ if response.code:
107
+ logger.debug("OAuth callback received authorization code")
108
+ else:
109
+ logger.error(f"OAuth callback error: {response.error} - {response.error_description}")
110
+
111
+ # Put response in queue
112
+ try:
113
+ self.response_queue.put_nowait(response)
114
+ except asyncio.QueueFull:
115
+ pass # Ignore if queue is already full
116
+
117
+ # Return success page
118
+ if response.code:
119
+ html = self._success_html()
120
+ else:
121
+ html = self._error_html(response.error, response.error_description)
122
+
123
+ return HTMLResponse(content=html)
124
+
125
+ routes = [Route("/callback", callback)]
126
+ return Starlette(routes=routes)
127
+
128
+ def _success_html(self) -> str:
129
+ """HTML response for successful authorization."""
130
+ return """
131
+ <!DOCTYPE html>
132
+ <html>
133
+ <head>
134
+ <title>Authorization Successful</title>
135
+ <style>
136
+ body {
137
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
138
+ display: flex;
139
+ justify-content: center;
140
+ align-items: center;
141
+ height: 100vh;
142
+ margin: 0;
143
+ background-color: #f5f5f5;
144
+ }
145
+ .container {
146
+ text-align: center;
147
+ padding: 2rem;
148
+ background: white;
149
+ border-radius: 8px;
150
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
151
+ }
152
+ h1 { color: #22c55e; margin-bottom: 0.5rem; }
153
+ p { color: #666; margin-top: 0.5rem; }
154
+ .icon { font-size: 48px; margin-bottom: 1rem; }
155
+ </style>
156
+ </head>
157
+ <body>
158
+ <div class="container">
159
+ <div class="icon">✅</div>
160
+ <h1>Authorization Successful!</h1>
161
+ <p>You can now close this window and return to your application.</p>
162
+ </div>
163
+ <script>
164
+ // Auto-close after 3 seconds
165
+ setTimeout(() => window.close(), 3000);
166
+ </script>
167
+ </body>
168
+ </html>
169
+ """
170
+
171
+ def _error_html(self, error: str | None, description: str | None) -> str:
172
+ """HTML response for authorization error."""
173
+ error_msg = error or "Unknown error"
174
+ desc_msg = description or "Authorization was not completed successfully."
175
+
176
+ return f"""
177
+ <!DOCTYPE html>
178
+ <html>
179
+ <head>
180
+ <title>Authorization Error</title>
181
+ <style>
182
+ body {{
183
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
184
+ display: flex;
185
+ justify-content: center;
186
+ align-items: center;
187
+ height: 100vh;
188
+ margin: 0;
189
+ background-color: #f5f5f5;
190
+ }}
191
+ .container {{
192
+ text-align: center;
193
+ padding: 2rem;
194
+ background: white;
195
+ border-radius: 8px;
196
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
197
+ max-width: 500px;
198
+ }}
199
+ h1 {{ color: #ef4444; margin-bottom: 0.5rem; }}
200
+ .error {{ color: #dc2626; font-weight: 600; margin: 1rem 0; }}
201
+ .description {{ color: #666; margin-top: 0.5rem; }}
202
+ .icon {{ font-size: 48px; margin-bottom: 1rem; }}
203
+ </style>
204
+ </head>
205
+ <body>
206
+ <div class="container">
207
+ <div class="icon">❌</div>
208
+ <h1>Authorization Error</h1>
209
+ <p class="error">{error_msg}</p>
210
+ <p class="description">{desc_msg}</p>
211
+ </div>
212
+ </body>
213
+ </html>
214
+ """