What an Authorization Server Needs for OAuth-Powered MCP Servers
If you want an MCP server to work with Emcy and other modern OAuth clients, your authorization server needs more than a token endpoint. Here is the practical checklist and how SqlOS maps to it today.
By SqlOS Team
If you want your MCP server to work with a modern OAuth client like Emcy, the right question is not "do I have login?"
It is: does my authorization server expose the exact protocol surface that an MCP client and MCP resource server expect?
That is a narrower question, and it is easier to answer precisely.
The short version is this:
- the MCP server is the OAuth resource server
- your authorization server is the thing that authenticates the user and issues tokens
- those are different responsibilities
SqlOS now provides the full authorization-server side for preregistered public-client MCP flows. That means branded browser auth, /authorize, /token, metadata, JWKS, PKCE, redirect URI validation, sessions, refresh tokens, logout, OIDC, and SAML.
What SqlOS does not do is turn your application into the MCP resource server automatically. Your MCP server still needs to expose protected-resource metadata and validate the bearer token it receives.
If you generate your MCP server with Emcy's openapi-to-mcp, that resource-server side is already handled.
The Split: Authorization Server vs MCP Server
For OAuth-powered MCP, there are two protocol surfaces:
-
Authorization server surface
- browser login/consent flow
- authorization code + PKCE
- token issuance
- authorization server metadata
- JWKS
-
MCP resource server surface
/.well-known/oauth-protected-resourceWWW-Authenticate: Bearer resource_metadata="..."- bearer token validation
- audience/resource enforcement
That split matters because a lot of teams implement only the second half or only the first half, then wonder why modern MCP auth clients still do not work cleanly.
The current MCP authorization spec and RFC 9728 make that split explicit:
- the MCP server exposes protected-resource metadata
- the authorization server exposes authorization-server metadata
- the client discovers both and runs a browser PKCE flow
If you want a concise mental model:
Emcy -> MCP server -> protected-resource metadata -> authorization server
Emcy popup -> authorization server -> user login -> auth code -> token
Emcy -> MCP server with bearer token
What the Authorization Server Must Do
For an Emcy-compatible, modern OAuth-powered MCP server, the authorization server needs to provide at least the following.
1. Publish authorization server metadata
The client needs a standards-facing metadata endpoint so it can discover where to send the browser and where to exchange the code.
That means exposing:
GET /.well-known/oauth-authorization-server
with the important fields:
issuerauthorization_endpointtoken_endpointjwks_uri- supported grant types
- supported PKCE methods
SqlOS provides this today through:
GET /sqlos/auth/.well-known/oauth-authorization-server
and
GET /sqlos/auth/.well-known/jwks.json
2. Support authorization code + PKCE for public clients
This is the browser-client happy path. For Emcy, it is the right path.
The client needs to be able to:
- redirect the user to
/authorize - include
code_challenge - use
code_challenge_method=S256 - exchange the code at
/token - send no client secret for public-client flows
SqlOS supports exactly that narrow V1 surface:
authorization_coderefresh_token- public PKCE clients
token_endpoint_auth_method = none
This is intentional. It is the right scope for browser-first MCP auth.
3. Validate preregistered client IDs and redirect URIs
This is the part people skip when they say "we have OAuth."
Emcy needs to appear to the authorization server as a real client. Today the cleanest way to do that is preregistration.
That means the auth server must know:
client_id- allowed
redirect_uris - whether PKCE is required
- optionally allowed scopes and audience
SqlOS supports that through client records and startup seeding.
For an Emcy integration, the minimum useful setup looks like this:
builder.AddSqlOS<AppDbContext>(options =>
{
options.UseFGA();
options.UseAuthServer(auth =>
{
auth.PublicOrigin = "https://api.example.com";
auth.Issuer = "https://api.example.com/sqlos/auth";
auth.SeedClient(client =>
{
client.ClientId = "emcy-cloud";
client.Name = "Emcy Cloud";
client.RedirectUris = ["https://app.emcy.ai/oauth/callback"];
client.AllowedScopes = ["openid", "profile", "email"];
client.Audience = "https://mcp.example.com";
client.ClientType = "public_pkce";
client.RequirePkce = true;
client.IsFirstParty = true;
});
auth.SeedClient(client =>
{
client.ClientId = "emcy-localhost";
client.Name = "Emcy Local";
client.RedirectUris = ["http://localhost:3100/oauth/callback"];
client.AllowedScopes = ["openid", "profile", "email"];
client.Audience = "https://mcp.example.com";
client.ClientType = "public_pkce";
client.RequirePkce = true;
client.IsFirstParty = true;
});
});
});
var app = builder.Build();
app.MapSqlOS();
That is enough for a real preregistered public-client flow.
4. Issue bearer tokens that the MCP server can validate
Once the code is exchanged, the auth server must mint access tokens with:
- a stable
iss - a verifiable signature
- a stable
kid - a usable
aud - user/session/client context in claims
In SqlOS, access tokens are RS256-signed JWTs, and the public keys are published via JWKS.
That gives the MCP resource server what it needs to validate:
- signature
- issuer
- audience
- expiry
Audience matters
This is the part to be explicit about.
SqlOS uses the client's configured Audience as the token audience. For an MCP server that validates bearer tokens against a resource URL, set that audience to the resource identifier the MCP server expects.
In other words: the auth server and MCP server need to agree on the audience model.
5. Support refresh, logout, and session revocation
For human-authorized agents, login is not the whole story.
You also need:
- refresh token rotation
- replay handling
- session revocation
- browser logout
- logout-all
SqlOS provides all of that today:
- refresh token rotation
- family revocation on replay
- session-scoped logout
- logout-all
- hosted browser logout routes and auth-page session cleanup
That matters because an embedded agent client like Emcy will keep using tokens across multiple tool calls and conversations. Without refresh and revocation, the system is technically usable but operationally weak.
6. Provide a real browser auth experience
Modern OAuth clients do not require a raw IdP page.
They require a valid OAuth authorization flow.
That means the browser entrypoint can absolutely be branded:
- your own login page
- your own signup page
- your own email-first flow
- your own SSO routing
SqlOS AuthPage is built for exactly that shape:
/login/signup/authorize- home realm discovery by email domain
- password
- OIDC providers
- SAML SSO
So if you want your MCP server auth to feel like your application rather than a third-party broker, that is a browser UX problem, not a protocol problem.
What SqlOS Provides Today
On the authorization-server side, SqlOS provides:
GET /authorizePOST /tokenGET /.well-known/oauth-authorization-serverGET /.well-known/jwks.json- hosted
/login,/signup,/logout, and/logged-out - public PKCE clients
- preregistered client records and startup seeding
- redirect URI validation
- RS256 signing keys with rotation
- refresh token rotation and logout
- password auth
- OIDC upstream login
- SAML SSO with home realm discovery
- branded hosted AuthPage
That is enough to act as the authorization server for an Emcy-compatible MCP deployment today.
What SqlOS Does Not Claim Yet
Two things should be said clearly.
SqlOS is not the MCP resource server
Your MCP server still needs to:
- expose
/.well-known/oauth-protected-resource - advertise
resource_metadatainWWW-Authenticate - validate bearer tokens against issuer, signature, expiry, and audience
If you are using Emcy's openapi-to-mcp generator, that side can be generated for you.
SqlOS does not yet ship CIMD or DCR
Today, SqlOS is strongest in the preregistered public-client path.
That is enough for Emcy and similar controlled integrations where the client and server already have a relationship.
It is not the same as saying:
- zero-relationship generic clients are fully solved
- CIMD is already implemented
- DCR is necessary
The current MCP priority order is:
- preregistration
- CIMD
- DCR
- user-entered client info
SqlOS fits cleanly into step 1 today.
That is a strong place to be for real product integrations.
The Practical Checklist
If you are evaluating whether your auth server is ready for an Emcy-compatible MCP server, ask:
- Do I expose
/.well-known/oauth-authorization-server? - Do I expose JWKS?
- Do I support
authorization_code+refresh_token? - Do I require PKCE S256 for public clients?
- Can I preregister a client ID and exact redirect URIs?
- Can I issue JWTs with stable
iss,kid, andaud? - Do I support browser login, refresh, logout, and session revocation?
- Can I keep the whole experience branded and app-owned?
If the answer is yes, you have the auth-server side.
Then your MCP server still needs to do its own resource-server half.
Bottom Line
For OAuth-powered MCP, the authorization server does not need to be magical. It needs to be correct.
SqlOS now gives .NET applications an embedded authorization server that is correct in the ways that matter for Emcy-style MCP auth today:
- standards-facing metadata
- PKCE public clients
- preregistered redirect URIs
- signed JWTs and JWKS
- refresh and logout
- branded browser auth
- OIDC and SAML behind the scenes
That means if you are building an app-owned MCP stack, SqlOS can own the authorization-server side without forcing you into a separate hosted identity product.
And if your MCP server is generated with openapi-to-mcp, you can cover the resource-server side too.
The result is a clean split:
- SqlOS for the authorization server
- your MCP server for the protected resource
- Emcy as the discovery-first browser client
That is a much more practical architecture than "just add login" and hope MCP OAuth works.