2424
2525import asyncio
2626import json
27- import subprocess
28- import sys
27+ import logging
2928import webbrowser
3029from typing import Any
3130from urllib .parse import urlparse
3635from mcp .shared .exceptions import McpError , UrlElicitationRequiredError
3736from mcp .types import URL_ELICITATION_REQUIRED
3837
38+ logger = logging .getLogger (__name__ )
39+
3940
4041async def handle_elicitation (
4142 context : RequestContext [ClientSession , Any ],
@@ -56,15 +57,19 @@ async def handle_elicitation(
5657 )
5758
5859
60+ ALLOWED_SCHEMES = {"http" , "https" }
61+
62+
5963async def handle_url_elicitation (
6064 params : types .ElicitRequestParams ,
6165) -> types .ElicitResult :
6266 """Handle URL mode elicitation - show security warning and optionally open browser.
6367
6468 This function demonstrates the security-conscious approach to URL elicitation:
65- 1. Display the full URL and domain for user inspection
66- 2. Show the server's reason for requesting this interaction
67- 3. Require explicit user consent before opening any URL
69+ 1. Validate the URL scheme before prompting the user
70+ 2. Display the full URL and domain for user inspection
71+ 3. Show the server's reason for requesting this interaction
72+ 4. Require explicit user consent before opening any URL
6873 """
6974 # Extract URL parameters - these are available on URL mode requests
7075 url = getattr (params , "url" , None )
@@ -75,6 +80,12 @@ async def handle_url_elicitation(
7580 print ("Error: No URL provided in elicitation request" )
7681 return types .ElicitResult (action = "cancel" )
7782
83+ # Reject dangerous URL schemes before prompting the user
84+ parsed = urlparse (str (url ))
85+ if parsed .scheme .lower () not in ALLOWED_SCHEMES :
86+ print (f"\n Rejecting URL with disallowed scheme '{ parsed .scheme } ': { url } " )
87+ return types .ElicitResult (action = "decline" )
88+
7889 # Extract domain for security display
7990 domain = extract_domain (url )
8091
@@ -105,7 +116,11 @@ async def handle_url_elicitation(
105116
106117 # Open the browser
107118 print (f"\n Opening browser to: { url } " )
108- open_browser (url )
119+ try :
120+ webbrowser .open (url )
121+ except Exception :
122+ logger .exception ("Failed to open browser" )
123+ print (f"Please manually open: { url } " )
109124
110125 print ("Waiting for you to complete the interaction in your browser..." )
111126 print ("(The server will continue once you've finished)" )
@@ -121,20 +136,6 @@ def extract_domain(url: str) -> str:
121136 return "unknown"
122137
123138
124- def open_browser (url : str ) -> None :
125- """Open URL in the default browser."""
126- try :
127- if sys .platform == "darwin" :
128- subprocess .run (["open" , url ], check = False )
129- elif sys .platform == "win32" :
130- subprocess .run (["start" , url ], shell = True , check = False )
131- else :
132- webbrowser .open (url )
133- except Exception as e :
134- print (f"Failed to open browser: { e } " )
135- print (f"Please manually open: { url } " )
136-
137-
138139async def call_tool_with_error_handling (
139140 session : ClientSession ,
140141 tool_name : str ,
0 commit comments