AWS Builder Center
Building Secure MCP Tools on AWS: Authorization and Abuse Tests

Building Secure MCP Tools on AWS: Authorization and Abuse Tests

Build secure MCP tools on AWS with trusted identity, tenant isolation, authorization checks, approval controls and abuse tests that protect customer records.

Software Enthusiast | AWS DevOps | Automation & n8n | WeblineIndia
An AI support assistant can summarize a customer ticket, retrieve account details and update a case through the same conversation. Once those actions become MCP tools, a mistaken request can affect real customer records. A tool call may be valid JSON and still request something the user has no permission to do.
The Model Context Protocol provides a standard way for agents to discover and call tools. The application still needs to decide which caller can perform each action and which records that action may touch. This article uses a customer-support example to explain an AWS architecture, the checks around each tool and the abuse tests that should pass before release. The examples are a proposed design, not results from a deployed benchmark.

Start with a tool that has a narrow job

Consider a support assistant serving several customer organizations. It needs two capabilities: reading a ticket and changing its status. Expose those as separate tools, each with a small input schema, rather than giving the agent a general database query tool.
The read tool should accept a ticket identifier. The update tool should accept a ticket identifier, an allowed status and a request identifier for duplicate detection. Neither tool needs an arbitrary SQL statement, a database table name or a tenant identifier selected by the model. Keeping those choices inside the service reduces the number of security decisions exposed through tool arguments.
Give the tools clear descriptions so the agent can choose between them, but enforce every restriction in code or policy. A description that says “only access the current customer's tickets” helps the model understand the task. It does not stop a crafted request from naming another customer's ticket.

Put an authorization boundary before execution

One AWS design uses Amazon Bedrock AgentCore Gateway to expose the tools, AWS Lambda for their business logic and Amazon DynamoDB for ticket records. An identity provider supplies the caller's credentials, while a policy layer determines which tools the caller can invoke.
The AgentCore Gateway documentation  describes how Gateway can expose Lambda functions as MCP tools and support inbound authentication configurations such as OAuth JWTs and AWS IAM. Select the configuration that matches the client and identity model. For this support application, use an authenticated entry point and retain enough trusted caller context to enforce customer permissions.
Keep the gateway's AWS permissions separate from the customer's application permissions. A gateway role may be allowed to invoke the ticket Lambda function and that function may be allowed to access the ticket table. Those AWS permissions do not establish that a particular customer can read a particular ticket. The application must enforce that relationship before returning data or writing a change.

Establish identity without trusting tool arguments

Authenticate the caller before using any identity claims to authorize a request. For a JWT-based design, the validation path must check the signature, issuer, intended audience and expiry according to the configured identity provider. Decoding a token and reading its fields is not sufficient.
Resolve the caller's tenant membership and allowed actions from verified claims and, where needed, an authoritative membership store. Never let a tool argument such as “tenant,” “role,” or “administrator” become evidence of permission. A customer can request an action, but the service must decide whether that customer is entitled to it.
Document how trusted identity reaches the business logic. Do not assume a Lambda target receives the end user's identity merely because the gateway authenticated the request. Configure and test the supported propagation path or perform the required checks at a trusted boundary that has access to the validated identity. Reject requests when required identity context is missing or cannot be verified.

Check tool permissions and record permissions separately

The authorization design should answer two questions. Can this caller invoke the status-update tool? Can this caller change this specific ticket to the requested status? A support viewer may be allowed to read tickets but have no permission to update them, while an editor may update tickets only within an assigned customer organization.
Policy in Amazon Bedrock AgentCore  supports Cedar policies for gateway tool access and evaluates applicable policies for tool invocations. Use that layer for rules that fit the gateway's trusted identity and request context. Keep record ownership and business-state checks in the backend when they require authoritative data that the gateway does not have.
For the proposed DynamoDB design, group ticket records under a tenant partition key and use the ticket identifier as the sort key. Build the partition key from trusted tenant context rather than from a model-supplied value. A request for another tenant's ticket should never trigger a fallback search across all tenants.
Apply the same scope to the write operation. If the update depends on the ticket's current status or version, use a conditional write so a concurrent change cannot bypass the rule. Avoid reading a record, checking it and then performing an unrestricted write after the record may have changed.

Validate arguments before they reach business logic

