Govern every tool call
GovernEnterprise 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.
- MCP clientSends each request over HTTPS with a Cognito access token as the Bearer JWT.
- AgentCore GatewayValidates the JWT against the Cognito OIDC discovery URL (CUSTOM_JWT authorizer).
- Request interceptorLambda that writes the audit record and blocks SQL-injection and abuse patterns.
- Cedar policy engineEvaluates the Cedar policies in ENFORCE mode and allows or denies the tool call.
- Tool LambdaThe target Lambda runs with the GATEWAY_IAM_ROLE credential.
- Response interceptorLambda that redacts PII and truncates large payloads before the result returns to the client.
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
| Fact | Value | Source |
|---|---|---|
| Validated regions | us-west-2 by default; configurable; no tested-regions list is published | README.md (opens in new tab) |
| Default region | us-west-2 | README.md: Deploy (opens in new tab) |
| What it deploys | AgentCore 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 Guardrail | README.md (opens in new tab) |
| First deploy | About 5 minutes for the five quickstart steps; the gateway stack itself takes about 2 minutes | README.md: Quickstart (opens in new tab) |
| Hands-on time | About 5 minutes for the quickstart; the governance walkthrough has no stated duration | README.md: Quickstart (opens in new tab) |
| Cost | not 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 code | AWS CDK (Python) with AWS::BedrockAgentCore L1 constructs; CDK CLI pinned to 2.1129.0 | README.md: Quickstart (opens in new tab) |
| Account topology | Single account; one gateway stack plus two optional connector stacks | README.md: Deploy (opens in new tab) |
| Auth and policy | Cognito OIDC JWT (CUSTOM_JWT authorizer) and a Cedar policy engine attached in ENFORCE mode | README.md: Verified architecture (opens in new tab) |
| Status | Sample and demonstration stack; not hardened for production | README.md: Security notes (opens in new tab) |
| Teardown | Disconnect 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) |
| Version | not documented No version badge, tag or CHANGELOG in the project folder. | none |
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)
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.txtRun from the enterprise-mcp-governance-gateway folder of the cloned repository.
1. Deploy the gateway
cd cdk && cdk bootstrap && cdk deploy EnterpriseMcpGatewayStack --require-approval never && cd ..One stack, about 2 minutes.
2. Create the demo users
bash scripts/seed-demo-users.shCloudFormation cannot set a Cognito password, so a post-deploy script does it.
3. Mint a JWT as admin@example.com
source scripts/get-token.sh4. 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 -qExpect 5 passed. Three Atlassian tests skip unless you also deploy the connector.
5. Drive it from a real agent
bash scripts/connect-coding-agent.sh kiro # or: claude-registerThen work through the governance walkthrough in the README to see allow, deny, block and redact.
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)
| Query | Ask the agent to | Expected governance outcome | Enforced by | Source |
|---|---|---|---|---|
| A | List every tool from enterprise-gateway | Only 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) |
| B | Call DocsAPI___get_page with pageId arch-overview | Succeeds and returns the page content. | Cedar allow-docs-read (ALLOW) | README row B (opens in new tab) |
| C | Call DatabaseAPI___drop_table with tableName users | The 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) |
| D | Call DatabaseAPI___execute_query with query DROP TABLE users; -- | Blocked before execution with the message Request blocked: dangerous SQL pattern detected. | REQUEST interceptor Lambda | README row D (opens in new tab) |
| E | Call DatabaseAPI___export_pii_report for department engineering | PII 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 + Guardrail | README row E (opens in new tab) |
| F | Call 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*")
};
- forbid
DatabaseAPI___drop_table. Forbids drop_table for every authenticated user, whatever their role. - 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. - forbid
DatabaseAPI___delete_records. Forbids delete_records for everyone. - 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. - 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)
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.
| Claim | Token | Reaches Cedar | Note | Source |
|---|---|---|---|---|
sub | Access token | Yes, as a tag | Becomes the sub principal tag. | README.md: Verified architecture (opens in new tab) |
username | Access token | Yes, as a tag | With this Cognito setup the access-token username is the user sub UUID, not the email address. | sensitive-tool-restrict.cedar (opens in new tab) |
scope | Access token | Yes, as a tag | The demo users share the default aws.cognito.signin.user.admin scope. | sensitive-tool-restrict.cedar (opens in new tab) |
email | ID token only | No | The gateway rejects the ID token because it carries no scope claim. | README.md (opens in new tab) |
custom:role | ID token only | No | Role-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.
Known limitations and support envelope
Each item is copied from the project README or docs without paraphrase.
- Not hardened for production. This is a sample / demonstration stack. It is deployed to a real account and is safe to demo, but it is not hardened for production README.md: Security notes (opens in new tab)
- Access token versus ID token. Query F is a known demo limitation, not a bug.
custom:roletherefore never reaches the policy engine, so the role-gatedpermitnever fires. README.md (opens in new tab) - Tighten the gateway-resource IAM scope for multi-gateway accounts. This sample deploys a single gateway, so the wildcard effectively resolves to it. If your account runs multiple gateways in the region, restrict the statement to the specific gateway ARN after the first deploy, or use a two-phase deploy (create the gateway, then update the policy with its exact ARN). README.md: Tracked production hardening (not in this sample) (opens in new tab)
- Env-based config profiles.
ENFORCE+ noexceptionLevelfor prod (DEBUGreturns verbose denial reasons, useful only for a demo). README.md: Tracked production hardening (not in this sample) (opens in new tab) - Cognito pre-token-generation Lambda. A Cognito pre-token-generation Lambda to surface
custom:rolein the access token, so the role-based Cedar policies fire (todayrole/emaillive only in the ID token, which the gateway does not validate). README.md: Tracked production hardening (not in this sample) (opens in new tab) - Per-Lambda log retention. Per-Lambda log retention. README.md: Tracked production hardening (not in this sample) (opens in new tab)
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 -vWhat 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.shWhat a pass proves: The same idea in one command: the deployed gateway, resolved from SSM, answers an authenticated request.
Interceptor and Cedar-parsing unit tests (tests/unit)
python3 -m pytest tests/unit -vWhat 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.
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 ..