Skip to content

feat: accept the HTTPS /mcp/<uuid> connector URL in --remote (#145) - #146

Closed
dzianisv wants to merge 2 commits into
fix/135-relay-lifecyclefrom
feat/cli-http-mcp-remote
Closed

feat: accept the HTTPS /mcp/<uuid> connector URL in --remote (#145)#146
dzianisv wants to merge 2 commits into
fix/135-relay-lifecyclefrom
feat/cli-http-mcp-remote

Conversation

@dzianisv

@dzianisv dzianisv commented Aug 14, 2026

Copy link
Copy Markdown
Member

Stacked on #136 (fix/135-relay-lifecycle) — review that first; this PR's diff is only the last commit.

Closes #145. Parent: #130.

Problem

The extension shows one canonical connector credential:

https://relay.api.vibebrowser.app/mcp/<uuid>

Claude/ChatGPT accept it directly, but the CLI required either a bare UUID or a separate wss://relay.api.vibebrowser.app/<uuid> value — two transport URLs for the same bearer capability.

Change

--remote (start, openclaw, browser CLI) and the MCP set_remote tool now accept the connector URL, normalized through the one shared entry point parseRemoteTarget in src/connection.ts:

--remote input Derived relay Status
https://host[/prefix]/mcp/<uuid> wss://host[/prefix] + <uuid> New, recommended
http://<loopback>[/prefix]/mcp/<uuid> ws://<loopback>[/prefix] + <uuid> New, self-hosted/local only
<uuid> default public relay + <uuid> Unchanged
ws(s)://host[/prefix]/<uuid> ws(s)://host[/prefix] + <uuid> Unchanged (advanced)

Security

All hardening from #136 is preserved and re-asserted by tests:

  • valid UUID only
  • embedded credentials, query strings, and fragments rejected
  • non-loopback plaintext (http:// / ws://) relay transport rejected
  • generic HTTP(S) URLs without the exact /mcp/<uuid> suffix rejected
  • the bearer value never appears in error messages, logs, help text, or CLI output; redactRemoteTarget still redacts both the full input and the UUID

Verified against bypass vectors: %2F-encoded separators rejected; .. and // normalized by WHATWG URL; uppercase /MCP/ rejected; default ports normalized; [::1] treated as loopback.

Only the documented non-routable placeholder 00000000-0000-0000-0000-000000000000 and the repo's pre-existing fabricated test constants appear in the diff. No real credential.

Tests

  • scripts/e2e-remote-lifecycle.mjs: connector normalization matrix (self-hosted path prefixes, non-default ports, all loopback forms), 11 malformed/hostile URL rejections each asserting no UUID/credential/host leak in the message, a live set_remote connect over the connector form against a fake relay, and a set_remote rejection of a generic HTTP URL
  • scripts/e2e-browser-cli.mjs: browser CLI status driven through an http://.../mcp/<uuid> connector URL, asserting identical structured output to the ws form with the session id redacted
  • scripts/e2e-cli-package-smoke.mjs: new CLI_PACK_GATE:REMOTE_HELP_OK gate proving the packaged CLI's --help advertises the connector form

npm run build and full npm test (12 gates) pass.

Docs

README, packages/cli/README.md, docs/openclaw-local-browser.md, and openclaw/vibebrowser/SKILL.md now lead with the connector URL, list UUID and ws(s):// as advanced/compatibility forms, document the self-hosted prefix and loopback derivations, and state the rejection rules. Duplicated two-variable example blocks were collapsed to a single VIBE_REMOTE_URL form.

Release readiness (commit b731023)

npm already serves @vibebrowser/mcp@0.3.3 and @vibebrowser/cli@0.3.1, so this branch was code-complete but unpublishable. Both packages are now bumped:

Package Was Now
@vibebrowser/mcp (root) 0.3.3 0.3.4
@vibebrowser/cli (packages/cli) 0.3.1 0.3.2
  • package-lock.json regenerated with npm version --no-git-tag-version (root + -w @vibebrowser/cli). Diff is version fields only: no dependency changes (3 lines).
  • Mirrored version references synced so the test:e2e:plugin-bundle version-consistency contract stays green: server.json (version + packages[0].version), mcpb/manifest.json, plugins/vibe-browser/.claude-plugin/plugin.json, the @vibebrowser/mcp@<version> pin in plugins/vibe-browser/.mcp.json, and .claude-plugin/marketplace.json.
  • .github/workflows/publish.yml needs no edit: its check_package step compares each package.json version against the live npm version and publishes only on a change. Both packages now differ from npm, so both will publish after merge through the existing publish workflow.
  • The one-shot Deprecate broken @vibebrowser/cli@0.3.0 step is gated on cli_current == '0.3.1' and will now skip. That is correct: @vibebrowser/cli@0.3.0 is already deprecated on npm with the exact expected message (npm view @vibebrowser/cli@0.3.0 deprecated -> Broken package: missing runtime file. Use 0.3.1 or later.).

Release verification results

Command Result
npm run build PASS (exit 0, tsc + prepare-cli-package.mjs)
npm run test:e2e:cli-package PASS - CLI_PACK_GATE:PACK_OK:vibebrowser-cli-0.3.2.tgz, INSTALL_OK, HELP_OK, REMOTE_HELP_OK, PASS
npm run test:e2e:plugin-bundle (version/publish contract) PASS - e2e ok - 13 checks passed, incl. "plugin .mcp.json pins the version this repo actually publishes", "marketplace entry resolves to the real plugin directory and version", and a live MCP handshake reporting vibebrowser-mcp 0.3.4
npm test (full suite, 14 gates) PASS (exit 0)

Not merged. Nothing published from this branch - publishing happens only via the publish.yml workflow on push to main.

Not merged, not published.

Users are shown one canonical connector credential in the extension
(https://relay.api.vibebrowser.app/mcp/<uuid>). That string could be pasted
into Claude/ChatGPT connectors but not into the CLI, which forced users to
learn a second transport URL for the same bearer capability.

`--remote` (start, openclaw, browser CLI) and the MCP `set_remote` tool now
accept it, deriving the relay target deterministically through the single
shared normalizer `parseRemoteTarget`:

  https://host[/prefix]/mcp/<uuid>      -> wss://host[/prefix] + <uuid>
  http://<loopback>[/prefix]/mcp/<uuid> -> ws://<loopback>[/prefix] + <uuid>

Bare UUID and ws(s) relay URL forms are unchanged.

All #136 hardening is preserved: valid UUID only, no credentials/query/
fragment, no non-loopback plaintext relay transport, and no bearer value in
errors, logs, help text, or CLI output. Generic HTTP(S) URLs without the
exact /mcp/<uuid> suffix are rejected.

Tests: connector normalization matrix (self-hosted path prefixes, ports,
loopback forms), malformed/hostile URL rejections with leak assertions, a
live set_remote connect over the connector form, a browser-CLI status run
through an http connector URL, and a packaged-CLI help gate.

Refs #145, #130

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dzianisv
dzianisv force-pushed the feat/cli-http-mcp-remote branch from 3bebf68 to 461d953 Compare August 14, 2026 17:55
…o 0.3.2

npm already serves @vibebrowser/mcp@0.3.3 and @vibebrowser/cli@0.3.1, so
merging #146 as-is would be a no-op for the publish workflow. Bump both
packages and sync every mirrored version reference the plugin-bundle
contract test asserts against package.json (server.json, mcpb/manifest.json,
plugin.json, .mcp.json pin, marketplace.json). package-lock.json was
regenerated via `npm version --no-git-tag-version`; no dependency changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dzianisv
dzianisv deleted the branch fix/135-relay-lifecycle August 14, 2026 18:29
@dzianisv dzianisv closed this Aug 14, 2026
dzianisv added a commit that referenced this pull request Aug 14, 2026
…o 0.3.2

npm already serves @vibebrowser/mcp@0.3.3 and @vibebrowser/cli@0.3.1, so
merging #146 as-is would be a no-op for the publish workflow. Bump both
packages and sync every mirrored version reference the plugin-bundle
contract test asserts against package.json (server.json, mcpb/manifest.json,
plugin.json, .mcp.json pin, marketplace.json). package-lock.json was
regenerated via `npm version --no-git-tag-version`; no dependency changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
dzianisv added a commit that referenced this pull request Aug 14, 2026
…147)

* feat: accept the HTTPS /mcp/<uuid> connector URL in --remote (#145)

Users are shown one canonical connector credential in the extension
(https://relay.api.vibebrowser.app/mcp/<uuid>). That string could be pasted
into Claude/ChatGPT connectors but not into the CLI, which forced users to
learn a second transport URL for the same bearer capability.

`--remote` (start, openclaw, browser CLI) and the MCP `set_remote` tool now
accept it, deriving the relay target deterministically through the single
shared normalizer `parseRemoteTarget`:

  https://host[/prefix]/mcp/<uuid>      -> wss://host[/prefix] + <uuid>
  http://<loopback>[/prefix]/mcp/<uuid> -> ws://<loopback>[/prefix] + <uuid>

Bare UUID and ws(s) relay URL forms are unchanged.

All #136 hardening is preserved: valid UUID only, no credentials/query/
fragment, no non-loopback plaintext relay transport, and no bearer value in
errors, logs, help text, or CLI output. Generic HTTP(S) URLs without the
exact /mcp/<uuid> suffix are rejected.

Tests: connector normalization matrix (self-hosted path prefixes, ports,
loopback forms), malformed/hostile URL rejections with leak assertions, a
live set_remote connect over the connector form, a browser-CLI status run
through an http connector URL, and a packaged-CLI help gate.

Refs #145, #130

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(release): bump @vibebrowser/mcp to 0.3.4 and @vibebrowser/cli to 0.3.2

npm already serves @vibebrowser/mcp@0.3.3 and @vibebrowser/cli@0.3.1, so
merging #146 as-is would be a no-op for the publish workflow. Bump both
packages and sync every mirrored version reference the plugin-bundle
contract test asserts against package.json (server.json, mcpb/manifest.json,
plugin.json, .mcp.json pin, marketplace.json). package-lock.json was
regenerated via `npm version --no-git-tag-version`; no dependency changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(remote): redact every UUID path segment from remote targets

A malformed-but-parseable connector URL such as
https://host/mcp/<u1>/mcp/<u2> parses to relay base wss://host/mcp/<u1>
and uuid <u2>. redactRemoteTarget only masked the full target and the
final path segment, so <u1> leaked through any message that echoed the
derived relay base.

Redact every UUID-shaped path segment (raw and percent-decoded), while
preserving full-target redaction, verbatim final-segment redaction and
case-insensitive matching.

Adds deterministic e2e-remote-lifecycle regressions for double-/mcp/
redaction, loopback-bypass hostnames rejecting plaintext, numeric
loopback canonicalization, and encoded/path-confusion accept/reject
outcomes. All UUIDs used are synthetic.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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