Skip to content
Merged
Show file tree
Hide file tree
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
48 changes: 22 additions & 26 deletions docker/mcp/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ async def search_with_key_sse(
This endpoint follows the Model Context Protocol (MCP) for providing
streaming search results that can be used as context for AI models.

- **api_key**: API key provided in the URL path or "webcat" to use the server's API key
- **api_key**: WebCAT API key provided in the URL path for authentication
- **query**: The search query to execute in the request body

Returns a streaming response with search results.
Expand All @@ -177,35 +177,31 @@ async def event_generator() -> AsyncIterator[Dict[str, Any]]:
# Add rate limit headers to response
for header_name, header_value in rate_limit_headers.items():
response.headers[header_name] = header_value

# Validate the WebCAT API key from URL
if not api_key:
logging.error("No WebCAT API key provided in URL path")
yield {"event": "error", "data": "Authentication failed: No API key provided."}
return

# If "webcat" is used as the API key, use the server's SERPER_API_KEY
if api_key == "webcat":
logging.debug(f"Using 'webcat' keyword - WEBCAT_API_KEY={'is set' if WEBCAT_API_KEY else 'is NOT set'}")
if not WEBCAT_API_KEY:
logging.error("Server's WEBCAT_API_KEY is not configured but 'webcat' was used as the API key")
yield {"event": "error", "data": "Server's WEBCAT_API_KEY is not configured."}
return

# Use the server's SERPER_API_KEY for the actual search
search_api_key = SERPER_API_KEY
if not search_api_key:
logging.error("Server's SERPER_API_KEY is not configured")
yield {"event": "error", "data": "Server's SERPER_API_KEY is not configured."}
return

logging.debug(f"Using server's SERPER_API_KEY for search")
elif not api_key:
logging.error("No API key provided in URL path")
yield {"event": "error", "data": "API key not provided in URL path."}
# Validate against WEBCAT_API_KEY
if api_key != WEBCAT_API_KEY:
logging.error(f"Invalid WebCAT API key provided: {api_key[:4]}...{api_key[-4:] if len(api_key) > 8 else '****'}")
yield {"event": "error", "data": "Authentication failed: Invalid API key."}
return

logging.debug(f"Using provided API key from URL path")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove unnecessary f-string prefix.

The logging statement doesn't contain any placeholders.

Apply this diff:

-            logging.debug(f"Using provided API key from URL path")
+            logging.debug("Using provided API key from URL path")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
logging.debug(f"Using provided API key from URL path")
logging.debug("Using provided API key from URL path")
🧰 Tools
🪛 Ruff (0.11.9)

193-193: f-string without any placeholders

Remove extraneous f prefix

(F541)

🪛 Pylint (3.3.7)

[warning] 193-193: Use lazy % formatting in logging functions

(W1203)


[warning] 193-193: Using an f-string that does not have any interpolated variables

(W1309)

🤖 Prompt for AI Agents
In docker/mcp/app.py at line 193, the logging.debug statement uses an f-string
without any placeholders, which is unnecessary. Remove the f-string prefix by
changing the statement to a regular string literal without the leading 'f'.


# After successful authentication, always use SERPER_API_KEY for search
if not SERPER_API_KEY:
logging.error("Server's SERPER_API_KEY is not configured")
yield {"event": "error", "data": "Server configuration error: Search API key not configured."}
return
else:
search_api_key = api_key
logging.debug(f"Using provided API key from URL path")

logging.info(f'MCP search stream with {"server" if api_key == "webcat" else "provided"} key: [{query}]')
logging.info(f"MCP search stream with provided key: [{query}]")

# Fetch search results
results = fetch_search_results(query, search_api_key)
# Fetch search results using the server's SERPER_API_KEY
results = fetch_search_results(query, SERPER_API_KEY)

if not results:
logging.warning(f"No search results found for query: {query}")
Expand Down
1 change: 0 additions & 1 deletion docker/mcp/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
class QueryRequest(BaseModel):
"""Request model for search queries."""
query: str = Field(..., description="The search query")
api_key: Optional[str] = Field(None, description="Optional API key for Serper")

class ApiKeyRequest(BaseModel):
"""Request model for API key updates."""
Expand Down
44 changes: 39 additions & 5 deletions docker/mcp/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,23 +49,57 @@ def scrape_search_result(result: Dict[str, Any]) -> SearchResult:
def fetch_search_results(query: str, api_key: str) -> List[Dict[str, Any]]:
"""Fetch search results from Serper API."""
serper_url = "https://google.serper.dev/search"

# Detailed logging of API key
if not api_key:
logging.error("API key is empty or None")
return []

logging.debug(f"API key length: {len(api_key)}")
logging.debug(f"API key first 4 chars: {api_key[:4] if len(api_key) >= 4 else 'too short'}")
logging.debug(f"API key last 4 chars: {api_key[-4:] if len(api_key) >= 4 else 'too short'}")

headers = {
'X-API-KEY': api_key,
'Content-Type': 'application/json'
}

logging.debug(f"Full headers being sent: {headers}")

payload = {
'q': query,
'gl': 'us',
'hl': 'en'
}

response = requests.post(serper_url, headers=headers, json=payload)
search_results = response.json()
logging.debug(f"Full payload being sent: {payload}")

if 'organic' not in search_results or not search_results['organic']:
return []
try:
logging.debug("Sending request to Serper API...")
response = requests.post(serper_url, headers=headers, json=payload)
logging.debug(f"Response status code: {response.status_code}")
logging.debug(f"Response headers: {response.headers}")

return search_results['organic'][:3]
# Log response content for debugging
try:
if response.status_code != 200:
logging.error(f"Error response content: {response.text}")
else:
logging.debug("Response received successfully")
except Exception as e:
logging.error(f"Error reading response content: {str(e)}")

response.raise_for_status() # Raise exception for 4XX/5XX status codes
search_results = response.json()

if 'organic' not in search_results or not search_results['organic']:
logging.warning(f"No organic results found in Serper API response for query: {query}")
return []

return search_results['organic'][:3]
except requests.exceptions.RequestException as e:
logging.error(f"Error calling Serper API: {str(e)}")
return []

def process_search_results(results: List[Dict[str, Any]]) -> List[SearchResult]:
"""Process search results in parallel."""
Expand Down
6 changes: 3 additions & 3 deletions docker/run_sse_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,8 @@ source venv/bin/activate
echo "📥 Installing required packages..."
pip install requests sseclient-py

# Run the test using the server's API key
echo "🧪 Running SSE test with server API key..."
python3 test_sse.py --server-key
# Run the test using the WebCAT API key
echo "🧪 Running SSE test with WebCAT API key..."
python3 test_sse.py --api-key "${WEBCAT_API_KEY}"

echo "✅ Test completed"
67 changes: 67 additions & 0 deletions docker/test_serper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""Test script for the Serper API."""

import os
import json
import requests
from dotenv import load_dotenv

# Load environment variables
load_dotenv()

def test_serper_api():
"""Test direct search with Serper API."""
print("🔍 Testing search functionality with Serper API...")

# Get API key from environment
api_key = os.environ.get("SERPER_API_KEY", "")

if not api_key:
print("❌ No API key found in environment variables.")
return

# Mask the key for display
key_length = len(api_key)
if key_length > 8:
masked_key = f"{api_key[:4]}{'*' * (key_length - 8)}{api_key[-4:]}"
else:
masked_key = "****"
print(f"🔑 Using API key: {masked_key}")

# Configure the Serper API request
serper_url = "https://google.serper.dev/search"
headers = {
'X-API-KEY': api_key,
'Content-Type': 'application/json'
}
query = "What is the capital of France?"
payload = {
'q': query,
'gl': 'us',
'hl': 'en'
}

print(f"🔎 Searching for: '{query}'")

try:
response = requests.post(serper_url, headers=headers, json=payload)
print(f"Status code: {response.status_code}")

if response.status_code == 200:
result = response.json()
print("✅ Success! Response received:")
# Print only a portion of the response to avoid clutter
if 'organic' in result and result['organic']:
print(f"Found {len(result['organic'])} organic results")
first_result = result['organic'][0]
print(f"First result: {json.dumps(first_result, indent=2)}")
else:
print("No organic results found.")
else:
print(f"❌ Error: {response.status_code}")
print(f"Response: {response.text}")
except Exception as e:
print(f"❌ Exception: {str(e)}")

if __name__ == "__main__":
test_serper_api()
29 changes: 17 additions & 12 deletions docker/test_sse.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,27 @@ def check_container_ports():
except requests.exceptions.RequestException as e:
print(f"❌ Port 9000: UNAVAILABLE - {str(e)}")

def test_sse(use_server_key=False):
def test_sse(webcat_api_key=None):
"""Test the SSE endpoint.

Args:
use_server_key: If True, use the server's WEBCAT_API_KEY by specifying 'webcat'
as the API key in the URL path.
webcat_api_key: The WebCAT API key to use for authentication.
This key is used to authenticate with the WebCAT API.
"""
# First check which servers are available
check_container_ports()

api_key = os.environ.get("SERPER_API_KEY", "")
if not api_key:
print("Warning: No API key provided. Set SERPER_API_KEY environment variable or use --server-key")
print("API requests will likely fail without a valid API key")
# Get WebCAT API key from parameter or environment
if not webcat_api_key:
webcat_api_key = os.environ.get("WEBCAT_API_KEY", "")

if not webcat_api_key:
print("Error: No WebCAT API key provided. Please provide a key with --api-key or set WEBCAT_API_KEY environment variable")
return

# Use the simplified endpoint format on port 9000 (new container)
url = f"http://localhost:9000/search/{api_key}/sse"
# Use the endpoint format expected by the server
# The API key in the URL is used for authentication with WebCAT API
url = f"http://localhost:9000/search/{webcat_api_key}/sse"

headers = {
"Content-Type": "application/json",
Expand All @@ -49,6 +53,7 @@ def test_sse(use_server_key=False):
}

print(f"Testing SSE endpoint: {url}")
print(f"Using WebCAT API key: {webcat_api_key[:4]}...{webcat_api_key[-4:] if len(webcat_api_key) > 8 else '****'}")

try:
response = requests.post(url, headers=headers, json=data, stream=True)
Expand All @@ -70,8 +75,8 @@ def test_sse(use_server_key=False):

if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Test SSE endpoint')
parser.add_argument('--server-key', action='store_true',
help="Use server's WEBCAT_API_KEY instead of your own SERPER_API_KEY")
parser.add_argument('--api-key',
help="WebCAT API key for authentication")
args = parser.parse_args()

test_sse(use_server_key=args.server_key)
test_sse(webcat_api_key=args.api_key)