MCP tools

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:

TypeKey fields
Siteid, name, slug, projectId, framework (auto-detected), repositoryUrl
Deploymentid, siteId, target (preview / staging / production), status, previewUrl?, createdAt
planthe create_change_plan proposal: a list of intended file edits plus a human-readable summary; not applied until apply_site_patch
checksper-check results from the workspace check matrix (lints, types, custom CI hooks)
summarya 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.