Skip to content

Commit 25069b4

Browse files
vladm-serpapiVlad M
andauthored
Add response mode parameter (#17)
* Add response mode parameter * Fix env file --------- Co-authored-by: Vlad M <vlad@serpapi.com>
1 parent 788cba8 commit 25069b4

File tree

3 files changed

+35
-161
lines changed

3 files changed

+35
-161
lines changed

.env.example

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,8 @@
11
# SerpApi MCP Server Configuration
22

3-
# Required: Your SerpApi API key
4-
# Get your key from: https://serpapi.com/manage-api-key
5-
SERPAPI_API_KEY=your_api_key_here
6-
7-
# Optional: Default search engine (default: google_light)
8-
# Options: google, google_light, bing, yahoo, duckduckgo, yandex, baidu, youtube_search, ebay, walmart
9-
SERPAPI_DEFAULT_ENGINE=google_light
10-
11-
# Optional: Request timeout in seconds (default: 30)
12-
SERPAPI_TIMEOUT=30
13-
14-
# Optional: MCP Server Configuration
3+
# MCP Server Configuration
4+
# Host to bind the server to (default: 0.0.0.0)
155
MCP_HOST=0.0.0.0
6+
7+
# Port to run the server on (default: 8000)
168
MCP_PORT=8000

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ A Model Context Protocol (MCP) server implementation that integrates with [SerpA
1111
- **Real-time Weather Data**: Location-based weather with forecasts via search queries
1212
- **Stock Market Data**: Company financials and market data through search integration
1313
- **Dynamic Result Processing**: Automatically detects and formats different result types
14-
- **Raw JSON Support**: Option to return full unprocessed API responses
15-
- **Structured Results**: Clean, formatted output optimized for AI consumption
14+
- **Flexible Response Modes**: Complete or compact JSON responses
15+
- **JSON Responses**: Structured JSON output with complete or compact modes
1616

1717
## Quick Start
1818

@@ -74,7 +74,7 @@ The parameters you can provide are specific for each API engine. Some sample par
7474
- `params.q` (required): Search query
7575
- `params.engine`: Search engine (default: "google_light")
7676
- `params.location`: Geographic filter
77-
- `raw`: Return raw JSON (default: false)
77+
- `mode`: Response mode - "complete" (default) or "compact"
7878
- ...see other parameters on the [SerpApi API reference](https://serpapi.com/search-api)
7979

8080
**Examples:**
@@ -83,7 +83,8 @@ The parameters you can provide are specific for each API engine. Some sample par
8383
{"name": "search", "arguments": {"params": {"q": "coffee shops", "location": "Austin, TX"}}}
8484
{"name": "search", "arguments": {"params": {"q": "weather in London"}}}
8585
{"name": "search", "arguments": {"params": {"q": "AAPL stock"}}}
86-
{"name": "search", "arguments": {"params": {"q": "news"}, "raw": true}}
86+
{"name": "search", "arguments": {"params": {"q": "news"}, "mode": "compact"}}
87+
{"name": "search", "arguments": {"params": {"q": "detailed search"}, "mode": "complete"}}
8788
```
8889

8990
**Supported Engines:** Google, Bing, Yahoo, DuckDuckGo, YouTube, eBay, and more.

src/server.py

Lines changed: 26 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -57,103 +57,12 @@ async def dispatch(self, request: Request, call_next):
5757
return await call_next(request)
5858

5959

60-
def format_answer_box(answer_box: dict[str, Any]) -> str:
61-
"""Format answer_box results for weather, finance, and other structured data."""
62-
if answer_box.get("type") == "weather_result":
63-
result = f"Temperature: {answer_box.get('temperature', 'N/A')}\n"
64-
result += f"Unit: {answer_box.get('unit', 'N/A')}\n"
65-
result += f"Precipitation: {answer_box.get('precipitation', 'N/A')}\n"
66-
result += f"Humidity: {answer_box.get('humidity', 'N/A')}\n"
67-
result += f"Wind: {answer_box.get('wind', 'N/A')}\n"
68-
result += f"Location: {answer_box.get('location', 'N/A')}\n"
69-
result += f"Date: {answer_box.get('date', 'N/A')}\n"
70-
result += f"Weather: {answer_box.get('weather', 'N/A')}"
71-
72-
# Add forecast if available
73-
if "forecast" in answer_box:
74-
result += "\n\nDaily Forecast:\n"
75-
for day in answer_box["forecast"]:
76-
result += f"{day.get('day', 'N/A')}: {day.get('weather', 'N/A')} "
77-
if "temperature" in day:
78-
high = day["temperature"].get("high", "N/A")
79-
low = day["temperature"].get("low", "N/A")
80-
result += f"(High: {high}, Low: {low})"
81-
result += "\n"
82-
83-
return result
84-
85-
elif answer_box.get("type") == "finance_results":
86-
result = f"Title: {answer_box.get('title', 'N/A')}\n"
87-
result += f"Exchange: {answer_box.get('exchange', 'N/A')}\n"
88-
result += f"Stock: {answer_box.get('stock', 'N/A')}\n"
89-
result += f"Currency: {answer_box.get('currency', 'N/A')}\n"
90-
result += f"Price: {answer_box.get('price', 'N/A')}\n"
91-
result += f"Previous Close: {answer_box.get('previous_close', 'N/A')}\n"
92-
93-
if "price_movement" in answer_box:
94-
pm = answer_box["price_movement"]
95-
result += f"Price Movement: {pm.get('price', 'N/A')} ({pm.get('percentage', 'N/A')}%) {pm.get('movement', 'N/A')}\n"
96-
97-
if "table" in answer_box:
98-
result += "\nFinancial Metrics:\n"
99-
for row in answer_box["table"]:
100-
result += f"{row.get('name', 'N/A')}: {row.get('value', 'N/A')}\n"
101-
102-
return result
103-
else:
104-
# Generic answer box formatting
105-
result = ""
106-
for key, value in answer_box.items():
107-
if key != "type":
108-
result += f"{key.replace('_', ' ').title()}: {value}\n"
109-
return result
110-
111-
112-
def format_organic_results(organic_results: list[Any]) -> str:
113-
"""Format organic search results."""
114-
formatted_results = []
115-
for result in organic_results:
116-
title = result.get("title", "No title")
117-
link = result.get("link", "No link")
118-
snippet = result.get("snippet", "No snippet")
119-
formatted_results.append(f"Title: {title}\nLink: {link}\nSnippet: {snippet}\n")
120-
return "\n".join(formatted_results) if formatted_results else ""
121-
122-
123-
def format_news_results(news_results: list[Any]) -> str:
124-
"""Format news search results."""
125-
formatted_results = []
126-
for result in news_results:
127-
title = result.get("title", "No title")
128-
link = result.get("link", "No link")
129-
snippet = result.get("snippet", "No snippet")
130-
date = result.get("date", "No date")
131-
source = result.get("source", "No source")
132-
formatted_results.append(
133-
f"Title: {title}\nSource: {source}\nDate: {date}\nLink: {link}\nSnippet: {snippet}\n"
134-
)
135-
return "\n".join(formatted_results) if formatted_results else ""
136-
137-
138-
def format_images_results(images_results: list[Any]) -> str:
139-
"""Format image search results."""
140-
formatted_results = []
141-
for result in images_results:
142-
title = result.get("title", "No title")
143-
link = result.get("link", "No link")
144-
thumbnail = result.get("thumbnail", "No thumbnail")
145-
formatted_results.append(
146-
f"Title: {title}\nImage: {link}\nThumbnail: {thumbnail}\n"
147-
)
148-
return "\n".join(formatted_results) if formatted_results else ""
149-
150-
15160
@mcp.tool()
152-
async def search(params: dict[str, Any] = {}, raw: bool = False) -> str:
61+
async def search(params: dict[str, Any] = {}, mode: str = "complete") -> str:
15362
"""Universal search tool supporting all SerpApi engines and result types.
15463
15564
This tool consolidates weather, stock, and general search functionality into a single interface.
156-
It dynamically processes multiple result types and provides structured output.
65+
It processes multiple result types and returns structured JSON output.
15766
15867
Args:
15968
params: Dictionary of engine-specific parameters. Common parameters include:
@@ -162,17 +71,24 @@ async def search(params: dict[str, Any] = {}, raw: bool = False) -> str:
16271
- location: Geographic location filter
16372
- num: Number of results to return
16473
165-
raw: If True, returns the raw JSON response from SerpApi (default: False)
74+
mode: Response mode (default: "complete")
75+
- "complete": Returns full JSON response with all fields
76+
- "compact": Returns JSON response with metadata fields removed
16677
16778
Returns:
168-
A formatted string of search results or raw JSON data, or an error message.
79+
A JSON string containing search results or an error message.
16980
17081
Examples:
17182
Weather: {"q": "weather in London", "engine": "google"}
17283
Stock: {"q": "AAPL stock", "engine": "google"}
17384
General: {"q": "coffee shops", "engine": "google_light", "location": "Austin, TX"}
85+
Compact: {"q": "news", "mode": "compact"}
17486
"""
17587

88+
# Validate mode parameter
89+
if mode not in ["complete", "compact"]:
90+
return "Error: Invalid mode. Must be 'complete' or 'compact'"
91+
17692
request = get_http_request()
17793
if hasattr(request, "state") and request.state.api_key:
17894
api_key = request.state.api_key
@@ -188,56 +104,21 @@ async def search(params: dict[str, Any] = {}, raw: bool = False) -> str:
188104
try:
189105
data = serpapi.search(search_params).as_dict()
190106

191-
# Return raw JSON if requested
192-
if raw:
193-
return json.dumps(data, indent=2, ensure_ascii=False)
194-
195-
# Process results in priority order
196-
formatted_output = ""
197-
198-
# 1. Answer box (weather, finance, knowledge graph, etc.) - highest priority
199-
if "answer_box" in data:
200-
formatted_output += "=== Answer Box ===\n"
201-
formatted_output += format_answer_box(data["answer_box"])
202-
formatted_output += "\n\n"
203-
204-
# 2. News results
205-
if "news_results" in data and data["news_results"]:
206-
formatted_output += "=== News Results ===\n"
207-
formatted_output += format_news_results(data["news_results"])
208-
formatted_output += "\n\n"
209-
210-
# 3. Organic results
211-
if "organic_results" in data and data["organic_results"]:
212-
formatted_output += "=== Search Results ===\n"
213-
formatted_output += format_organic_results(data["organic_results"])
214-
formatted_output += "\n\n"
215-
216-
# 4. Image results
217-
if "images_results" in data and data["images_results"]:
218-
formatted_output += "=== Image Results ===\n"
219-
formatted_output += format_images_results(data["images_results"])
220-
formatted_output += "\n\n"
221-
222-
# 5. Shopping results
223-
if "shopping_results" in data and data["shopping_results"]:
224-
formatted_output += "=== Shopping Results ===\n"
225-
shopping_results = []
226-
for result in data["shopping_results"]:
227-
title = result.get("title", "No title")
228-
price = result.get("price", "No price")
229-
link = result.get("link", "No link")
230-
source = result.get("source", "No source")
231-
shopping_results.append(
232-
f"Title: {title}\nPrice: {price}\nSource: {source}\nLink: {link}\n"
233-
)
234-
formatted_output += "\n".join(shopping_results) + "\n\n"
235-
236-
# Return formatted output or fallback message
237-
if formatted_output.strip():
238-
return formatted_output.strip()
239-
else:
240-
return "No results found for the given query. Try adjusting your search parameters or engine."
107+
# Apply mode-specific filtering
108+
if mode == "compact":
109+
# Remove specified fields for compact mode
110+
fields_to_remove = [
111+
"search_metadata",
112+
"search_parameters",
113+
"search_information",
114+
"pagination",
115+
"serpapi_pagination",
116+
]
117+
for field in fields_to_remove:
118+
data.pop(field, None)
119+
120+
# Return JSON response for both modes
121+
return json.dumps(data, indent=2, ensure_ascii=False)
241122

242123
except serpapi.exceptions.HTTPError as e:
243124
if "429" in str(e):

0 commit comments

Comments
 (0)