mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 07:09:11 +02:00
fix: preserve Horizon command contracts
This commit is contained in:
parent
8b930d207e
commit
e532e740be
5 changed files with 40 additions and 6 deletions
|
|
@ -94,9 +94,11 @@ fastmcp whoami
|
|||
fastmcp logout
|
||||
```
|
||||
|
||||
`fastmcp login` always shows a verification URL and code.
|
||||
`fastmcp login` first uses `HORIZON_API_KEY` or a valid stored key when one is available.
|
||||
When login needs a new key, it shows a verification URL and code.
|
||||
It opens a browser when the terminal supports it.
|
||||
If the browser does not open, use the shown URL and code on another device.
|
||||
To switch accounts, run `fastmcp logout` before you run `fastmcp login` again.
|
||||
|
||||
Use `--host` to connect to another Horizon environment.
|
||||
The CLI saves the host and clears the stored key before a host change.
|
||||
|
|
|
|||
|
|
@ -21,8 +21,10 @@ Sign in to Horizon from the FastMCP CLI with the device authorization flow.
|
|||
fastmcp login
|
||||
```
|
||||
|
||||
The command shows a verification URL and code before it opens the browser.
|
||||
The command uses an environment key or a valid stored key when one is available.
|
||||
When login needs a new key, it shows a verification URL and code before it opens the browser.
|
||||
If the browser cannot open, visit the shown URL and enter the code.
|
||||
To switch accounts, run `fastmcp logout` before you run `fastmcp login` again.
|
||||
New users can register and create their first Horizon organization in the browser.
|
||||
Use `fastmcp login --host <url>` to save a different Horizon host.
|
||||
A host change clears the stored credential before login.
|
||||
|
|
|
|||
|
|
@ -216,6 +216,7 @@ async def login(
|
|||
credential = await resolve_credential(
|
||||
credentials,
|
||||
authorize=device_authorization,
|
||||
expected_api_origin=configuration.api_origin,
|
||||
)
|
||||
|
||||
try:
|
||||
|
|
@ -234,6 +235,7 @@ async def login(
|
|||
credential = await resolve_credential(
|
||||
credentials,
|
||||
authorize=device_authorization,
|
||||
expected_api_origin=configuration.api_origin,
|
||||
)
|
||||
try:
|
||||
user = await _get_user(
|
||||
|
|
@ -277,7 +279,14 @@ async def whoami(
|
|||
)
|
||||
except HorizonUnauthorizedError as error:
|
||||
if credential is not None and credential.source == "stored":
|
||||
credentials.clear()
|
||||
try:
|
||||
credentials.clear()
|
||||
except StateFileError as cleanup_error:
|
||||
_fail_for_expected_error(
|
||||
"whoami",
|
||||
cleanup_error,
|
||||
json_output=json_output,
|
||||
)
|
||||
_fail_for_expected_error("whoami", error, json_output=json_output)
|
||||
except (
|
||||
AuthenticationRequiredError,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import fastmcp.cli.deploy.command as command_module
|
|||
from fastmcp.cli.deploy.command import login, logout, whoami
|
||||
from fastmcp.cli.deploy.credentials import CredentialStore
|
||||
from fastmcp.cli.deploy.horizon_client import HorizonClient
|
||||
from fastmcp.cli.deploy.state import StateFileError
|
||||
|
||||
|
||||
class HorizonAuthAPI:
|
||||
|
|
@ -259,6 +260,26 @@ async def test_login_never_persists_an_environment_key(
|
|||
)
|
||||
|
||||
|
||||
async def test_json_whoami_reports_a_failed_rejected_key_cleanup(
|
||||
use_horizon_api: Callable[[HorizonAuthAPI], None],
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
use_horizon_api(HorizonAuthAPI(invalid_api_key="fmcp_stale_key"))
|
||||
CredentialStore().save("fmcp_stale_key")
|
||||
|
||||
def fail_clear(store: CredentialStore) -> None:
|
||||
raise StateFileError("cleanup failed")
|
||||
|
||||
monkeypatch.setattr(CredentialStore, "clear", fail_clear)
|
||||
|
||||
with pytest.raises(SystemExit, match="1"):
|
||||
await whoami(json_output=True)
|
||||
|
||||
result = json.loads(capsys.readouterr().out)
|
||||
assert result["error"]["category"] == "state_error"
|
||||
|
||||
|
||||
async def test_json_whoami_does_not_start_device_authorization(
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ def test_tty_device_challenge_uses_the_sign_in_layout(
|
|||
emit_device_challenge(authorization(), json_output=False)
|
||||
|
||||
output = capsys.readouterr().out
|
||||
assert "╭" in output
|
||||
assert "│" in output
|
||||
assert "Deploy FastMCP on Horizon" in output
|
||||
assert "✓ Device authorization started" in output
|
||||
assert "https://horizon.prefect.io/oauth/device?user_code=ABCD-EFGH" in output
|
||||
|
|
@ -82,7 +82,7 @@ def test_tty_identity_uses_an_account_panel(
|
|||
emit_identity("whoami", user(), json_output=False)
|
||||
|
||||
output = capsys.readouterr().out
|
||||
assert "╭" in output
|
||||
assert "│" in output
|
||||
assert "Horizon Account" in output
|
||||
assert "Ada" in output
|
||||
assert "ada@example.com" in output
|
||||
|
|
@ -124,7 +124,7 @@ def test_tty_logout_uses_the_horizon_header(
|
|||
|
||||
output = capsys.readouterr().out
|
||||
assert "Logged out of Horizon" in output
|
||||
assert "╭" in output
|
||||
assert "│" in output
|
||||
|
||||
|
||||
def test_json_logout_has_stable_fields(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue