2025-04-02 13:09:28 -04:00
import asyncio
2025-04-02 10:33:37 -06:00
import json
import os
2025-02-07 02:48:13 +08:00
import shutil
import subprocess
import time
2025-04-03 00:46:57 -04:00
import uuid
from pathlib import Path
2025-02-07 02:48:13 +08:00
from typing import Optional
import typer
2025-04-02 13:09:28 -04:00
import uvicorn
2025-04-03 00:46:57 -04:00
from dotenv import load_dotenv , set_key
2025-04-02 13:09:28 -04:00
from mcp . server . fastmcp import FastMCP
2025-02-07 02:48:13 +08:00
2025-04-02 13:09:28 -04:00
from skyvern . agent import SkyvernAgent
from skyvern . config import settings
2025-04-03 00:46:57 -04:00
from skyvern . forge import app
from skyvern . forge . sdk . db . enums import OrganizationAuthTokenType
2025-04-02 10:33:37 -06:00
from skyvern . utils import detect_os , get_windows_appdata_roaming , migrate_db
2025-04-03 00:46:57 -04:00
load_dotenv ( )
cli_app = typer . Typer ( )
run_app = typer . Typer ( )
2025-04-03 04:10:03 -04:00
setup_app = typer . Typer ( )
2025-04-03 00:46:57 -04:00
cli_app . add_typer ( run_app , name = " run " )
2025-04-03 04:10:03 -04:00
cli_app . add_typer ( setup_app , name = " setup " )
2025-04-02 13:09:28 -04:00
mcp = FastMCP ( " Skyvern " )
@mcp.tool ( )
2025-04-03 02:50:12 -04:00
async def skyvern_run_task ( prompt : str , url : str ) - > dict [ str , str ] :
2025-04-03 04:10:03 -04:00
""" Use Skyvern to execute anything in the browser. Useful for accomplishing tasks that require browser automation.
2025-04-03 02:50:12 -04:00
This tool uses Skyvern ' s browser automation to navigate websites and perform actions to achieve
the user ' s intended outcome. It can handle tasks like form filling, clicking buttons, data extraction,
and multi - step workflows .
2025-04-02 13:09:28 -04:00
2025-04-03 04:10:03 -04:00
It can even help you find updated data on the internet if your model information is outdated .
2025-04-02 13:09:28 -04:00
Args :
2025-04-03 02:50:12 -04:00
prompt : A natural language description of what needs to be accomplished ( e . g . " Book a flight from
NYC to LA " , " Sign up for the newsletter " , " Find the price of item X " , " Apply to a job " )
url : The starting URL of the website where the task should be performed
2025-04-02 13:09:28 -04:00
"""
2025-04-03 00:46:57 -04:00
skyvern_agent = SkyvernAgent (
base_url = settings . SKYVERN_BASE_URL ,
api_key = settings . SKYVERN_API_KEY ,
extra_headers = { " X-User-Agent " : " skyvern-mcp " } ,
)
res = await skyvern_agent . run_task ( prompt = prompt , url = url )
2025-04-03 02:50:12 -04:00
# TODO: It would be nice if we could return the task URL here
output = res . model_dump ( ) [ " output " ]
base_url = settings . SKYVERN_BASE_URL
run_history_url = (
" https://app.skyvern.com/history " if " skyvern.com " in base_url else " http://localhost:8080/history "
)
return { " output " : output , " run_history_url " : run_history_url }
2025-04-02 13:09:28 -04:00
2025-02-07 02:48:13 +08:00
def command_exists ( command : str ) - > bool :
return shutil . which ( command ) is not None
def run_command ( command : str , check : bool = True ) - > tuple [ Optional [ str ] , Optional [ int ] ] :
try :
result = subprocess . run ( command , shell = True , check = check , capture_output = True , text = True )
return result . stdout . strip ( ) , result . returncode
except subprocess . CalledProcessError as e :
return None , e . returncode
def is_postgres_running ( ) - > bool :
if command_exists ( " pg_isready " ) :
2025-02-22 02:41:38 -08:00
result , _ = run_command ( " pg_isready " )
2025-02-07 02:48:13 +08:00
return result is not None and " accepting connections " in result
return False
def database_exists ( dbname : str , user : str ) - > bool :
check_db_command = f ' psql { dbname } -U { user } -c " \\ q " '
2025-02-22 02:41:38 -08:00
output , _ = run_command ( check_db_command , check = False )
return output is not None
2025-02-07 02:48:13 +08:00
def create_database_and_user ( ) - > None :
print ( " Creating database user and database... " )
run_command ( " createuser skyvern " )
run_command ( " createdb skyvern -O skyvern " )
print ( " Database and user created successfully. " )
def is_docker_running ( ) - > bool :
if not command_exists ( " docker " ) :
return False
_ , code = run_command ( " docker info " , check = False )
return code == 0
def is_postgres_running_in_docker ( ) - > bool :
_ , code = run_command ( " docker ps | grep -q postgresql-container " , check = False )
return code == 0
def is_postgres_container_exists ( ) - > bool :
_ , code = run_command ( " docker ps -a | grep -q postgresql-container " , check = False )
return code == 0
def setup_postgresql ( ) - > None :
print ( " Setting up PostgreSQL... " )
if command_exists ( " psql " ) and is_postgres_running ( ) :
print ( " PostgreSQL is already running locally. " )
if database_exists ( " skyvern " , " skyvern " ) :
print ( " Database and user exist. " )
else :
create_database_and_user ( )
return
if not is_docker_running ( ) :
print ( " Docker is not running or not installed. Please install or start Docker and try again. " )
exit ( 1 )
if is_postgres_running_in_docker ( ) :
print ( " PostgreSQL is already running in a Docker container. " )
else :
print ( " Attempting to install PostgreSQL via Docker... " )
if not is_postgres_container_exists ( ) :
run_command (
" docker run --name postgresql-container -e POSTGRES_HOST_AUTH_METHOD=trust -d -p 5432:5432 postgres:14 "
)
else :
run_command ( " docker start postgresql-container " )
print ( " PostgreSQL has been installed and started using Docker. " )
print ( " Waiting for PostgreSQL to start... " )
time . sleep ( 20 )
_ , code = run_command ( ' docker exec postgresql-container psql -U postgres -c " \\ du " | grep -q skyvern ' , check = False )
if code == 0 :
print ( " Database user exists. " )
else :
print ( " Creating database user... " )
run_command ( " docker exec postgresql-container createuser -U postgres skyvern " )
_ , code = run_command (
" docker exec postgresql-container psql -U postgres -lqt | cut -d \\ | -f 1 | grep -qw skyvern " , check = False
)
if code == 0 :
print ( " Database exists. " )
else :
print ( " Creating database... " )
run_command ( " docker exec postgresql-container createdb -U postgres skyvern -O skyvern " )
print ( " Database and user created successfully. " )
2025-04-03 00:46:57 -04:00
def update_or_add_env_var ( key : str , value : str ) - > None :
""" Update or add environment variable in .env file. """
env_path = Path ( " .env " )
if not env_path . exists ( ) :
env_path . touch ( )
# Write default environment variables using dotenv
defaults = {
" ENV " : " local " ,
" ENABLE_OPENAI " : " false " ,
" OPENAI_API_KEY " : " " ,
" ENABLE_ANTHROPIC " : " false " ,
" ANTHROPIC_API_KEY " : " " ,
" ENABLE_AZURE " : " false " ,
" AZURE_DEPLOYMENT " : " " ,
" AZURE_API_KEY " : " " ,
" AZURE_API_BASE " : " " ,
" AZURE_API_VERSION " : " " ,
" ENABLE_AZURE_GPT4O_MINI " : " false " ,
" AZURE_GPT4O_MINI_DEPLOYMENT " : " " ,
" AZURE_GPT4O_MINI_API_KEY " : " " ,
" AZURE_GPT4O_MINI_API_BASE " : " " ,
" AZURE_GPT4O_MINI_API_VERSION " : " " ,
" ENABLE_GEMINI " : " false " ,
" GEMINI_API_KEY " : " " ,
" ENABLE_NOVITA " : " false " ,
" NOVITA_API_KEY " : " " ,
" LLM_KEY " : " " ,
" SECONDARY_LLM_KEY " : " " ,
" BROWSER_TYPE " : " chromium-headful " ,
" MAX_SCRAPING_RETRIES " : " 0 " ,
" VIDEO_PATH " : " ./videos " ,
" BROWSER_ACTION_TIMEOUT_MS " : " 5000 " ,
" MAX_STEPS_PER_RUN " : " 50 " ,
" LOG_LEVEL " : " INFO " ,
" DATABASE_STRING " : " postgresql+psycopg://skyvern@localhost/skyvern " ,
" PORT " : " 8000 " ,
" ANALYTICS_ID " : " anonymous " ,
" ENABLE_LOG_ARTIFACTS " : " false " ,
}
for k , v in defaults . items ( ) :
set_key ( env_path , k , v )
load_dotenv ( env_path )
set_key ( env_path , key , value )
def setup_llm_providers ( ) - > None :
""" Configure Large Language Model (LLM) Providers. """
print ( " Configuring Large Language Model (LLM) Providers... " )
print ( " Note: All information provided here will be stored only on your local machine. " )
model_options = [ ]
# OpenAI Configuration
print ( " To enable OpenAI, you must have an OpenAI API key. " )
enable_openai = input ( " Do you want to enable OpenAI (y/n)? " ) . lower ( ) == " y "
if enable_openai :
openai_api_key = input ( " Enter your OpenAI API key: " )
if not openai_api_key :
print ( " Error: OpenAI API key is required. " )
print ( " OpenAI will not be enabled. " )
else :
update_or_add_env_var ( " OPENAI_API_KEY " , openai_api_key )
update_or_add_env_var ( " ENABLE_OPENAI " , " true " )
2025-04-16 02:12:33 -04:00
model_options . extend (
[
" OPENAI_GPT4_1 " ,
" OPENAI_GPT4_1_MINI " ,
" OPENAI_GPT4_1_NANO " ,
" OPENAI_GPT4O " ,
2025-04-16 21:34:00 -04:00
" OPENAI_O4_MINI " ,
" OPENAI_O3 " ,
2025-04-16 02:12:33 -04:00
]
)
2025-04-03 00:46:57 -04:00
else :
update_or_add_env_var ( " ENABLE_OPENAI " , " false " )
# Anthropic Configuration
print ( " To enable Anthropic, you must have an Anthropic API key. " )
enable_anthropic = input ( " Do you want to enable Anthropic (y/n)? " ) . lower ( ) == " y "
if enable_anthropic :
anthropic_api_key = input ( " Enter your Anthropic API key: " )
if not anthropic_api_key :
print ( " Error: Anthropic API key is required. " )
print ( " Anthropic will not be enabled. " )
else :
update_or_add_env_var ( " ANTHROPIC_API_KEY " , anthropic_api_key )
update_or_add_env_var ( " ENABLE_ANTHROPIC " , " true " )
model_options . extend (
[
" ANTHROPIC_CLAUDE3.5_SONNET " ,
" ANTHROPIC_CLAUDE3.7_SONNET " ,
]
)
else :
update_or_add_env_var ( " ENABLE_ANTHROPIC " , " false " )
# Azure Configuration
print ( " To enable Azure, you must have an Azure deployment name, API key, base URL, and API version. " )
enable_azure = input ( " Do you want to enable Azure (y/n)? " ) . lower ( ) == " y "
if enable_azure :
azure_deployment = input ( " Enter your Azure deployment name: " )
azure_api_key = input ( " Enter your Azure API key: " )
azure_api_base = input ( " Enter your Azure API base URL: " )
azure_api_version = input ( " Enter your Azure API version: " )
if not all ( [ azure_deployment , azure_api_key , azure_api_base , azure_api_version ] ) :
print ( " Error: All Azure fields must be populated. " )
print ( " Azure will not be enabled. " )
else :
update_or_add_env_var ( " AZURE_DEPLOYMENT " , azure_deployment )
update_or_add_env_var ( " AZURE_API_KEY " , azure_api_key )
update_or_add_env_var ( " AZURE_API_BASE " , azure_api_base )
update_or_add_env_var ( " AZURE_API_VERSION " , azure_api_version )
update_or_add_env_var ( " ENABLE_AZURE " , " true " )
model_options . append ( " AZURE_OPENAI_GPT4O " )
else :
update_or_add_env_var ( " ENABLE_AZURE " , " false " )
# Gemini Configuration
print ( " To enable Gemini, you must have an Gemini API key. " )
enable_gemini = input ( " Do you want to enable Gemini (y/n)? " ) . lower ( ) == " y "
if enable_gemini :
gemini_api_key = input ( " Enter your Gemini API key: " )
if not gemini_api_key :
print ( " Error: Gemini API key is required. " )
print ( " Gemini will not be enabled. " )
else :
update_or_add_env_var ( " GEMINI_API_KEY " , gemini_api_key )
update_or_add_env_var ( " ENABLE_GEMINI " , " true " )
2025-04-06 00:03:24 -04:00
model_options . extend (
[
" GEMINI_FLASH_2_0 " ,
" GEMINI_FLASH_2_0_LITE " ,
" GEMINI_2.5_PRO_PREVIEW_03_25 " ,
" GEMINI_2.5_PRO_EXP_03_25 " ,
]
)
2025-04-03 00:46:57 -04:00
else :
update_or_add_env_var ( " ENABLE_GEMINI " , " false " )
# Novita AI Configuration
print ( " To enable Novita AI, you must have an Novita AI API key. " )
enable_novita = input ( " Do you want to enable Novita AI (y/n)? " ) . lower ( ) == " y "
if enable_novita :
novita_api_key = input ( " Enter your Novita AI API key: " )
if not novita_api_key :
print ( " Error: Novita AI API key is required. " )
print ( " Novita AI will not be enabled. " )
else :
update_or_add_env_var ( " NOVITA_API_KEY " , novita_api_key )
update_or_add_env_var ( " ENABLE_NOVITA " , " true " )
model_options . extend (
[
" NOVITA_DEEPSEEK_R1 " ,
" NOVITA_DEEPSEEK_V3 " ,
" NOVITA_LLAMA_3_3_70B " ,
" NOVITA_LLAMA_3_2_1B " ,
" NOVITA_LLAMA_3_2_3B " ,
" NOVITA_LLAMA_3_2_11B_VISION " ,
" NOVITA_LLAMA_3_1_8B " ,
" NOVITA_LLAMA_3_1_70B " ,
" NOVITA_LLAMA_3_1_405B " ,
" NOVITA_LLAMA_3_8B " ,
" NOVITA_LLAMA_3_70B " ,
]
)
else :
update_or_add_env_var ( " ENABLE_NOVITA " , " false " )
2025-04-20 20:25:59 -04:00
# OpenAI Compatible Configuration
print ( " To enable an OpenAI-compatible provider, you must have a model name, API key, and API base URL. " )
enable_openai_compatible = input ( " Do you want to enable an OpenAI-compatible provider (y/n)? " ) . lower ( ) == " y "
if enable_openai_compatible :
openai_compatible_model_name = input ( " Enter the model name (e.g., ' yi-34b ' , ' mistral-large ' ): " )
openai_compatible_api_key = input ( " Enter your API key: " )
openai_compatible_api_base = input ( " Enter the API base URL (e.g., ' https://api.together.xyz/v1 ' ): " )
openai_compatible_vision = input ( " Does this model support vision (y/n)? " ) . lower ( ) == " y "
if not all ( [ openai_compatible_model_name , openai_compatible_api_key , openai_compatible_api_base ] ) :
print ( " Error: All required fields must be populated. " )
print ( " OpenAI-compatible provider will not be enabled. " )
else :
update_or_add_env_var ( " OPENAI_COMPATIBLE_MODEL_NAME " , openai_compatible_model_name )
update_or_add_env_var ( " OPENAI_COMPATIBLE_API_KEY " , openai_compatible_api_key )
update_or_add_env_var ( " OPENAI_COMPATIBLE_API_BASE " , openai_compatible_api_base )
# Set vision support
if openai_compatible_vision :
update_or_add_env_var ( " OPENAI_COMPATIBLE_SUPPORTS_VISION " , " true " )
else :
update_or_add_env_var ( " OPENAI_COMPATIBLE_SUPPORTS_VISION " , " false " )
# Optional: Ask for API version
openai_compatible_api_version = input ( " Enter API version (optional, press enter to skip): " )
if openai_compatible_api_version :
update_or_add_env_var ( " OPENAI_COMPATIBLE_API_VERSION " , openai_compatible_api_version )
update_or_add_env_var ( " ENABLE_OPENAI_COMPATIBLE " , " true " )
model_options . append ( " OPENAI_COMPATIBLE " )
else :
update_or_add_env_var ( " ENABLE_OPENAI_COMPATIBLE " , " false " )
2025-04-03 00:46:57 -04:00
# Model Selection
if not model_options :
print (
" No LLM providers enabled. You won ' t be able to run Skyvern unless you enable at least one provider. You can re-run this script to enable providers or manually update the .env file. "
)
else :
print ( " Available LLM models based on your selections: " )
for i , model in enumerate ( model_options , 1 ) :
print ( f " { i } . { model } " )
while True :
try :
model_choice = int ( input ( f " Choose a model by number (e.g., 1 for { model_options [ 0 ] } ): " ) )
if 1 < = model_choice < = len ( model_options ) :
break
print ( f " Please enter a number between 1 and { len ( model_options ) } " )
except ValueError :
print ( " Please enter a valid number " )
chosen_model = model_options [ model_choice - 1 ]
print ( f " Chosen LLM Model: { chosen_model } " )
update_or_add_env_var ( " LLM_KEY " , chosen_model )
print ( " LLM provider configurations updated in .env. " )
def get_default_chrome_location ( host_system : str ) - > str :
""" Get the default Chrome/Chromium location based on OS. """
if host_system == " darwin " :
return " /Applications/Google Chrome.app/Contents/MacOS/Google Chrome "
elif host_system == " linux " :
# Common Linux locations
chrome_paths = [ " /usr/bin/google-chrome " , " /usr/bin/chromium " , " /usr/bin/chromium-browser " ]
for path in chrome_paths :
if os . path . exists ( path ) :
return path
return " /usr/bin/google-chrome " # default if not found
elif host_system == " wsl " :
return " /mnt/c/Program Files/Google/Chrome/Application/chrome.exe "
else :
return " C: \\ Program Files \\ Google \\ Chrome \\ Application \\ chrome.exe "
def setup_browser_config ( ) - > tuple [ str , Optional [ str ] , Optional [ str ] ] :
""" Configure browser settings for Skyvern. """
print ( " \n Configuring web browser for scraping... " )
browser_types = [ " chromium-headless " , " chromium-headful " , " cdp-connect " ]
for i , browser_type in enumerate ( browser_types , 1 ) :
print ( f " { i } . { browser_type } " )
if browser_type == " chromium-headless " :
print ( " - Runs Chrome in headless mode (no visible window) " )
elif browser_type == " chromium-headful " :
print ( " - Runs Chrome with visible window " )
elif browser_type == " cdp-connect " :
print ( " - Connects to an existing Chrome instance " )
print ( " - Requires Chrome to be running with remote debugging enabled " )
while True :
try :
choice = int ( input ( " \n Choose browser type (1-3): " ) )
if 1 < = choice < = len ( browser_types ) :
selected_browser = browser_types [ choice - 1 ]
break
print ( f " Please enter a number between 1 and { len ( browser_types ) } " )
except ValueError :
print ( " Please enter a valid number " )
browser_location = None
remote_debugging_url = None
if selected_browser == " cdp-connect " :
host_system = detect_os ( )
default_location = get_default_chrome_location ( host_system )
print ( f " \n Default Chrome location for your system: { default_location } " )
browser_location = input ( " Enter Chrome executable location (press Enter to use default): " ) . strip ( )
if not browser_location :
browser_location = default_location
if not os . path . exists ( browser_location ) :
print ( f " Warning: Chrome not found at { browser_location } . Please verify the location is correct. " )
print ( " \n To use CDP connection, Chrome must be running with remote debugging enabled. " )
print ( " Example: chrome --remote-debugging-port=9222 " )
print ( " Default debugging URL: http://localhost:9222 " )
remote_debugging_url = input ( " Enter remote debugging URL (press Enter for default): " ) . strip ( )
if not remote_debugging_url :
remote_debugging_url = " http://localhost:9222 "
return selected_browser , browser_location , remote_debugging_url
2025-04-02 13:09:28 -04:00
async def _setup_local_organization ( ) - > str :
"""
Returns the API key for the local organization generated
"""
2025-04-03 03:20:40 -04:00
skyvern_agent = SkyvernAgent (
base_url = settings . SKYVERN_BASE_URL ,
api_key = settings . SKYVERN_API_KEY ,
)
2025-04-03 00:46:57 -04:00
organization = await skyvern_agent . get_organization ( )
2025-04-02 13:09:28 -04:00
org_auth_token = await app . DATABASE . get_valid_org_auth_token (
organization_id = organization . organization_id ,
token_type = OrganizationAuthTokenType . api ,
)
2025-04-02 13:57:26 -04:00
return org_auth_token . token if org_auth_token else " "
2025-04-02 13:09:28 -04:00
2025-04-03 00:46:57 -04:00
@cli_app.command ( name = " migrate " )
2025-02-07 02:48:13 +08:00
def migrate ( ) - > None :
migrate_db ( )
2025-03-30 16:42:12 -07:00
2025-04-02 10:33:37 -06:00
def get_claude_config_path ( host_system : str ) - > str :
""" Get the Claude Desktop config file path for the current OS. """
if host_system == " wsl " :
roaming_path = get_windows_appdata_roaming ( )
if roaming_path is None :
raise RuntimeError ( " Could not locate Windows AppData \\ Roaming path from WSL " )
return os . path . join ( str ( roaming_path ) , " Claude " , " claude_desktop_config.json " )
base_paths = {
" darwin " : [ " ~/Library/Application Support/Claude " ] ,
" linux " : [ " ~/.config/Claude " , " ~/.local/share/Claude " , " ~/Claude " ] ,
}
if host_system == " darwin " :
base_path = os . path . expanduser ( base_paths [ " darwin " ] [ 0 ] )
return os . path . join ( base_path , " claude_desktop_config.json " )
if host_system == " linux " :
for path in base_paths [ " linux " ] :
full_path = os . path . expanduser ( path )
if os . path . exists ( full_path ) :
return os . path . join ( full_path , " claude_desktop_config.json " )
raise Exception ( f " Unsupported host system: { host_system } " )
def get_claude_command_config (
host_system : str , path_to_env : str , path_to_server : str , env_vars : str
) - > tuple [ str , list ] :
""" Get the command and arguments for Claude Desktop configuration. """
base_env_vars = f " { env_vars } ENABLE_OPENAI=true LOG_LEVEL=CRITICAL "
artifacts_path = os . path . join ( os . path . abspath ( " ./ " ) , " artifacts " )
if host_system == " wsl " :
env_vars = f " { base_env_vars } ARTIFACT_STORAGE_PATH= { artifacts_path } BROWSER_TYPE=chromium-headless "
return " wsl.exe " , [ " bash " , " -c " , f " { env_vars } { path_to_env } { path_to_server } " ]
if host_system in [ " linux " , " darwin " ] :
env_vars = f " { base_env_vars } ARTIFACT_STORAGE_PATH= { artifacts_path } "
return path_to_env , [ path_to_server ]
raise Exception ( f " Unsupported host system: { host_system } " )
def is_claude_desktop_installed ( host_system : str ) - > bool :
""" Check if Claude Desktop is installed by looking for its config directory. """
try :
config_path = os . path . dirname ( get_claude_config_path ( host_system ) )
return os . path . exists ( config_path )
except Exception :
return False
def get_cursor_config_path ( host_system : str ) - > str :
""" Get the Cursor config file path for the current OS. """
if host_system == " wsl " :
roaming_path = get_windows_appdata_roaming ( )
if roaming_path is None :
raise RuntimeError ( " Could not locate Windows AppData \\ Roaming path from WSL " )
return os . path . join ( str ( roaming_path ) , " .cursor " , " mcp.json " )
# For both darwin and linux, use ~/.cursor/mcp.json
return os . path . expanduser ( " ~/.cursor/mcp.json " )
def is_cursor_installed ( host_system : str ) - > bool :
""" Check if Cursor is installed by looking for its config directory. """
try :
config_dir = os . path . expanduser ( " ~/.cursor " )
return os . path . exists ( config_dir )
except Exception :
return False
2025-04-03 00:46:57 -04:00
def is_windsurf_installed ( host_system : str ) - > bool :
""" Check if Windsurf is installed by looking for its config directory. """
2025-04-02 10:33:37 -06:00
try :
2025-04-03 00:46:57 -04:00
config_dir = os . path . expanduser ( " ~/.codeium/windsurf " )
return os . path . exists ( config_dir )
except Exception :
return False
2025-04-02 10:33:37 -06:00
2025-04-03 00:46:57 -04:00
def get_windsurf_config_path ( host_system : str ) - > str :
""" Get the Windsurf config file path for the current OS. """
return os . path . expanduser ( " ~/.codeium/windsurf/mcp_config.json " )
2025-04-02 10:33:37 -06:00
2025-04-03 00:46:57 -04:00
def setup_windsurf_config ( host_system : str , path_to_env : str ) - > bool :
""" Set up Windsurf configuration for Skyvern MCP. """
if not is_windsurf_installed ( host_system ) :
return False
2025-04-02 10:33:37 -06:00
2025-04-03 00:46:57 -04:00
load_dotenv ( " .env " )
skyvern_base_url = os . environ . get ( " SKYVERN_BASE_URL " , " " )
skyvern_api_key = os . environ . get ( " SKYVERN_API_KEY " , " " )
if not skyvern_base_url or not skyvern_api_key :
print (
" Error: SKYVERN_BASE_URL and SKYVERN_API_KEY must be set in .env file to set up Windsurf MCP. Please open {path_windsurf_config} and set the these variables manually. "
)
2025-04-02 10:33:37 -06:00
try :
2025-04-03 00:46:57 -04:00
path_windsurf_config = get_windsurf_config_path ( host_system )
os . makedirs ( os . path . dirname ( path_windsurf_config ) , exist_ok = True )
if not os . path . exists ( path_windsurf_config ) :
with open ( path_windsurf_config , " w " ) as f :
json . dump ( { " mcpServers " : { } } , f , indent = 2 )
2025-04-02 10:33:37 -06:00
2025-04-03 00:46:57 -04:00
windsurf_config : dict = { " mcpServers " : { } }
2025-04-02 10:33:37 -06:00
2025-04-03 00:46:57 -04:00
if os . path . exists ( path_windsurf_config ) :
try :
with open ( path_windsurf_config , " r " ) as f :
windsurf_config = json . load ( f )
windsurf_config [ " mcpServers " ] . pop ( " Skyvern " , None )
windsurf_config [ " mcpServers " ] [ " Skyvern " ] = {
" env " : {
" SKYVERN_BASE_URL " : skyvern_base_url ,
" SKYVERN_API_KEY " : skyvern_api_key ,
} ,
" command " : path_to_env ,
" args " : [ " -m " , " skyvern " , " run " , " mcp " ] ,
}
except json . JSONDecodeError :
print (
f " JSONDecodeError when reading Error configuring Windsurf. Please open { path_windsurf_config } and fix the json config first. "
)
return False
2025-04-02 10:33:37 -06:00
2025-04-03 00:46:57 -04:00
with open ( path_windsurf_config , " w " ) as f :
json . dump ( windsurf_config , f , indent = 2 )
except Exception as e :
print ( f " Error configuring Windsurf: { e } " )
return False
2025-04-02 10:33:37 -06:00
2025-04-03 00:46:57 -04:00
print ( f " Windsurf MCP configuration updated successfully at { path_windsurf_config } . " )
return True
2025-04-02 10:33:37 -06:00
2025-04-03 00:46:57 -04:00
def setup_mcp_config ( ) - > str :
"""
return the path to the python environment
"""
2025-04-03 13:01:42 -04:00
# Try to find Python in this order: python, python3, python3.12, python3.11, python3.10, python3.9
python_paths = [ ]
for python_cmd in [ " python " , " python3.11 " ] :
python_path = shutil . which ( python_cmd )
if python_path :
python_paths . append ( ( python_cmd , python_path ) )
if not python_paths :
print ( " Error: Could not find any Python installation. Please install Python 3.11 first. " )
path_to_env = typer . prompt (
" Enter the full path to your python 3.11 environment. For example in MacOS if you installed it using Homebrew, it would be /opt/homebrew/bin/python3.11 "
)
else :
# Show the first found Python as default
_ , default_path = python_paths [ 0 ]
path_to_env = default_path
2025-04-03 00:46:57 -04:00
return path_to_env
2025-04-02 10:33:37 -06:00
2025-04-03 00:46:57 -04:00
def setup_claude_desktop_config ( host_system : str , path_to_env : str ) - > bool :
2025-04-02 10:33:37 -06:00
""" Set up Claude Desktop configuration with given command and args. """
if not is_claude_desktop_installed ( host_system ) :
2025-04-03 04:10:03 -04:00
print ( " Claude Desktop is not installed. Please install it first. " )
2025-04-02 10:33:37 -06:00
return False
try :
path_claude_config = get_claude_config_path ( host_system )
os . makedirs ( os . path . dirname ( path_claude_config ) , exist_ok = True )
if not os . path . exists ( path_claude_config ) :
with open ( path_claude_config , " w " ) as f :
json . dump ( { " mcpServers " : { } } , f , indent = 2 )
2025-04-03 00:46:57 -04:00
# Read environment variables from .env file
load_dotenv ( " .env " )
skyvern_base_url = os . environ . get ( " SKYVERN_BASE_URL " , " " )
skyvern_api_key = os . environ . get ( " SKYVERN_API_KEY " , " " )
if not skyvern_base_url or not skyvern_api_key :
print ( " Error: SKYVERN_BASE_URL and SKYVERN_API_KEY must be set in .env file " )
2025-04-02 10:33:37 -06:00
with open ( path_claude_config , " r " ) as f :
claude_config = json . load ( f )
claude_config [ " mcpServers " ] . pop ( " Skyvern " , None )
2025-04-03 00:46:57 -04:00
claude_config [ " mcpServers " ] [ " Skyvern " ] = {
" env " : {
" SKYVERN_BASE_URL " : skyvern_base_url ,
" SKYVERN_API_KEY " : skyvern_api_key ,
} ,
" command " : path_to_env ,
" args " : [ " -m " , " skyvern " , " run " , " mcp " ] ,
}
2025-04-02 10:33:37 -06:00
with open ( path_claude_config , " w " ) as f :
json . dump ( claude_config , f , indent = 2 )
2025-04-03 00:46:57 -04:00
print ( f " Claude Desktop MCP configuration updated successfully at { path_claude_config } . " )
2025-04-02 10:33:37 -06:00
return True
except Exception as e :
print ( f " Error configuring Claude Desktop: { e } " )
return False
2025-04-03 00:46:57 -04:00
def setup_cursor_config ( host_system : str , path_to_env : str ) - > bool :
2025-04-02 10:33:37 -06:00
""" Set up Cursor configuration with given command and args. """
if not is_cursor_installed ( host_system ) :
return False
try :
path_cursor_config = get_cursor_config_path ( host_system )
os . makedirs ( os . path . dirname ( path_cursor_config ) , exist_ok = True )
2025-04-03 00:46:57 -04:00
if not os . path . exists ( path_cursor_config ) :
with open ( path_cursor_config , " w " ) as f :
json . dump ( { " mcpServers " : { } } , f , indent = 2 )
load_dotenv ( " .env " )
skyvern_base_url = os . environ . get ( " SKYVERN_BASE_URL " , " " )
skyvern_api_key = os . environ . get ( " SKYVERN_API_KEY " , " " )
if not skyvern_base_url or not skyvern_api_key :
print (
f " Error: SKYVERN_BASE_URL and SKYVERN_API_KEY must be set in .env file to set up Cursor MCP. Please open { path_cursor_config } and set the these variables manually. "
)
cursor_config : dict = { " mcpServers " : { } }
2025-04-02 10:33:37 -06:00
if os . path . exists ( path_cursor_config ) :
try :
with open ( path_cursor_config , " r " ) as f :
2025-04-03 00:46:57 -04:00
cursor_config = json . load ( f )
cursor_config [ " mcpServers " ] . pop ( " Skyvern " , None )
cursor_config [ " mcpServers " ] [ " Skyvern " ] = {
" env " : {
" SKYVERN_BASE_URL " : skyvern_base_url ,
" SKYVERN_API_KEY " : skyvern_api_key ,
} ,
" command " : path_to_env ,
" args " : [ " -m " , " skyvern " , " run " , " mcp " ] ,
}
2025-04-02 10:33:37 -06:00
except json . JSONDecodeError :
2025-04-03 00:46:57 -04:00
print (
f " JSONDecodeError when reading Error configuring Cursor. Please open { path_cursor_config } and fix the json config first. "
)
return False
2025-04-02 10:33:37 -06:00
with open ( path_cursor_config , " w " ) as f :
2025-04-03 00:46:57 -04:00
json . dump ( cursor_config , f , indent = 2 )
2025-04-02 10:33:37 -06:00
2025-04-03 00:46:57 -04:00
print ( f " Cursor MCP configuration updated successfully at { path_cursor_config } " )
2025-04-02 10:33:37 -06:00
return True
except Exception as e :
print ( f " Error configuring Cursor: { e } " )
return False
2025-04-02 13:09:28 -04:00
2025-04-03 04:10:03 -04:00
@setup_app.command ( name = " mcp " )
2025-04-03 00:46:57 -04:00
def setup_mcp ( ) - > None :
""" Configure MCP for different Skyvern deployments. """
host_system = detect_os ( )
path_to_env = setup_mcp_config ( )
# Configure both Claude Desktop and Cursor
2025-04-03 02:50:12 -04:00
claude_response = input ( " Would you like to set up MCP integration for Claude Desktop? (y/n) [y]: " ) . strip ( ) . lower ( )
if not claude_response or claude_response == " y " :
setup_claude_desktop_config ( host_system , path_to_env )
cursor_response = input ( " Would you like to set up MCP integration for Cursor? (y/n) [y]: " ) . strip ( ) . lower ( )
if not cursor_response or cursor_response == " y " :
setup_cursor_config ( host_system , path_to_env )
windsurf_response = input ( " Would you like to set up MCP integration for Windsurf? (y/n) [y]: " ) . strip ( ) . lower ( )
if not windsurf_response or windsurf_response == " y " :
setup_windsurf_config ( host_system , path_to_env )
2025-04-03 00:46:57 -04:00
2025-04-02 13:09:28 -04:00
@run_app.command ( name = " server " )
def run_server ( ) - > None :
2025-04-03 00:46:57 -04:00
load_dotenv ( )
2025-04-03 03:20:40 -04:00
load_dotenv ( " .env " )
2025-04-03 00:46:57 -04:00
from skyvern . config import settings
port = settings . PORT
2025-04-02 13:09:28 -04:00
uvicorn . run (
" skyvern.forge.api_app:app " ,
host = " 0.0.0.0 " ,
port = port ,
log_level = " info " ,
)
2025-04-03 02:50:12 -04:00
@run_app.command ( name = " ui " )
def run_ui ( ) - > None :
# FIXME: This is untested and may not work
""" Run the Skyvern UI server. """
# Check for and handle any existing process on port 8080
try :
result = subprocess . run ( " lsof -t -i :8080 " , shell = True , capture_output = True , text = True , check = False )
if result . stdout . strip ( ) :
response = input ( " Process already running on port 8080. Kill it? (y/n) [y]: " ) . strip ( ) . lower ( )
if not response or response == " y " :
subprocess . run ( " lsof -t -i :8080 | xargs kill " , shell = True , check = False )
else :
print ( " UI server not started. Process already running on port 8080. " )
return
except Exception :
pass
# Get the frontend directory path relative to this file
current_dir = Path ( __file__ ) . parent . parent . parent
frontend_dir = current_dir / " skyvern-frontend "
if not frontend_dir . exists ( ) :
print ( f " [ERROR] Skyvern Frontend directory not found at { frontend_dir } . Are you in the right repo? " )
return
if not ( frontend_dir / " .env " ) . exists ( ) :
shutil . copy ( frontend_dir / " .env.example " , frontend_dir / " .env " )
# Update VITE_SKYVERN_API_KEY in frontend .env with SKYVERN_API_KEY from main .env
main_env_path = current_dir / " .env "
if main_env_path . exists ( ) :
load_dotenv ( main_env_path )
skyvern_api_key = os . getenv ( " SKYVERN_API_KEY " )
if skyvern_api_key :
frontend_env_path = frontend_dir / " .env "
set_key ( str ( frontend_env_path ) , " VITE_SKYVERN_API_KEY " , skyvern_api_key )
else :
print ( " [ERROR] SKYVERN_API_KEY not found in .env file " )
else :
print ( " [ERROR] .env file not found " )
print ( " Successfully set up frontend .env file " )
# Change to frontend directory
os . chdir ( frontend_dir )
# Run npm install and start
try :
subprocess . run ( " npm install --silent " , shell = True , check = True )
subprocess . run ( " npm run start " , shell = True , check = True )
except subprocess . CalledProcessError as e :
print ( f " Error running UI server: { e } " )
return
2025-04-02 13:09:28 -04:00
@run_app.command ( name = " mcp " )
def run_mcp ( ) - > None :
""" Run the MCP server. """
mcp . run ( transport = " stdio " )
2025-04-03 00:46:57 -04:00
@cli_app.command ( name = " init " )
def init ( ) - > None :
run_local_str = (
input ( " Would you like to run Skyvern locally or in the cloud? (local/cloud) [cloud]: " ) . strip ( ) . lower ( )
)
run_local = run_local_str == " local " if run_local_str else False
if run_local :
setup_postgresql ( )
2025-04-18 00:09:43 +08:00
migrate_db ( )
2025-04-03 00:46:57 -04:00
api_key = asyncio . run ( _setup_local_organization ( ) )
if os . path . exists ( " .env " ) :
print ( " .env file already exists, skipping initialization. " )
redo_llm_setup = input ( " Do you want to go through LLM provider setup again (y/n)? " )
if redo_llm_setup . lower ( ) != " y " :
return
print ( " Initializing .env file... " )
setup_llm_providers ( )
# Configure browser settings
browser_type , browser_location , remote_debugging_url = setup_browser_config ( )
update_or_add_env_var ( " BROWSER_TYPE " , browser_type )
if browser_location :
update_or_add_env_var ( " CHROME_EXECUTABLE_PATH " , browser_location )
if remote_debugging_url :
update_or_add_env_var ( " BROWSER_REMOTE_DEBUGGING_URL " , remote_debugging_url )
print ( " Defaulting Skyvern Base URL to: http://localhost:8000 " )
update_or_add_env_var ( " SKYVERN_BASE_URL " , " http://localhost:8000 " )
else :
base_url = input ( " Enter Skyvern base URL (press Enter for https://api.skyvern.com): " ) . strip ( )
if not base_url :
base_url = " https://api.skyvern.com "
print ( " To get your API key: " )
print ( " 1. Create an account at https://app.skyvern.com " )
print ( " 2. Go to Settings " )
print ( " 3. Copy your API key " )
api_key = input ( " Enter your Skyvern API key: " ) . strip ( )
if not api_key :
print ( " API key is required " )
api_key = input ( " Enter your Skyvern API key: " ) . strip ( )
update_or_add_env_var ( " SKYVERN_BASE_URL " , base_url )
# Ask for email or generate UUID
analytics_id = input ( " Please enter your email for analytics (press enter to skip): " )
if not analytics_id :
analytics_id = str ( uuid . uuid4 ( ) )
update_or_add_env_var ( " ANALYTICS_ID " , analytics_id )
update_or_add_env_var ( " SKYVERN_API_KEY " , api_key )
print ( " .env file has been initialized. " )
# Ask if user wants to configure MCP server
configure_mcp = input ( " \n Would you like to configure the MCP server (y/n)? " ) . lower ( ) == " y "
if configure_mcp :
setup_mcp ( )
print ( " \n MCP server configuration completed. " )
2025-04-03 02:50:12 -04:00
if not run_local :
print ( " \n MCP configuration is complete! Your AI applications are now ready to use Skyvern Cloud. " )
if run_local :
2025-04-16 02:12:33 -04:00
print ( " \n Installing Chromium browser... " )
subprocess . run ( [ " playwright " , " install " , " chromium " ] , check = True )
print ( " Chromium installation complete. " )
2025-04-03 02:50:12 -04:00
print ( " \n To start using Skyvern, run: " )
print ( " skyvern run server " )