From 20d1ec47f9970caa119c3715a1c0c7a69e5aa65f Mon Sep 17 00:00:00 2001 From: sigoden Date: Wed, 11 Dec 2024 20:46:17 +0800 Subject: feat: support MCP bridge (#140) --- scripts/mcp.sh | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 scripts/mcp.sh (limited to 'scripts/mcp.sh') diff --git a/scripts/mcp.sh b/scripts/mcp.sh new file mode 100644 index 0000000..e974476 --- /dev/null +++ b/scripts/mcp.sh @@ -0,0 +1,176 @@ +#!/usr/bin/env bash +set -e + +ROOT_DIR="$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )/.." &> /dev/null && pwd)" +BIN_DIR="$ROOT_DIR/bin" +MCP_DIR="$ROOT_DIR/cache/__mcp__" +MCP_JSON_PATH="$ROOT_DIR/mcp.json" +FUNCTIONS_JSON_PATH="$ROOT_DIR/functions.json" +MCP_BRIDGE_PORT="${MCP_BRIDGE_PORT:-8808}" + +# @cmd Start/Restart mcp bridge server +start() { + if [[ ! -f "$MCP_JSON_PATH" ]]; then + _die "error: not found mcp.json" + fi + stop + mkdir -p "$MCP_DIR" + index_js="$ROOT_DIR/mcp/bridge/index.js" + llm_functions_dir="$ROOT_DIR" + if _is_win; then + index_js="$(cygpath -w "$index_js")" + llm_functions_dir="$(cygpath -w "$llm_functions_dir")" + fi + echo "Run MCP Bridge server" + nohup node "$index_js" "$llm_functions_dir" > "$MCP_DIR/mcp-bridge.log" 2>&1 & + wait-for-server + echo "Merge MCP tools into functions.json" + merge-functions > "$MCP_DIR/functions.json" + cp -f "$MCP_DIR/functions.json" "$FUNCTIONS_JSON_PATH" + build-bin +} + +# @cmd Stop mcp bridge server +stop() { + pid="$(get-server-pid)" + if [[ -n "$pid" ]]; then + if _is_win; then + taskkill /PID "$pid" /F > /dev/null 2>&1 || true + else + kill -9 "$pid" > /dev/null 2>&1 || true + fi + fi + mkdir -p "$MCP_DIR" + unmerge-functions > "$MCP_DIR/functions.original.json" + cp -f "$MCP_DIR/functions.original.json" "$FUNCTIONS_JSON_PATH" +} + +# @cmd Call mcp tool +# @arg tool![`_choice_tool`] The tool name +# @arg json The json data +call() { + if [[ -z "$argc_json" ]]; then + declaration="$(build-declarations | jq --arg tool "$argc_tool" -r '.[] | select(.name == $tool)')" + if [[ -n "$declaration" ]]; then + _ask_json_data "$declaration" + fi + fi + if [[ -z "$argc_json" ]]; then + _die "error: no JSON data" + fi + bash "$ROOT_DIR/scripts/run-mcp-tool.sh" "$argc_tool" "$argc_json" +} + +# @cmd Show logs +# @flag -f --follow Follow mode +logs() { + args="" + if [[ -n "$argc_follow" ]]; then + args="$args -f" + fi + if [[ -f "$MCP_DIR/mcp-bridge.log" ]]; then + tail $args "$MCP_DIR/mcp-bridge.log" + fi +} + +# @cmd Build tools to bin +build-bin() { + tools=( $(build-declarations | jq -r '.[].name') ) + for tool in "${tools[@]}"; do + if _is_win; then + bin_file="$BIN_DIR/$tool.cmd" + _build_win_shim > "$bin_file" + else + bin_file="$BIN_DIR/$tool" + ln -s -f "$ROOT_DIR/scripts/run-mcp-tool.sh" "$bin_file" + fi + echo "Build bin/$tool" + done +} + +# @cmd Merge mcp tools into functions.json +merge-functions() { + jq --argjson json1 "$(unmerge-functions)" --argjson json2 "$(build-declarations)" -n '($json1 + $json2)' +} + +# @cmd Unmerge mcp tools from functions.json +unmerge-functions() { + functions="[]" + if [[ -f "$FUNCTIONS_JSON_PATH" ]]; then + functions="$(cat "$FUNCTIONS_JSON_PATH")" + fi + printf "%s" "$functions" | jq 'map(select(has("mcp") | not))' +} + +# @cmd Build tools to bin +build-declarations() { + curl -sS http://localhost:$MCP_BRIDGE_PORT/tools | jq '.[] |= . + {mcp: true}' +} + +# @cmd Wait for mcp bridge server to ready +wait-for-server() { + while true; do + if [[ "$(curl -fsS http://localhost:$MCP_BRIDGE_PORT/health 2>&1)" == "OK" ]]; then + break; + fi + sleep 1 + done +} + +# @cmd +get-server-pid() { + curl -fsSL http://localhost:$MCP_BRIDGE_PORT/pid 2>/dev/null || true +} + +_ask_json_data() { + declaration="$1" + echo 'Missing the JSON data but here are its properties:' + echo "$declaration" | ./scripts/declarations-util.sh pretty-print | sed -n '2,$s/^/>/p' + echo 'Generate placeholder data:' + data="$(echo "$declaration" | _declarations_json_data)" + echo "> $data" + read -e -r -p 'JSON data (Press ENTER to use placeholder): ' res + if [[ -z "$res" ]]; then + argc_json="$data" + else + argc_json="$res" + fi +} + +_declarations_json_data() { + ./scripts/declarations-util.sh generate-json | tail -n +2 +} + +_build_win_shim() { + run="\"$(argc --argc-shell-path)\" --noprofile --norc" + cat <<-EOF +@echo off +setlocal + +set "bin_dir=%~dp0" +for %%i in ("%bin_dir:~0,-1%") do set "script_dir=%%~dpi" +set "script_name=%~n0" + +$run "%script_dir%scripts\run-mcp-tool.sh" "%script_name%" %* +EOF +} + +_is_win() { + if [[ "$OS" == "Windows_NT" ]]; then + return 0 + else + return 1 + fi +} + +_choice_tool() { + build-declarations | jq -r '.[].name' +} + +_die() { + echo "$*" >&2 + exit 1 +} + +# See more details at https://github.com/sigoden/argc +eval "$(argc --argc-eval "$0" "$@")" -- cgit v1.2.3