Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 18 additions & 20 deletions examples/snippets/clients/url_elicitation_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@

import asyncio
import json
import subprocess
import sys
import webbrowser
from typing import Any
from urllib.parse import urlparse
Expand Down Expand Up @@ -56,15 +54,19 @@ async def handle_elicitation(
)


ALLOWED_SCHEMES = {"http", "https"}


async def handle_url_elicitation(
params: types.ElicitRequestParams,
) -> types.ElicitResult:
"""Handle URL mode elicitation - show security warning and optionally open browser.

This function demonstrates the security-conscious approach to URL elicitation:
1. Display the full URL and domain for user inspection
2. Show the server's reason for requesting this interaction
3. Require explicit user consent before opening any URL
1. Validate the URL scheme before prompting the user
2. Display the full URL and domain for user inspection
3. Show the server's reason for requesting this interaction
4. Require explicit user consent before opening any URL
"""
# Extract URL parameters - these are available on URL mode requests
url = getattr(params, "url", None)
Expand All @@ -75,6 +77,12 @@ async def handle_url_elicitation(
print("Error: No URL provided in elicitation request")
return types.ElicitResult(action="cancel")

# Reject dangerous URL schemes before prompting the user
parsed = urlparse(str(url))
if parsed.scheme.lower() not in ALLOWED_SCHEMES:
print(f"\nRejecting URL with disallowed scheme '{parsed.scheme}': {url}")
return types.ElicitResult(action="decline")

# Extract domain for security display
domain = extract_domain(url)

Expand Down Expand Up @@ -105,7 +113,11 @@ async def handle_url_elicitation(

# Open the browser
print(f"\nOpening browser to: {url}")
open_browser(url)
try:
webbrowser.open(url)
except Exception as e:
print(f"Failed to open browser: {e}")
print(f"Please manually open: {url}")

print("Waiting for you to complete the interaction in your browser...")
print("(The server will continue once you've finished)")
Expand All @@ -121,20 +133,6 @@ def extract_domain(url: str) -> str:
return "unknown"


def open_browser(url: str) -> None:
"""Open URL in the default browser."""
try:
if sys.platform == "darwin":
subprocess.run(["open", url], check=False)
elif sys.platform == "win32":
subprocess.run(["start", url], shell=True, check=False)
else:
webbrowser.open(url)
except Exception as e:
print(f"Failed to open browser: {e}")
print(f"Please manually open: {url}")


async def call_tool_with_error_handling(
session: ClientSession,
tool_name: str,
Expand Down