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:

  1. 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.
  2. 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 or forbids a (principal, action, resource) request under when conditions, 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

PieceWhat it doesThe lesson
FunctionTarget / LambdaTarget / OpenAPITargetadapt a function, a Lambda handler, an OpenAPI spec into GatewayToolsGateway is a tool factory, not a tool
Gateway.list_tools / call_toolMCP tools/list + tools/call over the registered toolsthe MCP surface any agent can drive
PolicyEngine + permit / forbid + PolicyRule.matchesCedar evaluation: default-deny, forbid-overrides, when conditionsdeterministic authorization
Identity.verifya credential is required before any callauthentication is the first gate
Gateway.call_tool pipelineauthenticate → validate → authorize before execution → rundeny leaves no side effect

File map

FileRole
lab.pyyour implementation (fill the # TODOs)
solution.pyreference implementation + worked example (python solution.py)
test_lab.py24 tests: target conversion, MCP surface, Cedar semantics, identity, no-side-effect-on-deny
requirements.txtpytest 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/list and tools/call in MCP shape.
  • The Policy engine implements Cedar semantics: default-deny, forbid-overrides-permit, and when conditions over the request context.
  • Policy runs before execution — a denied call_tool never 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 lab and solution.

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/call result 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 { ... }; with forbid overriding and an implicit deny — exactly the three rules your PolicyEngine implements. 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.verify is the token check in miniature. Authenticate first, then authorize — the ordering in call_tool is 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 {...};) into PolicyRules so your policies are text, not Python.
  • Wire a real MCP client (Phase 03) to list_tools/call_tool and 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 when clauses) that intercepts before execution — least-privilege tool access enforced deterministically on the trusted side of the boundary."