BuildGate

For approvers and developers

Working from an MCP client

BuildGate is also a remote MCP server. Connect an assistant or a terminal by URL, and raise a deploy request, read what is waiting on you, decide it, and work the exception queue without opening the console.

Connecting a client

Approvers are not always in a browser, and neither are deployers. The same service the console talks to is a remote MCP server, so your own tooling can do the things you would otherwise open a tab for. There is no key to paste and no client to pre-register: a client needs the URL and nothing else.

  1. Copy the server URL.

    The console carries it on the MCP card at the bottom of Settings → Integrations, along with the protocol revision this deployment serves and the scopes a client may ask for. It is this deployment's own address with /mcp on the end.

  2. Add the server to your client by URL.

    The client registers itself and opens a browser. Nothing has been granted at this point — it is asking.

  3. Sign in with Google.

    The same account you use in the console, and the same sign-in. This is the whole point of the exercise; see why this is a sign-in and not a pasted key.

  4. Pick the organization, read the scopes, allow.

    The consent screen names what the client is asking to be able to do, in terms of what it can do to production rather than in scope names.

  5. Ask it who it is.

    The whoami tool answers with the person, organization, role and scopes the connection acts with. Worth doing first, because a token is bound to exactly one organization and this is how you find out which.

One token, one organization. If you approve in two organizations, authorize the client twice — there is no tool that switches between them, and an id from one organization reads as not found in the other.

This is for your own tooling, not for a server. A connection here exists because you signed in, and it acts as you when you ask it to. A nightly job pulling deploy records, or a pipeline raising requests on its own schedule, wants the HTTP API instead: an administrator creates a token for it, and nobody has to be at a browser.

If the console's MCP card says switched off, this deployment is not serving the endpoint and whoever runs the service has to turn it on. The card names the setting.

What a client may ask for

Nine scopes, and a client asks for the ones it needs. A client that asks for nothing gets approvals:read.

ScopeWhat allowing it means
approvals:readSee the deploy requests waiting on you, and the change each one describes
approvals:createRaise a deploy request as you, naming the app, the issue and who is being asked. Nothing is deployed by this
approvals:decideApprove or reject those deploys as you. An approval lets the requester deploy to live
exceptions:readSee the compliance exceptions raised in this organization, and how each was handled
exceptions:resolveClose those exceptions as you, with a note. Nothing is deleted
advisories:readSee the plugin advisories your organization has recorded, open and withdrawn
advisories:recordRecord a plugin advisory as you. Where a ruleset runs the plugin policy check, it can hold deploys. Owners and administrators only
alerts:readSee your organization's alert rules: which events tell whom, and when each last sent something. Owners and administrators only
alerts:manageCreate alert rules as you. Owners and administrators only; changing or removing one stays in the console

A scope is a ceiling, never a grant. Holding approvals:decide does not make anybody an approver: the decision still runs through the same check the console does, which reads your role out of the database, so a deployer holding that scope approves exactly nothing. The same is true of the others: advisories:record held by a deployer records nothing.

They are separate for a reason worth knowing before you grant one. Reading what is waiting and deciding it are the difference between an assistant that can tell you about your queue and one that can clear it. Raising is a third act by a different sort of person — a deployer who may decide nothing can raise a request, and an approver who let an agent work their own queue has not thereby asked it to put new work into everybody else's. And the exception queue is different evidence altogether: a client authorized to read what is pending has not been authorized to read, or close, the record of every time the gate was gone around. Plugin advisories are a pair of their own because recording one can hold other people's deploys, and alert rules another because they decide who is told about what.

The tools

A connection is only offered the tools its scopes cover, so a read-only client sees the five it can use rather than sixteen of which eleven would refuse.

ToolScopeWhat it does
whoamiapprovals:readWhich person, organization and role this connection acts as
list_pending_approvalsapprovals:readWhat is pending and yours to decide — including requests routed to a colleague, since any approver may decide them
list_deploy_requestsapprovals:readEverything the organization has raised, filterable by status. Anyone who can approve sees the history; a deployer sees what they raised or were named on
get_deploy_requestapprovals:readOne request: the change, the rollback plan, the issue
watch_deploy_requestsapprovals:readWhat has happened since a cursor — how to wait for a decision
list_appsapprovals:createThe apps a request can be raised against, with the Bubble app id and the branches each deploy moves between
list_approversapprovals:createWho could approve a request you raise — never you
create_deploy_requestapprovals:createRaise one, as you: the app, the Linear issue, the change, the rollback plan, and the colleague being asked
approve_deploy_requestapprovals:decideApprove it, as you
reject_deploy_requestapprovals:decideReject it, with a note the requester reads
list_exceptionsexceptions:readThe exception queue, filterable by status, kind of finding and app. Only an approver may read it
resolve_exceptionexceptions:resolveClose one open finding, with the note that becomes the evidence
list_plugin_advisoriesadvisories:readThe organization's plugin advisories, open by default, filterable by plugin. Any member may read them
list_alert_rulesalerts:readThe organization's alert rules, with apps by Bubble id and people by email
create_alert_rulealerts:manageCreate one, as you: the events, the apps or every app, and the roles, people and Slack channels to tell
record_plugin_advisoryadvisories:recordRecord one, as you: the plugin, severity, what is wrong, where it was found and the version that fixes it. Withdrawing stays in the console

Raising a request from an agent

A request raised this way is the same record as one raised from the Bubble editor. Your membership and the named approver's role are checked, you still cannot name yourself, the approver is notified as usual, and the issue is looked up in your own Linear or ClickUp by its key rather than taken as written. Pass issueProvider to say which; without it, every connected tracker is asked. A deploy that ships more than one issue names the rest in additionalIssueKeys, each looked up the same way — up to ten issues in all. To ask more than one person at once, name the rest in additionalApproverEmails: each is notified and any of them may approve, and the app's rulesets still decide how many approvals complete it.

It deploys nothing. There is no tool that releases a deploy token or records a completion: the deploy itself still runs from the Bubble editor with the extension, once somebody else has approved. Raising one is a second way to ask, not a second way to approve.

Two things are worth care, because an agent will happily write both badly.

  • The change description and the rollback plan are the evidence. They are read by the person deciding and they are what an auditor sees later. "Various fixes" is not evidence — say what changed, what was checked, and how it gets undone.
  • There is no diff behind it yet. A request raised this way carries no branch comparison until the extension exports the two branches, so the approver may be reading your description and nothing else.

The tool answers with approvalUrl — the page the approver signs in to and decides on. Hand it to them, and to whoever asked you to raise it.

Waiting for a decision

Nothing here pushes. This service holds no connection open and no subscription, so an assistant follows a request by asking what has changed since the last time it asked.

That is what watch_deploy_requests is for. Call it once with no cursor to learn where the record is now; call it again with the cursor it gave you, and it answers with everything that has happened since — approved, rejected, deployed, expired, withdrawn, commented on, reviewed — and a new cursor. Pass a request id to follow one rather than everything you can see.

Nothing between two cursors is ever skipped: the cursor is a position in the audit chain, and the chain has no gaps. So this is how to wait for a decision, rather than re-reading the request in a loop — and there is no faster answer to be had, because a decision cannot arrive between two polls in a way that leaves no event behind.

Why this is a sign-in and not a pasted key

This product refuses to put an approve button in Slack, and the argument is about identity: pressing a Slack button proves control of a Slack account, and a pasted API key proves possession of a string. Neither is what an approval control asks for. A connection here exists only because you completed the same Google sign-in the console uses.

Your membership is checked on every call — one query, no cache — so removing somebody's approver rights stops their agent on the next request rather than at the next expiry, and a token outliving a membership is not something anybody has to remember to clean up. Access tokens are short-lived and refresh anyway, but that is bookkeeping; the authority behind a call is read fresh each time.

The record keeps the distinction. A decision made this way is marked as having come through an agent, with the client's name beside it, and so is a request raised this way — because a person acting through an agent and a person at a keyboard are not the same act, and an auditor reading the chain should be able to tell.

When a tool refuses

Refusals come back as tool errors carrying the service's own vocabulary, so a client is told what the console would be told rather than that something went wrong.

  • You are missing a scope. The call is refused with insufficient_scope naming the one it needed, which is what lets a client go back and ask you for it. Authorize it again.
  • self_approval_forbidden. You raised it, so you cannot decide it. That rule has no exceptions and no scope turns it off.
  • already_decided, or an expired request. Somebody got there first, or the window closed. Approval opens the gate for a single deploy and fifteen minutes; see when the gate will not open.
  • Not found, for something you know exists. A token is bound to one organization, and an id belonging to another one reads exactly as an id belonging to nobody. whoami says which organization you are in.
  • Your client cannot connect at all. This endpoint serves the current protocol revision and refuses the retired handshake, naming both the revision asked for and the one served. That is a client to update, not a setting to change here.

Resolving an exception never deletes it, an already-resolved finding cannot be resolved again, and nothing on this surface can deploy. What a client can do wrong is raise a thin request or clear a queue you meant to read — which is why the scopes are separate and why the consent screen spells each one out.