MCP server
Wire any MCP-compatible client to VibeHost. The MCP server is the path for coding agents (Claude Code, Codex, Cursor, Antigravity, Grok Build) and chat clients (ChatGPT, Claude Desktop, Gemini Spark) that don't have a native CLI tool channel.
VibeHost ships a remote Model Context Protocol server at https://api.vibehost.com/mcp. Any MCP-compatible client can connect and use tools for the whole platform.
Where MCP fits
VibeHost has two integration channels. Which one fits depends on what is driving the work.
- Coding agents run in your editor or terminal. They have native MCP support and can also shell out to the
vibehostCLI when a script gives better control than tool calls. Ten of them have a first-class integration, each with its own guide:- Claude Code, Anthropic's terminal agent
- Codex CLI, OpenAI's terminal agent
- Cursor, the most-adopted IDE
- Antigravity, Google's agentic IDE
- Antigravity CLI, Google's open-source terminal agent
- Windsurf, an agentic IDE from Cognition, the makers of Devin
- OpenCode, SST's open-source terminal agent
- GitHub Copilot CLI, GitHub's terminal agent
- Hermes Agent, Nous Research's self-improving terminal agent
- Grok Build, xAI's terminal agent
- Chat clients (ChatGPT, Claude Desktop, Gemini Spark) don't have a CLI channel. They reach VibeHost only through MCP, and setup is one URL. See Chat clients below.
The rest of this page covers the MCP server itself: tool list, OAuth, error codes. Setup steps are not repeated here. Each client's guide is the one place they are written down.
What your agent can do
There is one tool per action, grouped by area below. The Tools → CLI cheatsheet below lists every tool with its read / write / destructive marking. Tools that change publicly-visible internet state carry openWorldHint: true so the client can prompt for confirmation.
Workspaces & apps
list_workspaces, list_apps, create_app, get_app, delete_app.
Deployments
deploy, get_deployment, get_logs, get_screenshot, promote,
rollback, request_deployment_download, plus check_blobs_missing and
request_upload for getting file bytes to the server before a deploy.
Channels
list_channels, create_channel.Custom domains
add_custom_domain, list_custom_domains, verify_custom_domain,
remove_custom_domain.
Redirect rules
list_redirect_rules, add_redirect_rule, remove_redirect_rule,
bulk_import_redirect_rules.
App access
list_app_grants, grant_app_email_access, grant_app_team_access,
revoke_app_email_access, revoke_app_team_access, set_app_visibility,
set_app_password, clear_app_password, get_app_password_info,
create_share_link, list_share_links, revoke_share_link.
Folders
move_app_to_folder, set_folder_visibility, list_folder_grants,
grant_folder_email_access, update_folder_email_grant,
remove_folder_email_grant, grant_folder_team_access,
update_folder_team_grant, remove_folder_team_grant.
Members & invitations
list_workspace_members, remove_workspace_member, invite_to_workspace,
list_workspace_invitations, revoke_workspace_invitation, list_teams,
list_team_members, remove_team_member, invite_to_team,
list_team_invitations, revoke_team_invitation.
Server-side files (no-egress clients)
create_file, edit_file, read_file, list_files, delete_file. They
write a deploy's files into a server-side draft tree when your client can't
upload blobs directly. See Choosing an upload path.
Choosing an upload path
A deploy needs your files on the server. There are two ways to get them there. The right one depends on whether your client's environment can make outbound HTTPS requests directly to api.vibehost.com, not on whether it has a local filesystem.
Both procedures were run end to end against the live API, and the field names below are the ones those calls returned. Every tool answers { ok: true, data } or { ok: false, error: { code, message } }; the fields named here sit inside data.
Manifest path, for clients that can reach the API
File bytes go straight to api.vibehost.com over short-lived signed URLs, outside the MCP channel. It carries any file type, images and fonts included, and the server keeps one copy of each distinct file. Use it whenever you can reach the API.
create_app(workspace, name)returnsid. Every later call takes it asappId. The app starts withvisibility: "private".- Hash each file with SHA-256 and build the manifest: one
{ path, sha256, size }per file. check_blobs_missing(shas)returnsmissing, the hashes the server does not hold yet. Blobs are shared across apps, so a file that any earlier deploy uploaded is not listed.- When
missingis not empty,request_upload(appId, shas: missing)returnsuploads, one{ sha, uploadUrl, expiresAt }per hash, together withmethod: "PUT"andcontentType: "application/octet-stream". The URLs expire after five minutes. It refuses an emptyshas, so skip this step and the next when nothing is missing. - PUT each file's raw bytes to its
uploadUrlwith that content type. This is a plain HTTP request, not a tool call. The response is an ordinary HTTP one with the same envelope:201and{ ok: true, data: { sha256, size, wasNew: true } }, or200withwasNew: falsewhen the server already held those bytes. Both mean the blob is there. Bytes that do not hash to theshain the URL are refused with400andVALIDATION_FAILED. check_blobs_missing(shas)again.missingis now[].deploy(appId, manifest)returnsid,statusandurl. A manifest entry whose blob was never uploaded is refused withVALIDATION_FAILED.- Read
status: it is one ofstarting,healthy,failedorsuperseded. A static deploy came back alreadyhealthy, withfinishedAtset. Only while it isstarting, callget_deployment(deploymentId)with theidfrom step 7 every few seconds.healthymeans it is live,failedcomes witherror, andsupersededmeans a newer deploy to the same channel replaced this one. Stop polling on any of the three. status: "healthy"is the confirmation. Fetchurlas well only when the app is public and has no password: the app from step 1 isprivate, and an anonymous request to a private app is not answered with the site, which says nothing about the deploy. When you do fetch it, every file other than HTML is served byte for byte. An HTML page can have VibeHost's badge markup added before</body>, as it was on the free workspace used for the run, so compare an HTML page by content rather than by checksum.
Redeploying. Repeat from step 2. The manifest describes the whole release, not a change to the last one: a file left out of it answers 404 afterwards. Only the bytes are incremental. With one file of five changed, missing listed one hash, and that was the only PUT. url does not change, and it can keep serving the previous release for a while after deploy returns healthy: 52 seconds when measured. A first deploy is served at once.
Draft path, for clients with no HTTP egress
Some clients can't make that PUT: a sandbox with no network egress, or a chat client like ChatGPT whose only channel to VibeHost is the tool call itself. They write the files into a server-side draft tree instead, and every step is a tool call.
create_app(workspace, name)returnsid, as above.create_file(appId, path, content)for each file returns{ path, sha256, size }.pathis POSIX-relative, such asindex.htmlorassets/app.js; an absolute path or a..segment is refused withVALIDATION_FAILED. Calling it again on the samepathoverwrites the file.list_files(appId)returnsfiles, each{ path, size, mode, updatedAt }, andread_file(appId, path)returnscontent, to check what is there.delete_file(appId, path)removes a file, and answersDRAFT_FILE_NOT_FOUNDwhen there is none.deploy(appId)with nomanifest. The server builds the manifest from the draft tree and returns the sameid,statusandurl. With an empty draft tree it answersVALIDATION_FAILED.- Steps 8 and 9 of the manifest path.
Redeploying. edit_file(appId, path, oldString, newString) replaces an exact string, then deploy(appId) again. It answers EDIT_NO_MATCH when oldString is absent and EDIT_NOT_UNIQUE when it occurs more than once, unless you pass replaceAll: true. The draft tree persists between deploys, so nothing else is sent again. That cuts both ways: a file your new build no longer produces stays in the tree, and keeps being deployed, until you delete_file it. The same delay before url serves the new release applies. Writing a draft needs the same deployer grant as deploying.
Text files only. content is a string, stored as its UTF-8 encoding. There is no binary or base64 input, and nothing rejects binary data either. A PNG and a WOFF2 passed as strings were accepted, deployed healthy, and served with content-type: image/png and font/woff2, but with the wrong bytes: whatever string arrives is what gets served. Sent one character per byte, a 379-byte PNG came back as 528 bytes beginning c2 89 50 4e 47 where the file begins 89 50 4e 47. Sent as base64, it came back as the base64 text. No response flags either case, so a site that needs images or fonts has to go through the manifest path.
Size. A draft file cannot exceed 1,048,576 bytes: create_file with more content, or an edit_file that would grow a file past that, answers CONTENT_TOO_LARGE and leaves the file as it was. The request carrying a tool call may be up to 6,356,992 bytes (six times the file cap plus 64 KiB), so JSON escaping, which turns a newline, quote or backslash into two bytes and a control character into six, cannot push a create_file under the file cap over it. edit_file sends two strings, and together they can: if both oldString and newString are near 1 MB, edit a smaller region per call. A request past that is refused before the tool runs with HTTP 413 and PAYLOAD_TOO_LARGE, whose details.limitBytes is the limit. A file too big for the cap has to go through the manifest path.
The decision is network reachability, not filesystem: an agent that has files on disk but no egress to
api.vibehost.comstill belongs on the draft path.
Endpoints
| What | URL |
|---|---|
| MCP server | https://api.vibehost.com/mcp |
| OAuth 2.1 authz server metadata (RFC 8414) | https://api.vibehost.com/.well-known/oauth-authorization-server |
| Protected resource metadata (RFC 9728) | https://api.vibehost.com/.well-known/oauth-protected-resource/mcp |
| Dynamic client registration (RFC 7591) | https://api.vibehost.com/api/v1/oauth/register |
Transport: Streamable HTTP (the MCP spec's recommended transport since 2025-06).
Authentication
OAuth 2.1 authorization-code flow with mandatory PKCE (S256). The first time your agent connects:
- Agent calls
/.well-known/oauth-protected-resource/mcpto discover the authz server. - Agent registers itself dynamically at
/api/v1/oauth/register(no client secret needed for public clients). - Agent kicks off the auth code flow with PKCE.
- You approve in browser; agent gets an access token.
- Access token is 60 minutes, refresh token is 30 days with single-use rotation and reuse detection.
Token audience is bound to https://api.vibehost.com/mcp via RFC 8707, so a token minted for VibeHost won't be accepted by another resource server.
Coding agents (recommended)
These have native MCP, run in your editor / terminal, and can also call the vibehost CLI directly when a script gives better control than tool calls. Setup for each one lives in its own guide.
Claude Code
One CLI command, or the vibehost-deploy skill for scripted deploys.
Codex CLI
Two CLI commands, or an entry in its config file.
Cursor
An install button, or paste a server entry in Settings.
Antigravity IDE
An entry in its MCP config file, then authenticate inside the IDE.
Antigravity CLI
Shares the IDE's MCP config file; authenticate from the CLI.
Windsurf
Add a custom MCP server from the Cascade panel, then authenticate.
OpenCode
An entry in its config file.
GitHub Copilot CLI
An entry in its MCP config file.
Hermes Agent
One CLI command that runs OAuth and writes the config for you.
Grok Build
An entry in .mcp.json, with a local bridge if your version needs one.
Chat clients (MCP-only)
These don't have a CLI channel, so MCP is the only way to drive VibeHost from them. Setup is one URL each, walked through in each client's guide.
ChatGPT
Needs a paid plan: add VibeHost as an app in Developer mode.
Claude Desktop
Add VibeHost as a custom connector. claude_desktop_config.json skips remote servers, so don't add it there.
Gemini Spark
Add VibeHost as a custom app under Connected Apps. Needs a personal Google Account with Keep Activity on.
Other MCP-compatible clients
Point Continue, Cline, Roo Code, Devin, Zed, Aider, Amazon Q, VS Code (Copilot Chat), JetBrains AI Assistant, Raycast, Warp, OpenClaw (chat-app gateway), or anything else that speaks MCP at https://api.vibehost.com/mcp. Native custom-scheme redirects (claude-desktop:, cursor:, vscode:, windsurf:, raycast:, gemini:, etc.) are pre-allowlisted, so OAuth completes without extra config.
Permissions
Tool calls run through the same per-app permissions the dashboard uses. The OAuth token represents you; the agent can only do what you can do.
viewergrant → read-only tools workdeployergrant → also deploy, promote, rollbackadmingrant → also manage settings + grants
Tools that change public state (deploy, add_custom_domain, set_app_password, etc.) carry openWorldHint: true so well-behaved clients prompt you before calling.
Privacy
The MCP server adds no extra data store. Every tool call:
- Authenticates against the same user/token records as the dashboard.
- Audit-logs the same way (operation, actor, resource, outcome).
- Hashes OAuth refresh tokens at rest.
Tools that reach beyond VibeHost (verify_custom_domain queries public DNS resolvers) declare openWorldHint: true. The invitation tools (invite_to_workspace, invite_to_team) return an invite URL for you to forward; the MCP server does not email it.
The same VibeHost privacy policy applies.
Tools → CLI cheatsheet
Every registered MCP tool, with the vibehost CLI command that does the same job. Useful for "what would this prompt actually do?". The mapping is not 1:1 in either direction: the draft-file tools have no CLI counterpart, and some CLI commands (vibehost env, vibehost channel delete, vibehost app update) have no MCP tool yet.
| MCP tool | CLI equivalent | Read/Write |
|---|---|---|
list_workspaces | vibehost workspace ls | read |
list_apps | vibehost app ls | read |
create_app | vibehost app create <name> | write |
get_app | vibehost app inspect | read |
delete_app | vibehost app delete --force | destructive, open-world |
check_blobs_missing | (part of vibehost deploy) | read |
request_upload | (part of vibehost deploy) | write |
deploy | vibehost deploy | write, open-world |
get_deployment | (none, vibehost deploy waits for the result) | read |
get_logs | vibehost logs | read |
get_screenshot | (none) | read |
promote | vibehost promote <deploymentId> | write, open-world |
rollback | vibehost rollback | write, open-world |
request_deployment_download | vibehost pull | read |
list_channels | vibehost channel list | read |
create_channel | (implicit via vibehost deploy --channel <name>) | write |
create_file | (none, the CLI uploads from disk) | write |
edit_file | (none, the CLI uploads from disk) | write |
read_file | (none, the CLI uploads from disk) | read |
list_files | (none, the CLI uploads from disk) | read |
delete_file | (none, the CLI uploads from disk) | destructive |
add_custom_domain | vibehost domain add <hostname> | write, open-world |
list_custom_domains | vibehost domain list | read |
verify_custom_domain | vibehost domain verify <hostname> | write, open-world |
remove_custom_domain | vibehost domain remove <hostname> | destructive, open-world |
list_redirect_rules | vibehost redirects list | read |
add_redirect_rule | vibehost redirects add <source> <destination> | write, open-world |
remove_redirect_rule | vibehost redirects remove <ruleId> | destructive, open-world |
bulk_import_redirect_rules | vibehost redirects upload <file> | destructive, open-world |
list_app_grants | vibehost app grants ls | read |
grant_app_email_access | vibehost app grants add-email <email> <role> | write |
revoke_app_email_access | vibehost app grants remove-email <email> | destructive |
grant_app_team_access | vibehost app grants add-team <team> <role> | write |
revoke_app_team_access | vibehost app grants remove-team <team> | destructive |
set_app_visibility | vibehost app visibility <value> | write, open-world |
set_app_password | vibehost app password set <password> | write, open-world |
clear_app_password | vibehost app password clear | destructive, open-world |
get_app_password_info | vibehost app password status | read |
create_share_link | vibehost app share-link create | write, open-world |
list_share_links | vibehost app share-link ls | read |
revoke_share_link | vibehost app share-link revoke <id> | destructive, open-world |
move_app_to_folder | vibehost folder move-app <app> <folder> | write, open-world |
set_folder_visibility | vibehost folder visibility <folder> <value> | write |
list_folder_grants | vibehost folder grants ls <folder> | read |
grant_folder_email_access | vibehost folder grants add-email <folder> <email> <role> | write |
update_folder_email_grant | (none) | write |
remove_folder_email_grant | vibehost folder grants remove-email <folder> <email> | destructive |
grant_folder_team_access | vibehost folder grants add-team <folder> <team> <role> | write |
update_folder_team_grant | (none) | write |
remove_folder_team_grant | vibehost folder grants remove-team <folder> <team> | destructive |
list_workspace_members | vibehost workspace members | read |
remove_workspace_member | vibehost workspace remove <email> | destructive |
invite_to_workspace | vibehost workspace invite <email> | write, open-world |
list_workspace_invitations | vibehost workspace invitations | read |
revoke_workspace_invitation | vibehost workspace uninvite <id> | destructive |
list_teams | vibehost team ls | read |
list_team_members | vibehost team members | read |
remove_team_member | vibehost team remove <email> | destructive |
invite_to_team | vibehost team invite <email> | write, open-world |
list_team_invitations | vibehost team invitations | read |
revoke_team_invitation | vibehost team uninvite <idOrEmail> | destructive |
Example prompts
Once connected, you can talk to your agent in natural language. The agent picks the right tool(s).
| What you say | What runs |
|---|---|
"Deploy ./dist to a new app called my-blog" | create_app, request_upload + a PUT per file, then deploy |
| "What apps do I have?" | list_apps |
| "Tell me about my-site" | get_app (full inspect: runtime, visibility, channels, grants) |
| "Make my-site public" | set_app_visibility(visibility: public) |
| "Add reviewer@acme.com as a viewer on my-site" | grant_app_email_access(role: viewer) |
| "Roll back my-site" | rollback (production by default) |
| "Add www.example.com to my-site" | add_custom_domain + prints CNAME instructions |
| "Verify www.example.com" | verify_custom_domain |
| "Mint a share link for my-site valid 7 days" | create_share_link(expiresInDays: 7) |
| "Show me the latest logs for my-app" | get_logs |
| "Set DATABASE_URL on my-app to ..." | No MCP tool yet. The agent runs vibehost env set if it has a shell |
For destructive operations (delete_app, remove_custom_domain, revoke_share_link, ...), well-behaved clients prompt you to confirm before calling. The destructiveHint: true annotation is part of the tool definition.
Confirmation prompts
Tools annotated openWorldHint: true change publicly-visible state and should trigger a confirmation in your MCP client. Whether they do depends on the client:
- Claude Desktop and Claude Code follow your tool permission settings. A tool you set to "always allow" runs without asking.
- Cursor prompts on first use and remembers your answer per tool.
- ChatGPT connectors prompt on the first call, and the user can mark them as "trusted".
- Codex and other programmatic clients depend on the host. Some skip confirmation by design, on the assumption that the agent may act on its own.
If you're scripting against MCP and don't want a human in the loop, use a PAT against the REST API instead. Tools are just convenience wrappers around the same endpoints.
Common errors
| Error | What it means | Fix |
|---|---|---|
Bearer token required | Token expired or never auth'd | Restart MCP client to re-trigger OAuth |
Tool '<name>' not found | Old MCP client schema | Update the MCP client |
FORBIDDEN on a write tool | OAuth user lacks role on the target app | Ask an admin to grant deployer (or higher) |
VALIDATION_FAILED from deploy | No manifest was passed and the app's draft tree is empty | Pass a manifest, or write files with create_file first |
See also
- For a programmatic alternative without OAuth, see Personal access tokens. A PAT drives the same REST API over HTTP.