Skip to content

docs(decisions): ADR-019 unify agent tools behind an AgentCore Gateway (#641) - #663

Merged
ClintEastman02 merged 10 commits into
aws-samples:mainfrom
ClintEastman02:docs/641-adr-agentcore-gateway
Aug 11, 2026
Merged

docs(decisions): ADR-019 unify agent tools behind an AgentCore Gateway (#641)#663
ClintEastman02 merged 10 commits into
aws-samples:mainfrom
ClintEastman02:docs/641-adr-agentcore-gateway

Conversation

@ClintEastman02

@ClintEastman02 ClintEastman02 commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

What

Adds ADR-019: Unify agent tools behind an AgentCore Gateway — the tool-plane complement to ADR-016. Records the decision to federate ABCA's agent tools behind an Amazon Bedrock AgentCore Gateway (managed MCP aggregation + dual-sided auth), portable across the AgentCore microVM and ECS/Fargate substrates.

Docs-only: the ADR plus its regenerated Starlight mirror.

Scope (near-term)

Lead with the simplest, most common target type — not the hardest. Per #641, OAuth is one option among several and most targets never touch it; the prior spike's mistake was exercising only a single 3LO-OAuth target. So the ADR phases delivery off the easiest outbound leg:

  • P0 — this ADR
  • P1 — a Lambda tool target behind the gateway (outbound auth = gateway execution role, no stored credential, no consent flow)
  • P2 — substrate parity (microVM + ECS/Fargate) + one credentialed simple target (IAM-signed HTTP/OpenAPI or API-key)
  • P3 — generalize to N registry-declared targets (Smithy, no-auth, OAuth 2LO)
  • P4 — hardening, semantic search, and the demanding 3LO-OAuth remote-MCP path, exercised last

Linear and Jira MCP are explicitly out of near-term scope. Linear stays deterministic by decision (ADR-016, enforced by strip_linear_mcp_servers()); Jira's live path stays the REST shim (ADR-015). Whether a gateway that owns the OAuth flow could unbreak the non-functional Jira MCP placeholder is recorded as a P4 speculative experiment, gated separately.

Key decisions

  1. Gateway is the tool plane, with a direct-MCP fallback retained (additive, no cutover).
  2. SigV4 (AWS_IAM) inbound by default; CUSTOM_JWT (Cognito M2M) only where a 3LO OAuth outbound target requires it.
  3. Outbound auth branches by target type (execution role / SigV4 / API-key / OAuth 2LO / OAuth 3LO), all vaulted through AgentCore Identity — the credential never enters the container.
  4. Targets are registry-declared (consumes the MCP-server asset kind from feat(registry): central agent asset registry for capabilities, skills, plugins, and MCP servers #246 / PR docs(decisions): ADR-022 propose central agent asset registry (#246) #548).

Governance note

Filing this ADR PR as the mechanism to move the #641 decision toward accepted. Implementation PRs (P1+) will follow ADR-003 governance once the decision lands.

Validation

  • mise //docs:sync — mirror regenerated and in sync.
  • mise //docs:link-check — clean.

Refs #641. Grounded in the feat/agentcore-gateway-mcp spike (verdict: GO) and reference samples aal80/agentcore-samples, aws-samples/sample-agent-assisted-sdlc, awslabs/agentcore-samples (SRE-agent).

bgagent added 2 commits July 27, 2026 11:58
Starlight requires `autogenerate` as a sibling of `label`, not wrapped
in `items[]`. The nested form failed `astro check` on main; this unblocks
the docs CI build for the ADR-019 PR.
@ClintEastman02
ClintEastman02 requested review from a team as code owners July 27, 2026 16:27

@isadeks isadeks left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for writing this up — the general decision is sound and matches the intent of #641: Gateway as a managed tool plane, substrate-portable inbound (SigV4 default / JWT), outbound auth that branches by target type, registry-declared targets, and the tool credential never entering the container. The ADR-016 relationship, the candidate-numbering note, and the astro.config.mjs sidebar fix are all handled well.

However, the near-term scope (Linear + Jira MCP as P1/P2) needs to change before this moves toward accepted. It contradicts #641, reverses ADR-016, and rests on a factual error about the code it cites. Requesting changes on those points; the mechanism itself is fine.

1. Factual error: "the two MCP servers ABCA uses today" — there is one, and it's a non-functional placeholder

The ADR's Context and Scope sections describe agent/src/channel_mcp.py as hosting "the two MCP servers ABCA uses today — Linear and Jira" and calls Linear "functional today via a direct Bearer ${LINEAR_API_TOKEN} entry." The code says the opposite:

  • One builder entry exists, not two: CHANNEL_MCP_BUILDERS = { "jira": (...) } — no Linear.
  • Linear MCP is not just absent, it's forbidden and actively stripped. The module docstring: "Linear is NOT written here… There is no Linear MCP… The Linear MCP was removed after it proved non-functional." strip_linear_mcp_servers() deletes any Linear MCP entry from .mcp.json before the SDK loads it, on every task, as ADR-016 enforcement.
  • Jira's entry is itself a non-functional placeholder (headless OAuth 2.1 can't connect; the live path is the jira_reactions.py REST shim, per ADR-015).

So the count of functional platform-managed MCP servers the agent uses today is zero, and Linear specifically was tried, proven non-functional, removed, and is now scrubbed. The ADR should state this accurately.

2. Scope contradicts #641

#641 was written specifically to steer away from a Linear-first cut:

  • The spike branch is "Linear-coupled and out of scope — use it as a reference for the mechanism, not a base… do NOT continue off it."
  • "Test multiple target TYPES (not just one OAuth server). The prior spike only exercised a single 3LO-OAuth MCP-server target."
  • "OAuth is one option among several… most targets we'd add never touch OAuth", and "the UX must not make API-key / IAM / 2LO targets pay [the 3LO] cost."

This ADR makes Linear + Jira — both 3LO-OAuth remote MCP servers, the single hardest auth path — the P1/P2 deliverable, and frames it as a positive ("the near-term tests exercise the hardest auth path"). That's the opposite of what #641 asked: lead with the simpler, common target types.

3. The ADR-016 reversal can't be deferred

The ADR's ADR-016 tension callout is honest and appreciated — but it treats the conflict as a "follow-up, not resolved unilaterally in this ADR." It's the crux, not a footnote: ADR-016 + shipping enforcement code (strip_linear_mcp_servers) mandate that the agent has no Linear MCP, while this ADR's P1 is "route Linear MCP through an agent-reachable gateway." An ADR can't reach accepted when its first deliverable is barred by an accepted ADR and by enforcement code in the tree. This must be resolved (reconcile or explicit carve-out) before landing, not after.

4. "Admin direction" isn't reflected in #641

The Linear+Jira scope is attributed to "admin direction," but #641 contains no such scoping — it de-scopes Linear. If there is a separate directive, please reconcile it with #641 and ADR-016 in writing; as it stands the ADR and its originating issue disagree on scope.


Suggested change — keep the mechanism, swap the exemplar

The decision is good; only the near-term exemplar is wrong. Concretely:

  • Pick a near-term target ABCA actually wants the agent to hold as an MCP tool — a Lambda tool, an IAM-signed internal service, or an API-key service. This exercises the simpler, common auth paths first (what #641 asked), proves substrate portability on both microVM and ECS/Fargate, and introduces no ADR-016 conflict.
  • Remove Linear and Jira from P1/P2. Linear is deterministic by decision (ADR-016); Jira's real path is the REST shim (ADR-015). If "can a gateway unbreak the non-functional Jira MCP placeholder?" is worth exploring, record it as a speculative later experiment, explicitly noting it does not re-introduce agent-side Linear/Jira MCP into the deterministic reaction/orchestration paths.
  • Fix the factual claims in Context/Scope about channel_mcp.py (one entry, non-functional; Linear removed and stripped per ADR-016).

Happy to pair on the revised scope. I'll also drop a note on #641 clarifying that Linear/Jira MCP is explicitly not the goal so this doesn't recur.

isadeks pushed a commit to isadeks/sample-autonomous-cloud-coding-agents that referenced this pull request Jul 28, 2026
…aws-samples#666) (aws-samples#669)

Adopt EARS (Easy Approach to Requirements Syntax) as the required syntax
for normative requirements in ADRs. The ADR states its own Decision in
EARS to dogfood the standard; explanatory prose is intentionally out of
scope. Regenerates the Starlight decisions mirror via docs:sync.

Status: proposed (flips to accepted when this PR merges).

Note: ADR-018 (aws-samples#548, aws-samples#662) and ADR-019 (aws-samples#663) are claimed by in-flight
PRs; 020 is the first free number. May need renumbering if the queue
shuffles before merge.

Co-authored-by: scottschreckengaust <[email protected]>
Co-authored-by: Claude Opus 4.8 <[email protected]>
…inear/Jira MCP (aws-samples#641)

Address review on aws-samples#663:
- Fix Context/Scope facts: zero functional platform-managed MCP servers
  today (one non-functional jira placeholder; Linear removed and stripped
  per ADR-016), not "Linear + Jira".
- Reorder phasing: P1 leads with a Lambda tool target (IAM execution role,
  no stored credential, no consent) per aws-samples#641; the 3LO-OAuth remote-MCP path
  is exercised last.
- Remove Linear + Jira from near-term scope; Linear stays deterministic
  (ADR-016), Jira stays the REST shim (ADR-015). Gateway-unbreaks-Jira-MCP
  demoted to a separately-gated speculative P4 experiment touching neither
  deterministic path.
- Drop "admin direction" framing and the deferred ADR-016 tension callout;
  no reversal remains to reconcile.
@ClintEastman02

ClintEastman02 commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review — agreed on all four points, and the mechanism-vs-exemplar framing was exactly the right cut. Addressed in fdae556:

  1. Facts corrected (Context + Scope). The ADR now states plainly that ABCA holds zero functional platform-managed MCP servers today: the single CHANNEL_MCP_BUILDERS entry is jira and it's a non-functional placeholder (headless OAuth 2.1 can't connect; live path is the REST shim in jira_reactions.py, ADR-015). Linear is documented as deterministic-by-decision (ADR-016) — removed after proving non-functional and actively stripped by strip_linear_mcp_servers() before the SDK loads .mcp.json. No more "two MCP servers / Linear functional" claims.

  2. Scope realigned to feat(registry): unify agent tools behind an AgentCore Gateway (managed MCP aggregation + dual-sided auth, any substrate) #641 — lead with the simplest target, not the hardest. P1 now onboards a Lambda tool target: outbound auth is the gateway execution role (IAM), no stored credential and no consent flow, proving the end-to-end path (provisioning, substrate-portable inbound, aggregation, agent routing) on the easiest outbound leg. The 3LO-OAuth remote-MCP path is now exercised last (P4), not first.

  3. ADR-016 reversal removed, not deferred. Linear + Jira are out of near-term scope entirely, so there's no reversal left to reconcile. Linear stays deterministic (ADR-016); Jira's live path stays the REST shim (ADR-015). The "can a gateway unbreak the non-functional Jira MCP placeholder?" question is demoted to a separately-gated speculative P4 experiment that explicitly does not re-introduce agent-side Linear/Jira MCP into the deterministic reaction/orchestration paths.

  4. "Admin direction" framing dropped. Removed the attribution and the deferred ADR-016 tension callout — the ADR no longer contradicts feat(registry): unify agent tools behind an AgentCore Gateway (managed MCP aggregation + dual-sided auth, any substrate) #641 on scope.

Phasing table and Testing section were reordered to match (P1 = Lambda/no-credential; the target × auth matrix broadens from simplest to hardest).

bgagent and others added 3 commits August 6, 2026 12:02
Commit 23e9810 rewrote the Decisions sidebar entry to the sibling form
`autogenerate: { directory: 'decisions' }`, but Starlight's config schema
rejects that here (sidebar.6 'Did not match union' → the `build (agentcore)`
CI job failed at //docs:build). The nested form on main —
`items: [{ autogenerate: { directory: 'decisions' } }]` — is what both
`astro check` (0 errors) and `docs:build` (69 pages) accept. Restore it.
- Drop stale candidate-numbering blockquote: ADR-018/020/021 are all on
  main now and ADR-019 is unambiguously free; PR aws-samples#548 (titled ADR-018) is
  the actual numbering collision, not this ADR.
- Clarify the AGENTCORE_GATEWAY_MCP_SPIKE.md references live only on the
  upstream feat/agentcore-gateway-mcp branch, not merged to main.
- Regenerate Starlight mirror.

@maya-minion maya-minion 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.

LGTM

@mayakost
mayakost requested a review from isadeks August 10, 2026 18:04
@mayakost
mayakost enabled auto-merge August 10, 2026 18:04
@codecov-commenter

codecov-commenter commented Aug 10, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@268b922). Learn more about missing BASE report.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #663   +/-   ##
=======================================
  Coverage        ?   91.93%           
=======================================
  Files           ?      298           
  Lines           ?    83853           
  Branches        ?     9319           
=======================================
  Hits            ?    77090           
  Misses          ?     6763           
  Partials        ?        0           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mayakost mayakost left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

Kalindi-Dev pushed a commit to Kalindi-Dev/sample-autonomous-cloud-coding-agents that referenced this pull request Aug 10, 2026
…ws-samples#246)

ADR-018 is already taken on main (ADR-018-linear-agent-session-interaction),
and 019/020/021 are claimed by open PR aws-samples#663 and merged main ADRs. 022 is the
next unclaimed number. Renames the source + Starlight mirror and updates the
H1 titles; numbers are never reused (docs/decisions/README.md).
@mayakost
mayakost added this pull request to the merge queue Aug 11, 2026
@ClintEastman02
ClintEastman02 removed this pull request from the merge queue due to a manual request Aug 11, 2026
@ClintEastman02
ClintEastman02 added this pull request to the merge queue Aug 11, 2026
Merged via the queue into aws-samples:main with commit 819c235 Aug 11, 2026
4 checks passed
@ClintEastman02
ClintEastman02 deleted the docs/641-adr-agentcore-gateway branch August 11, 2026 18:58
Kalindi-Dev pushed a commit to Kalindi-Dev/sample-autonomous-cloud-coding-agents that referenced this pull request Aug 12, 2026
…ws-samples#246)

ADR-018 is already taken on main (ADR-018-linear-agent-session-interaction),
and 019/020/021 are claimed by open PR aws-samples#663 and merged main ADRs. 022 is the
next unclaimed number. Renames the source + Starlight mirror and updates the
H1 titles; numbers are never reused (docs/decisions/README.md).
dreamorosi added a commit to dreamorosi/sample-autonomous-cloud-coding-agents that referenced this pull request Aug 28, 2026
…ples#665, model-config stack, DLQ alarms) into feat/645-lambda-microvm-p2

Upstream gained 18 commits across five overlapping areas: the standalone Agent
Registry (aws-samples#548 ADR-022, aws-samples#755, aws-samples#664, aws-samples#665), the ADR-019 tool Gateway (aws-samples#663,
aws-samples#755), the model-configuration stack (aws-samples#752 run.sh, aws-samples#753 docs, aws-samples#754 + aws-samples#768 Opus
5, aws-samples#763 budget docs, aws-samples#764 geo-configurable inference profiles), the Jira
orchestration work (aws-samples#725/aws-samples#726/aws-samples#727, aws-samples#710) and the OperationalAlerts SNS/KMS
channel (aws-samples#208, aws-samples#739). 26 files overlap this branch; 11 needed manual
resolution.

Bootstrap bundle: 1.4.0 -> 1.6.0
--------------------------------

Both sides bumped from the merge-base 1.3.0. Upstream took 1.4.0 (aws-samples#739: SNS
topic + customer-managed-KMS create/lifecycle for OperationalAlerts) and then
1.5.0 (aws-samples#664: Step Functions, Cognito group, CloudFormation nested-stack actions
for the registry), so this branch's `MicrovmPassRoles` statement becomes 1.6.0
rather than re-using a published number — the version is an operator-visible
contract (`CDKToolkit`'s `BootstrapPolicyVersion` output) and the guidance we
ship is a `>=` check.

The policy sets are disjoint and unioned cleanly: theirs edited
`application.ts` / `infrastructure.ts` / `observability.ts`, ours only
`compute-lambda-microvm.ts`. `resource-action-map.ts` auto-merged (their
registry/SNS/KMS entries plus our `iam:PassRole` on `AWS::Lambda::MicrovmImage`
and `AWS::Lambda::NetworkConnector`). Artifacts regenerated with
`mise //cdk:bootstrap:generate` — never hand-edited — and re-run to confirm a
zero diff; new hash `d30eb8e6…`, snapshot updated to match.

Every operator-facing ">= 1.4.0" reference we wrote is now 1.6.0:
DEPLOYMENT_GUIDE.md, DEPLOYMENT_ROLES.md (whose "bootstrapped at 1.3.0 or
earlier" becomes "1.5.0 or earlier"), USER_GUIDE.md, ADR-021 (sub-decision 4 +
the parity table), the `lambda-microvm-compute.ts` synth warning,
`package-microvm-artifact.sh` (4 sites) and `cdk/AGENTS.md`. No test hardcodes
the number.

Geo resolver: our constant becomes a derived value
--------------------------------------------------

aws-samples#764 landed first with `resolveBedrockGeoRegion` + `BEDROCK_GEO_REGIONS` +
`GEO_PREFIX_RE`, and hardcoded the haiku literal a second time as
`` `${bedrockGeoRegion}.anthropic.claude-haiku-4-5-20251001-v1:0` ``. Adopted
their resolver shape and derived our haiku value through it, exactly as the
heads-up on this PR asked:

- `DEFAULT_HAIKU_MODEL_ID` (bare id) is kept and still spliced into
  `DEFAULT_BEDROCK_MODEL_IDS` alongside their new `anthropic.claude-opus-5`
  entry, so grant and delivery cannot drift.
- `DEFAULT_HAIKU_INFERENCE_PROFILE_ID` (a `us.`-baked const) is REPLACED by
  `haikuInferenceProfileId(geoRegion)`. A const could only ever carry one
  geography, which is the split aws-samples#764 exists to prevent.
- Both delivery sites call it with the same resolved geography: the AgentCore
  runtime env block, and the lambda-microvm `platform_config` block — the
  "third site" flagged on aws-samples#746. A geo change that missed the second would leave
  one substrate calling a profile its role does not grant.

aws-samples#768's Opus 5 default needs nothing from `platform_config`: it carries no main
model (that arrives per-task from the repo config), only the auxiliary haiku
id. aws-samples#752's run.sh fix is Docker-invocation-only and does not touch the
`platform_config` env installs in server.py.

Resolved manually
-----------------

- `cdk/src/bootstrap/version.ts` — union bump history, 1.6.0, with the reason
  it is not 1.4.0 recorded in the JSDoc.
- `cdk/src/constructs/bedrock-models.ts` — as above; their Opus 5 entry plus
  our constant in the model list, `haikuInferenceProfileId` seated after
  `resolveBedrockGeoRegion`.
- `cdk/src/stacks/agent.ts` — import unions `haikuInferenceProfileId` with
  their `resolveBedrockGeoRegion`; the runtime env var and our
  `agentPlatformConfig.anthropicDefaultHaikuModel` both derive from
  `bedrockGeoRegion`; their `agentRegistryId` prop sits alongside our
  `agentPlatformConfig` block on the TaskOrchestrator call.
- `cdk/src/constructs/task-orchestrator.ts` — `AGENT_REGISTRY_ID` and our
  `platform_config` env block are both emitted; disjoint keys.
- `agent/src/runner.py` + `agent/tests/test_runner.py` — both helpers land
  after `_resolve_setting_sources` in call order (`_log_claude_cli_version`
  then `_register_gateway_server`), both call sites survive, both test classes
  kept, import lists unioned.
- `docs/guides/DEPLOYMENT_GUIDE.md` — our "Lambda MicroVMs backend
  (experimental)" section and their "Optional Agent Registry" section are both
  additive under the same heading level; kept in that order.
- `cdk/bootstrap/{BOOTSTRAP_VERSION,BOOTSTRAP_HASH,bootstrap-template.yaml}`
  and `test/bootstrap/__snapshots__/version.test.ts.snap` — regenerated, not
  merged.
- The two Starlight mirrors that conflicted (`Per-repo-overrides.md`,
  `Deployment-guide.md`) were regenerated by `mise //docs:sync`, which is
  idempotent on a second run.

Auto-merged, verified by hand (no re-seating needed)
----------------------------------------------------

- `agent/src/server.py` — their `resolved_assets` threading (aws-samples#665) lands in
  `_extract_invocation_params` and `_run_task_background`, both of which the
  MicroVM `/run` hook already reuses; `_spawn_background` forwards `**params`,
  so registry assets reach the guest on this backend for free. Our review-wave
  changes (`_PayloadFetchError`, ARN pinning, the no-`platform_config` 400,
  control-char rejection) are in disjoint regions and their seam-guard tests
  still pass.
- `cdk/src/handlers/shared/orchestrator.ts` — `resolveRegistryAssets` and
  `resolved_assets` go onto the shared `agentPayload`, which the
  lambda-microvm strategy forwards verbatim (inline or via S3), so no strategy
  change was needed. `heartbeatLivenessApplies` / `buildComputeMetadata` /
  `reconcileMicrovmSubstrateState` untouched.
- `cdk/src/handlers/shared/types.ts` + `cli/src/types.ts` — their
  `resolved_assets` sits after `resolved_workflow`, our `agent_heartbeat_at`
  after `completed_at`, in the same order in both packages, so
  `check:types-sync` still matches exactly.
- `agent/README.md`, `docs/design/DEPLOYMENT_ROLES.md`,
  `docs/guides/USER_GUIDE.md` — prose additions in different sections.

Verified: `mise run build` and `mise run drift-prevention` exit 0 (4261 cdk +
768 cli + 1739 agent tests), `//cdk:eslint` and `//cli:eslint` produce no
changes, `//cdk:bootstrap:generate` and `//docs:sync` are both a zero diff on
re-run, link-check clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants