Govern every tool call

Govern

Enterprise MCP Governance Gateway

Deployable governance layer that places Amazon Bedrock AgentCore Gateway in front of MCP tool servers with JWT authentication, Cedar policies in ENFORCE mode, Lambda interceptors and a Bedrock Guardrail.

First deploy
About 5 minutes for the five quickstart steps; the gateway stack itself takes about 2 minutes source for first deploy (opens in new tab)
Validated regions
us-west-2 by default; configurable; no tested-regions list is published source for validated regions (opens in new tab)
Infrastructure as code
AWS CDK (Python) with AWS::BedrockAgentCore L1 constructs; CDK CLI pinned to 2.1129.0 source for infrastructure as code (opens in new tab)
  1. MCP clientSends each request over HTTPS with a Cognito access token as the Bearer JWT.
  2. AgentCore GatewayValidates the JWT against the Cognito OIDC discovery URL (CUSTOM_JWT authorizer).
  3. Request interceptorLambda that writes the audit record and blocks SQL-injection and abuse patterns.
  4. Cedar policy engineEvaluates the Cedar policies in ENFORCE mode and allows or denies the tool call.
  5. Tool LambdaThe target Lambda runs with the GATEWAY_IAM_ROLE credential.
  6. Response interceptorLambda that redacts PII and truncates large payloads before the result returns to the client.
Request path as drawn in the README. README.md: Verified architecture (opens in new tab)

What it is

  • Single governed MCP endpoint in front of MCP tool servers
  • Cognito OIDC JWT authentication (CUSTOM_JWT authorizer)
  • Cedar policy engine in ENFORCE mode, one policy per statement
  • Request and response Lambda interceptors (SQL-injection blocking, business-hours gating, payload truncation)
  • Managed Bedrock Guardrail with regex PII redaction as defense in depth, plus structured audit logging
  • Optional per-user OAuth 3LO connectors (Atlassian)
  • Five governance tests that run against the live gateway, never mocked

Source: README.md (opens in new tab)

Status

Version
not documented No version badge, tag or CHANGELOG in the project folder.
Status
Sample and demonstration stack; not hardened for production README.md: Security notes (opens in new tab)

Recent changes: commit history for enterprise-mcp-governance-gateway on GitHub (opens in new tab)

No open advisories are tracked for this project.

At a glance

MCP Gateway facts with a source for every value
FactValueSource
Validated regionsus-west-2 by default; configurable; no tested-regions list is publishedREADME.md (opens in new tab)
Default regionus-west-2README.md: Deploy (opens in new tab)
What it deploysAgentCore Gateway and Cedar policy engine, four Lambdas (two interceptors, two targets), a Cognito user pool, a Secrets Manager secret, a customer-managed KMS key, SSM parameters and a Bedrock GuardrailREADME.md (opens in new tab)
First deployAbout 5 minutes for the five quickstart steps; the gateway stack itself takes about 2 minutesREADME.md: Quickstart (opens in new tab)
Hands-on timeAbout 5 minutes for the quickstart; the governance walkthrough has no stated durationREADME.md: Quickstart (opens in new tab)
Costnot documented The README lists what the stack creates (AgentCore Gateway and policy engine, four Lambdas, a Cognito user pool, a Secrets Manager secret, a customer-managed KMS key, SSM parameters, and a Bedrock Guardrail) but publishes no cost figure.none
Infrastructure as codeAWS CDK (Python) with AWS::BedrockAgentCore L1 constructs; CDK CLI pinned to 2.1129.0README.md: Quickstart (opens in new tab)
Account topologySingle account; one gateway stack plus two optional connector stacksREADME.md: Deploy (opens in new tab)
Auth and policyCognito OIDC JWT (CUSTOM_JWT authorizer) and a Cedar policy engine attached in ENFORCE modeREADME.md: Verified architecture (opens in new tab)
StatusSample and demonstration stack; not hardened for productionREADME.md: Security notes (opens in new tab)
TeardownDisconnect the MCP client first, then cdk destroy EnterpriseMcpGatewayStack (destroy the two connector stacks first if you deployed them). CloudWatch log groups are not removed.README.md: Teardown (opens in new tab)
Versionnot documented No version badge, tag or CHANGELOG in the project folder.none

Compare all four projects

Quickstart

Deploy and prove the governance

Prerequisites: MCP Gateway prerequisites on the Start pages.

Five steps, about 5 minutes. Prerequisites: Node.js with the CDK CLI at 2.1129.0, Python 3.12+, and AWS credentials for the target account.

Expected time: About 5 minutes for the five quickstart steps; the gateway stack itself takes about 2 minutes README.md: Quickstart (opens in new tab)

  1. Set the region and install the Python dependencies

    export AWS_REGION=us-west-2
    python3 -m venv .venv && source .venv/bin/activate
    pip install -r cdk/requirements.txt -r requirements-dev.txt

    Run from the enterprise-mcp-governance-gateway folder of the cloned repository.

    Source: README.md: Quickstart (opens in new tab)

  2. 1. Deploy the gateway

    cd cdk && cdk bootstrap && cdk deploy EnterpriseMcpGatewayStack --require-approval never && cd ..

    One stack, about 2 minutes.

    Source: README.md: Quickstart (opens in new tab)

  3. 2. Create the demo users

    bash scripts/seed-demo-users.sh

    CloudFormation cannot set a Cognito password, so a post-deploy script does it.

    Source: README.md: Quickstart (opens in new tab)

  4. 3. Mint a JWT as admin@example.com

    source scripts/get-token.sh

    Source: README.md: Quickstart (opens in new tab)

  5. 4. Run the five governance tests against the live gateway

    GATEWAY_URL="$(aws ssm get-parameter --region "$AWS_REGION" \
      --name /enterprise-mcp-gateway/gateway/url --query Parameter.Value --output text)" \
    AUTH_TOKEN="$AGENTCORE_JWT" python3 -m pytest tests/integration -q

    Expect 5 passed. Three Atlassian tests skip unless you also deploy the connector.

    Source: README.md: Quickstart (opens in new tab)

  6. 5. Drive it from a real agent

    bash scripts/connect-coding-agent.sh kiro     # or: claude-register

    Then work through the governance walkthrough in the README to see allow, deny, block and redact.

    Source: README.md: Quickstart (opens in new tab)

See it work

The README walkthrough drives the governed tools from a real coding agent. The outcome is produced by the gateway, not the agent. Cedar both hides a forbidden tool and denies it, and hiding wins. An agent therefore never triggers the deny in case C; the README shows a direct curl call that does. README.md (opens in new tab)

Six queries from the README walkthrough and the governance outcome each one produces
QueryAsk the agent toExpected governance outcomeEnforced bySource
AList every tool from enterprise-gatewayOnly the Cedar-permitted tools appear (for example DocsAPI___get_page, search_pages, list_spaces, execute_query, export_pii_report). Forbidden tools like drop_table are filtered out of the list entirely.Cedar (visibility)README row A (opens in new tab)
BCall DocsAPI___get_page with pageId arch-overviewSucceeds and returns the page content.Cedar allow-docs-read (ALLOW)README row B (opens in new tab)
CCall DatabaseAPI___drop_table with tableName usersThe agent cannot even attempt it. Cedar filtered the tool out at step A, so a well-behaved agent replies that no such tool exists. Called directly with curl, the gateway answers Tool Execution Denied and names forbid_destructive_db.Cedar forbid-destructive-db (DENY)README row C (opens in new tab)
DCall DatabaseAPI___execute_query with query DROP TABLE users; --Blocked before execution with the message Request blocked: dangerous SQL pattern detected.REQUEST interceptor LambdaREADME row D (opens in new tab)
ECall DatabaseAPI___export_pii_report for department engineeringPII comes back masked, for example Name: {NAME}, SSN: {US_SOCIAL_SECURITY_NUMBER}. The {TYPE} form means the managed Bedrock Guardrail anonymized it. With the guardrail disabled the local regex backstop produces [REDACTED_SSN] instead. Business hours only; see the note below.RESPONSE interceptor + GuardrailREADME row E (opens in new tab)
FCall DocsAPI___create_page (a write)Denied for demo users with No policy applies to the request (denied by default). create_page is gated on role admin, and custom:role never reaches the access token the gateway validates.Cedar (default deny)README row F (opens in new tab)

export_pii_report and query_audit_logs are gated to 09:00 to 17:00 UTC by the request interceptor. Outside that window they are blocked before execution. README.md (opens in new tab)

Source: README.md (opens in new tab)

Compared with the workshop

How this differs from the workshop's Module 3b

Both place an Amazon Bedrock AgentCore Gateway with Cedar policies and interceptors in front of tools. The workshop module is a teaching build: it creates an AgentCore Registry, registers three MCP tools, walks through the Publisher and Admin approval workflow, and attaches its Cedar policy in LOG_ONLY mode because ENFORCE would empty tools/list there. This project is a deployable governance layer: the policy engine runs in ENFORCE mode with one single-action statement per tool, and five integration tests run against the live gateway.

The glossary lists what MCP Gateway means in each of the four projects.

Source: workshop-building-agentic-ai-platform/README.md: What you'll build (opens in new tab), module-3b/index.en.md (opens in new tab), step-7/index.en.md (opens in new tab), enterprise-mcp-governance-gateway/README.md: Verified architecture (opens in new tab), forbid-destructive-db.cedar (opens in new tab), enterprise-mcp-governance-gateway/README.md: Quickstart (opens in new tab)

Cedar policy

forbid-destructive-db.cedar carries no role or identity gate, so every statement in it applies to the seeded users. The CDK deploys one policy per statement. The notes below follow the statements in file order.

// Policy 3: Forbid destructive database operations for everyone, and constrain
// execute_query to read-only SELECT statements.
// Resource ARN is substituted at deploy time (token __GATEWAY_ARN__).

// Block ALL users from calling destructive database tools regardless of role.
// One single-action forbid per tool: the policy engine does not match Cedar
// action set-membership (`action in [...]`) for tool authorization.
forbid(
  principal is AgentCore::OAuthUser,
  action == AgentCore::Action::"DatabaseAPI___drop_table",
  resource == AgentCore::Gateway::"__GATEWAY_ARN__"
);

forbid(
  principal is AgentCore::OAuthUser,
  action == AgentCore::Action::"DatabaseAPI___truncate_table",
  resource == AgentCore::Gateway::"__GATEWAY_ARN__"
);

forbid(
  principal is AgentCore::OAuthUser,
  action == AgentCore::Action::"DatabaseAPI___delete_records",
  resource == AgentCore::Gateway::"__GATEWAY_ARN__"
);

// Allow read-only database queries for all authenticated users.
permit(
  principal is AgentCore::OAuthUser,
  action == AgentCore::Action::"DatabaseAPI___execute_query",
  resource == AgentCore::Gateway::"__GATEWAY_ARN__"
)
when {
  context.input has query &&
  context.input.query like "SELECT *"
};

// Forbid execute_query if it contains dangerous patterns.
forbid(
  principal is AgentCore::OAuthUser,
  action == AgentCore::Action::"DatabaseAPI___execute_query",
  resource == AgentCore::Gateway::"__GATEWAY_ARN__"
)
when {
  context.input has query &&
  (context.input.query like "*DROP*" ||
   context.input.query like "*DELETE*" ||
   context.input.query like "*TRUNCATE*" ||
   context.input.query like "*INSERT*" ||
   context.input.query like "*UPDATE*")
};
  1. forbid DatabaseAPI___drop_table. Forbids drop_table for every authenticated user, whatever their role.

    Source: forbid-destructive-db.cedar (opens in new tab)

  2. forbid DatabaseAPI___truncate_table. Forbids truncate_table for everyone. Each tool gets its own single-action statement because the policy engine does not match Cedar action set-membership.

    Source: forbid-destructive-db.cedar (opens in new tab)

  3. forbid DatabaseAPI___delete_records. Forbids delete_records for everyone.

    Source: forbid-destructive-db.cedar (opens in new tab)

  4. permit DatabaseAPI___execute_query. Permits execute_query only when the query argument matches like "SELECT *". In Cedar the asterisk is a wildcard, so this is a case-sensitive prefix match on SELECT followed by a space, not a literal SELECT * statement. A lower-case select does not match and falls to the default deny.

    Source: forbid-destructive-db.cedar (opens in new tab)

  5. forbid DatabaseAPI___execute_query. Forbids execute_query when the query contains DROP, DELETE, TRUNCATE, INSERT or UPDATE in upper case. Cedar like is case-sensitive; the case-insensitive catch is the request interceptor regex, compiled with re.IGNORECASE, which runs before Cedar.

    Source: forbid-destructive-db.cedar (opens in new tab), index.py (opens in new tab)

All 7 policy files, each with a purpose line

Claims that reach Cedar

The gateway validates the Cognito access token, and its claims become Cedar principal tags. The ID token is rejected, so claims that live only there never reach a policy.

Cognito token claims and whether each one becomes a Cedar principal tag
ClaimTokenReaches CedarNoteSource
subAccess tokenYes, as a tagBecomes the sub principal tag.README.md: Verified architecture (opens in new tab)
usernameAccess tokenYes, as a tagWith this Cognito setup the access-token username is the user sub UUID, not the email address.sensitive-tool-restrict.cedar (opens in new tab)
scopeAccess tokenYes, as a tagThe demo users share the default aws.cognito.signin.user.admin scope.sensitive-tool-restrict.cedar (opens in new tab)
emailID token onlyNoThe gateway rejects the ID token because it carries no scope claim.README.md (opens in new tab)
custom:roleID token onlyNoRole-gated permits therefore never fire for the seeded users.README.md (opens in new tab)

Before you demo

Three of the five active policy files carry gates that never engage for the seeded users

admin-write-only.cedar permits writes only for role admin. block-large-queries.cedar lifts its bulk_export forbid only for role data-engineer. sensitive-tool-restrict.cedar permits query_audit_logs only for a username equal to an email address. custom:role lives in the ID token only and the access-token username is a sub UUID, so none of these conditions holds for the seeded users. The result is deny, which the files describe as the safe default.

Source: admin-write-only.cedar (opens in new tab), block-large-queries.cedar (opens in new tab), sensitive-tool-restrict.cedar (opens in new tab), README.md: Tracked production hardening (not in this sample) (opens in new tab)

The managed Guardrail is wired to the unpinned DRAFT version

gateway_stack.py sets GUARDRAIL_VERSION to DRAFT for the interceptor Lambdas, and the guardrail helper defaults to DRAFT when the variable is unset. DRAFT changes whenever the guardrail is edited, so what is enforced is not pinned to a numbered version.

Source: gateway_stack.py (opens in new tab), guardrail.py (opens in new tab)

On a Guardrail API error the interceptors log and continue

The README states that a guardrail API error on the request path is logged and the local controls still apply, and the request interceptor comments say not to fail the request on a guardrail outage. The regex SQL blocking and the regex PII redaction still run. The README suggests tuning to fail-closed for stricter environments.

Source: enterprise-mcp-governance-gateway/README.md: Security notes (opens in new tab), enterprise-mcp-governance-gateway/README.md: Managed guardrail (Amazon Bedrock Guardrails) (opens in new tab), index.py (opens in new tab)

Architecture and figures

The gateway README has no architecture image. The request path in the page header is drawn from its Verified architecture section. README.md: Verified architecture (opens in new tab) Read it in the rendered README.

Architecture across all four projects

Known limitations and support envelope

Each item is copied from the project README or docs without paraphrase.

Support envelope for all projects

Evidence

The README ships five integration tests that run against the deployed gateway, a one-command smoke test, and local unit tests for the interceptors.

What runs against live AWS

C1 to C5 run against the deployed, live gateway and are never mocked. C6 is the local interceptor unit tests, the only row that needs no AWS.

Source: enterprise-mcp-governance-gateway/README.md: What the tests prove (opens in new tab), enterprise-mcp-governance-gateway/README.md: Quickstart (opens in new tab)

Five governance tests against the live gateway (tests/integration)

source scripts/get-token.sh
GATEWAY_URL="$(aws ssm get-parameter --region "$AWS_REGION" \
  --name /enterprise-mcp-gateway/gateway/url --query Parameter.Value --output text)" \
AUTH_TOKEN="$AGENTCORE_JWT" \
  python3 -m pytest tests/integration -v

What a pass proves: tools/list is Cedar-filtered (C1), an allowed call succeeds (C2), a forbidden tool is denied (C3), a SQL-injection payload is blocked by the request interceptor (C4), and PII in a response is redacted (C5). Expect 5 passed; the three Atlassian tests skip unless the connector is also deployed.

Source: enterprise-mcp-governance-gateway/README.md: Run tests (opens in new tab), enterprise-mcp-governance-gateway/README.md: What the tests prove (opens in new tab), enterprise-mcp-governance-gateway/README.md: Quickstart (opens in new tab)

Curl smoke test (scripts/test-gateway.sh)

source scripts/get-token.sh
bash scripts/test-gateway.sh

What a pass proves: The same idea in one command: the deployed gateway, resolved from SSM, answers an authenticated request.

Source: README.md: Run tests (opens in new tab)

Interceptor and Cedar-parsing unit tests (tests/unit)

python3 -m pytest tests/unit -v

What a pass proves: The interceptor and Cedar-parsing logic behaves correctly against fakes. No AWS and no token: these pass whether or not anything is deployed, so they check code changes, not a deploy.

Source: README.md: Run tests (opens in new tab)

Documentation on this site

  • README: Enterprise MCP Governance Gateway (AWS AgentCore)
  • Atlassian connector: Atlassian connector (Jira & Confluence): per-user 3LO

Teardown

Deploy and prove the governance

Disconnect the MCP client first. If you deployed the Atlassian connector, destroy its two stacks before the gateway stack. CloudWatch log groups are not removed by cdk destroy.

cd cdk
cdk destroy EnterpriseMcpGatewayStack
cd ..

Source: README.md: Teardown (opens in new tab)

Costs and cleanup for all projects