Skip to content

feat: api key outbound authorization with configurable placement for gateway targets#1527

Closed
aidandaly24 wants to merge 14 commits into
mainfrom
feat/api-key-mcp-target-auth-pr
Closed

feat: api key outbound authorization with configurable placement for gateway targets#1527
aidandaly24 wants to merge 14 commits into
mainfrom
feat/api-key-mcp-target-auth-pr

Conversation

@aidandaly24

Copy link
Copy Markdown
Contributor

Description

Adds an API key outbound authorization strategy with configurable placement to MCP / gateway targets in the CLI.

Users can now control how the gateway sends an API key to an upstream target via three new add gateway-target flags:

agentcore add gateway-target \
  --type mcp-server --name secureTools \
  --endpoint https://api.example.com/mcp --gateway my-gateway \
  --outbound-auth api-key --credential-name MyKey \
  --api-key-location HEADER \
  --api-key-parameter-name Authorization \
  --api-key-prefix Bearer

This sends Authorization: Bearer <key> — previously the placement was hardcoded to the x-api-key header, which the new control makes configurable. mcpServer targets now support API_KEY outbound auth (previously OAUTH/NONE only); the same placement applies to openApiSchema and apiGateway targets.

How it works

The credential itself is unchanged — add credential produces an AgentCore Identity API-key provider, and the gateway target references it by ARN plus the per-target placement. Placement is modeled as an optional nested apiKey: { location?, parameterName?, prefix? } block on outboundAuth; defaults (HEADER / x-api-key) are applied in the CDK so existing configs are unaffected.

Changes by layer

  • Schema: ApiKeyOutboundConfigSchema + optional apiKey on OutboundAuthSchema; mcpServer allowed API_KEY; guard rule; regenerated agentcore.schema.v1.json (lockstep with @aws/agentcore-cdk).
  • Command-line: --api-key-location / --api-key-parameter-name / --api-key-prefix with validation; shared buildApiKeyPlacement helper.
  • TUI: shared ApiKeyPlacementInput sub-form — forced credential selection, then a skippable placement checklist; reuses the existing AddIdentityScreen for credential creation (unchanged).
  • Import: import gateway now round-trips placement (was silently dropping it).
  • Docs: docs/gateway.md updated with the placement flags + the Authorization: Bearer example.

Depends on aws/agentcore-l3-cdk-constructs#249 — that CDK PR must merge and publish first (the vended CDK consumes the lockstep schema + emits the placement). Schema regions are byte-identical between the two repos.

Related Issue

Closes #

Documentation PR

docs/gateway.md updated in this PR. (No separate agent-docs PR.)

Type of Change

  • New feature

Testing

How have you tested the change?

  • I ran npm run test:unit and npm run test:integ
  • I ran npm run typecheck
  • I ran npm run lint
  • If I modified src/assets/, I ran npm run test:update-snapshots — N/A (no src/assets/ changes)

Additional verification:

  • 290 feature unit tests pass; full suite green except 5 pre-existing failures on main (unrelated useDevDeploy/LogsScreen) — zero regressions introduced.
  • 15-agent bug bash across the full local + live-AWS matrix (every target type × placement permutation, defaults-elision, one-credential-many-placements, fuzz). One in-scope validation gap found and fixed (lambda-function-arn now rejects placement flags).
  • Live AWS verified: real deploys to a test account; get-gateway-target confirmed the exact placement reached the service. mcpServer + API_KEY proven against a reachable MCP endpoint.
  • Backwards compatibility proven by byte-diff: 30/30 old-shaped configs still validate; old vs new CDK produce identical CloudFormation for configs without apiKey.

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published — blocked on aws/agentcore-l3-cdk-constructs#249

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Converts raw placement inputs (location/parameterName/prefix) into the
optional ApiKeyOutboundConfig block, returning undefined when values
match the CDK defaults (HEADER / x-api-key / no prefix) to avoid
writing redundant config blocks.
…way-target

Registers three new optional CLI flags on `add gateway-target` for
API key placement customisation (HEADER vs QUERY_PARAMETER, parameter
name, and value prefix). Validation rejects placement flags when
outbound auth type is not API_KEY and enforces valid location values
and length constraints on name/prefix. Placement is threaded into the
outboundAuth block for the mcpServer, schema-based (openApiSchema/
smithyModel), and apiGateway branches via buildApiKeyPlacement.

