Commit 1bc25d9
[ROB-3999] Fix oauth client secret post 200 (HolmesGPT#2106)
The two-attempt token exchange
After the user clicks "Allow" in the browser, Slack redirects to Holmes
with an authorization code. Holmes calls exchange_code_for_tokens to
swap that code for an access_token.
Attempt 1 — HTTP Basic Auth (unchanged):
auth = httpx.BasicAuth(client_id, client_secret)
resp = httpx.post(token_url, data=data, auth=auth, ...)
Holmes sends client_id + client_secret in the Authorization header.
For Slack, this returns:
HTTP/2 200
{"ok": false, "error": "bad_client_secret"}
That's the trap: Slack returns 200 OK with an error body because its
token endpoint only accepts client_secret_post, not Basic Auth.
Attempt 2 — POST-body fallback (the fix):
Before:
if client_secret and not resp.is_success:
# retry with secret in body
This never fired for Slack, because resp.is_success was True.
After:
def _has_access_token(r):
try:
return "access_token" in r.json()
except Exception:
return False
if client_secret and (not resp.is_success or not
_has_access_token(resp)):
data["client_secret"] = client_secret
resp = httpx.post(token_url, data=data, ...) # no auth=, secret in body
The new clause not _has_access_token(resp) treats a 200 response without
an access_token field as a soft failure. For Slack's 200-with-error
pattern, this fires the body retry — Holmes re-POSTs with client_secret
inside the form-encoded body. Slack accepts that and responds:
{"ok": true, "access_token": "xoxp-…", "scope": "...", "team": {...}}
The function then validates "access_token" in token_data and returns the
dict.
Why this is safe for IdPs that worked before
IdPs that accept Basic Auth (e.g. Notion) still succeed on attempt 1 —
the response contains access_token, _has_access_token returns True, no
retry happens.
IdPs that return 4xx on Basic Auth (e.g. Supabase) still trigger the
retry via the original not resp.is_success branch.
The change only adds a third trigger for the retry; no existing IdP
loses any behavior.
What you'd see end-to-end (verified live earlier in this session)
CLI/cluster opens browser → user authorizes → callback delivers code.
exchange_code_for_tokens POSTs with Basic Auth → Slack returns 200 +
bad_client_secret.
New gate detects no access_token → POSTs again with secret in body →
Slack returns 200 + xoxp-… token.
Holmes caches the token and uses it as Authorization: Bearer xoxp-…
against https://mcp.slack.com/mcp, then calls tools/list → tools come
back → Holmes can invoke slack_read_user_profile, slack_read_file, etc.
The two regression tests in tests/test_mcp_oauth.py exercise both
Slack-style outcomes (success on body retry; failure if body retry also
returns no token), so the behavior is locked in.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* OAuth token exchange now properly handles successful HTTP responses
that lack access tokens, automatically retrying with alternative
credential delivery methods—improving compatibility with certain
identity providers.
* Enhanced error diagnostics for OAuth tool loading failures to better
identify configuration mismatches or workspace/token inconsistencies.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Signed-off-by: avi@robusta.dev <avi@robusta.dev>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent ac5e944 commit 1bc25d9
3 files changed
Lines changed: 135 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
76 | 76 | | |
77 | 77 | | |
78 | 78 | | |
79 | | - | |
80 | | - | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
81 | 90 | | |
82 | 91 | | |
83 | 92 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
131 | 131 | | |
132 | 132 | | |
133 | 133 | | |
| 134 | + | |
134 | 135 | | |
135 | 136 | | |
136 | 137 | | |
| |||
227 | 228 | | |
228 | 229 | | |
229 | 230 | | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
230 | 285 | | |
231 | 286 | | |
232 | 287 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
32 | 32 | | |
33 | 33 | | |
34 | 34 | | |
| 35 | + | |
35 | 36 | | |
| 37 | + | |
36 | 38 | | |
37 | 39 | | |
38 | 40 | | |
| |||
370 | 372 | | |
371 | 373 | | |
372 | 374 | | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
373 | 442 | | |
374 | 443 | | |
375 | 444 | | |
| |||
0 commit comments