This project provides LangGraph ReAct agents that connect to a Neo4j MCP server via AWS Bedrock AgentCore Gateway.
A simple notebook to test LangGraph with Bedrock in SageMaker Unified Studio.
Run this from CLI/CloudShell (not inside a notebook):
cd langgraph-neo4j-mcp-agent
# See available options
./setup-inference-profile.sh --help
# Create a profile (choose one):
./setup-inference-profile.sh haiku # Fast & cheap - great for testing
./setup-inference-profile.sh sonnet # Balanced - recommended for production
./setup-inference-profile.sh sonnet4 # Latest Claude Sonnet 4
./setup-inference-profile.sh --all # Create ALL model profiles
# Create and test in one step:
./setup-inference-profile.sh --test haikuThe script will output something like:
╔════════════════════════════════════════════════════════════╗
║ COPY THIS TO YOUR NOTEBOOK ║
╚════════════════════════════════════════════════════════════╝
INFERENCE_PROFILE_ARN = "arn:aws:bedrock:us-west-2:123456789:application-inference-profile/abc123"
Open minimal_langgraph_agent.ipynb and paste the ARN in the configuration cell:
#################################################
# CONFIGURATION - Paste your inference profile ARN
#################################################
INFERENCE_PROFILE_ARN = "PASTE_YOUR_ARN_HERE"
REGION = "us-west-2"
################################################## Interactive menu
./setup-inference-profile.sh
# Create specific model profile
./setup-inference-profile.sh haiku
./setup-inference-profile.sh sonnet
./setup-inference-profile.sh sonnet4
./setup-inference-profile.sh sonnet45
# Create and test profile
./setup-inference-profile.sh --test haiku
# Create all model profiles at once
./setup-inference-profile.sh --all
# List existing profiles (shows which have the magic tag)
./setup-inference-profile.sh --list
# Delete profiles
./setup-inference-profile.sh --delete haiku
./setup-inference-profile.sh --delete-all
# Show detected DataZone IDs
./setup-inference-profile.sh --detect| Model | Command | Description |
|---|---|---|
| Claude 3.5 Haiku | ./setup-inference-profile.sh haiku |
Fast & cheap - great for testing |
| Claude 3.5 Sonnet v2 | ./setup-inference-profile.sh sonnet |
Balanced - recommended |
| Claude Sonnet 4 | ./setup-inference-profile.sh sonnet4 |
Latest version |
| Claude Sonnet 4.5 | ./setup-inference-profile.sh sonnet45 |
Most capable |
| File | Description |
|---|---|
minimal_langgraph_agent.ipynb |
Jupyter notebook for SageMaker Studio |
test_models.ipynb |
Testing notebook for model configuration |
setup-inference-profile.sh |
CLI tool to create working inference profiles |
MODEL.md |
Detailed explanation of the secret sauce |
Before running the script, you need a Bedrock IDE export in the parent directory. This provides the DataZone IDs needed for the magic tag.
- Go to SageMaker Unified Studio → Build → Bedrock IDE
- Create any app (agent, chat, etc.)
- Export the app (this creates
amazon-bedrock-ide-app-export-*folder)
The script auto-detects these IDs from the export.
The full-featured agents connect to a Neo4j MCP server via AWS Bedrock AgentCore Gateway and answer natural language questions using Claude. The simple agent is a minimal implementation for testing and learning, while the production agent adds automatic OAuth2 token refresh for long-running deployments.
# Install dependencies
uv sync
# Copy credentials from neo4j-agentcore-mcp-server deployment
cp ../neo4j-agentcore-mcp-server/.mcp-credentials.json .File: simple-agent.py
The simple agent is a minimal implementation designed for quick testing and learning. It reads the access_token directly from the credentials file and uses it as a static bearer token for all requests to the AgentCore Gateway.
Key characteristics:
- Uses a pre-generated access token from
.mcp-credentials.json - No automatic token refresh - when the token expires (typically after 1 hour), the agent stops working
- Simpler codebase with fewer dependencies
- Ideal for local development, demos, and understanding the core agent flow
When to use: Quick tests, learning the agent architecture, or short-lived sessions where token expiration is not a concern.
uv run python simple-agent.py # Run demo
uv run python simple-agent.py "What is the schema?"File: agent.py
The production agent is a robust implementation suitable for long-running applications and production deployments. It implements the OAuth2 client credentials flow to automatically refresh access tokens before they expire.
Key characteristics:
- Automatically refreshes tokens using OAuth2 client credentials grant
- Proactively refreshes tokens that are about to expire (within 5 minutes of expiry)
- Handles token refresh failures gracefully with clear error messages
- Production-ready with proper credential management
- Uses
httpxfor async HTTP requests to the Cognito token endpoint
When to use: Production deployments, long-running sessions, automated pipelines, or any scenario where the agent needs to run for more than an hour without interruption.
./agent.sh # Run demo
./agent.sh "What is the schema?"┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ User Input │────▶│ LangGraph Agent │────▶│ AgentCore │
│ (Natural Lang) │ │ (ReAct Pattern) │ │ Gateway │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │
│ MCP Protocol │ OAuth2 JWT
│ over HTTP │
▼ ▼
┌──────────────────┐ ┌─────────────────┐
│ langchain-mcp │ │ Neo4j MCP │
│ -adapters │ │ Server │
└──────────────────┘ └─────────────────┘
│
│ AWS Bedrock
▼
┌──────────────────┐
│ Claude Sonnet 4 │
│ (Converse API) │
└──────────────────┘
The production agent adds a token management layer that intercepts requests and ensures a valid OAuth2 token is always available. When a token is expired or nearing expiration, the agent automatically requests a new one from Amazon Cognito using the client credentials stored in the credentials file.
The production agent handles OAuth2 token lifecycle automatically. Here's how it works:
1. Startup Check
When the agent starts, it loads the credentials file and checks if the stored access token is still valid. The agent considers a token invalid if:
- No
token_expires_attimestamp exists in the credentials file - The current time is within 5 minutes of the expiry time (proactive refresh)
2. Token Refresh Process
If the token needs refreshing, the agent performs an OAuth2 client credentials grant:
- Reads
token_url,client_id,client_secret, andscopefrom.mcp-credentials.json - Sends an HTTP POST request to the Amazon Cognito token endpoint (
token_url) - Includes the grant type (
client_credentials), client ID, client secret, and scope in the request body - Receives a new access token and its TTL (typically 3600 seconds / 1 hour)
- Calculates the new expiry timestamp and updates the credentials file
- Uses the fresh token for all subsequent MCP server requests
3. Credentials Used
The agent uses two separate sets of credentials for different purposes:
| Credential Type | Source | Used For |
|---|---|---|
| OAuth2 Client Credentials | .mcp-credentials.json |
Authenticating with AgentCore Gateway via Cognito |
| AWS Credentials | AWS CLI profile or environment variables | Calling AWS Bedrock for Claude LLM inference |
OAuth2 credentials (client_id, client_secret) are specific to the AgentCore Gateway deployment. They are generated when you deploy the neo4j-agentcore-mcp-server and authorize access to the MCP server. These credentials authenticate requests to the gateway's Cognito user pool.
AWS credentials are your standard AWS access credentials (access key ID and secret access key, or IAM role). These authenticate requests to AWS Bedrock's Converse API for Claude inference. The agent uses whichever credentials are configured in your environment:
AWS_PROFILEenvironment variable (named profile)AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEYenvironment variables- Default AWS CLI profile
- IAM instance role (when running on EC2/Lambda)
4. Token Storage
After a successful refresh, the agent writes the new token back to .mcp-credentials.json:
access_token- The new bearer tokentoken_expires_at- ISO 8601 timestamp of when the token expires
This means subsequent agent runs within the token's validity period can skip the refresh step entirely.
| Aspect | Simple Agent | Production Agent |
|---|---|---|
| Token handling | Static bearer token | Auto-refreshing OAuth2 |
| Session duration | Limited to token TTL (~1 hour) | Unlimited |
| Complexity | Minimal | More complex |
| Dependencies | Fewer | Includes httpx for token refresh |
| Use case | Testing/learning | Production deployments |
Copy .mcp-credentials.json from your neo4j-agentcore-mcp-server deployment:
cp ../neo4j-agentcore-mcp-server/.mcp-credentials.json .Required fields:
| Field | Description |
|---|---|
gateway_url |
AgentCore Gateway endpoint URL |
token_url |
Cognito token endpoint for OAuth2 |
client_id |
OAuth2 client ID |
client_secret |
OAuth2 client secret |
scope |
OAuth2 scope for MCP invocation |
region |
AWS region for Bedrock access |
The agent automatically refreshes tokens when they expire using the client credentials flow.
| Variable | Description | Default |
|---|---|---|
MCP_CREDENTIALS_FILE |
Path to credentials file | .mcp-credentials.json |
AWS_REGION |
AWS region (overrides credentials) | From credentials |
AWS_PROFILE |
AWS CLI profile to use | default |
| Command | Description |
|---|---|
./agent.sh |
Run demo queries |
./agent.sh "<question>" |
Ask a question |
uv sync |
Install dependencies |
./agent.sh help |
Show help |
./agent.sh "What is the database schema?"
./agent.sh "How many aircraft are in the database?"
./agent.sh "Show aircraft with recent maintenance events"
./agent.sh "What sensors monitor the engines?"
./agent.sh "Find components needing attention"
./agent.sh "List 5 airports with their city and country"
./agent.sh "Show me 3 recent maintenance events with their severity"======================================================================
Neo4j MCP Agent
======================================================================
Loading credentials...
Refreshing OAuth2 token...
Token refreshed. New expiry: 2026-01-06T21:10:25.158870+00:00
Gateway: https://your-gateway.amazonaws.com/mcp
Token expires: 2026-01-06T21:10:25.158870+00:00
Initializing LLM (Bedrock, region: us-west-2)...
Using: us.anthropic.claude-sonnet-4-20250514-v1:0
Connecting to MCP server...
Loaded 2 tools:
- neo4j-mcp-server-target___get-schema
- neo4j-mcp-server-target___read-cypher
Creating agent...
======================================================================
Question: How many Aircraft are in the database?
======================================================================
Answer:
----------------------------------------------------------------------
There are **60 Aircraft** in the database.
----------------------------------------------------------------------
When tools are accessed via AgentCore Gateway, they include a target prefix:
{target-name}___{tool-name}
For example: neo4j-mcp-server-target___get-schema
The langchain-mcp-adapters library handles this automatically - the LLM sees the full prefixed names and uses them correctly.
- Python 3.10+ - Required runtime
- uv - Python package manager (
curl -LsSf https://astral.sh/uv/install.sh | sh) - AWS CLI - Configured with credentials that have Bedrock access
- Deployed Neo4j MCP Server -
neo4j-agentcore-mcp-serverwith AgentCore Gateway
- Load Credentials: Reads gateway URL and OAuth2 credentials from
.mcp-credentials.json - Auto-Refresh Token: If token is expired or expiring soon, refreshes using client credentials
- Initialize LLM: Creates AWS Bedrock Claude client via Converse API
- Connect to MCP: Uses
MultiServerMCPClientwith streamable HTTP transport - Load Tools: Discovers and converts MCP tools to LangChain format
- Create Agent: Builds ReAct agent with system prompt for database queries
- Process Query: Agent reasons, calls tools, and returns formatted answer
ERROR: Token refresh failed: 401
Check that client_id and client_secret in .mcp-credentials.json are correct.
botocore.exceptions.NoCredentialsError
Configure AWS CLI: aws configure or set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.
AccessDeniedException: You don't have access to the model
Enable Claude Sonnet 4 model access in your AWS Bedrock console.
httpx.ConnectError
Verify the gateway URL is correct and the MCP server is deployed.
[project]
dependencies = [
"langgraph>=0.2.0",
"langchain-mcp-adapters>=0.1.0",
"langchain-aws>=0.2.0",
"httpx>=0.27.0",
"boto3>=1.35.0",
]- neo4j-agentcore-mcp-server - The MCP server this agent connects to
- LangGraph Documentation
- LangChain MCP Adapters
- Model Context Protocol
- AWS Bedrock Converse API
SageMaker Unified Studio has a permissions boundary (SageMakerStudioProjectUserRolePermissionsBoundary) that blocks direct Bedrock model access:
AccessDeniedException: User is not authorized to perform: bedrock:InvokeModel
on resource: arn:aws:bedrock:us-west-2::foundation-model/anthropic.claude-*
What doesn't work in SageMaker Unified Studio:
- Direct model IDs (
anthropic.claude-3-5-sonnet-20241022-v2:0) - Cross-region profiles (
us.anthropic.claude-3-5-sonnet-20241022-v2:0) - CLI-created inference profiles without the magic tag
After extensive testing, we discovered the one critical tag that makes CLI-created inference profiles work:
AmazonBedrockManaged = true
The SageMaker permissions boundary policy likely has a condition like:
{
"Condition": {
"StringEquals": {
"aws:ResourceTag/AmazonBedrockManaged": "true"
}
}
}This means only resources tagged as managed by Bedrock are allowed through the permissions boundary.
The setup-inference-profile.sh script creates inference profiles with all the required elements:
-
Correct Naming Pattern:
{domain_id} {project_id} {model}- Matches the pattern used by Bedrock IDE
-
Required Tags:
AmazonBedrockManaged = true ← THE KEY! AmazonDataZoneProject = {project_id} AmazonDataZoneDomain = {domain_id} -
Auto-Detection: Extracts the correct DataZone IDs from Bedrock IDE exports
- Project ID comes from
bedrockServiceRoleArn(notexportProjectId!) - Domain ID comes from the
dzd-*pattern
- Project ID comes from
| Profile Type | Has AmazonBedrockManaged=true |
Works in Studio? |
|---|---|---|
| Bedrock IDE created | ✅ Yes (automatic) | ✅ Yes |
| Old CLI-created | ❌ No | ❌ No |
| Script-created | ✅ Yes | ✅ Yes! |
Use the --list command to see which profiles have the magic tag:
./setup-inference-profile.sh --listOutput shows:
Profiles with AmazonBedrockManaged=true:
✓ dzd-xxx yyy haiku ← Will work
✓ dzd-xxx yyy sonnet ← Will work
✗ langgraph-lab ← Won't work (missing tag)
In your notebook, use the ARN with the provider parameter:
from langchain_aws import ChatBedrockConverse
INFERENCE_PROFILE_ARN = "arn:aws:bedrock:us-west-2:ACCOUNT:application-inference-profile/ID"
REGION = "us-west-2"
llm = ChatBedrockConverse(
model=INFERENCE_PROFILE_ARN,
provider="anthropic", # REQUIRED when using ARN!
region_name=REGION,
temperature=0,
)Important: The provider="anthropic" parameter is required when using an ARN. Without it, you'll get errors.
See MODEL.md for the full investigation history and troubleshooting guide.