Also widens the TUI config types (McpServerTargetConfig,
ApiGatewayTargetConfig, SchemaBasedTargetConfig, GatewayTargetWizardState)
to carry the optional apiKey placement field, unblocking downstream
TUI tasks.
Add a concrete end-to-end test that drives ApiKeyPlacementInput through
the checklist (cursor down x2 to prefix, Space toggle, Enter confirm)
into the prefix TextInput, types 'Bearer', and asserts onComplete is
called with { prefix: 'Bearer' }. Also tighten the default-labels test
to assert the full annotation strings ('Location (default: HEADER)' and
'Parameter name (default: x-api-key)') instead of loose substrings.
…help text + wizard back-nav

Adds a TDD-verified guard in the lambdaFunctionArn validation branch that
rejects --api-key-* placement flags before the file-existence check, closes
the silent-drop bug. Updates placement sub-form help text to MULTI_SELECT
and adds exitEnabled={false} to AddGatewayTargetScreen to match the sibling
gateway wizard and activate onBack/onExit handlers correctly.
@aidandaly24 aidandaly24 requested a review from a team June 11, 2026 14:15
@github-actions github-actions Bot added the size/l PR size: L label Jun 11, 2026
@github-actions github-actions Bot added the agentcore-harness-reviewing AgentCore Harness review in progress label Jun 11, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added the claude-security-reviewing Claude Code /security-review in progress label Jun 11, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: the review did not actually analyze this PR (model took 0 turns — the skill likely failed during setup). See the run for details; a later push or re-run is needed for a real review.

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Jun 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Package Tarball

aws-agentcore-0.19.0.tgz

How to install

gh release download pr-1527-tarball --repo aws/agentcore-cli --pattern "*.tgz" --dir /tmp/pr-tarball
npm install -g /tmp/pr-tarball/aws-agentcore-0.19.0.tgz

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 36.68% 12329 / 33609
🔵 Statements 35.99% 13108 / 36417
🔵 Functions 31.2% 2058 / 6596
🔵 Branches 30.66% 8022 / 26158
Generated in workflow #3644 for commit c5e6a1f by the Vitest Coverage Report Action

@agentcore-cli-automation agentcore-cli-automation left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work — schema/CLI/TUI/import all hang together cleanly, the lockstep with the CDK is well-justified, and test coverage (schema guards, CLI validation, import round-trip, integ) is solid.

Found one regression I'd like to see fixed before merging: setting exitEnabled={false} at the top of AddGatewayTargetScreen globally disables Escape/Ctrl+Q exit for the entire wizard, not just the placement sub-form. Details inline.

onExit={onExit}
helpText={helpText}
headerContent={headerContent}
exitEnabled={false}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wizard exit (Escape / Ctrl+Q) is now broken on the first step.

exitEnabled={false} disables useExitHandler on the parent Screen for every step of the wizard, but the per-step useListNavigation hooks all bind Escape/Ctrl+Q to wizard.goBack(), which is a no-op at the first step (steps[currentIndex - 1] is undefined). Net effect:

  • Pre-PR, on the target-type step: Esc → nav hook calls goBack (no-op) → Screen handler also fires → wizard exits. ✅
  • Post-PR, on the target-type step: Esc → nav hook calls goBack (no-op) → Screen handler is disabled → nothing happens. User is stuck on the wizard with only Ctrl+C (SIGINT) as an escape hatch. Same for Ctrl+Q.

I suspect the goal here was to stop double-firing on non-first steps (where the old behavior was goBack and exit at the same time — a separate pre-existing bug). A few ways to fix:

  1. Conditional exitEnabled — only let the Screen handle exit on the first step and when no sub-form is active:
    exitEnabled={wizard.currentIndex === 0 && !awaitingApiKeyPlacement}
  2. Route Screen's onExit through goBack semantics — pass a smart handler so Esc at the screen level mirrors the existing TextInput pattern at line 352:
    onExit={() => (wizard.currentIndex === 0 ? onExit() : wizard.goBack())}
    exitEnabled={!awaitingApiKeyPlacement}
    This also fixes the pre-existing double-fire (because nav hook would goBack and Screen handler would also goBack, idempotent).
  3. Keep exitEnabled true and stop nav hooks from intercepting at step 0 — narrower change, but each useListNavigation site needs onExit: () => (wizard.currentIndex === 0 ? onExit() : wizard.goBack()), which is more invasive.

