AWS AgentCore — Gateway + External MCP
Introduction
This sample demonstrates how to use AWS AgentCore Gateway to connect to external Neo4j MCP servers running on AWS Fargate. The stack provisions a minimal Amazon Cognito OAuth setup for machine-to-machine (client_credentials) authentication, and uses a Lambda Interceptor to translate OAuth tokens into Neo4j credentials so you can run the official Neo4j MCP server in HTTP mode.
Key Features:
-
AgentCore Gateway: Enterprise tool governance and centralized MCP management
-
Lambda Request Interceptor: Translates OAuth tokens to Neo4j Basic Auth headers
-
Official Neo4j MCP: Uses unmodified official Docker image in HTTP mode
-
ECS Fargate Deployment: Serverless container orchestration
-
Built-in Cognito OAuth: Secure M2M authentication for external clients
Use Cases:
-
Enterprise deployments using official vendor-supported images
-
Centralized identity management with OAuth 2.0
-
Secure credential injection at the edge (Gateway) rather than in containers
-
High availability architecture with public load balancers
Architecture Design
Components
-
AWS AgentCore Gateway
-
Reverse proxy for MCP servers
-
Inbound JWT validation using Cognito OIDC discovery (
CUSTOM_JWT) -
Lambda Interceptor execution (REQUEST interception point)
-
-
Gateway Target
-
Registers the Neo4j MCP ALB as an MCP server target
-
Automatically linked to the Gateway via
gateway_identifier
-
-
Request Interceptor Lambda
-
Executes before request reaches backend
-
Retrieves Neo4j credentials from Secrets Manager
-
Replaces OAuth
Authorizationheader withBasic <user:pass>
-
-
Application Load Balancer (Public, HTTPS)
-
Terminates TLS using an ACM certificate
-
Custom domain registered via Route53 A-record alias
-
Distributes traffic to Fargate tasks on port 443
-
-
Neo4j MCP Container (Official)
-
Runs
mcp/neo4j:latestimage -
Configured in HTTP mode (
NEO4J_TRANSPORT_MODE=http) -
Stateless authentication via per-request Basic Auth
-
-
AWS Secrets Manager
-
Stores Neo4j credentials
-
Accessed by Interceptor Lambda for header injection
-
In-Depth Analysis
Authentication Flow (Token Exchange)
The core innovation in this pattern is the Auth Interceptor that enables compatibility between OAuth-based Gateway clients and Basic Auth-based Neo4j MCP.
External Agent (OAuth Token)
↓
[OAuth Validation - Cognito User Pool (created by stack)]
↓
AgentCore Gateway
↓
Request Interceptor Lambda
├─ Retrieve Secret ("neo4j/creds")
├─ Compute Basic Auth Header
└─ Replace "Authorization" Header
↓
[Basic Auth: neo4j:password]
↓
Application Load Balancer (Public)
↓
Official Neo4j MCP (HTTP Mode)
Benefits:
-
Security: Neo4j credentials never exposed to client
-
Compliance: Uses standard OAuth patterns for clients
-
Simplicity: Backend uses stateless, standard HTTP auth
Fargate Service Architecture
Neo4j MCP Service:
-
Image:
mcp/neo4j:latest -
Environment variables:
-
NEO4J_TRANSPORT_MODE:http -
NEO4J_MCP_HTTP_PORT:8080 -
NEO4J_MCP_HTTP_HOST:0.0.0.0 -
NEO4J_READ_ONLY:true
-
-
Secrets (from Secrets Manager):
-
NEO4J_URI,NEO4J_DATABASE
-
Load Balancer Configuration
AgentCore Gateway requires HTTPS endpoints for MCP target registration, so the ALB is configured with a TLS listener and a custom domain:
-
Listeners: HTTPS:443 (ACM certificate attached)
-
Custom Domain:
<subdomain>.<domain_name>— Route53 A-record alias created automatically by CDK -
Target Groups: IP-mode targets for Fargate tasks
-
Health Check:
/mcpendpoint, codes200-499 -
Security Groups: Allow inbound from AgentCore Gateway IP ranges (optional hardening)
Domain and Certificate Configuration
The stack reads three CDK context variables:
| Context key | Default | Description |
|---|---|---|
|
(required) |
Apex domain of an existing Route53 hosted zone (e.g. |
|
|
Subdomain prefix — results in |
|
(required) |
ARN of an existing ACM certificate covering the FQDN (e.g. |
The Cognito hosted domain prefix is generated automatically as neo4j-mcp-<account>-<region>.
The certificate is imported by ARN (from_certificate_arn) and must reside in the same region as the stack.
How to Use This Example
Prerequisites
-
AWS Account with Bedrock, ECS, and AgentCore access
-
AWS CLI and CDK installed
-
Neo4j database (or use the default Neo4j Demo Database)
-
A Route53 public hosted zone for your domain already configured in your AWS account
-
An ACM certificate in the deploy region covering
<subdomain>.<domain_name>(or a matching wildcard) — copy its ARN from the ACM console -
No pre-existing Cognito setup is required; this stack creates User Pool, app client, and hosted domain
Step 1: Clone the Repository
git clone https://github.com/neo4j-labs/neo4j-agent-integrations.git
cd neo4j-agent-integrations/aws-agentcore/samples/2-gateway-external-mcp
Step 3: Configure Your Domain
Open cdk.json and fill in your domain details under the context key:
{
"context": {
"domain_name": "example.com",
"subdomain": "mcp",
"certificate_arn": "arn:aws:acm:us-east-1:123456789012:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
}
Step 4: Deploy Infrastructure
# Bootstrap CDK (first time only)
cdk bootstrap
# Deploy the stack
cdk deploy Neo4jAgentCoreGatewayStack \
-c domain_name=example.com \
-c subdomain=mcp \
-c certificate_arn=arn:aws:acm:us-east-1:123456789012:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Stack Resources Created:
-
VPC with public subnets
-
ECS Cluster and Fargate Service (Neo4j MCP)
-
Public Application Load Balancer (HTTPS/443, ACM cert attached)
-
Route53 A-record alias → ALB (
<subdomain>.<domain_name>) -
Lambda Interceptor Function
-
Secrets Manager Secret (Neo4j credentials)
-
Amazon Cognito User Pool, hosted domain, resource server scope, and app client
-
IAM Roles (Fargate task, Lambda, Gateway)
-
AgentCore MCP Gateway
-
Gateway Target to Neo4j MCP (using custom domain endpoint)
Stack Outputs:
| Output | Description |
|---|---|
|
Custom domain for the Neo4j MCP Service |
|
HTTPS URL of the Neo4j MCP Service |
|
ARN of the Neo4j credentials secret |
|
ARN of the Request Interceptor Lambda |
|
ARN of the AgentCore MCP Gateway |
|
URL of the AgentCore MCP Gateway |
|
Cognito User Pool ID used by the gateway authorizer |
|
Cognito app client ID for OAuth client credentials |
|
OIDC discovery URL configured in gateway authorizer |
|
Cognito token endpoint for client credentials flow |
|
Required scope for gateway calls ( |
Step 5: Test with the Demo Notebook (optional)
Open demo.ipynb in Jupyter or VS Code. Set STACK_NAME to your stack name, then run the cells to obtain an OAuth token from Cognito and call the Gateway (list tools, call Neo4j MCP tools). Requires requests and AWS credentials.
Step 6: Get OAuth Token and Test Integration (CLI)
-
Verify DNS: The Route53 alias record should resolve to the ALB immediately after deploy.
dig mcp.example.com
-
Check HTTPS: Confirm the MCP endpoint responds over TLS.
curl https://mcp.example.com/mcp
-
Read stack outputs:
STACK_NAME=Neo4jAgentCoreGatewayStack
GATEWAY_URL=$(aws cloudformation describe-stacks \
--stack-name "$STACK_NAME" \
--query "Stacks[0].Outputs[?OutputKey=='GatewayUrl'].OutputValue" \
--output text)
COGNITO_USER_POOL_ID=$(aws cloudformation describe-stacks \
--stack-name "$STACK_NAME" \
--query "Stacks[0].Outputs[?OutputKey=='CognitoUserPoolId'].OutputValue" \
--output text)
COGNITO_CLIENT_ID=$(aws cloudformation describe-stacks \
--stack-name "$STACK_NAME" \
--query "Stacks[0].Outputs[?OutputKey=='CognitoAppClientId'].OutputValue" \
--output text)
COGNITO_TOKEN_ENDPOINT=$(aws cloudformation describe-stacks \
--stack-name "$STACK_NAME" \
--query "Stacks[0].Outputs[?OutputKey=='CognitoTokenEndpoint'].OutputValue" \
--output text)
COGNITO_SCOPE=$(aws cloudformation describe-stacks \
--stack-name "$STACK_NAME" \
--query "Stacks[0].Outputs[?OutputKey=='CognitoScope'].OutputValue" \
--output text)
-
Retrieve Cognito app client secret:
COGNITO_CLIENT_SECRET=$(aws cognito-idp describe-user-pool-client \
--user-pool-id "$COGNITO_USER_POOL_ID" \
--client-id "$COGNITO_CLIENT_ID" \
--query "UserPoolClient.ClientSecret" \
--output text)
-
Request OAuth token (
client_credentials):
OAUTH_TOKEN=$(curl -s -X POST "$COGNITO_TOKEN_ENDPOINT" \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode "grant_type=client_credentials" \
--data-urlencode "client_id=$COGNITO_CLIENT_ID" \
--data-urlencode "client_secret=$COGNITO_CLIENT_SECRET" \
--data-urlencode "scope=$COGNITO_SCOPE" \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["access_token"])')
-
Call Gateway:
curl -X POST "$GATEWAY_URL" \
-H "Authorization: Bearer $OAUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"list-tools-request","method":"tools/list"}'
-
Verify Flow:
-
Gateway validates token
-
Interceptor swaps token for Basic Auth
-
Neo4j MCP accepts request and returns schema
-
Troubleshooting
-
invalid_client: confirmCOGNITO_CLIENT_IDandCOGNITO_CLIENT_SECRETwere taken from the sameCognitoUserPoolId. -
insufficient_scope: ensure token request includesscope=$COGNITO_SCOPEexactly as returned by stack outputs. -
Unauthorized response from gateway: verify the token is unexpired and issued by this stack’s
CognitoDiscoveryUrl.