GitHub user aymansalkhatib added a comment to the discussion: MCP server: how 
to map the JWT identity to a Superset user? (MCP_USER_RESOLVER in docs, not 
found in code)

Short answer: `MCP_USER_RESOLVER` exists, but **not in the 6.1.0 code**. The 
6.1.0 docs describe a feature that only ships from **7.0** onward (it's in 
`7.0.0rc1` and `7.0.0rc2`). That's why you see JWT validation succeed and then 
"No authenticated user found".

### What 6.1.0 actually does
In the [6.1.0 
`auth.py`](https://github.com/apache/superset/blob/6.1.0/superset/mcp_service/auth.py),
 `get_user_from_request()` only tries two sources:
1. `g.user`, if something already set it ("Preset workspace middleware", per 
the docstring);
2. `MCP_DEV_USERNAME`.

Nothing reads the validated token's claims. `mcp_config.py` in 6.1.0 does 
define a `default_user_resolver()`, but `auth.py` never calls it, and 
`MCP_USER_RESOLVER` isn't referenced anywhere in that tag. So the JWT gets 
through the transport layer, no user is derived from it, and every call ends up 
either failing or running as the single `MCP_DEV_USERNAME` user. That matches 
what you describe.

### Where it was added
On `master`, `auth.py` gained `_resolve_user_from_jwt_context()`, first in 
[#38747](https://github.com/apache/superset/pull/38747), with follow-ups such 
as #39106 and #39385. It reads the token through FastMCP's `get_access_token()` 
and calls:

```python
resolver = app.config.get("MCP_USER_RESOLVER", default_user_resolver)
username = resolver(app, access_token)
```

The [default 
resolver](https://github.com/apache/superset/blob/master/superset/mcp_service/mcp_config.py)
 checks `preferred_username`, then `username`, `email`, `sub`. The result is 
looked up as a Superset username, with an email fallback when it contains `@`. 
It **fails closed**: if the user doesn't exist, or the token has no usable 
claim, the call is denied and there's no fallback to `MCP_DEV_USERNAME`. 
Deactivated accounts are also refused.

### Mapping `sub` on 7.0
If your IdP's `sub` is an opaque ID, a custom resolver can return the Superset 
username yourself:

```python
# superset_config.py
def resolve_mcp_user(app, access_token):
    claims = getattr(access_token, "claims", None) or {}
    return claims.get("preferred_username") or claims.get("email")

MCP_USER_RESOLVER = resolve_mcp_user
```

Users still have to exist in Superset beforehand; the resolver doesn't create 
them. If `MCP_JWT_ISSUER` trusts **more than one issuer**, 7.0 refuses to start 
without an issuer-aware resolver 
([#42935](https://github.com/apache/superset/pull/42935), see `UPDATING.md`). 
In that case, include the `iss` claim in the identity you return.

### Until you can run 7.0
- **Test `7.0.0rc2`** in staging if you can. It contains the code above, and 
it's what the docs are describing.
- On 6.1.0 there's no supported per-user mapping. The least-bad interim setup 
is **one MCP server instance per access level**, each with its own 
`MCP_DEV_USERNAME` pointing to a dedicated, least-privileged service account 
(for example `mcp_readonly_sales`, `mcp_readonly_finance`), with JWT auth kept 
on so only the right clients can reach each instance. You get role-level RBAC 
this way, but not per-person auditing.
- If you need 6.1.x specifically, ask the maintainers about backporting #38747 
and its follow-ups. It touches the auth path, so I wouldn't patch it locally.


GitHub link: 
https://github.com/apache/superset/discussions/45457#discussioncomment-19065575

----
This is an automatically sent email for [email protected].
To unsubscribe, please send an email to: 
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to