MCP tool reference
Every Showly MCP tool — scope, parameters, return shape, audit behavior.
Every tool the Showly MCP server exposes. Each entry lists the required scope, the input schema in short form, the return shape, and what gets written to the audit log.
All tools require an authenticated MCP client. Tokens are issued from Workspace settings → MCP clients. The canonical tool + scope list is [packages/shared/src/mcp-catalog.ts](https://github.com/flot/showly-platform/blob/main/packages/shared/src/mcp-catalog.ts). A catalog-parity test fails the build if this page drifts from the catalog. When you add a tool, update both files together.
list_projects
Scope: project:read
Lists projects (workspaces) the token can see.
Input: {}
Output: { ok, data: Array<{ id, name, slug, createdAt }> }
Audit: mcp.list_projects
list_sites
Scope: site:read
Lists sites in the current workspace. Pass projectId to scope the listing; a project-scoped MCP token forces this filter regardless of the parameter.
Input: { projectId?: string }
Output: { ok, data: Array<Site> }
Audit: mcp.list_sites
get_site_context
Scope: site:read
Returns the site, its detected framework, routes, referenced env var names, the latest preview URL, and the last production deployment id for a given siteId. See the Site shape in [Common types](#common-types).
Input: { siteId }
Output: { ok, data: { site, framework, routes, envReferences, latestPreviewUrl, lastProductionDeploymentId } }
Audit: mcp.get_site_context (records siteId)
create_change_plan
Scope: site:read
Produces a change plan proposal. Does _not_ modify any files. The agent typically reads the returned plan, asks the user for confirmation, then calls apply_site_patch.
Input: { siteId, request: string }
Output: { ok, data: { siteId, request, plan, nextStep } }
Audit: mcp.create_change_plan
apply_site_patch
Scope: site:write
Stages file edits for a site. The staged changeset is temporary and must be materialized by create_preview.
Input: { siteId, files: Array<{ path, content }>, message: string }
Output: { ok, data: { changesetId, siteId, fileCount, ttlSeconds, nextStep } }
Audit: mcp.apply_site_patch (records siteId + changesetId + file count)
create_preview
Scope: preview:create
Builds the patched workspace and produces a preview URL. The user owns the private-access decision: omit access for a short server-generated XXX-XXX password, pass a custom 6–128 character password, or select organization (Pro+) or organization_or_password. Generated plaintext is returned once and cannot be retrieved later. A Preview cannot be public; publish it Live when it should be visible to everyone.
Input: { changesetId?, siteId?, files?, access?: { mode, password? } }
Output: { deploymentId, previewUrl, framework?, fileCount?, access: { mode, passwordConfigured, password? } }
Audit: mcp.create_preview
create_github_preview
Scope: preview:create
Builds a private Preview from the latest commit on a site's connected GitHub branch. The installation credential stays inside Showly. Omit access to get a short server-generated XXX-XXX password returned once, pass a custom 6–128 character password, or choose organization-member access on Pro+. This tool never publishes Live. Poll get_preview_status with the returned deploymentId.
If the site has no active GitHub App repository, connect it in Showly Web first. Repository auto-build currently supports static deployment targets; a dynamic container target returns repository_build_target_unsupported before anything is queued. An unverified Showly email returns email_verification_required with a Web URL to complete verification.
set_preview_access
Scope: preview:create
Changes an existing preview's access policy without changing its URL. Saving a password mode rotates the password; pass a 6–128 character value or omit password to generate a short XXX-XXX sharing code server-side. The response shows the new plaintext password once alongside previewUrl. Every policy change invalidates previously issued preview-access cookies.
Organization modes verify the visitor's active Showly organization membership, so teammates sign in instead of sharing a password. organization_or_password keeps that internal flow while allowing an external reviewer to use a password.
Input: { deploymentId, access: { mode: "password" | "organization" | "organization_or_password", password? } }
Output: { deploymentId, previewUrl, access: { mode, passwordConfigured, password? }, policyVersion }
Audit: mcp.set_preview_access
Example custom password rotation:
{
"deploymentId": "00000000-0000-4000-8000-000000000000",
"access": { "mode": "password", "password": "ABC-123" }
}
retry_deployment
Scope: preview:create
Rebuilds a failed or canceled preview deployment. Re-supply the same files you gave create_preview — the source is not retained server-side, so files is required. Mints a new deploymentId (the failed one stays as history) in status: "building" and returns it for polling. Each retry counts against your monthly deploy quota — it is a fresh build. Returns 409 not_retryable if the deployment is still building or already ready, 404 if it isn't visible to your token, 402 if you're over quota.
Input: { deploymentId, files: [{ path, content }] }
Output: { ok, data: { deploymentId, status: "building", pollUrl, retriedFrom } }
Audit: mcp.retry_deployment
run_checks
Scope: checks:run
Runs the workspace's check matrix (lints, types, custom CI hooks) against a preview. checks is a list of { id, status } rows (lint / typecheck / build / audit-gate); summary is a one-line string such as "3 passed / 1 pending".
Input: { deploymentId }
Output: { ok, data: { deploymentId, checks: [{ id, status }], summary } }
Audit: mcp.run_checks
request_publish
Scope: publish:request
Opens an approval request for a ready preview deployment. The response includes a webApprovalUrl deep link; surface that link so the user can review the exact Preview and complete any plan-required second-person approval. Publishing does not require OTP/MFA enrollment. The user bound to the MCP token must have a verified Showly account email. If the tool returns email_verification_required, send the user to webVerificationUrl to resend and complete verification before retrying.
Input: { deploymentId, message: string }
Output: { approvalId, deploymentId, state: "pending", expiresAt, reused, webApprovalUrl }
Audit: mcp.request_publish
publish_site
Scope: publish:confirm
Publishes a ready preview deployment to production directly from the conversation — for solo workspaces and plans without approval workflows. The user bound to the MCP token must have a verified Showly account email. If the tool returns email_verification_required, send the user to webVerificationUrl, wait for them to complete verification, and then restart the two-step flow; the site is not live yet. Two-step human-confirmed: call with siteId + deploymentId (no confirmationToken) to get a summary + short-lived confirmationToken; show the user what is about to go live, then call again with the token. Step 2 returns 202 publishing. On plans with approval workflows enabled, use request_publish instead — this tool routes you there.
Step 1: { siteId, deploymentId } → { confirmationToken, summary }
Step 2: { siteId, deploymentId, confirmationToken } → { siteId, deploymentId, status: "publishing" }
Audit: mcp.publish_site
get_preview_status
Scope: preview:read
Returns the current status of a preview deployment. Optionally long-polls (default 30s, up to 60s) until the status transitions away from a known value — useful after request_publish while waiting on a reviewer. When status is failed or canceled, the result also carries errorCode, errorMessage, stage, and a short logTail explaining why the build failed; pair with retry_deployment to rebuild.
Input: { deploymentId, waitForChange?: boolean, currentStatus?: string, timeoutMs?: number }
Output: { ok, data: Deployment & { productionUrl?, errorCode?, errorMessage?, stage?, logTail? }, changed?, timedOut? }
Audit: mcp.get_preview_status
get_deployment_logs
Scope: logs:read
Returns the build log tail for a deployment (the last lineCount lines, default 200, of the captured build output). source discriminates db (real log lines), pending (deployment exists but no log captured yet — still building or no tail), or not-found (no such deployment for this token).
Input: { deploymentId, lineCount?: number }
Output: { ok, data: { deploymentId, lineCount, source, lines } }
Audit: mcp.get_deployment_logs
diagnose_deployment
Scope: logs:read
Self-diagnose one of your own deployments. Returns a single structured, AI-consumable diagnostic bundle so the agent can reason about why a build failed in one call — then fix the source and retry_deployment — rather than stitching together separate get_preview_status / get_deployment_logs reads. The bundle aggregates: the build failure (stage, errorCode, errorMessage, a logTail), related runtime errors from Sentry (correlated by commit SHA + environment + a window around the deploy, fail-soft), the deploy's ops-job status, the org's quota status, any agent-pushed clientLogs (redacted + capped), and deterministic hypotheses — likely root causes with a confidence (e.g. quota_exceeded / build_install_failed), derived by rules (not AI) as a high-quality starting point.
Tenant-isolated: you can only diagnose deployments in your own org. A deployment id that isn't visible to your token returns 404 (indistinguishable from a non-existent id — no cross-org existence leak). This is the agent-facing twin of the staff Diagnostics Center bundle; both share one backend aggregator. Calls GET /deployments/:deploymentId/diagnostics.
Input: { deploymentId }
Output: { ok, data: { deployment, failure, jobRun, quota, sentry, clientLogs, hypotheses } }
Audit: mcp.diagnose_deployment
list_templates
Scope: template:read
Lists Showly site templates available to the current token. Pair with create_site_from_template to onboard a new site without a Git repo.
Input: { framework?: string }
Output: { ok, data: Array<{ slug, displayName, description, framework, screenshots }> }
Audit: mcp.list_templates
create_site_from_template
Scopes: template:create, site:write
Materialises a new Showly-managed site from a template and builds its first private Preview. Omitted access generates a server-owned password returned exactly once with initialPreviewUrl; organization modes require Pro.
Input: { projectId, templateSlug, name, siteSlug, variables?: Record<string, unknown>, previewSlug?, access?: { mode, password? } }
Output: { ok, data: { siteId, projectId, initialVersionId, initialPreviewDeploymentId, initialPreviewUrl, access, templateSlug, createdAt } }
Audit: mcp.create_site_from_template
create_site_from_html
Scopes: site:write, preview:create
Creates a new Showly-managed site directly from plain HTML/CSS/JS files — no template, no framework, no Git repo. Pass either the files inline (index.html required; encoding: "base64" for binary assets), or a sourceBundleId for a large source you uploaded out-of-band via request_upload_url (exactly one of files / sourceBundleId). The site is created and its first preview is built in one call; poll the returned deploymentId with get_preview_status. Production stays on the publish flow.
Input: { projectId, name, slug, files?: Array<{ path, content, encoding?: "utf8" | "base64" }>, sourceBundleId?, framework?, previewSlug?, access?: { mode, password? } }
Output: { ok, data: { siteId, deploymentId, status | previewUrl, access, ... } }
Audit: mcp.create_site_from_html
request_upload_url
Scopes: site:write, preview:create
Mints a short-TTL presigned upload URL for a large site source that can't be passed inline through a tool call (an agent can't reliably emit hundreds of KB of exact bytes as a tool argument). Flow: call this, PUT a tar of your files to the returned uploadUrl (Content-Type application/x-tar) — the bytes go straight to storage, never through the model — then call create_site_from_html with the returned sourceBundleId instead of files. The URL is single-use, ~15 min TTL, and S3-backed; the server reads the tar back from its own bucket (no client-supplied URL, so no SSRF surface).
Input: {}
Output: { ok, data: { uploadUrl, sourceBundleId, contentType, expiresInSeconds } }
Audit: mcp.request_upload_url
request_download_url
Scope: site:read
The read mirror of request_upload_url. For a large existing site you want to edit but can't read back inline (get_site_files would stream hundreds of KB of path → content through the model). Pass the deploymentId (from list_deployments); you get a short-TTL presigned downloadUrl for that deployment's durable source tar. GET it off-LLM (e.g. curl -fSL "<downloadUrl>" -o source.tar), untar and edit locally, then re-deploy with create_preview passing sourceBundleId (from request_upload_url). Single-use, short TTL, S3-backed; the key is re-derived server-side from the deployment (no client URL, no SSRF). Returns source_not_retained (422) when that deployment kept no source bundle — use get_site_files for small sites.
Input: { deploymentId }
Output: { ok, data: { downloadUrl, expiresInSeconds } }
Audit: mcp.request_download_url
create_trial_site
Scopes: site:write, preview:create
Deploys a no-account trial site from plain HTML/CSS/JS files. Returns a real, private Preview URL on a ~1 hour link, a short server-generated XXX-XXX password shown once, and a claimUrl. Present the Preview URL and password together as one ready-to-share block. Only a separately published Live deployment is public.
Input: { files: Array<{ path, content, encoding?: "utf8" | "base64" }> }
Output: { ok, data: { trialId, guestToken, previewUrl, access: { mode: "password", password }, expiresAt, claimUrl } }
Audit: mcp.create_trial_site
claim_trial_site
Scopes: site:write
Claims a trial site (created by create_trial_site) into the current authenticated account, so it stops expiring and becomes a permanent site. Pass trialId + guestToken (both from the create_trial_site result). Use this when the user already has a Showly account and wants to keep a trial you just made for them — instead of sending them to the claimUrl. Fails if the trial already expired (make a fresh one) or the account's site limit is reached.
Input: { trialId: string, guestToken: string }
Output: { ok, data: { trialId, siteId, claimed: true } }
Audit: mcp.claim_trial_site
delete_preview
Scope: preview:create
Soft-deletes a preview deployment by id. Returns deletedAt. Idempotent — deleting an already-deleted preview returns 404 preview_not_found. Only previews are deletable here; production is unaffected.
Input: { deploymentId }
Output: { ok, data: { deploymentId, deletedAt } }
Audit: mcp.delete_preview
delete_site
Scope: site:delete
Soft-deletes a site and cascades to its deployments, versions, and custom domains. Two-step human-confirmed: call with siteId (no confirmationToken) to get a summary (the slug + how many deployments cascade) plus a short-lived confirmationToken; show the user, then call again with the token to delete. Recoverable only from backup.
Step 1: { siteId } → { confirmationToken, summary: { siteSlug, cascade: { deployments } } }
Step 2: { siteId, confirmationToken } → { siteId, deletedAt }
Audit: mcp.delete_site
list_site_versions
Scope: site:read
Lists a site's version history (newest first): id, source, changeSummary, author, createdAt. Pair with get_site_files (passing a versionId) to read that version's content.
Keyset-paginated. limit caps at 100 per page; to reach older versions, pass the previous response's pagination.nextCursor back as cursor. A nextCursor of null means you have reached the end of the history. The cursor is opaque and bound to one site — it pins the catalog to the instant of your first page, so versions created while you page cannot shift rows onto a page you already read.
hasMore is deprecated and mirrors pagination.nextCursor !== null; prefer pagination.
Input: { siteId, limit?, cursor? }
Output: { ok, data: {
versions: [{ id, source, changeSummary, authorUserId, createdAt }],
hasMore,
pagination: { limit, nextCursor: string | null }
} }
Audit: mcp.list_site_versions
list_deployments
Scope: site:read
Lists a site's deployments (newest first): id, target (preview / staging / production), status, url, createdAt. This is where the deploymentId comes from — use the returned id with retry_deployment, delete_preview, request_publish, or publish_site.
Input: { siteId }
Output: { ok, data: [{ id, siteId, target, status, url, createdAt }] }
Audit: mcp.list_deployments
get_site_files
Scope: site:read
Reads a site version's file tree (path → content) so you can see the current content before editing. Pass siteId + versionId (from list_site_versions). Large/manifest-backed versions return files: null plus a note.
Input: { siteId, versionId }
Output: { ok, data: { versionId, source, changeSummary, files: Record<string,string> | null, note? } }
Audit: mcp.get_site_files
diff_site_versions
Scope: site:read
Compares two versions and returns exactly what changed: per-file status (added / removed / changed) plus line-level add / remove / context. Pass siteId + versionA (the older "before") + versionB (the newer "after"), both from list_site_versions. Answers questions like "what changed between yesterday and today". Large/manifest-backed versions can't be diffed and return an error.
Input: { siteId, versionA, versionB }
Output: { ok, data: {
versionA: { id, source, changeSummary, createdAt },
versionB: { id, source, changeSummary, createdAt },
summary: { filesChanged, filesAdded, filesRemoved, linesChanged },
files: Array<{ path, status, lines: Array<{ type, text }> }>
} }
Audit: mcp.diff_site_versions
rollback_to_version
Scope: rollback:confirm
Rolls PRODUCTION back to an older version and publishes it WITHOUT a preview — the most consequential tool here. Two-step human-confirmed: call with siteId + versionId (no confirmationToken) to get a warning + summary + confirmationToken; show the user the warning, then call again with the token. Step 2 returns 202 building — Showly builds a preview of that version and auto-promotes it to production. Recoverable by rolling forward to a newer version.
Step 1: { siteId, versionId } → { confirmationToken, warning, summary: { changeSummary, versionCreatedAt, previewed: false } }
Step 2: { siteId, versionId, confirmationToken } → { siteId, versionId, deploymentId, status: "building" }
Audit: mcp.rollback_to_version
Production tools (not MCP-exposed)
Not MCP-exposed — requires Web/API approval flow.
The legacy rollback_deployment verb is intentionally not registered with the MCP server — it does not appear in tools/list and cannot be called from an MCP token; it lives on the web UI behind step-up MFA. The shared catalog (packages/shared/src/mcp-catalog.ts) marks it productionWriteable: false and kind: "production" so audit copy and docs can resolve the name without the MCP server registering it.
Production publishing is MCP-callable: publish_site (two-step confirmation, above) publishes directly, and rollback_to_version rolls production back — both kind: "confirm-publish"-style tools that never act on the first call.
Common types
The reference uses a few named shapes above. The concrete field set is shown in the end-to-end flow, which walks a full session with real JSON. Quick summaries:
| Type | Key fields |
|---|---|
Site | id, name, slug, projectId, framework (auto-detected), repositoryUrl |
Deployment | id, siteId, target (preview / staging / production), status, previewUrl?, createdAt |
plan | the create_change_plan proposal: a list of intended file edits plus a human-readable summary; not applied until apply_site_patch |
checks | per-check results from the workspace check matrix (lints, types, custom CI hooks) |
summary | a roll-up of checks (counts of passed/failed) returned by run_checks |
status is one of queued, building, ready, failed, canceled. ready is the terminal success state for both preview and production deployments. framework is auto-detected at build time and is one of astro, vite, next-export, static-html, custom, or unknown — it is not something you set on the manifest.
Versioning
Tool schemas follow semver via the MCP version field. Breaking changes ship a new tool name (apply_site_patch_v2); the old name continues to work for at least one release cycle with a deprecation note in notes.