Files
bantam/extras/websearch
T

107 lines
3.4 KiB
Bash
Executable File

#!/bin/sh
# websearch - simple web search for Bantam via Exa's MCP server (Streamable HTTP).
#
# Talks to https://mcp.exa.ai/mcp using the JSON-RPC MCP protocol:
# 1. initialize -> obtains an Mcp-Session-Id from response headers
# 2. notifications/initialized -> no reply
# 3. tools/call (web_search_exa) -> prints the formatted result text
#
# Usage:
# websearch "your query"
# websearch "your query" [num_results]
#
# Environment:
# EXA_MCP_ENDPOINT optional; default https://mcp.exa.ai/mcp
# EXA_API_KEY optional; if set, sent as an URL query parameter (?api_key=)
# so that NO extra HTTP headers are added beyond Content-Type.
#
# Dependencies: curl, jq.
set -eu
if [ "$#" -lt 1 ]; then
echo "Usage: websearch \"query\" [num_results]" >&2
exit 1
fi
QUERY="$1"
NUM="${2:-5}"
ENDPOINT="${EXA_MCP_ENDPOINT:-https://mcp.exa.ai/mcp}"
# Append API key as a query parameter (no extra headers).
if [ -n "${EXA_API_KEY:-}" ]; then
URL="${ENDPOINT}?api_key=${EXA_API_KEY}"
else
URL="${ENDPOINT}"
fi
# Build the tools/call arguments JSON safely (proper query escaping).
ARGS=$(jq -n --arg q "$QUERY" --argjson n "$NUM" \
'{query:$q, numResults:$n}')
# --- Step 1: initialize, capture headers (for Mcp-Session-Id) and body. ---
INIT_HEADERS=$(mktemp)
INIT_BODY=$(mktemp)
curl -s -D "$INIT_HEADERS" -X POST "$URL" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"websearch","version":"1.0"}}}' \
> "$INIT_BODY"
SID=$(grep -i '^mcp-session-id:' "$INIT_HEADERS" | tr -d '\r' | awk '{print $2}')
if [ -z "$SID" ]; then
echo "websearch: failed to obtain MCP session id" >&2
cat "$INIT_BODY" >&2
rm -f "$INIT_HEADERS" "$INIT_BODY"
exit 1
fi
# --- Step 2: send initialized notification (fire and forget). ---
curl -s -X POST "$URL" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: $SID" \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}' > /dev/null
# --- Step 3: call web_search_exa and extract the text result. ---
CALL_BODY=$(mktemp)
curl -s -X POST "$URL" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: $SID" \
-d "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"web_search_exa\",\"arguments\":$ARGS}}" \
> "$CALL_BODY"
# Parse the response: take the last "data:" SSE payload (or the whole body
# if it is plain JSON) and print the text blocks from the result.
DATA=$(grep -E '^data:[[:space:]]' "$CALL_BODY" | tail -n1 | sed 's/^data:[[:space:]]*//')
if [ -z "$DATA" ]; then
DATA=$(cat "$CALL_BODY")
fi
if [ -z "$DATA" ]; then
echo "websearch: no response from MCP server" >&2
rm -f "$INIT_HEADERS" "$INIT_BODY" "$CALL_BODY"
exit 1
fi
# Surface server/protocol errors, then emit text content.
ERROR_MSG=$(printf '%s' "$DATA" | jq -r 'if .error then (.error|tostring) else empty end')
if [ -n "$ERROR_MSG" ]; then
echo "websearch error: $ERROR_MSG" >&2
rm -f "$INIT_HEADERS" "$INIT_BODY" "$CALL_BODY"
exit 1
fi
printf '%s' "$DATA" | jq -r '
(.result.content // [])
| map(select(.type == "text"))
| map(.text)
| .[]
'
rm -f "$INIT_HEADERS" "$INIT_BODY" "$CALL_BODY"
exit 0