Fluxo de autorização do MCP — registro de client, consent no frontend, troca code+verifier por tokens.
O servidor MCP usa OAuth 2.1 com PKCE obrigatório (S256). O consentimento acontece no frontend uPubly — o usuário autoriza com a conta já logada e recebe o código na tela para entregar ao agente.
GET https://api.upubly.com/.well-known/oauth-authorization-server
GET https://api.upubly.com/.well-known/oauth-protected-resourcePOST /oauth/register
Content-Type: application/json
{"client_name": "Meu Agente", "redirect_uris": ["http://localhost:9999/callback"]}→ 201 {"client_id": "...", ...}. redirect_uris aceita https://,
loopback (http://localhost|127.0.0.1) e schemes custom de app;
http:// público é rejeitado.
O agente gera code_verifier (aleatório) e code_challenge = base64url(sha256(verifier)),
depois abre no browser:
GET /oauth/authorize?response_type=code&client_id=<id>
&redirect_uri=<uri>&code_challenge=<challenge>
&code_challenge_method=S256&state=<state>A API valida e redireciona (302) para a tela de consent do front:
https://upubly.com/oauth/consent?request=<request_id>O front consulta os detalhes e aprova/nega com a sessão do usuário:
GET /oauth/requests/<id> → detalhes (nome do client, redirect)
POST /oauth/requests/<id>/approve → {"code": "...", "redirect_to": "..."}
POST /oauth/requests/<id>/deny → {"redirect_to": "...?error=access_denied"}redirect_to aponta para o redirect_uri do client com code + state —
agentes com listener local recebem automático. A tela também exibe o
código para copiar — agentes headless/SSH recebem o código colado pelo
usuário. Exibir o code é seguro: sem o code_verifier (que nunca sai do
agente) ele não troca por token.
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=<code>&redirect_uri=<uri>
&client_id=<id>&code_verifier=<verifier>→ {"access_token": "<JWT>", "refresh_token": "...", "expires_in": 28800}.
POST /oauth/token
grant_type=refresh_token&refresh_token=<token>&client_id=<id>Rotação na mesma sessão: o refresh antigo morre, um novo par volta.
POST /oauth/revoke
token=<refresh_token>&client_id=<id>S256, plain é rejeitado.redirect_uri deve ser exact match de uma URI registrada.