Custom HolaApp polish (URL/OAuth) + runtime: agents-optional & single-flight authorize - #439
Merged
Merged
Conversation
Many providers (HeyGen, …) give ONLY a remote MCP URL for an "add custom connector" flow — e.g. https://mcp.heygen.com/mcp/v1/ — not a JSON blob. parseCustomMcpConfig now accepts a bare http(s) URL as shorthand for { url }; no headers ⇒ treated as OAuth, so sign-in opens on add (same as a headerless JSON config). Config field help updated to say "or just the remote server URL". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The MCP field's placeholder showed a full { mcpServers } JSON, so it read
as "JSON required" even though a bare URL works. Lead the placeholder with
a bare URL (…or a full JSON / npx mcp-remote config below), and drop
"config" from the label ("MCP server").
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The app was saved BEFORE the headerless-MCP OAuth ran, so cancelling the sign-in still left the app added (openable, MCP unauthorized). Now a headerless (OAuth) MCP app is only KEPT if sign-in succeeds: it's saved so the MCP can attach + authorize, then rolled back (deleted → detached via catalog refresh) if the user cancels or auth fails. - runCustomAppOAuth returns whether the server ended up authorized. - Cancelling the "Signing in…" state (Cancel / X / Esc / backdrop) aborts the add cleanly and rolls the pending app back. - Static-token / no-MCP apps add immediately, as before. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cheme The app URL and the MCP field both required an explicit http(s):// scheme. Now a scheme-less URL (heygen.com, mcp.heygen.com/mcp/v1/) is accepted and normalized to https://: - normalizeRemoteUrl() prepends https:// when no scheme is present. - bareUrlOrNull() accepts a scheme-less bare token only if it has a dotted host, so a stray word isn't misread as a URL; the app URL and JSON-config urls are normalized too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
After a cancelled sign-in the form stays pre-filled, but each retry minted a NEW app id — churning the MCP attach/detach and standing up a fresh server every time, so the retry's authorize raced/failed and showed the cancelled error again. Now the pending app is a single DRAFT reused across retries (same id → same attached server, no attach/detach churn). It's committed on success and discarded ONLY when the dialog is actually closed (Cancel / X / Esc / backdrop) — cancel = don't add. Stops deleting + re-adding the app on each cancel, which was the source of the churn. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The custom-app add showed "Sign-in was cancelled" for every failure mode (actual cancel, authorize error, server-never-registered), hiding what went wrong. runCustomAppOAuth now returns the runtime's authorize `detail` (and a distinct "didn't register in time" for a poll timeout), and the dialog shows it: "Sign-in didn't complete: <reason>." Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ent)
A workspace.yaml with no top-level `agents` block failed every run with
"missing object field 'agents'". This kept recurring: the desktop MCP
registry rewrites workspace.yaml, and a General/onboarding workspace may
never have had an `agents` block to begin with.
loadGeneralConfig now defaults a TRULY-ABSENT `agents` (undefined/null) to
the built-in single agent (holaboss / gpt-5.4 — the session picker
overrides the model). A PRESENT-but-malformed `agents` (a list, or the
legacy {general} shape) is still rejected, so both existing tests stand;
added a test for the default path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…NUSE The OAuth callback binds a FIXED loopback port (51703). A cancelled/ abandoned sign-in leaves its authorize child running — it waits out the full ~210s consent timeout still holding the port — so the retry's child can't bind it and fails immediately with "listen EADDRINUSE 127.0.0.1:51703". authorizeMcpServerViaHost now tracks the in-flight child and SIGKILLs it (awaiting its exit so the OS frees the port) before spawning the next one. Only one authorize runs at a time, which matches the fixed-port design. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…etry When single-flight SIGKILLs an in-flight authorize child to free the fixed callback port for a newer attempt, its close now reports "superseded by a newer sign-in attempt" instead of the cryptic "produced no result". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A headerless-MCP app is saved as a DRAFT (pending) during sign-in — kept for retry, committed on success, discarded on close. But a session killed mid-add leaked the draft into the sidebar/grid. Mark drafts `pending`, drop the flag on a successful commit, and purge any still-pending drafts once at startup (first catalog load), before the app-owned sync re-attaches their MCPs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up polish on the "Create your own HolaApp" feature, plus two runtime fixes surfaced while testing it (an OAuth-MCP custom app against Heygen/Linear).
Desktop — custom-app UX
https://mcp.heygen.com/mcp/v1/(or any provider's "add custom connector" URL); no JSON blob required. The field placeholder/label now lead with the URL.https://— scheme-less URLs (heygen.com,mcp.heygen.com/mcp/v1/) are accepted and normalized (with a dotted-host guard so a stray word isn't mistaken for a URL). Applies to both the app URL and the MCP field.pendingdraft so the MCP can attach + authorize, then rolled back if you cancel or close the dialog.detail(e.g.EADDRINUSE,superseded by a newer sign-in attempt) instead of a generic "cancelled".pendingdrafts are swept once at startup.Runtime
agentsis optional (workspace-runtime-plan.ts) — aworkspace.yamlwith no top-levelagentsblock now defaults to the built-in single agent (holaboss/gpt-5.4, overridden per-session by the picker) instead of failing every run withmissing object field 'agents'. A present-but-malformedagentsis still rejected (existing tests stand; added a default-path test).mcp-authorize-host.ts) — the OAuth callback binds a FIXED loopback port, so a cancelled/abandoned sign-in (which waits out its full consent timeout) held the port and the retry died withEADDRINUSE 127.0.0.1:51703. The api-server now SIGKILLs any in-flight authorize child (awaiting its exit so the OS frees the port) before spawning a new one, and reports "superseded" for the one it killed.Testing
turbo run typecheckgreen forholaboss-localand@holaboss/runtime-api-server.workspace-runtime-plan.test.ts: 20/20 pass (added a test for the default-agent path).main.tsintentionally not touched here).🤖 Generated with Claude Code