Skip to content

Amazon Quick Legal Hold Agent

A deployable legal-hold management agent for Amazon Quick. From inside Amazon Quick chat, a Legal administrator can search identities, place and release legal holds on users and groups, and list active holds - with every action attributed to the individual admin who performed it (chain-of-custody).

The agent's tools are exposed to Amazon Quick over the Model Context Protocol (MCP) through an Amazon Bedrock AgentCore Gateway, secured with OAuth 2.0 per-user login (3LO) through Amazon Cognito. Amazon Quick connects to it as an MCP connector. When a hold is placed, it is written to an Amazon DynamoDB table that a preservation data path (Amazon Data Firehose to a filter AWS Lambda function to a write-once-read-many (WORM) Amazon S3 bucket) reads, so held users' chat records are automatically preserved to an Object Lock bucket.

This is a sample intended for evaluation and as a starting point. Review it against your own security and compliance requirements before using it with production data.

Contents

  1. Architecture
  2. What gets created
  3. The agent's tools
  4. Prerequisites
  5. Step 1 - Deploy the agent backend
  6. Step 2 - Connect the agent to Amazon Quick
  7. Step 3 - Create the Legal Hold agent in Amazon Quick
  8. Step 4 - Validate
  9. Cleanup
  10. Repository layout
  11. Contributing
  12. License

Architecture

Amazon Quick Legal Hold Agent architecture. Data path (top): a user under legal hold's Amazon Quick prompts and AI outputs flow as CHAT_LOGS to Amazon CloudWatch vended logs, then to Amazon Data Firehose and a filter AWS Lambda function that keeps only active-hold users (matched by Amazon QuickSight ARN) and partitions by group and user, into an Amazon S3 Object Lock (WORM) bucket encrypted by an AWS KMS key for e-discovery retrieval. Control path (bottom): a Legal admin asks in Amazon Quick, which connects through an OAuth 2.0 and Amazon Cognito MCP connector to an Amazon Bedrock AgentCore Gateway exposing the search_identities, list_group_members, place_hold, release_hold, and list_holds tools, which directly invokes the Hold Manager AWS Lambda function that resolves users and expands groups through AWS IAM Identity Center and idempotently writes and releases holds in an Amazon DynamoDB held-user table. The filter function looks up user_arn in that same Amazon DynamoDB table.

  • Control path: Amazon Quick -> Gateway -> Hold Manager Lambda -> DynamoDB.
  • Data path: chat logs -> Firehose -> filter (keep only active-hold users) -> WORM S3 (original).
  • Redaction path (optional): new WORM object -> redact Lambda (Comprehend) -> redacted-copy bucket. The agent's get_held_document serves only the redacted copy; the original is reachable only via the break-glass role when configured.
  • Placing a hold via the agent immediately governs what the data path preserves.

What gets created

All resources share the prefix quick-legalhold-mcp-.

Control path (the agent)

  • quick-legalhold-mcp-hold-manager AWS Lambda function - the seven tools (identity search, hold place/release/list, and held-document list/get).
  • quick-legalhold-mcp-interceptor AWS Lambda function - REQUEST interceptor that injects caller identity.
  • quick-legalhold-mcp-gateway-role - Gateway execution role.
  • AWS::BedrockAgentCore::Gateway quick-legalhold-mcp-gw (MCP, CUSTOM_JWT) + Lambda target.

Preservation data path

  • AWS KMS key for the WORM bucket, DynamoDB, and Firehose - either a customer-managed CMK the stack creates (rotation enabled, retained on delete) or a bring-your-own key (kmsKeyArn).
  • Amazon S3 WORM bucket quick-legalhold-mcp-worm-<account> - Object Lock (mode and retention are parameterized via objectLockMode / objectLockDays; GOVERNANCE/1-day sample default, COMPLIANCE/years for production), versioned, SSE-KMS, public access blocked, retained on delete.
  • Amazon S3 server-access-log bucket quick-legalhold-mcp-worm-logs-<account>.
  • Amazon DynamoDB table quick-legalhold-mcp-users (partition key user_arn, on-demand, CMK-encrypted, point-in-time recovery on).
  • Amazon Data Firehose stream quick-legalhold-mcp-stream-dp -> WORM bucket, with filter AWS Lambda function quick-legalhold-mcp-filter (keeps only active-hold user_arns).
  • A delivery-wiring custom resource that binds the chat-logs vended-log delivery to the stream.

