#!/bin/bash # Supabase MCP-USE Deployment Script # This script automates the deployment of an mcp-use app to Supabase Edge Functions set -e # Exit on error # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Print colored messages print_info() { echo -e "${BLUE}ℹ${NC} $1" } print_success() { echo -e "${GREEN}✓${NC} $1" } print_error() { echo -e "${RED}✗${NC} $1" } print_warning() { echo -e "${YELLOW}⚠${NC} $1" } # Check if Docker is running check_docker_running() { if docker info > /dev/null 2>&1; then return 0 else return 1 fi } # Check if project has widgets has_widgets() { local resources_dir="resources" # Check if resources directory exists if [ ! -d "$resources_dir" ]; then return 1 fi # Check for widget files: *.tsx, *.ts files directly in resources/, or widget.tsx in subdirectories # Exclude macOS resource fork files and hidden files for item in "$resources_dir"/*; do # Skip if no matches found (glob expansion) [ -e "$item" ] || continue local basename=$(basename "$item") # Skip hidden/system files if [[ "$basename" =~ ^\._ ]] || [[ "$basename" == ".DS_Store" ]]; then continue fi # Check if it's a file with .tsx or .ts extension if [ -f "$item" ] && [[ "$basename" =~ \.(tsx|ts)$ ]]; then return 0 fi # Check if it's a directory with widget.tsx inside if [ -d "$item" ] && [ -f "$item/widget.tsx" ]; then return 0 fi done return 1 } # Help function show_help() { echo -e "${BLUE}Supabase MCP-USE Deployment Script${NC}" echo "" echo "Usage: ./deploy.sh [project-id] [function-name] [bucket-name] [--version VERSION]" echo "" echo "Arguments (optional if run interactively):" echo " project-id : Your Supabase project ID" echo " function-name : Name of the edge function (default: mcp-server)" echo " bucket-name : Name of the storage bucket (default: widgets)" echo "" echo "Flags:" echo " --version : Specify mcp-use version (default: latest)" echo " Examples: latest, canary, 1.5.1, 1.5.1.canary.3" echo "" echo "Examples:" echo " ./deploy.sh # Interactive mode" echo " ./deploy.sh nnpumlykjksvxivhywwo # With project ID" echo " ./deploy.sh nnpumlykjksvxivhywwo my-function # With custom function name" echo " ./deploy.sh nnpumlykjksvxivhywwo mcp-server widgets --version canary # Use canary version" echo " ./deploy.sh nnpumlykjksvxivhywwo mcp-server widgets --version 1.5.1.canary.3 # Use specific version" echo "" } # Parse flags and arguments MCP_USE_VERSION="latest" PROJECT_ID="" FUNCTION_NAME="" BUCKET_NAME="" NEXT_IS_VERSION=false for arg in "$@"; do if [ "$NEXT_IS_VERSION" = true ]; then MCP_USE_VERSION="$arg" NEXT_IS_VERSION=false elif [ "$arg" = "--version" ]; then NEXT_IS_VERSION=true elif [ -z "$PROJECT_ID" ]; then PROJECT_ID="$arg" elif [ -z "$FUNCTION_NAME" ]; then FUNCTION_NAME="$arg" elif [ -z "$BUCKET_NAME" ]; then BUCKET_NAME="$arg" fi done # Interactive prompts if arguments not provided echo -e "${BLUE}Supabase MCP-USE Deployment Script${NC}" echo "" # Prompt for project ID if not provided if [ -z "$PROJECT_ID" ]; then read -p "Enter your Supabase project ID: " PROJECT_ID /dev/null; then print_error "Supabase CLI is not installed" echo "" echo "Please install it using:" echo " npm install -g supabase" echo "or visit: https://supabase.com/docs/guides/cli" exit 1 fi print_success "Supabase CLI found" # Check if supabase is initialized print_info "Checking if Supabase is initialized..." if [ ! -f "supabase/config.toml" ]; then print_error "Supabase is not initialized in this directory" echo "" echo "Please run: supabase init" exit 1 fi print_success "Supabase is initialized" # Check if user is logged in print_info "Checking Supabase authentication..." if ! supabase projects list &> /dev/null; then print_error "You are not logged in to Supabase" echo "" echo "Please run: supabase login" exit 1 fi print_success "Authenticated with Supabase" # Check Docker if project has widgets if has_widgets; then print_info "Widgets detected, checking Docker..." if ! check_docker_running; then print_error "Docker is required for widgets data. Please start Docker and try again." exit 1 fi print_success "Docker is running" fi # Check if project is linked print_info "Checking if project is linked..." LINKED_PROJECT="" if [ -f "supabase/config.toml" ]; then LINKED_PROJECT=$(grep "^project_id = " supabase/config.toml | sed 's/project_id = "\(.*\)"/\1/' | tr -d '"' || echo "") fi if [ -z "$LINKED_PROJECT" ]; then print_warning "Project not linked. Linking to project: $PROJECT_ID" if ! supabase link --project-ref "$PROJECT_ID"; then print_error "Failed to link to project $PROJECT_ID" exit 1 fi print_success "Linked to project $PROJECT_ID" else print_success "Project already linked: $LINKED_PROJECT" if [ "$LINKED_PROJECT" != "$PROJECT_ID" ]; then print_warning "Linked project ($LINKED_PROJECT) differs from specified project ($PROJECT_ID)" # Check if linked project looks like a valid Supabase project ref (20 chars alphanumeric) if [[ ! "$LINKED_PROJECT" =~ ^[a-z0-9]{20}$ ]]; then print_warning "Linked project ID '$LINKED_PROJECT' doesn't look like a valid Supabase project ref" print_info "Relinking to $PROJECT_ID..." supabase unlink supabase link --project-ref "$PROJECT_ID" print_success "Relinked to project $PROJECT_ID" else read -p "Continue with linked project? (y/N): " -n 1 -r ] section exists and is properly configured if ! grep -q "\[functions.$FUNCTION_NAME\]" supabase/config.toml; then print_warning "Function configuration not found in config.toml" echo "" >> supabase/config.toml echo "[functions.$FUNCTION_NAME]" >> supabase/config.toml echo "verify_jwt = false" >> supabase/config.toml # Only include HTML files and metadata - JS/CSS served from Storage echo "static_files = [ \"./functions/$FUNCTION_NAME/dist/resources/widgets/**/index.html\", \"./functions/$FUNCTION_NAME/dist/mcp-use.json\" ]" >> supabase/config.toml print_success "Added function configuration to config.toml" else # Update existing static_files configuration if it exists if grep -q "static_files = " supabase/config.toml; then # Use sed to update the static_files line to only include HTML and metadata sed -i.bak "s|static_files = .*|static_files = [ \"./functions/$FUNCTION_NAME/dist/resources/widgets/**/index.html\", \"./functions/$FUNCTION_NAME/dist/mcp-use.json\" ]|g" supabase/config.toml rm -f supabase/config.toml.bak print_success "Updated static_files configuration in config.toml" fi fi else print_error "supabase/config.toml not found" exit 1 fi # Build the project print_info "Building the project..." # Optional: Clean dist directory before building to prevent stale assets if [ -d "dist" ]; then print_info "Cleaning dist directory..." rm -rf dist fi # MCP_URL: Where widget assets (JS/CSS) are stored (storage bucket) MCP_URL="https://${PROJECT_ID}.supabase.co/storage/v1/object/public/${BUCKET_NAME}" export MCP_URL print_info "Using MCP_URL: $MCP_URL" # MCP_SERVER_URL: Where the MCP server runs (edge function) for API calls MCP_SERVER_URL="https://${PROJECT_ID}.supabase.co/functions/v1/${FUNCTION_NAME}" export MCP_SERVER_URL print_info "Using MCP_SERVER_URL: $MCP_SERVER_URL" if ! $PKG_MANAGER run build; then print_error "Build failed" exit 1 fi print_success "Build completed successfully" # Check if dist directory exists if [ ! -d "dist" ]; then print_error "dist directory not found after build" exit 1 fi # Create function directory structure print_info "Setting up function directory structure..." FUNCTION_DIR="supabase/functions/$FUNCTION_NAME" mkdir -p "$FUNCTION_DIR" print_success "Created $FUNCTION_DIR" # Copy ONLY HTML files and metadata to function directory # Heavy JS/CSS assets are served from Storage print_info "Copying HTML files and metadata..." if [ -d "$FUNCTION_DIR/dist" ]; then rm -rf "$FUNCTION_DIR/dist" fi mkdir -p "$FUNCTION_DIR/dist/resources/widgets" mkdir -p "$FUNCTION_DIR/dist" # Copy only index.html from each widget if [ -d "dist/resources/widgets" ]; then for widget_dir in dist/resources/widgets/*/; do if [ -d "$widget_dir" ] && [ -f "$widget_dir/index.html" ]; then widget_name=$(basename "$widget_dir") mkdir -p "$FUNCTION_DIR/dist/resources/widgets/$widget_name" cp "$widget_dir/index.html" "$FUNCTION_DIR/dist/resources/widgets/$widget_name/" fi done fi # Copy mcp-use.json if it exists if [ -f "dist/mcp-use.json" ]; then cp "dist/mcp-use.json" "$FUNCTION_DIR/dist/" fi print_success "Copied HTML files and metadata to $FUNCTION_DIR" # Check if index.ts exists, if not copy from current root or src/ if [ ! -f "$FUNCTION_DIR/index.ts" ]; then print_warning "index.ts not found in $FUNCTION_DIR" # Try to find the source file in order of preference SOURCE_FILE="" if [ -f "index.ts" ]; then SOURCE_FILE="index.ts" elif [ -f "src/index.ts" ]; then SOURCE_FILE="src/index.ts" elif [ -f "src/server.ts" ]; then SOURCE_FILE="src/server.ts" fi if [ -n "$SOURCE_FILE" ]; then print_info "Copying $SOURCE_FILE..." cp "$SOURCE_FILE" "$FUNCTION_DIR/index.ts" # Update PROJECT_REF in index.ts if it exists sed -i.bak "s/nnpumlykjksvxivhywwo/$PROJECT_ID/g" "$FUNCTION_DIR/index.ts" rm -f "$FUNCTION_DIR/index.ts.bak" print_success "Copied and updated index.ts from $SOURCE_FILE" else print_error "No index.ts or server.ts found" echo "" echo "Please ensure you have one of the following files:" echo " - index.ts (in current root)" echo " - src/index.ts" echo " - src/server.ts" exit 1 fi fi # Create/override deno.json with mcp-use dependency print_info "Creating deno.json with mcp-use@$MCP_USE_VERSION dependency..." # Note: Hybrid zod mapping to satisfy conflicting requirements: # - @modelcontextprotocol/sdk imports "zod/v3" and needs "z.custom" (available in Zod v3) # - Other deps import "zod/v4" and "zod/v4-mini" # - We map "zod" and "zod/v3" to Zod v3, and "zod/v4" variants to Zod v4 # - We use esm.sh for all zod imports to match the deployment environment cat > "$FUNCTION_DIR/deno.json" << EOF { "imports": { "mcp-use/client": "https://esm.sh/mcp-use@${MCP_USE_VERSION}/client?no-dts&external=zod", "mcp-use/server": "https://esm.sh/mcp-use@${MCP_USE_VERSION}/server?no-dts&external=zod", "zod": "https://esm.sh/zod@3.24.4", "zod/v3": "https://esm.sh/zod@3.24.4", "zod/v4": "https://esm.sh/zod@4.2.0", "zod/v4-mini": "https://esm.sh/zod@4.2.0", "zod/v4/mini": "https://esm.sh/zod@4.2.0", "zod/v4/core": "https://esm.sh/zod@4.2.0" } } EOF print_success "Created deno.json with mcp-use@$MCP_USE_VERSION dependency" # Set environment variables BEFORE deploying the function print_info "Setting environment variables for edge function..." FUNCTION_BASE_URL="https://${PROJECT_ID}.supabase.co/functions/v1/${FUNCTION_NAME}" CSP_URLS="https://${PROJECT_ID}.supabase.co,https://*.supabase.com" # Set MCP_URL if supabase secrets set MCP_URL="$FUNCTION_BASE_URL" --project-ref "$PROJECT_ID"; then print_success "MCP_URL environment variable set to: $FUNCTION_BASE_URL" else print_warning "Failed to set MCP_URL environment variable" fi # Set CSP_URLS to allow widget assets from storage bucket if supabase secrets set CSP_URLS="$CSP_URLS" --project-ref "$PROJECT_ID"; then print_success "CSP_URLS environment variable set to: $CSP_URLS" else print_warning "Failed to set CSP_URLS environment variable" fi # Deploy the function (will pick up the environment variables set above) print_info "Deploying function to Supabase..." if ! supabase functions deploy "$FUNCTION_NAME" --use-docker; then print_error "Function deployment failed" exit 1 fi print_success "Function deployed successfully" # Upload widgets to storage if [ -d "dist/resources/widgets" ]; then print_info "Uploading widgets to storage bucket: $BUCKET_NAME" # Upload widgets to storage (upload contents, not the folder itself) if supabase storage cp -r dist/resources/widgets/ "ss:///${BUCKET_NAME}/" --experimental 2>&1; then print_success "Widgets uploaded successfully" # Important: The bucket must be public for widgets to be accessible echo "" print_warning "The storage bucket needs to be set to PUBLIC for widgets to be accessible" echo "" echo "Please follow these steps:" echo -e " 1. Go to: ${BLUE}https://supabase.com/dashboard/project/$PROJECT_ID/storage/files/buckets/$BUCKET_NAME${NC}?edit=true" echo " 2. Click on the bucket settings (gear icon)" echo -e " 3. Find 'Bucket Settings' and toggle '${BLUE}Public${NC}' ON" echo " 4. Save the changes" echo "" read -p "Press ENTER once you've made the bucket public..." -r &1; then print_success "Public files uploaded successfully" else print_warning "Public file upload failed" echo "You may need to check your bucket permissions" fi else print_warning "No public directory found at dist/public" echo "Skipping public files upload..." fi # Calculate URLs MCP_ENDPOINT="https://${PROJECT_ID}.supabase.co/functions/v1/${FUNCTION_NAME}/mcp" FUNCTION_DASHBOARD="https://supabase.com/dashboard/project/${PROJECT_ID}/functions/${FUNCTION_NAME}" INSPECTOR_URL="https://inspector.mcp-use.com/inspector?autoConnect=$(echo -n "$MCP_ENDPOINT" | python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.stdin.read()))')" # Wait for the MCP server to be ready echo "" print_info "Waiting for MCP server to be ready..." MAX_RETRIES=30 RETRY_COUNT=0 RETRY_DELAY=2 while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do # Use POST request - the /mcp endpoint is SSE so GET hangs, but POST returns immediately # with 415 (Unsupported Media Type) or similar, proving the server is up HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -m 5 -X POST "$MCP_ENDPOINT" 2>/dev/null) || HTTP_CODE="000" # Any response code > 0 means the server is responding # Common codes: 415 (wrong content type), 400 (bad request), 405 (method not allowed) if [ "$HTTP_CODE" != "000" ] && [ "$HTTP_CODE" != "502" ] && [ "$HTTP_CODE" != "503" ] && [ "$HTTP_CODE" != "504" ]; then print_success "MCP server is up and running! (HTTP $HTTP_CODE)" break fi RETRY_COUNT=$((RETRY_COUNT + 1)) if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then echo -n "." sleep $RETRY_DELAY else echo "" print_warning "Server didn't respond after ${MAX_RETRIES} attempts, but deployment completed" print_warning "It may take a few more moments to become available" fi done echo "" # Print deployment summary echo "" print_success "=========================================" print_success "Deployment completed successfully!" print_success "=========================================" echo "" print_info "MCP Endpoint:" echo " $MCP_ENDPOINT" echo "" print_info "Function Dashboard:" echo " $FUNCTION_DASHBOARD" echo "" print_info "Inspector:" echo " $INSPECTOR_URL" echo "" echo ""