Lab 02 — AgentCore Gateway (the MCP tool factory) + Policy (Cedar guardrails)
Phase 20 · Lab 02 · Phase README · Warmup
The problem
An enterprise agent is only as useful as the tools it can reach — and only as safe as the control point in front of them. Two AgentCore services own that surface:
- Gateway turns the backends you already have — a Python function, a Lambda function, an OpenAPI REST API, an existing service — into MCP-compatible tools and exposes them behind one endpoint any agent framework can list and call. It kills the M×N glue problem by speaking one protocol (MCP, from Phase 03) on the front.
- Policy puts a deterministic check in front of every tool call. AgentCore Policy is
written in natural language or Cedar (AWS's open-source policy language). A Cedar policy
permits orforbids a (principal,action,resource) request underwhenconditions, with two rules that make it safe by construction: default-deny and forbid-overrides. Policy intercepts before execution, so a denied call has no side effect.
Add Identity (a credential is required to call a tool) and you have the enterprise tool-access pipeline: authenticate → authorize → execute. You build all three.
What you build
| Piece | What it does | The lesson |
|---|---|---|
FunctionTarget / LambdaTarget / OpenAPITarget | adapt a function, a Lambda handler, an OpenAPI spec into GatewayTools | Gateway is a tool factory, not a tool |
Gateway.list_tools / call_tool | MCP tools/list + tools/call over the registered tools | the MCP surface any agent can drive |
PolicyEngine + permit / forbid + PolicyRule.matches | Cedar evaluation: default-deny, forbid-overrides, when conditions | deterministic authorization |
Identity.verify | a credential is required before any call | authentication is the first gate |
Gateway.call_tool pipeline | authenticate → validate → authorize before execution → run | deny leaves no side effect |
File map
| File | Role |
|---|---|
lab.py | your implementation (fill the # TODOs) |
solution.py | reference implementation + worked example (python solution.py) |
test_lab.py | 24 tests: target conversion, MCP surface, Cedar semantics, identity, no-side-effect-on-deny |
requirements.txt | pytest only |
Run it
pytest test_lab.py -v # your lab.py (red until you implement)
LAB_MODULE=solution pytest test_lab.py -v # the reference (green)
python solution.py # worked example
Success criteria
-
A plain function, a Lambda handler, and an OpenAPI spec each become callable MCP tools; the
Gateway serves
tools/listandtools/callin MCP shape. -
The Policy engine implements Cedar semantics: default-deny, forbid-overrides-permit,
and
whenconditions over the request context. -
Policy runs before execution — a denied
call_toolnever touches the target, proven by an unchanged side-effect log. -
A valid credential is required (missing/invalid →
UNAUTHENTICATED, before policy runs). -
All 24 tests pass under both
labandsolution.
How this maps to the real stack
- Gateway in production ingests an OpenAPI/Smithy spec, a Lambda ARN, or an existing MCP
server and publishes an MCP endpoint (with semantic tool search over large tool sets). Your
Target.to_tools()adapters are that ingestion in miniature; the MCP descriptor +tools/callresult shapes are the real ones from Phase 03. The point it drives home: Gateway is framework-agnostic on both sides — any MCP client (any agent) talks to any backend. - Policy is backed by Cedar, the same engine as Amazon Verified Permissions and AWS
Verified Access. Real Cedar policies read
permit(principal, action, resource) when { ... };withforbidoverriding and an implicit deny — exactly the three rules yourPolicyEngineimplements. AgentCore Policy evaluates on every Gateway tool call, which is why it is a deterministic guardrail: the same request always yields the same decision, unlike an LLM-based check. - Identity integrates any IdP (Cognito, Okta, Entra ID, Auth0) via OAuth and issues workload
credentials to the agent; your
Identity.verifyis the token check in miniature. Authenticate first, then authorize — the ordering incall_toolis the real one. - This is least-privilege at the tool boundary (Phase 10): the model proposes a tool call
(untrusted), and a deterministic policy on your side of the trust boundary decides whether it
runs. No prompt can talk its way past a Cedar
forbid.
Limits of the miniature. Real Cedar has a typed schema, entity hierarchies, and a formal
evaluator; ours matches on exact ids/wildcards with an injected when predicate — the policy
algebra (default-deny, forbid-overrides) is faithful, the surface syntax is not. Real Gateway
does OAuth, rate limiting, semantic tool discovery, and observability we don't model. Argument
schema validation here is a presence check (full JSON-Schema validation is Phase 02).
Extensions (your own machine)
- Add a JSON-Schema validator (Phase 02) so tool arguments are type-checked, not just present.
- Add audit logging + OTEL spans around
call_tool(Phase 14) so every allow/deny is a trace event — the observability half of a real Gateway. - Parse a tiny subset of real Cedar syntax (
permit(...) when {...};) intoPolicyRules so your policies are text, not Python. - Wire a real MCP client (Phase 03) to
list_tools/call_tooland drive the Gateway from an actual agent loop.
Interview / resume signal
"Built a miniature Bedrock AgentCore Gateway + Policy: adapted functions, Lambda handlers, and OpenAPI specs into MCP tools behind one endpoint, and gated every call with a Cedar-style engine (default-deny, forbid-overrides, conditional
whenclauses) that intercepts before execution — least-privilege tool access enforced deterministically on the trusted side of the boundary."