_ jw t_username_var set in _resolve_token for both JWT and API key auth.
Passed to handlers via gns3_ctx['jwt_username']. No raw key exposure,
no fake-user fallback.
Dict comprehension overwrote earlier entries when the same device
appeared multiple times, causing all entries' outputs to collapse
into the last one. Now commands are appended for duplicate names.
config_tools_nornir expects config_commands, while
display_tools_nornir and vpcs_tools_netmiko expect commands.
Render template now uses the correct field name.
- Add optional 'template' param with Jinja2 syntax
- Each device entry can use 'vars' dict instead of 'config_commands'
- Template rendered per device, merged with existing commands
- Rendering errors returned inline for AI self-correction
- Each tool accepts either node_id (single) or node_ids (batch)
- Batch mode runs actions in parallel via ThreadPoolExecutor
- Useful for starting/stopping nodes by topology region
- node_create accepts nodes=[{template_id, x, y, name?}] for batch creation
- link_create accepts links=[{nodes, link_type?, filters?}] for batch creation
- Uses ThreadPoolExecutor for parallel REST API calls
- Max 10 concurrent workers per batch, backward compatible with single mode
REST API auth already supports gns3_ keys, so there's no need
to create a temporary 5-min JWT. The raw API key is passed
through as the Bearer token, eliminating token expiry issues.
Removing the soft-delete approach — revoked keys are now deleted
from the database entirely via DELETE endpoint. This prevents the
api_keys table from accumulating stale records.
_db_engine is not available when register_starlette_routes() is
called (it's set later during lifespan startup). Store the app
reference instead and access app.state._db_engine lazily.
- New db model: api_keys table with bcrypt-hashed keys
- New API: POST/GET/DELETE /v3/access/api-keys endpoints
- MCP _resolve_token: validates API keys, resolves to 5-min JWT
- API keys inherit the creating user's RBAC permissions
- MCP auth supports both JWT (24h) and API key (permanent) tokens
joserfc.jwt.decode() does not validate the exp claim by default,
so expired tokens were accepted indefinitely. Added explicit check
after decoding. See issue #2781.
Previously compute_id was typed as str, so 'local' would pass MCP validation
and reach the controller API where it crashed. Now uuid.UUID type ensures
Pydantic rejects any non-UUID string before the handler runs.
The end /v3/computes/{compute_id} expects the compute_id to be a
valid UUID. Previously the MCP tool defaulted to the string 'local',
which caused a ValueError in the database layer. Now compute_id is
required and callers must use compute_list first to resolve names to UUIDs.