@clawne-me/loopback-helper
v0.1.0
Published
Local helper that completes an OAuth authorization for a Clawne Me connector against an authorization server that only supports loopback (native-app - rfc8252) redirects.
Downloads
26
Readme
@clawne-me/loopback-helper
A small command-line tool that completes a Clawne Me connector's sign-in
against an authorization server that only accepts a loopback redirect
(http://127.0.0.1:<port>/callback) instead of Clawne Me's normal hosted
callback. Some authorization servers — Robinhood's MCP OAuth among them — are
built exclusively for native/CLI-style apps and will not accept any other
redirect shape (RFC 8252). Since Clawne Me runs as a hosted service with no
process on your machine, nothing it does on its own can catch that kind of
redirect — this tool is the thing that does.
You only need this when the Connectors page tells you to. For every authorization server that accepts the normal hosted flow — the large majority — you never need this tool at all.
What it does, and does not do
- It binds one local port on your machine, opens your browser at the sign-in page, waits for that browser to redirect back to the local port, and forwards what it captured to Clawne Me.
- It never talks to the authorization server itself — every real OAuth call (discovery, registration, token exchange) happens on Clawne Me's servers, not on your machine.
- It never sees or stores a token, a client secret, or your Clawne Me credentials. The only thing it holds is a pairing code shown to you on the Connectors page — a one-time, short-lived value that expires in a few minutes whether or not you use it.
Usage
The Connectors page shows you a command to copy after you choose Connect via local helper. It looks like:
bunx @clawne-me/loopback-helper start <pairingCode>or, if you downloaded a standalone binary from a release instead of using
bunx:
./clawne-me start <pairingCode>That's the only command most people ever run. It:
- Binds a local port and calls Clawne Me to start the sign-in.
- Opens your default browser at the authorization server's sign-in page.
- Waits for you to finish signing in there.
- Reports back to Clawne Me and prints whether it worked.
If it can't open a browser automatically, it prints a URL for you to open yourself.
Advanced subcommands
These exist for scripting or for recovering from an interrupted start —
you will not need them in the normal case.
# Just the first step: claim the pairing for a port you've already bound,
# and print the authorization URL instead of opening it.
clawne-me claim <pairingCode> --port <port>
# Just the last step: manually submit a code/state you captured some other
# way (e.g. you copied the final redirect URL by hand).
clawne-me consume --pairing-code <code> --state <state> --code <authCode>Checking what's installed
clawne-me --versionPrints just the version number (nothing else), so it's safe to use in a script — a CI step or an install-time check that wants to confirm the tool is on PATH and see which build it got, without parsing a sentence.
Pointing this at a self-hosted Clawne Me
By default this talks to Clawne Me's production API. If you run your own
deployment, set CLAWNE_API_URL or pass --api-url:
CLAWNE_API_URL=https://api.your-domain.example clawne-me start <pairingCode>Is it a problem that the API URL is baked into a public binary?
No — https://api.clawne.me isn't a secret. It's the exact same host the
Connectors page already calls from client-side JS in your browser, visible in
any devtools Network tab, so shipping it in the CLI (or a compiled binary)
reveals nothing that wasn't already public. A compiled binary doesn't hide
plain string constants either way — strings ./clawne-me | grep clawne.me
shows it instantly.
What actually needs protecting is the two endpoints this tool calls
(/oauth/loopback/claim and /oauth/loopback/callback), and that's a
server-side concern, already handled there rather than by obscuring a URL:
- They're intentionally unauthenticated — this CLI has no way to hold a Clawne Me session — but rate-limited, keyed by both the pairing code and the caller's source IP (10 attempts/60s per code, 30/60s per IP, on both routes).
- A pairing code is a 256-bit random token with a 5-minute TTL, consumed atomically on first successful claim — so even at the rate limit's ceiling, brute-forcing a live code isn't realistic.
- Even a lucky guess has a bounded blast radius: at worst, an attacker starts an authorization attempt for a connector they don't own. No token, secret, or session is ever exposed through this path — the local helper only ever relays one browser redirect.
The same reasoning covers a custom CLAWNE_API_URL (e.g. a dev tunnel):
whoever sets it already knows their own URL, and it's pointed at the same
rate-limited, short-lived, unauthenticated-by-design routes.
Troubleshooting
- "Could not open a browser automatically." Some sandboxed or headless
environments can't launch a GUI browser. Copy the printed URL into any
browser (even on another device) and complete sign-in there — the local
port only needs to be reachable from a browser running as you locally, but
copying the URL to a different machine's browser will fail once it tries to
redirect back to
127.0.0.1, so this only works on the same machine. - A firewall prompt appears the first time you run this. That's your OS asking whether this process may listen on a local port — accept it, since the port only accepts connections from your own machine either way.
- "This pairing code is invalid or has expired." Pairing codes expire a few minutes after they're issued. Go back to the Connectors page and start again.
- Nothing happens after signing in in the browser. The tool waits up to five minutes for the redirect. If your browser shows a "connected" page but the terminal is still waiting, the redirect likely already succeeded — check the Connectors page directly.
Development
bun install # from the repo root
bun test # unit tests (no network access needed)
bun run typecheck
bun run build # produces this release's artifacts in dist/<version>/
bun run build:clean # same, after removing any previous dist/ output firstThis package intentionally has no dependency on the rest of the Clawne Me monorepo — it is meant to be installed and run standalone, on an owner's own machine.
Release artifacts
bun run build writes everything for one release under dist/<version>/,
where <version> is package.json's version (no v prefix):
dist/<version>/
binaries/
clawne-me-<version>-x86_64-linux.gz + .gz.sha256
clawne-me-<version>-aarch64-linux.gz + .gz.sha256
clawne-me-<version>-x86_64-darwin.gz + .gz.sha256
clawne-me-<version>-aarch64-darwin.gz + .gz.sha256
clawne-me-<version>-x86_64-windows.exe.gz + .gz.sha256
clawne-me-<version>-aarch64-windows.exe.gz + .gz.sha256
packages/
clawne-me-loopback-helper-<version>.tgz
clawne-me-loopback-helper-<version>.tgz.sha256binaries/— one gzip-compressed, single-file, dependency-free executable per platform/arch (bun build --compile, thenBun.gzipSync), for an owner without Bun installed. The naming (<arch>-<os>, gzip,.exeplaced before.gzon Windows) matches what the public installers —clawne-me.sh/clawne-me.ps1, published from thebe-hapirepo, both underhttps://get.mcp.com.ai/— expect from aclawne-me/cliGitHub Release. If either installer's platform-detection or archive format ever changes, updateTARGETSinscripts/build-binaries.tsto match — that script is the one place this convention is defined on the build side.packages/— the npm-compatible source tarball (bun pm pack), the same artifactnpm publishwould upload; installable viabunx,npx, or a plainnpm installfrom a local path or a registry. Unrelated to the installers above — this is thebunx @clawne-me/loopback-helperpath.Every file gets a sibling
.sha256insha256sum -cformat (hash of the file as shipped — the.gz, not the binary inside it), so a download can be verified before trusting it — which is exactly what both installers do automatically before extracting:cd dist/<version>/binaries sha256sum -c clawne-me-<version>-x86_64-linux.gz.sha256
Publishing these (attaching them to a GitHub Release under clawne-me/cli,
npm publish) is an operational step outside this script and outside this
repo's CI today — but the resulting filenames in binaries/ are already
exactly what the release's assets need to be named.