PII-redaction path (only when redactPii=true)

  • quick-legalhold-mcp-redact AWS Lambda function - triggered by S3 OBJECT_CREATED on the WORM bucket; runs Amazon Comprehend DetectPiiEntities over the chat text fields, masks the detected spans, and writes a redacted copy. Fails closed (masks the whole field on error).
  • Amazon S3 redacted-copy bucket quick-legalhold-mcp-redacted-<account> (SSE-KMS, reviewer- readable, not Object-Locked - the redacted copy is a derived artifact).

Authentication (Amazon Cognito)

  • User pool quick-legalhold-mcp-pool + hosted-UI domain + resource server holds-api (scope invoke).
  • App client quick-legalhold-mcp-3lo (authorization_code / 3LO; scopes openid email profile holds-api/invoke; confidential client with secret; callbacks = Quick's https://us-east-1.quicksight.aws.amazon.com/sn/oauthcallback and http://localhost:8765/callback).
  • A sample end-user legal-admin-test - created only when createTestUser=true, with the password supplied via the TestUserPassword NoEcho CloudFormation parameter, for evaluation only.

Authentication is per-user (3LO) only, so every hold is attributed to the individual logged-in Legal admin.

Identity provider note: This sample uses Amazon Cognito as the OIDC identity provider so it works out of the box. In a real deployment you should instead use whichever identity provider your organization already uses (for example AWS IAM Identity Center, Okta, Microsoft Entra ID / Azure AD, Ping, or any OIDC-compliant IdP). The AgentCore Gateway uses a CUSTOM_JWT authorizer, so point its OIDC discovery URL and allowed client/audience at your IdP, and configure the Quick MCP connector with that IdP's authorize/token URLs — Cognito is only the stand-in for this reference implementation.

The agent's tools

Tool Purpose Required args
search_identities Search AWS IAM Identity Center users and groups by substring (IDC mode) query (optional)
list_group_members Expand an AWS IAM Identity Center group to its member users group_name
place_hold Place an active hold on a user or (expanded) group; one item per user_arn type, name, matter_id (+ optional custodian)
release_hold Set matching holds to status=released type, name
list_holds List all current hold items none
list_held_documents List the preserved (redacted) chat-log documents for a held user or group. Resolves the supplied name / email / user_arn / group against the hold records (and Identity Center in IDC mode) before listing. Requires redactPii=true. user or group_name
get_held_document Return one preserved document as redacted content plus a short-lived pre-signed URL. Never returns the un-redacted original (break-glass only). key

Configuration options

All options are passed as CDK context (-c key=value) at deploy time; every one has a safe sample default, so a bare deploy works. For a production posture, set the values marked below.

Option Default Purpose
kmsKeyArn (create a CMK) Bring your own KMS key for the WORM bucket, DynamoDB, and Firehose. If omitted, the stack creates a customer-managed CMK (rotation enabled). The created key is retained on stack delete so retained evidence stays readable.
objectLockMode GOVERNANCE S3 Object Lock mode on the WORM evidence bucket. Set COMPLIANCE for production — COMPLIANCE cannot be shortened or bypassed by anyone (including root) until retention expires. GOVERNANCE is the safe sample default (an admin with s3:BypassGovernanceRetention can still clean up a trial).
objectLockDays 1 Default retention days for the WORM bucket. Set to your legal retention period (e.g. 2555 ≈ 7 years) for production.
redactPii false When true, an S3-triggered Lambda runs Amazon Comprehend DetectPiiEntities over each delivered WORM object and writes a separate redacted copy to a reviewer-readable bucket (…-redacted-<account>). The original in the WORM bucket is never modified. Off by default to preserve evidence fidelity (redacting the only copy can constitute spoliation).
comprehendLanguage en Language code for Comprehend PII detection.
breakGlassRoleArn (none) When set, a bucket policy denies reading the un-redacted WORM evidence to every principal except this role (and the account root). e-discovery production of originals goes through this role; day-to-day review uses the redacted copies.
createTestUser false When true, creates a synthetic Cognito user (legal-admin-test) with the password from testUserPassword, for evaluation only. A real deployment authenticates real identities via the IdP and leaves this off.
testUserPassword (empty) Permanent password for the synthetic test user (required only when createTestUser=true). Prefer the NoEcho CloudFormation parameter of the same name.
identityMode / identityStoreId direct idc + an IAM Identity Center store id enables real (read-only) IDC user/group resolution; direct uses synthetic ARNs.
quickRedirectUri Quick's us-east-1 callback OAuth redirect URI registered on the Cognito 3LO client.
envTag test Value of the Env resource tag.

Example production deploy:

cdk deploy \
  -c kmsKeyArn=arn:aws:kms:us-east-1:111122223333:key/<id> \
  -c objectLockMode=COMPLIANCE -c objectLockDays=2555 \
  -c redactPii=true \
  -c breakGlassRoleArn=arn:aws:iam::111122223333:role/legal-ediscovery

PII redaction and evidentiary integrity: redaction never alters the original. The authoritative record stays immutable in the WORM bucket (accessible only via the break-glass role when one is set); the redacted copy is a derived artifact for reviewer / agent access. The get_held_document tool serves only the redacted copy.

Prerequisites

  • Node.js 18 or later, npm, AWS CLI v2, and Python 3, all on your PATH.
  • AWS credentials for your target account (aws configure, IAM Identity Center / SSO, or environment variables). The deploy script verifies these with aws sts get-caller-identity.
  • Permissions to create the resources listed above and to run cdk bootstrap if the environment is not already bootstrapped (the script bootstraps only if needed).
  • An Amazon Quick account where you can add MCP connectors and create agents (admin access).

Step 1 - Deploy the agent backend

git clone <this-repo>
cd quick-legal-hold-agent
./scripts/deploy.sh
# optional overrides:
#   AWS_REGION=us-west-2 ./scripts/deploy.sh
#   QUICK_REDIRECT_URI=<your-quick-callback> ./scripts/deploy.sh

scripts/deploy.sh runs the full pipeline and is idempotent:

  1. Preflight - verify node, npm, aws, python3.
  2. Identity - aws sts get-caller-identity.
  3. npm install.
  4. CDK bootstrap - only if the CDKToolkit stack is missing.
  5. cdk diff (informational).
  6. cdk deploy --require-approval never.
  7. Write the AWS CloudFormation outputs to outputs/deployment-outputs.json.

There are no manual post-deploy steps for the backend; the stack creates all of its own infrastructure.

Manual equivalent

npm install
npx cdk diff
npx cdk deploy --require-approval never
# override Quick redirect URI:
# npx cdk deploy -c quickRedirectUri=<uri> --require-approval never

Step 2 - Connect the agent to Amazon Quick

This step registers the deployed AgentCore Gateway as an MCP connector in Amazon Quick.

CLI availability: Amazon Quick exposes aws quicksight create-action-connector, but its connector --type values are HTTP and SaaS integrations (GENERIC_HTTP, SALESFORCE_CRM, JIRA_CLOUD, AMAZON_S3, and similar) - there is no MCP connector type. So an MCP connector cannot be created from the CLI today and must be added in the Amazon Quick console. Everything up to the paste is automated below.

Run the helper to print exact, ready-to-paste values pulled live from your deployed stack (it also retrieves the app client secret from Cognito):

./scripts/quick-connector-info.sh

It prints the following and writes outputs/quick-connector.json:

Field Source
MCP Gateway URL GatewayUrl output
Auth method OAuth 2.0 - authorization code (3LO)
Authorize URL ThreeLoAuthorizeUrl output
Token URL ThreeLoTokenUrl output
OIDC discovery URL CognitoDiscoveryUrl output
Client ID ThreeLoClientId output
Client secret Retrieved from Cognito (confidential client)
Scopes openid email profile holds-api/invoke
Redirect URI ThreeLoRedirectUri (Quick's .../sn/oauthcallback)

Your deployed connector values. Read them from this stack's CloudFormation Outputs (Console → CloudFormation → the quick-legalhold-mcp stack → Outputs, or aws cloudformation describe-stacks). Every field the connector needs is an output — including the client secret (ThreeLoClientSecret):

Field Stack output
MCP Gateway URL GatewayUrl
Auth method OAuth 2.0 - authorization code (3LO)
Authorize URL ThreeLoAuthorizeUrl
Token URL ThreeLoTokenUrl
OIDC discovery URL CognitoDiscoveryUrl
Client ID ThreeLoClientId
Client secret ThreeLoClientSecret
Scopes ThreeLoScopes (openid email profile holds-api/invoke)
Redirect URI ThreeLoRedirectUri (Quick's .../sn/oauthcallback)

Or run ./scripts/quick-connector-info.sh to print them ready-to-paste and write outputs/quick-connector.json.

In the Amazon Quick console:

  1. Open Amazon Quick and go to the left nav Integrations (Admins may find it under Manage Quick > Connectors). Select the Actions tab.
  2. Under Set up a new integration, find the Model Context Protocol (MCP) tile and choose the plus (+) sign.
  3. On the Create integration page, enter a Name (for example Legal Hold Tools), an optional description, and paste the MCP Gateway URL from the table above. Choose Next.
  4. For the authentication method choose OAuth 2.0 - authorization code, then paste the Authorize URL, Token URL (or the OIDC discovery URL), Client ID, Client secret, and Scopes openid email profile holds-api/invoke.
  5. Confirm the Redirect URI shown matches Quick's callback (https://us-east-1.quicksight.aws.amazon.com/sn/oauthcallback). If your account uses a different regional callback, redeploy with -c quickRedirectUri=<uri> and re-run the helper.
  6. Choose Create and continue. Quick performs OAuth protected-resource-metadata discovery, then runs MCP initialize and tools/list and surfaces the five tools (search_identities, list_group_members, place_hold, release_hold, list_holds).
  7. Complete the Authenticate step: you are redirected to the Cognito hosted UI to sign in as a Legal admin. The registry/connector does not store your credentials; each admin logs in with their own.
  8. Share the connector with your Legal-admin role or user group so only Legal can invoke holds. If you skip sharing, only you can use it and teammates will not see the tools.

Each Legal admin authenticates individually through the Cognito hosted UI the first time they use the agent, and their holds are attributed to them.

Tip (optional): make the connector a pre-configured card. If you publish the Gateway to an AWS Agent Registry that is linked to your Amazon Quick account, its MCP record appears as a pre-configured connector card on the Connectors page, so admins create the connector from the card instead of typing every field. See AWS Agent Registry integration.

After the connector exists (Step 2), create the agent and attach the connector as an action. This can be done in the console or with the AWS CLI.

Option A - AWS CLI

aws quicksight create-agent creates the agent and can attach action connectors by ARN via --action-connectors. Get your connector's ARN first (from the console connector details or by listing connectors), then create the agent:

The agent prompt goes in --custom-prompt-input, not in --welcome-message or --starter-prompts (those are only the greeting and the suggested-prompt chips). The instructions live in the tagged-union field NewPrompt.CustomInstructions. Because the prompt is long, put it in a JSON file and pass it with file:// rather than inline.

First, save the agent prompt (the full text in the Agent prompt section below) into a NewPrompt.CustomInstructions JSON file:

cat > custom-prompt.json <<'JSON'
{
  "NewPrompt": {
    "CustomInstructions": "You are the Legal Hold Assistant for the Legal and e-discovery team. You help authorized Legal administrators place, release, and review legal holds on Amazon Quick users and groups by calling the connected hold-management tools. You never manage holds any other way.\n\nTools available to you:\n- search_identities(query): find IAM Identity Center users and groups by name substring. Use this first when the admin refers to a person or team by name and you need to confirm the exact identity before acting.\n- list_group_members(group_name): expand a group to its member users. Use this to show the admin exactly who a group hold will cover before you place it.\n- place_hold(type, name, matter_id): place an active hold on a user or group. type is \"user\" or \"group\". For a group, the tool expands membership and writes one hold per member user. matter_id is the legal matter the hold belongs to.\n- release_hold(type, name): set the matching holds to released.\n- list_holds(): list all current holds.\n\nHow to behave:\n1. Confirm before you write. Before calling place_hold or release_hold, restate the target and, for a hold, the matter_id, and ask the admin to confirm. Do not place or release a hold until they confirm.\n2. Resolve names first. If the admin gives a name rather than an exact identifier, call search_identities (and list_group_members for a group) and show what you found. If a name is ambiguous or returns no match, ask the admin to clarify rather than guessing.\n3. Always require a matter_id before placing a hold. If the admin does not provide one, ask for it. Never invent a matter_id.\n4. Groups. When the target is a group, expand it with list_group_members, show the members, and only after confirmation call place_hold with type \"group\". Report how many members were covered.\n5. Never place a hold on yourself, and never place holds on everyone or on wildcard targets. If asked to do either, decline and ask for a specific, scoped target.\n6. Report back precisely. After any action, report the resolved user_arns, the matter_id, the recorded custodian (the signed-in admin), and the resulting status. If a hold already existed, say so instead of implying a new one was created.\n7. Read-only on the directory. Identity resolution is read-only; you cannot modify IAM Identity Center users or groups. Say so if asked.\n8. Fail closed. If a tool returns an error or you cannot resolve a target, stop and report the error plainly. Do not fabricate a success, an ARN, or a hold that was not written.\n9. Stay in scope. You only handle legal-hold search, place, release, and list. For anything else, tell the admin it is outside your scope.\n\nEvery hold you place is attributed to the signed-in administrator for chain-of-custody, so be accurate and deliberate.",
    "Identity": "A precise, security-conscious Legal Hold assistant for the Legal and e-discovery team.",
    "Tone": "Professional, concise, and deliberate."
  }
}
JSON

Then create the agent, attaching the connector and the prompt:

ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

# Find the MCP connector's ARN (created in Step 2).
aws quicksight list-action-connectors \
  --aws-account-id "$ACCOUNT_ID" --region us-east-1 \
  --query "ActionConnectorSummaries[].{Name:Name,Arn:Arn}" --output table

# Create the agent, attach the connector as an action, and set the prompt.
aws quicksight create-agent \
  --aws-account-id "$ACCOUNT_ID" --region us-east-1 \
  --agent-id legal-hold-assistant \
  --name "Legal Hold Assistant" \
  --description "Search identities and place/release/list legal holds." \
  --action-connectors "arn:aws:quicksight:us-east-1:${ACCOUNT_ID}:actionconnector/<your-connector-id>" \
  --custom-prompt-input file://custom-prompt.json \
  --agent-lifecycle PUBLISHED \
  --welcome-message "Ask me to search identities or place, release, and list legal holds." \
  --starter-prompts "List all current holds" "Search for users named smith"

Notes:

  • --agent-id must match [0-9a-zA-Z-_.+]+; --name is 1-50 characters.
  • --custom-prompt-input is a tagged union: use NewPrompt (as above) for a fresh prompt, or ExistingPrompt to reference a saved model-profile prompt. NewPrompt.CustomInstructions is the agent's system prompt; Identity, Tone, OutputStyle, and ResponseLength are optional refinements. --welcome-message and --starter-prompts are NOT the prompt, they are only the greeting and the suggested-prompt chips.
  • Use --agent-lifecycle PREVIEW to stage the agent, then re-run with PUBLISHED (or use update-agent) when ready.
  • To change the prompt later, use aws quicksight update-agent --custom-prompt-input file://custom-prompt.json ....
  • To restrict who can use the agent, attach spaces with --spaces <space-arn> ... and manage sharing/permissions so only the Legal-admin role has access.
  • If you created the agent before the connector existed, attach it later with aws quicksight update-agent --action-connectors <connector-arn> ....

Option B - Amazon Quick console

  1. Go to Agents and choose Create agent (or Create custom agent).
  2. Name it, for example Legal Hold Assistant.
  3. Under Actions, add the MCP connector from Step 2 so the agent can call the five hold tools.
  4. Paste the agent prompt below into the agent's Instructions field.
  5. Restrict the agent's visibility and sharing to the Legal-admin role or group.
  6. Save and publish the agent to the Legal team's Amazon Quick workspace.

Agent prompt

Paste this into the agent's Instructions (system prompt). It tells the agent how and when to use each of the five tools, and enforces confirm-before-write and chain-of-custody behavior.

You are the Legal Hold Assistant for the Legal and e-discovery team. You help authorized
Legal administrators place, release, and review legal holds on Amazon Quick users and
groups by calling the connected hold-management tools. You never manage holds any other way.

Tools available to you:
- search_identities(query): find IAM Identity Center users and groups by name substring.
  Use this first when the admin refers to a person or team by name and you need to confirm
  the exact identity before acting.
- list_group_members(group_name): expand a group to its member users. Use this to show the
  admin exactly who a group hold will cover before you place it.
- place_hold(type, name, matter_id): place an active hold on a user or group. type is
  "user" or "group". For a group, the tool expands membership and writes one hold per
  member user. matter_id is the legal matter the hold belongs to.
- release_hold(type, name): set the matching holds to released.
- list_holds(): list all current holds.

How to behave:
1. Confirm before you write. Before calling place_hold or release_hold, restate the target
   (user or group) and, for a hold, the matter_id, and ask the admin to confirm. Do not
   place or release a hold until they confirm.
2. Resolve names first. If the admin gives a name rather than an exact identifier, call
   search_identities (and list_group_members for a group) and show what you found. If a
   name is ambiguous or returns no match, ask the admin to clarify rather than guessing.
3. Always require a matter_id before placing a hold. If the admin does not provide one, ask
   for it. Never invent a matter_id.
4. Groups. When the target is a group, expand it with list_group_members, show the members,
   and only after confirmation call place_hold with type "group". Report how many members
   were covered.
5. Never place a hold on yourself, and never place holds on everyone or on wildcard targets.
   If asked to do either, decline and ask for a specific, scoped target.
6. Report back precisely. After any action, report the resolved user_arns, the matter_id,
   the recorded custodian (the signed-in admin), and the resulting status. If a hold already
   existed, say so instead of implying a new one was created.
7. Read-only on the directory. Identity resolution is read-only; you cannot modify IAM
   Identity Center users or groups. Say so if asked.
8. Fail closed. If a tool returns an error or you cannot resolve a target, stop and report
   the error plainly. Do not fabricate a success, an ARN, or a hold that was not written.
9. Stay in scope. You only handle legal-hold search, place, release, and list. For anything
   else, tell the admin it is outside your scope.

Every hold you place is attributed to the signed-in administrator for chain-of-custody, so
be accurate and deliberate.

Tip: keep a short welcome message such as "Ask me to search identities or place, release, and list legal holds," and a couple of starter prompts like "List all current holds" and "Search for users named smith."

Example prompts a Legal admin can use in chat:

  • "Search for users named smith."
  • "Place a hold on the group Finance-EMEA for matter M-2026-0142."
  • "List all current holds."
  • "Release the hold on user jdoe."

Step 4 - Validate

Test the agent from inside Amazon Quick — the same way real Legal admins will use it. In the Quick chat with the Legal Hold agent:

  1. Ask it to list holds (expect an empty list on a fresh deploy).
  2. Ask it to place a hold on the sample test user (or any name).
  3. Ask it to list holds again to confirm the hold now appears.
  4. Optionally ask it to release the hold and list again.

Because the connector uses per-user 3LO login, each hold is attributed to the signed-in admin — you can confirm this in the DynamoDB quick-legalhold-mcp-users table (the custodian / custodian_source attributes reflect the logged-in user).

Real IAM Identity Center users and groups are only ever resolved read-only; holds are written keyed on the resolved user ARN.

Cleanup

./scripts/teardown.sh

This clears S3 Object Lock legal holds, empties the WORM bucket, then runs cdk destroy. It removes only this stack's resources (KMS, S3, DynamoDB, Firehose, Lambdas, the Cognito pool / app client / sample user / secret, and the Gateway and target). The bucket uses Object Lock in GOVERNANCE mode (never COMPLIANCE), so an administrator can remove holds and versions and everything is deletable. The script is safe to re-run.

Repository layout

quick-legal-hold-agent/
├── bin/app.ts                              # CDK app entry
├── lib/quick-legalhold-mcp-stack.ts        # Self-contained stack: KMS + S3 (WORM + redacted + logs)
│                                           #   + DynamoDB + Firehose + filter + redact
│                                           #   + Gateway (3LO) + interceptor + Cognito + sample user
├── lambda/
│   ├── hold_manager_mcp/handler.py         # 7 tools; custodian from injected caller identity
│   ├── filter/handler.py                   # Firehose transform: keep only active-hold user_arns
│   ├── redact/handler.py                   # S3-triggered: Comprehend PII masking -> redacted bucket
│   ├── interceptor/handler.py              # REQUEST interceptor: decode JWT, inject _caller
│   └── delivery_wiring/handler.py          # vended-log delivery binding (custom resource)
├── scripts/
│   ├── deploy.sh                           # one-command deploy (preflight, identity, install, bootstrap, deploy, outputs)
│   ├── quick-connector-info.sh             # print Amazon Quick MCP connector values (3LO)
│   └── teardown.sh                         # WORM cleanup + cdk destroy (this stack only)
├── outputs/                                # generated at deploy time (gitignored)
├── images/                                 # architecture diagram used in this README
├── cdk.json / package.json / tsconfig.json / .npmrc
└── README.md

Contributing

See How to Contribute.

License

This library is licensed under the MIT-0 License. See the LICENSE file.