added some extra sample tool scripts

This commit is contained in:
Luxferre
2026-08-17 09:53:02 +03:00
parent dea6650b98
commit b298c53406
3 changed files with 343 additions and 0 deletions
Executable
+190
View File
@@ -0,0 +1,190 @@
#!/bin/sh
# weather - query current weather and forecasts for any location via wttr.in.
#
# wttr.in (https://github.com/chubin/wttr.in) is a console-oriented weather
# service backed by World Weather Online data. It supports several output
# formats: a graphical ANSI view for terminals (the default), one-line text
# formats (built-in presets 1-4 or a custom %-notation string), a rich JSON
# document (?format=j1 / j2) for scripts and APIs, plus PNG / HTML /
# Prometheus metrics.
#
# This script is a thin, friendly wrapper around the wttr.in HTTP API. It
# builds the request URL from the supplied location and options, fetches the
# data with curl and (when JSON is requested) pretty-prints it with jq.
#
# Usage:
# weather [options] [location]
#
# Options:
# -l, --location LOC Location: city, airport code, domain, IP,
# "lat,lon" coordinates, or "~Name" for a custom label.
# Default: auto-detect from the request IP.
# -u, --units U Unit system: m (metric, default), u (USCS/imperial),
# M (metric with wind speed in m/s).
# -L, --lang LANG Output language code, e.g. de, fr, ru, zh-cn.
# -f, --format FMT Output format:
# j1 -> rich JSON document (default for --json)
# j2 -> JSON document, imperial units
# 1-4 -> built-in one-line presets
# "%..." -> custom one-line %-notation format
# Omit to get the default graphical terminal view.
# -j, --json Alias for --format j1 (emit JSON).
# -0 Current weather only.
# -1 Current weather + today's forecast.
# -2 Current weather + today's + tomorrow's forecast.
# -q, --quiet Quiet: no "Weather report" header / city name.
# -A Force ANSI output (ignore User-Agent detection).
# -h, --help Show this help text.
#
# Environment:
# WTTRAPI Base endpoint. Default: https://wttr.in
# WEATHER_TIMEOUT curl connect/read timeout in seconds. Default: 20
#
# Dependencies: curl, jq (jq only needed for the JSON format).
#
# Examples:
# weather
# weather London
# weather -u u -L de Berlin
# weather -f 3 "New York"
# weather -f "%l: %c %t (feels %f), wind %w %m" Paris
# weather -j Tokyo
set -eu
WTTRAPI="${WTTRAPI:-https://wttr.in}"
TIMEOUT="${WEATHER_TIMEOUT:-20}"
# Single-letter options (view/quiet/units) concatenated into one run.
SHORT=""
LANG_CODE=""
FORMAT=""
LOCATION=""
usage() {
sed -n '/^# Usage:/,/^# Examples:/p' "$0" | sed 's/^# \{0,1\}//'
}
# --- Parse arguments. ---
while [ "$#" -gt 0 ]; do
case "$1" in
-h|--help)
usage
exit 0
;;
-l|--location)
[ "$#" -ge 2 ] || { echo "weather: $1 requires an argument" >&2; exit 1; }
LOCATION="$2"
shift 2
;;
-u|--units)
[ "$#" -ge 2 ] || { echo "weather: $1 requires an argument" >&2; exit 1; }
case "$2" in
m|u|M) SHORT="${SHORT}$2" ;;
*) echo "weather: unknown unit '$2' (use m, u or M)" >&2; exit 1 ;;
esac
shift 2
;;
-L|--lang)
[ "$#" -ge 2 ] || { echo "weather: $1 requires an argument" >&2; exit 1; }
LANG_CODE="$2"
shift 2
;;
-f|--format)
[ "$#" -ge 2 ] || { echo "weather: $1 requires an argument" >&2; exit 1; }
FORMAT="$2"
shift 2
;;
-j|--json)
FORMAT="j1"
shift
;;
-0|-1|-2|-q|-A)
SHORT="${SHORT}${1#-}"
shift
;;
--)
shift
LOCATION="${LOCATION:+$LOCATION }$(printf '%s' "$*" | sed 's/^ //')"
break
;;
-*)
echo "weather: unknown option '$1' (try --help)" >&2
exit 1
;;
*)
# Positional: append to location (preserves multi-word locations).
LOCATION="${LOCATION:+$LOCATION }$1"
shift
;;
esac
done
# --- Assemble the query string. ---
# wttr.in accepts a leading run of single-letter options, then '&'-joined
# long options (key=value). We build both parts and join them.
QUERY="$SHORT"
# Long options: format and lang.
LONG=""
if [ -n "$FORMAT" ]; then
case "$FORMAT" in
j1|j2)
LONG="${LONG:+$LONG&}format=${FORMAT}"
;;
[1-4])
LONG="${LONG:+$LONG&}format=${FORMAT}"
;;
*)
# Custom %-notation one-line format -> URL-encode it.
ENC=$(printf '%s' "$FORMAT" | jq -sRr @uri)
LONG="${LONG:+$LONG&}format=${ENC}"
;;
esac
fi
if [ -n "$LANG_CODE" ]; then
LONG="${LONG:+$LONG&}lang=$(printf '%s' "$LANG_CODE" | jq -sRr @uri)"
fi
# Combine short + long parts into a single query string.
if [ -n "$QUERY" ] && [ -n "$LONG" ]; then
QUERY="${QUERY}&${LONG}"
elif [ -z "$QUERY" ] && [ -n "$LONG" ]; then
QUERY="$LONG"
fi
# URL-encode the location (spaces -> %20, etc.). Empty means auto-detect.
if [ -n "$LOCATION" ]; then
LOC_ENC=$(printf '%s' "$LOCATION" | jq -sRr @uri)
else
LOC_ENC=""
fi
URL="${WTTRAPI}/${LOC_ENC}"
if [ -n "$QUERY" ]; then
URL="${URL}?${QUERY}"
fi
# --- Fetch. ---
# wttr.in returns the graphical ANSI art whenever the request looks like it
# comes from a console client (curl's default User-Agent), and HTML for
# browsers. So we keep curl's default identity. The "-A" flag above is a
# wttr.in URL option ("force ANSI") and is already part of the query string.
RESP=$(curl -s -L --max-time "$TIMEOUT" "$URL") || {
echo "weather: failed to reach $WTTRAPI" >&2
exit 1
}
# --- Emit. ---
if [ "$FORMAT" = "j1" ] || [ "$FORMAT" = "j2" ]; then
if command -v jq >/dev/null 2>&1; then
printf '%s\n' "$RESP" | jq . 2>/dev/null || printf '%s\n' "$RESP"
else
printf '%s\n' "$RESP"
fi
else
printf '%s\n' "$RESP"
fi
exit 0
+106
View File
@@ -0,0 +1,106 @@
#!/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