MSSQL source: authenticate to the database as the caller (per-user identity)
Feature request
The mssql source opens one connection pool from the user and password in the
configuration, so every request reaches SQL Server as the same principal. With
mssql-execute-sql in the toolset that means the executed statement is the only thing
between a caller and data they are not entitled to: the enforcement lives in the prompt
and the tool description rather than in the database.
Toolbox already carries the caller's access token down to Invoke, and BigQuery, Spanner
and Looker already use it to act as the caller. SQL Server can do the same — Azure SQL
Database and Azure SQL Managed Instance accept a Microsoft Entra access token as the
connection credential — but no database source implements it.
Why it is worth having here
Row-level security and per-user grants are the mechanism SQL Server users already have for this. They only work if the database knows who is asking. Once it does, a query for someone else's rows comes back as a refusal from the server, whatever the model was persuaded to generate.
It also removes a stored secret: with the caller's identity doing the work, there is no shared password in the configuration at all.
Proposed shape
Reuse the existing field name and semantics, so it reads the same as the other sources:
kind: source
name: my-mssql-source
type: mssql
host: my-server.database.windows.net
port: 1433
database: my_db
useClientOAuth: "true"
azureOnBehalfOf:
clientId: ${AZURE_CLIENT_ID}
clientSecret: ${AZURE_CLIENT_SECRET}
tenantId: ${AZURE_TENANT_ID}useClientOAuthturns on per-caller connections; each caller gets their own pool, cached by a digest of their token and closed when the entry expires.- The token an MCP client sends is normally issued for Toolbox rather than for the
database, because Toolbox is what it authenticated against.
azureOnBehalfOfexchanges it for a database-scoped token using the on-behalf-of flow. Omitting the block passes the caller's token on unchanged, for clients that already hold one for the database. - A request without a token is refused rather than downgraded to a shared identity, and
user/passwordmust be omitted so there is no fallback identity to reach by accident.
Alternatives considered
EXECUTE ASdriven by a claim the client asserts. Reads as per-user control while remaining spoofable by anything that can call the endpoint, which is worse than a shared login that at least does not claim what it lacks.- One source and one login per group of users. Works today with no code, and is the right answer for a small pilot, but it is group-level and the audit names the instance rather than a person.
Prior art in this repository
- #2721 asks for user impersonation for Looker; #3533 proposes per-query impersonation for Trino. Neither covers SQL Server.
- #3571 covers Toolbox authenticating with an Entra identity of its own, which is a different thing: it removes the password, not the sharing.
I have this implemented, tested and documented, and will open a PR against this issue.
Source: googleapis/mcp-toolbox