Explainer
MCP authentication vs. authorization: what a token allows
Suppose your team uses an agent to help manage incidents. An engineer connects it to an MCP server and approves read and write access. The agent can now add notes to an incident. Can it also close one?
If your team reserves that decision for the on-call lead, a valid token with write access doesn't settle the question. The service also needs to check who's requesting the closure and whether they're allowed to close this incident.
OAuth grants access. Your application has to decide what that access permits in practice.
What does a valid token tell you?
Authentication checks identity: which software agent is making the request, and which person, if any, it's acting for. Authorization checks permission: may that caller perform this action on this incident?
OAuth belongs on the authorization side. Although a person may sign in during the flow, OAuth gives a client permission to access a service. Calling it “just authentication” misses the access it actually grants.
For an HTTP-based MCP connection using OAuth, the client obtains an access token and presents it to the MCP server. The server must validate the token, including that it's intended for that server and hasn't expired. A token issued for another service won't do. These requirements come from the MCP authorization specification.
Validating the token is necessary. Whether it permits closing an incident depends on the access granted and the service's rules for that action.
What does “write access” include?
Imagine the incident server defines two scopes: incidents:read and incidents:write. These are example names, not standard MCP scopes. A read-only grant should let the agent retrieve an incident, but not change it.
The harder case is a write grant that covers both adding a note and closing an incident. Your team wants engineers to add notes, but only on-call leads to close incidents for their own team. The scope tells the server that writes are permitted. It doesn't express those role and team restrictions.
You could introduce a separate scope for closing incidents. You could also check the person's role and team when the call arrives. Either way, the service must enforce the intended restriction. More scope names won't help if nobody checks which team owns the incident.
Use the incident system's records for that ownership check. An agent-supplied argument saying team: payments isn't proof that the incident belongs to Payments.
The agent and the person are different identities
The same incident agent might serve an engineer and an on-call lead. Its software identity stays the same; the person requesting the action changes. A decision based only on the agent's identity can't distinguish them.
When those calls go through Dome, the agent authenticates with its own credential. Gateway access lets it reach the configured tools, while Rules decide whether a governed call is permitted. Rules can use verified user claims when that identity is present. No matching permit means denial, and a matching forbid overrides a permit.
For an application acting on behalf of a person, configure delegated identity and supply verifiable user context on each request. That context supplements the agent's identity; it doesn't replace it. Dome's interactive OAuth flow supplies the human identity from its own access token instead, so that flow doesn't need an additional Act-As header.
Only rely on user claims that your configured verification method establishes. The component checking incident ownership still needs the incident system's data. Connecting through Dome doesn't automatically import every backend permission.
Try the same action with different permissions
In a test environment, keep the agent and incident fixed. Set the example policy to allow engineers to add notes and on-call leads to close incidents for their own team. Then vary the granted scopes and verified caller:
| Test case | Expected result | Reason |
|---|---|---|
| Engineer adds a note with write access | Allow | Both the scope and the team's policy permit it |
| Engineer closes the incident with write access | Deny | Closing requires an on-call lead |
| On-call lead closes the incident with read-only access | Deny | The role doesn't expand the OAuth grant |
| On-call lead closes the incident with write access | Allow | The scope, role, and incident ownership checks pass |
| Unverified caller claims to be the on-call lead | Deny | A claimed role isn't a verified role |
Next, repeat the allowed closure against another team's incident. It should be denied. Record which component makes each decision: the MCP server, Dome's Rules, or the backend application. These checks may live in different places, but all required checks must pass before the incident is closed.
If both people get the same result when the policy calls for different results, check whether the decision-maker received the verified role and the correct incident data. Don't assume the OAuth connection is broken just because an individual operation is denied.
Consent isn't a record of every later action
Approving the integration can let the agent make many calls without asking the user each time. That is useful for recurring work. It doesn't mean the person individually chose every action the agent later attempted.
If someone asks why an incident was closed, the consent record explains the original access grant. You'll also need the call record: which agent acted, which verified person it represented, which incident it targeted, why the action was permitted, and whether the closure succeeded. Collect that evidence from the components that made the decisions and performed the change.
Use Dome's per-end-user tutorial to try different verified identities against Rules. For the wider review of a new integration, use the remote MCP server review checklist.