Treat every tool call as untrusted input, even when it comes from your own agent. Validate required fields, data types, allowed status values, identifier formats and size limits. Reject unexpected fields rather than silently accepting a future “admin override” argument that was never part of the contract.
Schema validation handles structure, while business validation handles meaning. A status may be part of the allowed set but still be an invalid transition for the current ticket. The service should check that transition and reject it before any update occurs.
AWS's guide to fine-grained access control with Gateway interceptors  shows how custom Lambda interceptors can apply request checks before tool execution. An interceptor is useful for shared checks, but the backend should still protect its own data operations. Verify the deployed request format and enforcement behavior instead of assuming that an illustrative handler is ready to deploy.

Keep approvals outside the model's control

Reading a ticket and closing a sensitive customer case have different consequences. For selected actions, require an approval created through a trusted user interface or approval service. A Boolean argument saying “approved” cannot establish that a human reviewed the action.
Bind an approval to the caller, tenant, ticket, proposed change and expiry. If any of those details change, the approval should no longer authorize the request. Check the caller's current permissions again when executing the action, because permission may have been removed after approval was granted.
Design retries alongside approval handling. A timeout can leave the client uncertain whether a write succeeded, so a repeated request should return the stored outcome for the same operation rather than repeat its side effects. Reject reuse of the request identifier with different arguments and make duplicate detection and mutation atomic where the storage design permits it.

Test the boundary directly before testing the agent

Begin with deterministic tests against the authorization code and tool backend. These tests should construct requests that the normal assistant might never generate. A read-only user calling an update tool is still a case the service must handle correctly.
Then run tests through the deployed gateway in an isolated environment. A local unit test cannot prove that the gateway uses the intended authorizer, that a policy engine is attached correctly or that trusted identity reaches the correct component. Include allowed requests as controls so an accidentally blocked service cannot appear secure simply because every request fails.
Use a compact abuse matrix to define the expected behavior and the evidence needed to verify it:
Test caseExpected behaviorEvidence to inspect
Missing, expired or wrong-audience tokenReject before tool executionGateway decision and no backend invocation
Viewer calls the update toolDeny the write actionAuthorization decision and unchanged record
Tenant A requests Tenant B's ticketReturn no foreign ticket dataResponse content and tenant-scoped access
Request adds an administrator or tenant overrideReject unexpected fieldsValidation result and no data operation
Status change lacks required approvalDeny the changeApproval decision and unchanged record
Approval is reused for different argumentsDeny the changed operationApproval binding check and no mutation
Successful write is retriedReturn the existing resultOne recorded mutation and stable outcome
Policy or membership service is unavailableDeny actions requiring that decisionControlled error and no unauthorized access
For each denial, check more than the response message. Inspect the ticket version, stored status and any downstream events to confirm there was no mutation. For reads, check the complete response for leaked customer fields or sensitive error details. Avoid errors that reveal whether a foreign ticket exists.

Add prompt injection tests without depending on refusal

After the direct checks pass, introduce hostile text into places the agent reads, such as ticket descriptions and retrieved notes. A test ticket might instruct the assistant to ignore earlier rules, fetch another customer's case or call the update tool with an invented approval. Keep these fixtures in a test tenant with synthetic records.
Record whether the model requests the forbidden action, whether the authorization boundary blocks it and whether the backend remains unchanged. A model refusal is useful behavior, but it does not exercise the tool's deny path. Pair the agent test with a direct forbidden call so the boundary is tested even when the model refuses.
Repeat these cases when changing models, system instructions, retrieval behavior or tool descriptions. Model behavior can vary across runs. Deterministic authorization should continue to enforce the same permissions regardless of which tool call the model proposes.

Make security decisions visible without logging customer content

Log a correlation identifier, caller identifier, tenant identifier, tool name, decision and a safe reason code. Record the target operation's outcome and policy version where available so an investigation can connect the request to the rule that allowed or blocked it.
Keep tokens, approval credentials and full ticket descriptions out of routine logs. Redact sensitive values, restrict log access and define a retention period appropriate to the application. An audit trail should help explain access decisions without creating another repository of customer data.
Alert on repeated cross-tenant attempts, denied writes, unusual call volume and abrupt changes in failure rates. These signals need context: a burst of failures might indicate an attack, an expired credential or a broken deployment. Preserve enough evidence to distinguish those cases without exposing private records.

Release when the checks prove the boundary holds

A useful release gate combines allowed-path tests, direct authorization tests, gateway tests and agent-driven abuse cases. Store the results with the relevant policy and tool versions so reviewers can see exactly which behavior was verified. Treat new tools and broader permissions as changes that require fresh coverage.
The model decides which action to propose; the service determines whether that action may execute. Narrow tools, trusted identity, tenant-scoped data access and observable deny paths make that division enforceable. When an agent attempts the wrong action, the release evidence should show that the system rejected it and protected the underlying records.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article