feat: support MCP bridge (#140)
This commit is contained in:
+176
@@ -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" "$@")"
|
||||
Executable
+93
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
main() {
|
||||
root_dir="$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )/.." &> /dev/null && pwd)"
|
||||
self_name=run-mcp-tool.sh
|
||||
parse_argv "$@"
|
||||
load_env "$root_dir/.env"
|
||||
run
|
||||
}
|
||||
|
||||
parse_argv() {
|
||||
if [[ "$0" == *"$self_name" ]]; then
|
||||
tool_name="$1"
|
||||
tool_data="$2"
|
||||
else
|
||||
tool_name="$(basename "$0")"
|
||||
tool_data="$1"
|
||||
fi
|
||||
if [[ "$tool_name" == *.sh ]]; then
|
||||
tool_name="${tool_name:0:$((${#tool_name}-3))}"
|
||||
fi
|
||||
if [[ -z "$tool_data" ]] || [[ -z "$tool_name" ]]; then
|
||||
die "usage: ./run-tool.sh <tool-name> <tool-data>"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
load_env() {
|
||||
local env_file="$1" env_vars
|
||||
if [[ -f "$env_file" ]]; then
|
||||
while IFS='=' read -r key value; do
|
||||
if [[ "$key" == $'#'* ]] || [[ -z "$key" ]]; then
|
||||
continue
|
||||
fi
|
||||
if [[ -z "${!key+x}" ]]; then
|
||||
env_vars="$env_vars $key=$value"
|
||||
fi
|
||||
done < <(cat "$env_file"; echo "")
|
||||
if [[ -n "$env_vars" ]]; then
|
||||
eval "export $env_vars"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
run() {
|
||||
no_llm_output=0
|
||||
if [[ -z "$LLM_OUTPUT" ]]; then
|
||||
no_llm_output=1
|
||||
export LLM_OUTPUT="$(mktemp)"
|
||||
fi
|
||||
curl -sS "http://localhost:${MCP_BRIDGE_PORT:-8808}/tools/$tool_name" \
|
||||
-X POST \
|
||||
-H 'content-type: application/json' \
|
||||
-d "$tool_data" > "$LLM_OUTPUT"
|
||||
|
||||
if [[ "$no_llm_output" -eq 1 ]]; then
|
||||
cat "$LLM_OUTPUT"
|
||||
else
|
||||
dump_result
|
||||
fi
|
||||
}
|
||||
|
||||
dump_result() {
|
||||
if [ ! -t 1 ]; then
|
||||
return;
|
||||
fi
|
||||
|
||||
local agent_env_name agent_env_value func_env_name func_env_value show_result=0
|
||||
agent_env_name="LLM_AGENT_DUMP_RESULT_$(echo "$LLM_AGENT_NAME" | tr '[:lower:]' '[:upper:]' | tr '-' '_')"
|
||||
agent_env_value="${!agent_env_name:-"$LLM_AGENT_DUMP_RESULT"}"
|
||||
func_env_name="${agent_env_name}_$(echo "$LLM_AGENT_FUNC" | tr '[:lower:]' '[:upper:]' | tr '-' '_')"
|
||||
func_env_value="${!func_env_name}"
|
||||
if [[ "$agent_env_value" == "1" || "$agent_env_value" == "true" ]]; then
|
||||
if [[ "$func_env_value" != "0" && "$func_env_value" != "false" ]]; then
|
||||
show_result=1
|
||||
fi
|
||||
else
|
||||
if [[ "$func_env_value" == "1" || "$func_env_value" == "true" ]]; then
|
||||
show_result=1
|
||||
fi
|
||||
fi
|
||||
if [[ "$show_result" -ne 1 ]]; then
|
||||
return
|
||||
fi
|
||||
cat <<EOF
|
||||
$(echo -e "\e[2m")----------------------
|
||||
$(cat "$LLM_OUTPUT")
|
||||
----------------------$(echo -e "\e[0m")
|
||||
EOF
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user