feat(management): Flux API key bearer tokens - #20
Merged
Conversation
Hosted MCP connectors accept a single token value and send it as `Authorization: Bearer <token>`; the scheme is not selectable. Flux accepts only `Simple` and `Secure`, so every authentication-required Flux API was unreachable from them — and since writes always require an authenticated key, that ruled out agent memory entirely. Adds the two Management API calls that manage the credential, on both the sync and the async client: - `issue_flux_api_key_bearer_token(key)` issues or replaces the token and returns the plaintext. It is returned only here and only once: the service stores a hash, exactly as for `secret_key`, so a lost token is re-issued rather than recovered. - `revoke_flux_api_key_bearer_token(key)` revokes it. Both address the token sub-resource, never the key. Issuing, re-issuing and revoking all leave `public_key`, `secret_key`, `role` and grants untouched, so `Simple` and `Secure` integrations keep working — that is what makes a re-issue a way to cut off a connector without recreating a key and reconfiguring everything that uses it. `FluxAPIKeySummary` gains `bearer_token_prefix` and `bearer_token_issued_at`, both OPTIONAL so the model still validates a response from a server that predates the feature — the SDK ships ahead of the deployment. The prefix is the first 12 characters: enough to recognise a token in a config file, never enough to use one. Tests cover both clients and pin that each call reaches the sub-resource and not the key's own URL. A request to the key URL would delete the key and take its Simple/Secure credentials down with it, which is the one mistake here that fails silently. Requires a server with bearer-token support; against an older one both methods return 404. Version bumped in src/foxnose_sdk/_version.py, the single source the build backend reads, and in the test that pins it. The changelog entry goes in docs/changelog.md, which is the maintained one — it is published through mkdocs and carries 0.7.0 and 0.7.1. The root CHANGELOG.md stopped at 0.6.0 some releases ago and is left untouched here rather than half revived.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds the Management API calls for a Flux API key's bearer token — an opaque
fxk_credential for hosted MCP connectors.Why
A hosted MCP connector accepts a single token value and sends it as
Authorization: Bearer <token>. The scheme is not selectable. Flux accepts onlySimpleandSecure, so every authentication-required Flux API was unreachable from one — and since writes always require an authenticated key, that ruled out agent memory entirely: a connector could read public APIs and nothing else.The token is a second credential on an existing key, not a new kind of key. It identifies that key and nothing more — role, grants and per-collection permissions are the key's own.
What
Both clients, sync and async:
issue_flux_api_key_bearer_token(key)— issues or replaces the token, returningFluxAPIKeyBearerToken.revoke_flux_api_key_bearer_token(key)— revokes it.FluxAPIKeySummarygainsbearer_token_prefixandbearer_token_issued_at.Two things worth reviewing closely
The calls address the token sub-resource, never the key. A request to the key's own URL would delete the key and take its
public_key/secret_keydown with it, silently breaking every integration already using them. Both tests assert the URL ends in/bearer-token/and explicitly assert it does not end in/api-keys/{key}/— the negative half is the one that catches this, and it is the mistake here that fails quietly.The new fields are optional. The SDK ships ahead of the server deployment, so the model has to keep validating a response from a server that predates the feature. There is a test for exactly that:
FluxAPIKeySummaryvalidates the old payload shape with both fieldsNone, and validates the new one with them populated.The show-once contract
The plaintext is returned only by the issue call, and only once — the service stores a hash, exactly as it does for
secret_key. A lost token is re-issued, not recovered. Every later read exposes onlybearer_token_prefix, the first 12 characters: enough to recognise a token in a config file, never enough to use one. The test asserts a validatedFluxAPIKeySummarycarries nobearer_tokenattribute at all.Issuing does not disturb the key
public_key,secret_key,roleand grants survive an issue, a re-issue and a revoke. That is the point of a separate sub-resource rather than a field on the key: re-issuing cuts off a connector while everything using the key pair keeps working — revocation without recreating the key and reconfiguring its consumers.Requires
A server with bearer-token support. Against an older one both methods return 404.
Tests
Full suite green;
ruff check .andruff format --check .clean over the files this branch touches.