Skip to content

feat(server): connect a GCP project from the conversation - #37

Open
JeroenSoeters wants to merge 10 commits into
mcp-connectfrom
mcp-gcp-connect
Open

feat(server): connect a GCP project from the conversation#37
JeroenSoeters wants to merge 10 commits into
mcp-connectfrom
mcp-gcp-connect

Conversation

@JeroenSoeters

Copy link
Copy Markdown
Contributor

Summary

The skill could ask which cloud and then dead-end on GCP. It no longer does.

GCP gets one tool where AWS has three, and that asymmetry is the cloud's rather than an inconsistency here. The AWS trio is link-then-register because CloudFormation's quick-create URL lets the operator apply a stack in their own console and come back with a role. GCP retired its only console-deployable template service, so there is no link to hand anyone: formae provisions with the operator's own credentials, and provisioning and registering are a single call. Splitting that into two tools would invent a step that does not exist.

workload_identity_provider is optional but it is not a second mode a caller picks freely — supplying it means the operator stood the federation up themselves (Terraform, say, because they will not give a CLI provisioning rights), which is the only case where formae has nothing to provision. Its description states what that path does not check, so "registered" cannot be read as "working".

Details worth flagging

  • registeredDoc carries whichever coordinate its cloud uses. GCP gets its own renderer rather than reusing the AWS one: renderRegistered prints a Role: line and a GCP connection has no role, so reuse would invite the reader to believe in a value that never existed. A test mutating it back to renderRegistered goes red.
  • The tool carries DestructiveHint, like provision_cloud_role: the mutation happens immediately, with no console step and nothing for the user to apply.
  • The skill now says what is being granted — editor plus the ability to manage the project's IAM — before the call, and warns that a browser may open for gcloud auth application-default login.
  • The skill's target step gains the GCP files: a gcp.Config with OidcAuth in place of the aws.Config, discoverable set explicitly, and the coordinate taken from the registration rather than the user's keyboard.
  • The remaining "not built yet" branches are Azure only, which is correct.

Base

Targets mcp-connect rather than main, because that is where the connect tools live. Rebase onto main if PLA-700 lands first.

Depends on formae PR #685 for connect gcp and its GCP registration document.

The skill could ask which cloud and then dead-end on GCP. It no longer does.

GCP gets one tool where AWS has three, and that asymmetry is the cloud's
rather than an inconsistency here. The AWS trio is link-then-register because
CloudFormation's quick-create URL lets the operator apply a stack in their own
console and come back with a role. GCP retired its only console-deployable
template service, so there is no link to hand anyone: formae provisions with
the operator's own credentials, and provisioning and registering are a single
call. Splitting that into two tools would invent a step that does not exist.

The optional workload_identity_provider is not a second mode a caller picks
freely. Supplying it means the operator stood the federation up themselves,
which is the only case where formae has nothing to provision, and the
description says what that path does not check so "registered" cannot be read
as "working".

registeredDoc carries whichever coordinate its cloud uses, and GCP gets its
own renderer rather than reusing the AWS one: renderRegistered prints a role
line, and a GCP connection has no role. Printing an empty one would invite the
reader to believe in a value that never existed.

The tool carries DestructiveHint for the same reason provision_cloud_role
does: the mutation happens immediately, with no console step and nothing for
the user to apply themselves. The skill says what is about to be granted, in
those words, before the call rather than after.
The CLI grew four GCP failure codes and this map did not, so a run with no
Google credentials reported "formae connect failed (credentials_required)":
a refusal with no remedy, when the remedy is one command the producer already
names in the failure's details.

Found by running the flow rather than by reading it, which is why the test
added here checks the codes carry a description at all and that the one whose
remedy is a command actually names it.
The operator is in front of this server: it runs on their machine, started by
their own agent session. Machine output says how results are rendered, not
whether a person is present, so without saying so explicitly the sign-in was
unreachable from here and every operator was handed a command to run by hand.

--no-input still applies and still means what it says: no terminal prompt this
server would have to answer.
The description stopped at "the user must install it", which invites the
agent to tell them to log in as well. There is one more step and it is calling
this tool again: the sign-in happens then.
@JeroenSoeters
JeroenSoeters marked this pull request as ready for review August 28, 2026 22:15
…ject home

Cursor 2.4 reads the same ~/.agents/skills location Codex does, so one
symlink serves both and the guide is the Codex one with Cursor's own MCP
registration: ~/.cursor/mcp.json, since Cursor has no CLI for it. The guide
states the one thing that does not port - several skills cross-reference
each other as /formae:<name>, which is Claude Code syntax and means nothing
elsewhere - rather than leaving it to be discovered.

The connect flow proposed the current directory as the new project's home
unconditionally, on the reasoning that git init, npm init and cargo init do
the same. They do not: the user cd'd there and typed the command, so they
chose it. We are proposing, and a harness that starts in / then has us
propose scattering three files into the filesystem root. Seen for real in a
container, where Claude Code starts in /.

Now: the current directory unless it is / or the home directory itself, and
otherwise ~/<label>, reusing the target label rather than asking a second
naming question.
…find

Both came out of driving the server over stdio the way Cursor launches it,
under a deliberately narrow PATH.

formae itself was a prerequisite the guide listed and never explained. The
Claude Code plugin provisions formae and the oidc auth plugin behind the
scenes; go install provides neither, so a reader who only ever used the
plugin has no reason to know they are missing. A hosted sign-in without
oidc cannot succeed.

FORMAE_BIN is the one that misleads. A formae the server cannot reach is
not caught at startup: it registers, lists all 43 tools and looks healthy,
then every call fails with exec: "formae": executable file not found in
$PATH, because resolution is lazy and per call. The guide warned about a
narrow PATH for the server binary, where the failure is obvious, and said
nothing about the one where it is not.

Also notes that go install honours GOBIN before GOPATH/bin, which is where
the binary actually lands under a version-managed Go.
…nted

The headless case was a dead end, and the last mile was here rather than
in the CLI.

Where a browser cannot be opened, gcloud prints a sign-in URL and waits for
a verification code on a stdin that is /dev/null under this server. The run
fails having already printed the one thing that would let the operator
finish, the CLI now carries it in the failure details - and this build
decoded schemaVersion, code and message only, so the details never crossed
and the caller got a canned line telling them to run a command that fails
the same way.

Details are now relayed by an allowlist, per code and per key. Not a
passthrough: the producer may attach anything to any failure, and a blanket
relay would make every future detail a disclosure decision nobody took.
The producer's message stays withheld exactly as before, for the reason
already recorded - it is built from plugin error strings and a Pkl failure
quotes profile source, which for a classic profile can hold an inline
password. A named key from a named code is a different thing.

Three tests: the URL reaches the caller while the message does not, an
unlisted code relays nothing, and a failure with no details reads as it
always did.
…t cannot work

The remedy named for credentials_required was "run gcloud auth
application-default login and try again", which is right where a browser
can be opened and useless where one cannot: gcloud then falls back to
printing a URL and reading a verification code typed back, so running it
the same way fails identically.

Says where to run it, and why. The relayed transcript underneath already
showed the EOF, but the instruction above it still read as "try harder".
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant