You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(mcp): browser OAuth2 login on first run when no token is cached (#80)
When the bridge has no cached token it now runs the interactive browser login
(using CLIENT_ID/CLIENT_SECRET from its environment) and holds the handshake
until it completes, instead of failing fast. This lets the npx-launched bridge
authenticate with no prior xurl install or setup, then caches and auto-refreshes
the token. Login output stays on stderr so the stdout JSON-RPC channel is
unaffected; set a generous startup_timeout_sec on the server. Headless hosts
authenticate out-of-band first with `xurl auth oauth2 --headless`.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,6 +2,12 @@
2
2
3
3
All user-visible bugs and enhancements should be recorded here.
4
4
5
+
## v1.2.1 - 2026-06-29
6
+
7
+
### Changed
8
+
9
+
-[2026-06-29]`mcp` bridge now runs the interactive browser OAuth2 login on first run when no token is cached (using `CLIENT_ID`/`CLIENT_SECRET` from its environment), instead of failing fast. This lets the bridge authenticate with no prior xurl setup — e.g. straight from `npx … mcp` — and then caches/auto-refreshes the token. The MCP handshake is held until the login completes (set a generous `startup_timeout_sec` on the server), and login diagnostics stay on stderr so the stdout JSON-RPC channel is unaffected. On a headless host, authenticate out-of-band first with `xurl auth oauth2 --headless`.
Copy file name to clipboardExpand all lines: README.md
+5-7Lines changed: 5 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -277,12 +277,7 @@ If no token is available (and none can be refreshed), it exits non-zero with a h
277
277
278
278
`xurl mcp` turns xurl into a [Model Context Protocol](https://modelcontextprotocol.io) bridge for the hosted X API MCP server. It reads newline-delimited JSON-RPC from stdin, relays each message to a remote Streamable HTTP MCP endpoint with an `Authorization: Bearer <token>` header, and writes the server's responses (plain JSON or `text/event-stream`) back to stdout as newline-delimited JSON. The MCP session id is maintained automatically and the token is refreshed in-process as it expires.
279
279
280
-
Because X's OAuth requires your own app (there is no dynamic client registration), xurl holds the app identity and mints/refreshes the token. **Authenticate once before starting the bridge** — `xurl mcp` will refresh an expired token automatically but never opens a browser itself (its stdio is the MCP channel), so it fails fast with instructions if no token exists:
281
-
282
-
```bash
283
-
xurl auth oauth2 --app my-app # local machine with a browser
Because X's OAuth requires your own app (there is no dynamic client registration), xurl holds the app identity and mints/refreshes the token. On first run with no cached token, the bridge opens the browser for a one-time OAuth2 login using the `CLIENT_ID`/`CLIENT_SECRET` from its environment, then caches and auto-refreshes the token for subsequent runs. The MCP handshake is held until that login completes, so give the server a generous `startup_timeout_sec`.
286
281
287
282
Use it directly from any MCP client (Claude Desktop, Cursor, etc.) with a standard MCP server config — no separate install step is needed thanks to the npm launcher:
288
283
@@ -292,12 +287,15 @@ Use it directly from any MCP client (Claude Desktop, Cursor, etc.) with a standa
Requirements for the first-run browser login: a browser on the machine running the client, and your X app must have the OAuth2 redirect URI `http://localhost:8080/callback` registered (or set `REDIRECT_URI` to one that is). On a headless host with no reachable browser, authenticate out-of-band first with `xurl auth oauth2 --headless` (the bridge then just reuses the cached token).
298
+
301
299
The `<url>` positional is optional and defaults to `https://api.x.com/mcp`. `--app` is honored, so you can point a client at a specific registered app:
-**Default user:** When no `-u` flag is given, xurl uses the default user for the active app (set via `xurl auth default`). If no default user is set, it uses the first available token.
415
415
-**Token storage:**`~/.xurl` is YAML. Each app stores its own credentials and tokens. Never read or send this file to LLM context.
416
416
-**Access tokens:**`xurl token` prints a valid (refreshed) OAuth2 access token for the active app to stdout, refreshing and persisting it if expired. It never opens a browser. The output is a secret — use it only in the user's own scripts, never in agent/LLM sessions.
417
-
-**MCP bridge:**`xurl mcp [URL]` bridges a stdio MCP client to a remote Streamable HTTP MCP server (default `https://api.x.com/mcp`), injecting `Authorization: Bearer <token>` and refreshing the token automatically. Authenticate once first (`xurl auth oauth2 --app APP_NAME`, or `--headless` on a remote host) — the bridge refreshes an existing token but never opens a browser itself, so it fails fast with guidance if none exists. Configure it in an MCP client via the npm launcher: `{"command":"npx","args":["-y","@xdevplatform/xurl","mcp","https://api.x.com/mcp"],"env":{"CLIENT_ID":"...","CLIENT_SECRET":"..."}}`.
417
+
-**MCP bridge:**`xurl mcp [URL]` bridges a stdio MCP client to a remote Streamable HTTP MCP server (default `https://api.x.com/mcp`), injecting `Authorization: Bearer <token>` and refreshing the token automatically. On first run with no cached token it opens the browser for a one-time OAuth2 login using the `CLIENT_ID`/`CLIENT_SECRET` from its environment (the handshake waits for it, so set a generous `startup_timeout_sec`); on a headless host, authenticate out-of-band first with `xurl auth oauth2 --headless`. Configure it in an MCP client via the npm launcher: `{"command":"npx","args":["-y","@xdevplatform/xurl","mcp","https://api.x.com/mcp"],"env":{"CLIENT_ID":"...","CLIENT_SECRET":"..."},"startup_timeout_sec":300}`.
0 commit comments