feat(mcp): give an invalidated Hub token a recovery path #110
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/YT-51-hub-token-recovery-path"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Every
refresh_hub_tokenfailure collapsed into one opaque anyhow chain, so a dead refresh token, a client secret that disagrees with Hub, and a network blip were indistinguishable, and none of them told the caller what to do next. The stored row was also unreachable: no route or CLI clearedhub_tokens(the admin disable path revokes only app-issued tokens), and the browser session is a signed cookie the server cannot invalidate, so the only way out was hand-written SQL against the encrypted table.Failures are now classified on the RFC 6749 error code.
invalid_grantmeans the grant is provably dead: the unrecoverable row is deleted and the user is told to sign in again.invalid_clientis the operator's misconfiguration, so the message names YOUTRACK_MCP_HUB_CLIENT_SECRET and the row is kept, since the user's grant is not at fault and works again once the secret matches. Everything else, including transport failures and any other code, stays transient and keeps the row: only the one code that proves the grant is dead may delete it. The classified error carries just its message and nosource, so the oauth2 text never reaches the MCP client.yt mcp auth reset --user <login> | --allclears rows out of band, editing the SQLite state DB directly likeyt mcp allowlist(so it works whether or not the service is running; the YT-49 busy_timeout makes the concurrent write wait). One of the two targets is required: clearing a token costs someone a re-login, so there is no default.usersgained alogincolumn (migration 0002) because the Hub login an operator names a user by was not stored anywhere; email and Hub user id also resolve, which keeps pre-migration rows addressable until their next login backfills it.Clearing a row only works if something forces the Hub leg to re-run.
/authorizetrusted the session cookie alone, and that cookie stays valid after the row is gone, so a reset would have moved the failure from "Invalid token" to "no Hub token stored for user". It now checks for a stored Hub token and falls through to a fresh Hub exchange when there is none. Deleting the row costs nothing else: theusersrow, its admin flag, and the allowlist entry all survive, unlike the CASCADE path that was previously the only way to remove a token.Covered by unit tests per classification branch (asserting the row disposition each one implies), a
/authorizefall-through test, and an end-to-end test that runs the realyt mcp auth resetcode path against a live service DB and drives the same session cookie back through a full Hub exchange to a working/mcpbearer. Both new fall-through tests were confirmed to fail without the/authorizechange.#YT-51