MCP Tool Authorization After Login: A Layered Control Checklist

An authenticated author asks an agent to update a risk record. Should the request succeed? You still need to know whether that author may use the tool, whether the record belongs to their tenant, and whether they own it.
MCP tool authorization needs those decisions at the points where they can be enforced: token validation at the gateway, tool policy before invocation, and resource checks inside the tool and its data operation. This guide uses AWS's Model Context Protocol (MCP) authorization pattern as a reference, then follows one hypothetical update_risk request through those boundaries.
Key Takeaways
- A tool allowlist permits an operation; tenant and owner rules decide which records that operation may affect.
- Only trusted identity context should supply the caller and business tenant. Tool arguments supply the requested change, not authority to make it.
- Unknown authorization stops the write. An unknown outcome after submitting a write requires reconciliation before a retry.
Where AWS puts the controls
AWS's Amazon Quick walkthrough connects Entra ID, AgentCore Gateway, a Lambda REQUEST interceptor, tool functions, and DynamoDB. It assumes the AWS resources already exist. Its four named gates have different activation conditions:
| Gate | Enforcement and activation |
|---|---|
| MFA | Entra Conditional Access, when configured for the caller and resource application, requires MFA before token issuance. Optional REQUIRE_MFA adds an interceptor check for IdPs that emit usable amr evidence. |
| Country | REQUIRE_COUNTRY=true checks the token's ctry against ALLOWED_COUNTRIES. |
| Group RBAC | Core gate: map group IDs to policy; no matching group means deny. |
| Tool permission | Core gate: require the requested tool in the matched policy's allowlist. |
An omitted conditional flag disables that interceptor check. It does not disable an independently configured IdP policy. AWS describes admins as bypassing conditional gates; that does not establish a bypass of JWT validation, core tool policy, or object rules. Define each permitted exception explicitly.
The gateway and tool also have different jobs. AgentCore documents gateway, tool, operation, and parameter controls. Its REQUEST interceptor runs before the target and can return a denial immediately. Use that boundary for tool decisions, then enforce the requested record's permissions where the tool accesses data.
A hypothetical update_risk request, three different outcomes
Suppose your application stores risk R-42 in business tenant north. Ada owns it; its status is open and its version is 7. Your application policy lets authors change the status of risks they own within their tenant. Readers cannot update risks.
This is an illustrative design, with fictional identities and data. The AWS walkthrough labels update_risk with an owner check and describes tenant-scoped keys. The rules, fields, and transaction below define our hypothetical application.
Each caller asks for the same change:
{
"risk_id": "R-42",
"expected_version": 7,
"patch": { "status": "mitigated" }
}
Keep two inputs separate. Trusted context identifies the caller through a validated issuer and subject, maps that identity to an internal principal, resolves the allowed business tenant server-side, and supplies the effective tool policy. It also carries a request correlation ID. In Ada's case, the principal is user-ada and the tenant is north. This example does not assume an IdP tenant claim is automatically the application's business tenant.
Untrusted arguments contain risk_id, expected_version, and patch. Validate their types and allowed fields. A claimed version is a comparison value, not proof of ownership. This tool accepts only a status patch; supplied tenant_id, owner_id, actor, or role fields are rejected rather than merged into trusted context. The owner used for authorization comes from the stored record.
Assume all three callers have valid tokens, recognized groups, and satisfy the active conditional gates. Evaluate each row independently against the original version of R-42:
| Caller and relationship | Tool decision | Object decision | Intended result |
|---|---|---|---|
Rhea: reader in north |
update_risk is not allowed |
Tool never loads the record | Tool denial; no risk mutation; record the policy refusal |
Ada: author in north, owner of R-42 |
Allowed | Tenant and owner match | Commit status mitigated, version 8, and a success audit record together |
Ben: author in north, not the owner |
Allowed | Stored owner does not match user-ben |
Object denial; no risk mutation; record the ownership refusal |
Ben's request shows why passing the gateway is insufficient. The tool can read the authorization metadata it needs, but it should not return the protected record or apply the patch when ownership fails. Denial logging is an intended side effect; the business record stays unchanged.
Now have Ada request R-99, which exists only in tenant south. Her trusted tenant remains north, so the tool looks up (north, R-99) and finds nothing. It must not fall back to a global lookup. Return a consistent not-found/denied response without revealing whether another tenant has that ID. Adding tenant_id: south to the arguments instead fails input validation.
Turn the example into an enforceable policy path
The following structured steps define the hypothetical application's required behavior. They are a design walkthrough; the storage and context mechanisms still need implementation and testing.
Establish identity and readiness. Validate the credential for the intended resource. Require valid policy configuration and resolve a trusted principal and business tenant. A missing tenant mapping or unavailable policy service stops processing before a business write. Distinguish unavailable authorization from a confirmed permission denial internally.
Decide tool access at the gateway. Evaluate the active conditional checks and the core group/tool policy. An unknown group, an unauthorized tool, or an unresolved required check stops invocation. Rhea stops here. An approved admin exception applies only to its named conditional check; it does not skip the core checks.
Authenticate context at the tool and validate arguments. Accept forwarded identity only through a protected, authenticated server-to-server boundary bound to this request. Otherwise, independently validate the caller and reconstruct context. Recheck the applicable policy so an alternate invocation route cannot manufacture a permit. Then validate the argument schema and status-only field allowlist. Missing trusted context, forged identity fields, and evaluator errors all stop the write.
Read within the tenant and check ownership. Fetch by trusted tenant plus validated risk ID. Do not search other tenants when the lookup misses. For a found record, require a known owner equal to the trusted principal. Ben stops here. A missing owner or a failed lookup is insufficient to authorize a mutation; do not interpret either as an unrestricted record.
Resolve retries, then commit conditionally. Bind a stable operation ID server-side to the tenant, principal, tool, and normalized payload. A retry must match that binding and pass steps 1–4 again. Check its recorded outcome before any new write: return a confirmed success without repeating the mutation; stop to reconcile a potentially submitted operation whose outcome is still unknown. For a new operation or one confirmed not committed, validate the
opentomitigatedtransition and the submitted version. At commit, require matching tenant, owner, version, and starting status; update the status and increment the version. Commit this guarded update and a uniquely keyed success audit entry containing the operation ID and result atomically. If either write cannot commit, neither should be applied.Report the actual outcome. Return success only for a confirmed commit. A known condition failure means this attempted mutation did not commit; record a conflict or denial rather than success. If the commit response times out, the outcome is unknown: do not assert that the data is unchanged. Preserve the operation ID and enter the reconciliation path in step 5 before any further write. An absent result alone is insufficient to conclude that an in-flight operation failed.
For a DynamoDB implementation, transactions support all-or-nothing changes across items and tables. That is one possible building block for step 5. You still have to supply the authorization conditions, audit write, and retry handling; a transaction does not choose the policy for you.
Under this contract, Ada's authorized request reaches the commit, while Rhea and Ben stop before it. If someone changes R-42's owner or version between Ada's read and write, the condition prevents this update from committing. The transaction protects this operation's writes; it does not imply that no other operation changed the record.
Make required policy explicit
An optional software feature can be mandatory for a particular deployment. If your environment requires country checking, treat a missing activation setting or country allowlist as invalid deployment configuration. Record an intentionally disabled gate explicitly so operators can distinguish it from an omission.
Apply the same approach to core configuration: issuer/audience settings, group mappings, tool policies, and the trusted-context mechanism must be valid before serving protected traffic. Refusing startup or failing readiness is a design recommendation here, not a verified behavior of the AWS sample.
At request time, only an affirmative authorization result may proceed. An unrecognized group is a denial; a failed tenant-mapping lookup is an authorization error. Both stop the mutation, but they deserve different operational signals. AWS's fail-safe-default guidance supports denying access when permission cannot be determined.
Validation checklist: change one condition at a time
Use the same fixture in a test environment, restoring its starting state before independent cases. Compare tool invocation records, stored risk state, and audit outcomes. A message in the agent UI is not enough to establish what happened underneath.
| Change from the example | Boundary to inspect | Expected evidence under the stated policy |
|---|---|---|
| Missing, expired, wrong-issuer, or wrong-audience token | Gateway | Authentication rejection; no tool invocation |
| Remove recognized groups or request as Rhea | Core policy | Group/tool refusal; no target invocation or risk write |
| Remove required country/MFA evidence while its interceptor check is enabled | Active conditional gate | Refusal before invocation; test IdP enforcement separately |
| Request as Ben | Tool ownership check | Tool runs; owner comparison refuses the patch; no business mutation |
Ada requests R-99 or supplies a foreign tenant argument |
Scoped lookup/input validation | No lookup in south; no record disclosure or mutation |
| Remove tenant/owner context or make policy lookup fail | Context/resource authorization | No write; unknown state does not become an allow decision |
| Change owner/version after Ada's read | Conditional commit | This operation does not commit; conflict is distinguishable from success |
| Force the success audit write to fail definitively | Atomic commit | Neither the risk update nor its success audit entry commits |
| Lose the commit response, then retry | Outcome reconciliation | Resolve the original operation; no unsupported “unchanged” claim or blind second mutation |
| Use an admin exception | Conditional policy | Only the named check is bypassed; tool and object rules still apply |
| Remove required configuration | Deployment/readiness | Protected traffic is not served |
| Invoke the target directly | Tool trust boundary | Verified context and applicable policy are still required |
In AWS's documented behavior, missing/invalid tokens yield 401; valid tokens lacking required scopes can yield 403. The walkthrough also uses 403 for interceptor gate denials. A 403 therefore does not identify the failing layer by itself. Use the response details and correlated records, as described in AgentCore inbound authorization. Application validation, conflicts, and unavailable dependencies need their own handling.
Make the audit trail explain the decision
For this example, correlate the request ID, trusted principal, business tenant, tool, policy version, decision stage, and reason. Record a success only after the mutation commits. Rhea's tool denial and Ben's ownership denial are separate decision events; neither should look like a successful update.
Successful mutation auditing is part of the example's transaction contract. Denial events travel through a separate logging path. If that path fails, keep the request denied and raise an operational alert; do not describe a missing event as recorded. After a timeout, preserve an unknown outcome until reconciliation supplies a confirmed result.
The AWS walkthrough describes immutable mutation records, but atomicity alone does not establish immutability. Verify audit access controls and retention independently. Keep bearer tokens and unnecessary personal data out of events. These measures do not establish a compliance guarantee.
Start with one real tool and reproduce the three decisions: reader denied, authorized owner allowed, non-owner denied. Then change the tenant, remove required context, and interrupt the commit response. When each result can be traced to its enforcing layer and its actual data outcome, you have a concrete basis for extending the policy to the next tool.
Authentication establishes identity; tool policy and tenant-scoped ownership decide what a request may change. Follow a hypothetical update_risk request through authorization, conditional writes, auditing, and retry reconciliation. The design walkthrough is not a tested deployment or a security certification.


