mirror of
https://github.com/Skyvern-AI/skyvern.git
synced 2026-04-28 11:40:32 +00:00
212 lines
7.7 KiB
Python
212 lines
7.7 KiB
Python
"""Skyvern MCP folder tools — CRUD for organizing workflows."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Annotated, Any
|
|
|
|
from pydantic import Field
|
|
|
|
from skyvern.client.errors import NotFoundError
|
|
|
|
from ._common import ErrorCode, Timer, make_error, make_result
|
|
from ._session import get_skyvern
|
|
from ._validation import validate_folder_id
|
|
|
|
|
|
def _serialize_folder(folder: Any) -> dict[str, Any]:
|
|
return {
|
|
"folder_id": folder.folder_id,
|
|
"organization_id": folder.organization_id,
|
|
"title": folder.title,
|
|
"description": folder.description,
|
|
"workflow_count": folder.workflow_count,
|
|
"created_at": folder.created_at.isoformat() if folder.created_at else None,
|
|
"modified_at": folder.modified_at.isoformat() if folder.modified_at else None,
|
|
}
|
|
|
|
|
|
def _folder_not_found(action: str, folder_id: str, timer: Timer) -> dict[str, Any]:
|
|
return make_result(
|
|
action,
|
|
ok=False,
|
|
timing_ms=timer.timing_ms,
|
|
error=make_error(
|
|
ErrorCode.INVALID_INPUT,
|
|
f"Folder {folder_id!r} not found",
|
|
"Use skyvern_folder_list to find valid folder IDs.",
|
|
),
|
|
)
|
|
|
|
|
|
async def skyvern_folder_list(
|
|
page: Annotated[int, Field(description="Page number (1-based)", ge=1)] = 1,
|
|
page_size: Annotated[int, Field(description="Results per page", ge=1, le=500)] = 50,
|
|
search: Annotated[str | None, "Search folders by title or description"] = None,
|
|
) -> dict[str, Any]:
|
|
"""List available folders. Use when you need to discover folder IDs before assigning workflows."""
|
|
|
|
skyvern = get_skyvern()
|
|
|
|
with Timer() as timer:
|
|
try:
|
|
folders = await skyvern.get_folders(page=page, page_size=page_size, search=search)
|
|
timer.mark("sdk")
|
|
except Exception as e:
|
|
return make_result(
|
|
"skyvern_folder_list",
|
|
ok=False,
|
|
timing_ms=timer.timing_ms,
|
|
error=make_error(ErrorCode.API_ERROR, str(e), "Check your API key and Skyvern connection."),
|
|
)
|
|
|
|
return make_result(
|
|
"skyvern_folder_list",
|
|
data={
|
|
"folders": [_serialize_folder(folder) for folder in folders],
|
|
"page": page,
|
|
"page_size": page_size,
|
|
"count": len(folders),
|
|
# Heuristic until the API exposes pagination metadata.
|
|
"has_more": len(folders) == page_size,
|
|
},
|
|
timing_ms=timer.timing_ms,
|
|
)
|
|
|
|
|
|
async def skyvern_folder_create(
|
|
title: Annotated[str, Field(description="Folder title", min_length=1, max_length=255)],
|
|
description: Annotated[str | None, Field(description="Optional folder description")] = None,
|
|
) -> dict[str, Any]:
|
|
"""Create a new folder for organizing workflows."""
|
|
|
|
skyvern = get_skyvern()
|
|
|
|
with Timer() as timer:
|
|
try:
|
|
folder = await skyvern.create_folder(title=title, description=description)
|
|
timer.mark("sdk")
|
|
except Exception as e:
|
|
return make_result(
|
|
"skyvern_folder_create",
|
|
ok=False,
|
|
timing_ms=timer.timing_ms,
|
|
error=make_error(ErrorCode.API_ERROR, str(e), "Check the folder title and your permissions."),
|
|
)
|
|
|
|
data = _serialize_folder(folder)
|
|
data["sdk_equivalent"] = f"await skyvern.create_folder(title={title!r}, description={description!r})"
|
|
return make_result("skyvern_folder_create", data=data, timing_ms=timer.timing_ms)
|
|
|
|
|
|
async def skyvern_folder_get(
|
|
folder_id: Annotated[str, "Folder ID (starts with fld_)"],
|
|
) -> dict[str, Any]:
|
|
"""Get details for a specific folder."""
|
|
|
|
if err := validate_folder_id(folder_id, "skyvern_folder_get"):
|
|
return err
|
|
|
|
skyvern = get_skyvern()
|
|
|
|
with Timer() as timer:
|
|
try:
|
|
folder = await skyvern.get_folder(folder_id)
|
|
timer.mark("sdk")
|
|
except NotFoundError:
|
|
return _folder_not_found("skyvern_folder_get", folder_id, timer)
|
|
except Exception as e:
|
|
return make_result(
|
|
"skyvern_folder_get",
|
|
ok=False,
|
|
timing_ms=timer.timing_ms,
|
|
error=make_error(ErrorCode.API_ERROR, str(e), "Check your API key and Skyvern connection."),
|
|
)
|
|
|
|
data = _serialize_folder(folder)
|
|
data["sdk_equivalent"] = f"await skyvern.get_folder({folder_id!r})"
|
|
return make_result("skyvern_folder_get", data=data, timing_ms=timer.timing_ms)
|
|
|
|
|
|
async def skyvern_folder_update(
|
|
folder_id: Annotated[str, "Folder ID (starts with fld_)"],
|
|
title: Annotated[str | None, Field(description="New folder title", min_length=1, max_length=255)] = None,
|
|
description: Annotated[str | None, Field(description="New folder description")] = None,
|
|
) -> dict[str, Any]:
|
|
"""Update a folder's title or description."""
|
|
|
|
if err := validate_folder_id(folder_id, "skyvern_folder_update"):
|
|
return err
|
|
if title is None and description is None:
|
|
return make_result(
|
|
"skyvern_folder_update",
|
|
ok=False,
|
|
error=make_error(
|
|
ErrorCode.INVALID_INPUT,
|
|
"At least one of title or description must be provided",
|
|
"Provide title, description, or both.",
|
|
),
|
|
)
|
|
|
|
skyvern = get_skyvern()
|
|
|
|
with Timer() as timer:
|
|
try:
|
|
folder = await skyvern.update_folder(folder_id, title=title, description=description)
|
|
timer.mark("sdk")
|
|
except NotFoundError:
|
|
return _folder_not_found("skyvern_folder_update", folder_id, timer)
|
|
except Exception as e:
|
|
return make_result(
|
|
"skyvern_folder_update",
|
|
ok=False,
|
|
timing_ms=timer.timing_ms,
|
|
error=make_error(ErrorCode.API_ERROR, str(e), "Check the folder ID and updated values."),
|
|
)
|
|
|
|
data = _serialize_folder(folder)
|
|
data["sdk_equivalent"] = f"await skyvern.update_folder({folder_id!r}, title={title!r}, description={description!r})"
|
|
return make_result("skyvern_folder_update", data=data, timing_ms=timer.timing_ms)
|
|
|
|
|
|
async def skyvern_folder_delete(
|
|
folder_id: Annotated[str, "Folder ID (starts with fld_)"],
|
|
delete_workflows: Annotated[
|
|
bool,
|
|
"Also delete workflows in the folder instead of just removing their folder assignment",
|
|
] = False,
|
|
force: Annotated[bool, "Must be true to confirm deletion — prevents accidental deletes"] = False,
|
|
) -> dict[str, Any]:
|
|
"""Delete a folder. By default workflows remain and are removed from the folder."""
|
|
|
|
if err := validate_folder_id(folder_id, "skyvern_folder_delete"):
|
|
return err
|
|
if not force:
|
|
return make_result(
|
|
"skyvern_folder_delete",
|
|
ok=False,
|
|
error=make_error(
|
|
ErrorCode.INVALID_INPUT,
|
|
f"Deletion of folder {folder_id!r} requires confirmation",
|
|
"Set force=true to confirm deletion.",
|
|
),
|
|
)
|
|
|
|
skyvern = get_skyvern()
|
|
|
|
with Timer() as timer:
|
|
try:
|
|
result = await skyvern.delete_folder(folder_id, delete_workflows=delete_workflows)
|
|
timer.mark("sdk")
|
|
except NotFoundError:
|
|
return _folder_not_found("skyvern_folder_delete", folder_id, timer)
|
|
except Exception as e:
|
|
return make_result(
|
|
"skyvern_folder_delete",
|
|
ok=False,
|
|
timing_ms=timer.timing_ms,
|
|
error=make_error(ErrorCode.API_ERROR, str(e), "Check the folder ID and your permissions."),
|
|
)
|
|
|
|
data = result if isinstance(result, dict) else {}
|
|
data["sdk_equivalent"] = f"await skyvern.delete_folder({folder_id!r}, delete_workflows={delete_workflows!r})"
|
|
return make_result("skyvern_folder_delete", data=data, timing_ms=timer.timing_ms)
|