Validate the iss authorization-response parameter (RFC 9207 / SEP-2468) (#2921)
This commit is contained in:
parent
b7a5bffed0
commit
48cf4950dc
2 changed files with 335 additions and 103 deletions
|
|
@ -19,7 +19,7 @@ from urllib.parse import parse_qs, urlparse
|
|||
|
||||
import httpx
|
||||
from mcp.client._transport import ReadStream, WriteStream
|
||||
from mcp.client.auth import OAuthClientProvider, TokenStorage
|
||||
from mcp.client.auth import AuthorizationCodeResult, OAuthClientProvider, TokenStorage
|
||||
from mcp.client.session import ClientSession
|
||||
from mcp.client.sse import sse_client
|
||||
from mcp.client.streamable_http import streamable_http_client
|
||||
|
|
@ -69,6 +69,7 @@ class CallbackHandler(BaseHTTPRequestHandler):
|
|||
if "code" in query_params:
|
||||
self.callback_data["authorization_code"] = query_params["code"][0]
|
||||
self.callback_data["state"] = query_params.get("state", [None])[0]
|
||||
self.callback_data["iss"] = query_params.get("iss", [None])[0]
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/html")
|
||||
self.end_headers()
|
||||
|
|
@ -112,7 +113,7 @@ class CallbackServer:
|
|||
self.port = port
|
||||
self.server = None
|
||||
self.thread = None
|
||||
self.callback_data = {"authorization_code": None, "state": None, "error": None}
|
||||
self.callback_data = {"authorization_code": None, "state": None, "iss": None, "error": None}
|
||||
|
||||
def _create_handler_with_data(self):
|
||||
"""Create a handler class with access to callback data."""
|
||||
|
|
@ -156,10 +157,16 @@ class CallbackServer:
|
|||
time.sleep(0.1)
|
||||
raise Exception("Timeout waiting for OAuth callback")
|
||||
|
||||
def get_state(self):
|
||||
"""Get the received state parameter."""
|
||||
@property
|
||||
def state(self):
|
||||
"""The received state parameter."""
|
||||
return self.callback_data["state"]
|
||||
|
||||
@property
|
||||
def iss(self):
|
||||
"""The received iss parameter."""
|
||||
return self.callback_data["iss"]
|
||||
|
||||
|
||||
class SimpleAuthClient:
|
||||
"""Simple MCP client with auth support."""
|
||||
|
|
@ -183,12 +190,12 @@ class SimpleAuthClient:
|
|||
callback_server = CallbackServer(port=3030)
|
||||
callback_server.start()
|
||||
|
||||
async def callback_handler() -> tuple[str, str | None]:
|
||||
"""Wait for OAuth callback and return auth code and state."""
|
||||
async def callback_handler() -> AuthorizationCodeResult:
|
||||
"""Wait for OAuth callback and return auth code, state, and iss."""
|
||||
print("⏳ Waiting for authorization callback...")
|
||||
try:
|
||||
auth_code = callback_server.wait_for_callback(timeout=300)
|
||||
return auth_code, callback_server.get_state()
|
||||
return AuthorizationCodeResult(code=auth_code, state=callback_server.state, iss=callback_server.iss)
|
||||
finally:
|
||||
callback_server.stop()
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import httpx
|
|||
from pydantic import AnyUrl
|
||||
|
||||
from mcp import ClientSession
|
||||
from mcp.client.auth import OAuthClientProvider, TokenStorage
|
||||
from mcp.client.auth import AuthorizationCodeResult, OAuthClientProvider, TokenStorage
|
||||
from mcp.client.streamable_http import streamable_http_client
|
||||
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
|
||||
|
||||
|
|
@ -46,10 +46,14 @@ async def handle_redirect(auth_url: str) -> None:
|
|||
print(f"Visit: {auth_url}")
|
||||
|
||||
|
||||
async def handle_callback() -> tuple[str, str | None]:
|
||||
async def handle_callback() -> AuthorizationCodeResult:
|
||||
callback_url = input("Paste callback URL: ")
|
||||
params = parse_qs(urlparse(callback_url).query)
|
||||
return params["code"][0], params.get("state", [None])[0]
|
||||
return AuthorizationCodeResult(
|
||||
code=params["code"][0],
|
||||
state=params.get("state", [None])[0],
|
||||
iss=params.get("iss", [None])[0],
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
|
|
|
|||
Loading…
Reference in a new issue