feat: describe device authorization requests

This commit is contained in:
Edward Park 2026-08-07 23:31:32 -07:00
commit 8b930d207e
2 changed files with 30 additions and 0 deletions

View file

@ -2,6 +2,7 @@
from __future__ import annotations
import platform
import sys
import webbrowser
from typing import Annotated, NoReturn
@ -9,6 +10,7 @@ from typing import Annotated, NoReturn
from cyclopts import Parameter
from rich.status import Status
import fastmcp
from fastmcp.cli.deploy.authentication import (
DeviceAuthorizationDeniedError,
DeviceAuthorizationError,
@ -25,6 +27,7 @@ from fastmcp.cli.deploy.credentials import (
)
from fastmcp.cli.deploy.horizon_client import (
DeviceAuthorization,
DeviceMetadata,
HorizonClient,
HorizonResponseError,
HorizonUnauthorizedError,
@ -64,6 +67,15 @@ def _can_open_browser() -> bool:
return sys.stdin.isatty() and sys.stdout.isatty()
def _device_metadata() -> DeviceMetadata:
return DeviceMetadata(
device_name=platform.node() or None,
platform=platform.system().lower() or None,
architecture=platform.machine().lower() or None,
client_version=fastmcp.__version__,
)
def _fail(
command: CommandName,
category: ErrorCategory,
@ -193,6 +205,7 @@ async def login(
async with HorizonClient(configuration.api_origin) as client:
return await authorize_device(
client,
metadata=_device_metadata(),
on_challenge=show_challenge,
open_browser=not json_output and _can_open_browser(),
browser_opener=webbrowser.open,

View file

@ -1,6 +1,7 @@
import json
from collections.abc import Callable
from unittest.mock import Mock
from urllib.parse import parse_qs
import httpx2
import pytest
@ -111,6 +112,10 @@ async def test_json_login_writes_one_result_and_challenge_to_stderr(
use_horizon_api(api)
browser_open = Mock()
monkeypatch.setattr(command_module.webbrowser, "open", browser_open)
monkeypatch.setattr(command_module.platform, "node", lambda: "Avery's laptop")
monkeypatch.setattr(command_module.platform, "system", lambda: "Darwin")
monkeypatch.setattr(command_module.platform, "machine", lambda: "arm64")
monkeypatch.setattr(command_module.fastmcp, "__version__", "4.0.0")
await login(json_output=True)
@ -135,6 +140,18 @@ async def test_json_login_writes_one_result_and_challenge_to_stderr(
"userCode": "ABCD-EFGH",
}
browser_open.assert_not_called()
authorization_request = next(
request
for request in api.requests
if request.url.path == "/api/v0/oauth/device/authorization"
)
assert parse_qs(authorization_request.content.decode()) == {
"client_id": ["fastmcp-cli"],
"device_name": ["Avery's laptop"],
"platform": ["darwin"],
"architecture": ["arm64"],
"client_version": ["4.0.0"],
}
state = json.loads(CredentialStore().path.read_text())
assert state == {"schemaVersion": 1, "apiKey": "fmcp_device_key"}