From c7e62f45d3ca86a32fde8673f4b559d05baa5ce1 Mon Sep 17 00:00:00 2001 From: Shuchang Zheng Date: Wed, 2 Apr 2025 13:09:28 -0400 Subject: [PATCH] skyvern run server, skyvern run mcp (#2072) --- skyvern/__main__.py | 4 ++ skyvern/cli/commands.py | 84 +++++++++++++++++++++++++++++++++++++++-- skyvern/mcp/server.py | 30 --------------- 3 files changed, 85 insertions(+), 33 deletions(-) create mode 100644 skyvern/__main__.py delete mode 100644 skyvern/mcp/server.py diff --git a/skyvern/__main__.py b/skyvern/__main__.py new file mode 100644 index 00000000..f29020b6 --- /dev/null +++ b/skyvern/__main__.py @@ -0,0 +1,4 @@ +from skyvern.cli.commands import app + +if __name__ == "__main__": + app() diff --git a/skyvern/cli/commands.py b/skyvern/cli/commands.py index e264ca29..8b7f85e0 100644 --- a/skyvern/cli/commands.py +++ b/skyvern/cli/commands.py @@ -1,3 +1,4 @@ +import asyncio import json import os import shutil @@ -6,11 +7,35 @@ import time from typing import Optional import typer +import uvicorn from click import Choice from dotenv import load_dotenv +from mcp.server.fastmcp import FastMCP +from skyvern.agent import SkyvernAgent +from skyvern.config import settings +from skyvern.schemas.runs import RunEngine from skyvern.utils import detect_os, get_windows_appdata_roaming, migrate_db +mcp = FastMCP("Skyvern") +skyvern_agent = SkyvernAgent( + base_url=settings.SKYVERN_BASE_URL, + api_key=settings.SKYVERN_API_KEY, +) + + +@mcp.tool() +async def skyvern_run_task(prompt: str, url: str) -> str: + """Browse the internet using a browser to achieve a user goal. + + Args: + prompt: brief description of what the user wants to accomplish + url: the target website for the user goal + """ + res = await skyvern_agent.run_task(prompt=prompt, url=url, engine=RunEngine.skyvern_v1) + return res.model_dump()["output"] + + load_dotenv() app = typer.Typer() @@ -115,18 +140,54 @@ def setup_postgresql() -> None: print("Database and user created successfully.") +async def _setup_local_organization() -> str: + """ + Returns the API key for the local organization generated + """ + from skyvern.forge import app + from skyvern.forge.sdk.core import security + from skyvern.forge.sdk.db.enums import OrganizationAuthTokenType + from skyvern.forge.sdk.services.org_auth_token_service import API_KEY_LIFETIME + + organization = await app.DATABASE.get_organization_by_domain("skyvern.local") + if not organization: + organization = await app.DATABASE.create_organization( + organization_name="Skyvern-local", + domain="skyvern.local", + max_steps_per_run=10, + max_retries_per_step=3, + ) + api_key = security.create_access_token( + organization.organization_id, + expires_delta=API_KEY_LIFETIME, + ) + # generate OrganizationAutoToken + await app.DATABASE.create_org_auth_token( + organization_id=organization.organization_id, + token=api_key, + token_type=OrganizationAuthTokenType.api, + ) + org_auth_token = await app.DATABASE.get_valid_org_auth_token( + organization_id=organization.organization_id, + token_type=OrganizationAuthTokenType.api, + ) + return org_auth_token.token + + @app.command(name="init") def init( openai_api_key: str = typer.Option(..., help="The OpenAI API key"), - log_level: str = typer.Option("CRITICAL", help="The log level"), + log_level: str = typer.Option("INFO", help="The log level"), ) -> None: setup_postgresql() + api_key = asyncio.run(_setup_local_organization()) # Generate .env file with open(".env", "w") as env_file: env_file.write("ENABLE_OPENAI=true\n") env_file.write(f"OPENAI_API_KEY={openai_api_key}\n") env_file.write(f"LOG_LEVEL={log_level}\n") env_file.write("ARTIFACT_STORAGE_PATH=./artifacts\n") + env_file.write(f"SKYVERN_API_KEY={api_key}\n") print(".env file created with the parameters provided.") @@ -332,8 +393,8 @@ def get_command_config(host_system: str, command: str, target: str, env_vars: st raise Exception(f"Unsupported host system: {host_system}") -@run_app.command(name="mcp") -def run_mcp() -> None: +@run_app.command(name="setupmcp") +def setup_mcp() -> None: """Configure MCP for different Skyvern deployments.""" host_system = detect_os() @@ -432,3 +493,20 @@ def setup_cursor_config(host_system: str, command: str, target: str, env_vars: s except Exception as e: print(f"Error configuring Cursor: {e}") return False + + +@run_app.command(name="server") +def run_server() -> None: + port = int(os.environ.get("PORT", 8000)) + uvicorn.run( + "skyvern.forge.api_app:app", + host="0.0.0.0", + port=port, + log_level="info", + ) + + +@run_app.command(name="mcp") +def run_mcp() -> None: + """Run the MCP server.""" + mcp.run(transport="stdio") diff --git a/skyvern/mcp/server.py b/skyvern/mcp/server.py deleted file mode 100644 index 2eba9aa0..00000000 --- a/skyvern/mcp/server.py +++ /dev/null @@ -1,30 +0,0 @@ -import os - -from mcp.server.fastmcp import FastMCP - -from skyvern.agent import SkyvernAgent - -mcp = FastMCP("Skyvern") - -if "SKYVERN_MCP_CLOUD_URL" in os.environ and "SKYVERN_MCP_API_KEY" in os.environ: - skyvern_agent = SkyvernAgent( - base_url=os.environ.get("SKYVERN_MCP_CLOUD_URL"), api_key=os.environ.get("SKYVERN_MCP_API_KEY") - ) -else: - skyvern_agent = SkyvernAgent() - - -@mcp.tool() -async def skyvern_run_task(prompt: str, url: str) -> str: - """Browse the internet using a browser to achieve a user goal. - - Args: - prompt: brief description of what the user wants to accomplish - url: the target website for the user goal - """ - res = await skyvern_agent.run_task(prompt=prompt, url=url) - return res.model_dump()["output"] - - -if __name__ == "__main__": - mcp.run(transport="stdio")