Option 1 or 2 seems cleanest. Whichever you pick, please add a test that covers Escape on the target-type step closing the wizard so this doesn't regress again.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c5e6a1f:

  • Reverted exitEnabled={false} on AddGatewayTargetScreen. It was an out-of-scope tweak that globally disabled Escape/Ctrl+Q exit for the whole wizard (and, as you noted, broke exit on the first step where goBack is a no-op). The screen is back to its pre-PR behavior; the placement sub-form's own back/exit handling is unaffected. The underlying 'Esc should step back rather than exit' question is pre-existing and out of scope for this PR.
  • Also dropped the committed schemas/agentcore.schema.v1.json change — I'd regenerated it locally, but the schema-check gate correctly flags that the JSON schema is regenerated during the release workflow, not in feature PRs. Reverted to match main.

PR title fixed too (lowercase subject for validate-pr-title). All three CI failures should clear on the next run.

@github-actions github-actions Bot removed the agentcore-harness-reviewing AgentCore Harness review in progress label Jun 11, 2026
@aidandaly24 aidandaly24 changed the title feat: API key outbound authorization with configurable placement for gateway targets feat: api key outbound authorization with configurable placement for gateway targets Jun 11, 2026
…rated schema

- Remove exitEnabled={false} from AddGatewayTargetScreen: it globally disabled
  Escape/Ctrl+Q exit for the whole wizard (regression on the first step where
  goBack is a no-op). Reverts to the pre-PR Screen behavior. The placement
  sub-form's own back handling is unaffected.
- Revert schemas/agentcore.schema.v1.json to main: the repo regenerates the
  JSON schema during the release workflow; it must not be committed in feature
  PRs (schema-check CI gate).
@github-actions github-actions Bot added size/l PR size: L and removed size/l PR size: L labels Jun 11, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added the claude-security-reviewing Claude Code /security-review in progress label Jun 11, 2026
@agentcore-devx-automation

Copy link
Copy Markdown
Contributor

Claude Security Review: the review did not actually analyze this PR (model took 0 turns — the skill likely failed during setup). See the run for details; a later push or re-run is needed for a real review.

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label Jun 11, 2026
@github-actions github-actions Bot added the agentcore-harness-reviewing AgentCore Harness review in progress label Jun 11, 2026

@agentcore-cli-automation agentcore-cli-automation left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work — the design (lockstep schema + CDK-default elision, shared buildApiKeyPlacement, sub-form reuse for the TUI) is clean and the integration coverage looks solid. One real gap before merge: the new feature surface isn't instrumented for telemetry. Details inline.

: {}),
// apiGateway only ever resolves to API_KEY | NONE (OAUTH is rejected in validation),
// so the helper's wider OAUTH | API_KEY | NONE type is narrowed here.
...(buildOutboundAuthForCli(cliOptions) as Pick<ApiGatewayTargetConfig, 'outboundAuth'>),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing telemetry for the new placement feature.

AddGatewayTargetAttrs in src/cli/telemetry/schemas/command-run.ts still only records gateway_target_type, gateway_target_host, and outbound_auth_type. Per src/cli/telemetry/README.md, new features should be instrumented — without it we can't tell how many users actually customize placement vs. take the default, or which location they pick (the whole point of this PR).

A minimal addition that fits the safe-schema rules:

  • api_key_placement_customized: z.boolean() — true when any of --api-key-location / --api-key-parameter-name / --api-key-prefix is set (or when the TUI sub-form returned a non-undefined placement). Should also be true for non-default placement coming through the OAuth/API_KEY auth step in the TUI.
  • api_key_location: z.enum(['header', 'query_parameter', 'none']).optional() — the resolved location, or 'none' when outbound_auth_type !== 'api-key'.

Both should be added to safeSchema in command-run.ts, derived from cliOptions here (and from the TUI completion path), and merged into telemetryAttrs around line 387. The same attrs should be set on the apiGateway / schema-based / mcpServer branches since all three now support placement.

Happy to leave the exact attribute shape to your judgment, but please don't ship the feature uninstrumented.

@github-actions github-actions Bot removed the agentcore-harness-reviewing AgentCore Harness review in progress label Jun 11, 2026
@aidandaly24

Copy link
Copy Markdown
Contributor Author

Closing — this work is shipping via the private staging repo's summit release branch instead (ported onto feat/summit_release). Thanks for the review; the wizard-exit regression and schema-check feedback were carried over to the new PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l PR size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants