diff --git a/scripts/build-bundle.sh b/scripts/build-bundle.sh new file mode 100755 index 0000000..2c5908e --- /dev/null +++ b/scripts/build-bundle.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# build-bundle.sh — build the WhatsApp connector binaries for the Neuron app's bundled +# connector-servers dir. Produces stripped native binaries (default darwin/arm64). +# +# whatsapp-mcp — the MCP server connectd spawns (@bin/whatsapp-mcp) +# whatsapp-bridge — the whatsmeow sidecar that holds the paired session (@bin/whatsapp-bridge) +# +# Usage: scripts/build-bundle.sh [OUT_DIR] +# OUT_DIR defaults to the neuron-ui bundled servers dir. The bridge uses go-sqlite3 (CGO), so +# native builds need a C toolchain (present on macOS); cross-OS builds need a cross toolchain. +set -eu + +ROOT=$(cd "$(dirname "$0")/.." && pwd) +OUT="${1:-$HOME/Development/neuron-technologies/neuron-ui/resources/macos-arm64/neuron-connectd/servers}" +GOOS="${GOOS:-darwin}" +GOARCH="${GOARCH:-arm64}" + +mkdir -p "$OUT" +echo "==> building whatsapp-mcp + whatsapp-bridge ($GOOS/$GOARCH, stripped) -> $OUT" + +( cd "$ROOT/whatsapp-mcp-server" && \ + CGO_ENABLED=1 GOOS="$GOOS" GOARCH="$GOARCH" go build -ldflags="-s -w" -o "$OUT/whatsapp-mcp" . ) +( cd "$ROOT/whatsapp-bridge" && \ + CGO_ENABLED=1 GOOS="$GOOS" GOARCH="$GOARCH" go build -ldflags="-s -w" -o "$OUT/whatsapp-bridge" . ) + +chmod +x "$OUT/whatsapp-mcp" "$OUT/whatsapp-bridge" +echo "==> done:" +ls -lh "$OUT/whatsapp-mcp" "$OUT/whatsapp-bridge" diff --git a/whatsapp-mcp-server/helpers/mcp_tool.go b/whatsapp-mcp-server/helpers/mcp_tool.go index 73660ca..219608a 100644 --- a/whatsapp-mcp-server/helpers/mcp_tool.go +++ b/whatsapp-mcp-server/helpers/mcp_tool.go @@ -62,20 +62,27 @@ func InitMcpTool() { Description: "Get most recent WhatsApp message involving the contact.", }, getLastInteractionHandler) - mcp.AddTool[sendMessageInput, map[string]any](server, &mcp.Tool{ - Name: "send_message", - Description: "Send a text message to a person or group on WhatsApp. For groups use the group JID.", - }, sendMessageHandler) + // Send tools are registered unless the connector runs in read-only mode. + // WHATSAPP_READ_ONLY=true (or 1) hides all outbound tools — defense-in-depth on top of the + // soul's per-send consent gate. Default: send tools available (consent still required upstream). + readOnly := strings.ToLower(ReadEnv("WHATSAPP_READ_ONLY", "false")) == "true" || + ReadEnv("WHATSAPP_READ_ONLY", "0") == "1" + if !readOnly { + mcp.AddTool[sendMessageInput, map[string]any](server, &mcp.Tool{ + Name: "send_message", + Description: "Send a text message to a person or group on WhatsApp. For groups use the group JID.", + }, sendMessageHandler) - mcp.AddTool[sendFileInput, map[string]any](server, &mcp.Tool{ - Name: "send_file", - Description: "Send image, video, document or any file via WhatsApp.", - }, sendFileHandler) + mcp.AddTool[sendFileInput, map[string]any](server, &mcp.Tool{ + Name: "send_file", + Description: "Send image, video, document or any file via WhatsApp.", + }, sendFileHandler) - mcp.AddTool[sendAudioMessageInput, map[string]any](server, &mcp.Tool{ - Name: "send_audio_message", - Description: "Send audio/voice message (converted to Opus .ogg if needed).", - }, sendAudioMessageHandler) + mcp.AddTool[sendAudioMessageInput, map[string]any](server, &mcp.Tool{ + Name: "send_audio_message", + Description: "Send audio/voice message (converted to Opus .ogg if needed).", + }, sendAudioMessageHandler) + } mcp.AddTool[downloadMediaInput, map[string]any](server, &mcp.Tool{ Name: "download_media", diff --git a/whatsapp-mcp-server/helpers/tools.go b/whatsapp-mcp-server/helpers/tools.go index 5c48924..396c0ec 100644 --- a/whatsapp-mcp-server/helpers/tools.go +++ b/whatsapp-mcp-server/helpers/tools.go @@ -12,7 +12,9 @@ import ( var apiBaseURL = readApiBaseURL() func readApiBaseURL() string { - if v := ReadEnv("API_BASE_URL", "http://192.168.178.119:30015/api"); v != "" { + // Default to the local bridge. The connector launches the bridge on loopback :8080; a dev/LAN + // override can still be supplied via API_BASE_URL. + if v := ReadEnv("API_BASE_URL", "http://localhost:8080/api"); v != "" { return v } const fallback = "http://localhost:8080/api"