Skip to content

feat(registry): federate agent tools behind an AgentCore Gateway — P1 (#641) - #755

Merged
mayakost merged 4 commits into
aws-samples:mainfrom
ClintEastman02:feat/641-gateway-lambda-p1
Aug 12, 2026
Merged

feat(registry): federate agent tools behind an AgentCore Gateway — P1 (#641)#755
mayakost merged 4 commits into
aws-samples:mainfrom
ClintEastman02:feat/641-gateway-lambda-p1

Conversation

@ClintEastman02

Copy link
Copy Markdown
Contributor

What & why

Implements the P1 slice of ADR-019 (issue #641): a single read-only Lambda tool (abca_repo_config) exposed through a managed AgentCore Gateway with SigV4 (AWS_IAM) inbound auth and gateway-execution-role outbound (no credential). This is the thin, end-to-end proving slice for tool federation — one tool, one target, both auth sides real — that the fuller registry builds on.

The entire feature is context-gated: resources synthesize only under --context enableToolGateway=true. The default synth is byte-for-byte unchanged, so no new CFN types enter the bootstrap policy set and existing deploys are unaffected.

Changes

CDK

  • constructs/tool-gateway.ts — AgentCore L2 Gateway with GatewayAuthorizer.usingAwsIam() (explicit — the L2 default is Cognito, which would silently create a user pool). One Lambda target (GATEWAY_IAM_ROLE outbound), inline tool schema, read-only RepoTable GetItem, IAM4/IAM5 nag suppressions, public grantInvoke().
  • handlers/tool-repo-config.ts — curated read-only lookup that omits secret ARNs; resolves the federated tool name (<target>___<tool>), validates the owner/name slug, returns {onboarded:false} for un-onboarded repos.
  • stacks/agent.ts + constructs/ecs-agent-cluster.ts — the context gate plus substrate parity: both the AgentCore runtime role and the ECS task role get bedrock-agentcore:InvokeGateway and ABCA_TOOL_GATEWAY_URL.

Agent

  • gateway_tools.py — an in-process SigV4-signing MCP bridge. This deviates from the stdio-proxy shape ADR-019 originally sketched: the Claude Agent SDK's remote MCP client can only attach a static Authorization header, so it cannot sign SigV4 (which signs each request's body + timestamp). The bridge registers an SDK MCP server that signs each Streamable-HTTP request with the compute role's ambient credentials (service bedrock-agentcore) and proxies to the federated tool by suffix. Inert unless ABCA_TOOL_GATEWAY_URL is set.
  • runner.py — registers the bridge when the feature is deployed.

Tests

  • tool-gateway.test.ts (7), tool-repo-config.test.ts, test_gateway_tools.py (18). New files at 100% coverage.
  • Full CDK suite (3935 tests) and full agent suite pass. Default-synth gate asserted (0 Gateway resources without the flag; AWS_IAM Gateway + Lambda target with it).

Docs

  • ADR-019 P1 phasing row updated to record the in-process-bridge deviation and its rationale; Starlight mirror regenerated via mise //docs:sync.

Notes for reviewers

  • Bootstrap policies: default synth is unchanged and the gateway is off by default, so no new CFN types reach the bootstrap policy set on the default path. Reviewers should confirm whether enabling enableToolGateway=true warrants a bootstrap update in a follow-up (the gated path introduces AWS::BedrockAgentCore::Gateway / GatewayTarget).
  • Local pre-push masking hook: the pre-push security:sast:masking scan currently flags ~15 pre-existing files unrelated to this PR — the identical scan fails the same way on pristine main (local semgrep is newer than the version that last cleared these under bug(security): security:sast fails on main — 2 semgrep findings from #695 block pre-push #729/fix(security): clear the two security:sast findings blocking pre-push (#729) #730; mise.toml pins semgrep = "latest"). This PR's new files produce zero masking findings. CI is the authoritative gate.

Closes #641 (P1 scope).

…aws-samples#641)

Implements the P1 slice of ADR-019: a single read-only Lambda tool
(`abca_repo_config`) exposed through a managed AgentCore Gateway with
SigV4 (AWS_IAM) inbound auth and gateway-execution-role outbound (no
credential). The whole feature is context-gated — resources synthesize
only under `--context enableToolGateway=true`, so the default synth is
byte-for-byte unchanged and no new CFN types enter the bootstrap policy
set.

CDK
- constructs/tool-gateway.ts: AgentCore L2 Gateway (usingAwsIam, explicit
  — L2 default is Cognito), one Lambda target (GATEWAY_IAM_ROLE outbound),
  inline tool schema, read-only RepoTable GetItem, IAM4/IAM5 nag
  suppressions, public grantInvoke().
- handlers/tool-repo-config.ts: curated read-only lookup (secret ARNs
  omitted); resolves the federated tool name, validates the owner/name
  slug, returns {onboarded:false} for un-onboarded repos.
- stacks/agent.ts + constructs/ecs-agent-cluster.ts: context gate +
  substrate parity — both the AgentCore runtime role and the ECS task
  role get bedrock-agentcore:InvokeGateway and ABCA_TOOL_GATEWAY_URL.

Agent
- gateway_tools.py: in-process SigV4-signing MCP bridge (not the stdio
  proxy ADR-019 originally sketched — the SDK's remote MCP client can
  only attach a static Bearer header, so it cannot sign SigV4). Registers
  an SDK MCP server that signs each Streamable-HTTP request with the
  compute role's ambient creds (service bedrock-agentcore) and proxies to
  the federated tool by suffix. Inert unless ABCA_TOOL_GATEWAY_URL is set.
- runner.py: register the bridge when the feature is deployed.

Tests: tool-gateway (7), tool-repo-config, agent gateway bridge (18) —
new files at 100%. Full CDK + agent suites pass; masking-scan fallback
allowlisted with a justified inline nosemgrep.

Docs: ADR-019 P1 row updated to record the in-process-bridge deviation;
Starlight mirror regenerated.
@ClintEastman02
ClintEastman02 requested review from a team as code owners August 12, 2026 12:07
@codecov-commenter

codecov-commenter commented Aug 12, 2026

Copy link
Copy Markdown

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

Codecov Report

❌ Patch coverage is 99.00794% with 5 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@819c235). Learn more about missing BASE report.

Files with missing lines Patch % Lines
agent/src/gateway_tools.py 95.60% 4 Missing ⚠️
agent/src/runner.py 85.71% 1 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #755   +/-   ##
=======================================
  Coverage        ?   91.97%           
=======================================
  Files           ?      301           
  Lines           ?    84357           
  Branches        ?     8415           
=======================================
  Hits            ?    77589           
  Misses          ?     6768           
  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.

… parity, type safety

Blocking:
- B1: map AWS::BedrockAgentCore::Gateway + ::GatewayTarget in the bootstrap
  least-privilege resource-action map (create actions covered by the existing
  bedrock-agentcore:* grant), matching the lambda-microvm gated-type precedent.
  Add a gated synth-coverage regression test proving both types are mapped.
- B2: assert substrate parity — with enableToolGateway + compute_type=ecs, the
  ECS task defs carry ABCA_TOOL_GATEWAY_URL and the task role is granted
  bedrock-agentcore:InvokeGateway. This is isadeks' both-substrates ADR bar.

Non-blocking:
- N1: replace gateway.gatewayUrl! non-null assertions with a guarded
  ToolGateway.gatewayUrl accessor that throws (fails synth) instead of shipping
  the literal "undefined" into the env. Used at both substrate call sites.
- N3: disable the agent bridge with a clear WARN when the URL is set but no AWS
  region is resolvable, instead of an opaque downstream SigV4 signing failure.
- N4: model RepoConfigToolResult as a discriminated union keyed on `onboarded`
  so "config present iff onboarded" is a compile-time invariant.
- N5: reframe the neutral-naming comments as a convention, not something
  strip_linear_mcp_servers enforces (it only scrubs on-disk .mcp.json).
- N6: extract _register_gateway_server and unit-test the runner wiring
  (registered under GATEWAY_SERVER_NAME when enabled, absent when disabled).
bgagent added 2 commits August 12, 2026 10:01
…g, error visibility, type polish

Non-blocking + nit fixes from the second review pass on the AgentCore
Gateway P1 (aws-samples#641):

- test: scope the ECS substrate-parity InvokeGateway assertion to the
  EcsAgentCluster TaskRole via `Roles`. The AgentCore runtime role is
  granted the same action in the same template, so the unscoped
  assertion stayed green even if the ECS grant were removed — the exact
  cross-substrate regression the test exists to catch.
- fix(agent): log a WARN on the ImportError (SDK-missing) disable path in
  build_gateway_server, matching the region-unset path — an opted-in
  feature that can't be honored no longer disables silently.
- fix(agent): classify _repo_config_impl failures — expected transport
  errors (OSError/TimeoutError/httpx.HTTPError/McpError) stay WARN;
  anything else (bad response shape, RuntimeError from _match_remote_tool,
  …) routes through log_error_cw so coding/deployment bugs reach
  APPLICATION_LOGS instead of hiding in a WARN line. Still never raises.
- test(agent): cover the N3 region guard (URL-set-but-no-region → None +
  WARN) and the AWS_DEFAULT_REGION fallback; add expected-vs-unexpected
  error-classification tests.
- refactor(agent): add SdkToolResult TypedDict for the {content,isError}
  tool-result shape so drift across return sites is type-checked.
- nit(agent): tighten the _sigv4_auth rotation comment to reflect that
  re-freezing only picks up rotation from a RefreshableCredentials chain.
- nit(cdk): use ComputeType (not string) for compute_type in
  RepoConfigToolResult.
- nit(test): synth tool-gateway.test.ts once in beforeAll (aws-samples#366).
The SdkToolResult TypedDict added for the review nit is rejected by `ty`
(the agent typecheck gate): a TypedDict is not assignable to
`dict[str, Any]`, and the SDK's @tool decorator expects the closure to
return `Awaitable[dict[str, Any]]`, so the type cannot flow through the
decorator either. Revert to `dict[str, Any]` return annotations; the
other 7 review fixes are unaffected.

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

Principal-architect review — P1 AgentCore Gateway tool federation

Verdict: Approve. High-quality, disciplined P1 — context-gated, additive, byte-for-byte-unchanged default synth, 100%-covered new files, docs + Starlight mirror updated in the same PR. No blocking correctness or security bugs. One small pre-merge fix recommended (B1) plus non-blocking follow-ups.

Reviewed with the pr-review-toolkit agents (code-reviewer, silent-failure-hunter, type-design-analyzer, pr-test-analyzer, comment-analyzer) + a focused security pass, then principal-architect judgment.

Vision alignment

Matches ADR-019 P1 exactly: one read-only Lambda target, AWS_IAM/SigV4 inbound, gateway-execution-role outbound (no credential), off by default. Bounded blast radius (read-only, secrets omitted, no new CFN types on the default path). The one deviation — in-process SigV4 bridge instead of the .mcp.json-writer shape the ADR originally sketched (the SDK's remote MCP client can only attach a static header) — is recorded in the ADR phasing table in this same PR. 👍

Recommended pre-merge fix

  • B1 — outbound Gateway call drops solution-UA attribution (#319). agent/src/gateway_tools.py _SigV4.auth_flow signs a raw httpx request to bedrock-agentcore but never injects the md/uksb-wt64nei4u6#agent User-Agent segment that AGENTS.md mandates for every outbound AWS call (the bridge uses botocore only for credential resolution, bypassing the aws_session wiring that normally adds it). ~3 lines using the existing ua.static_user_agent_extra(); User-Agent isn't a default-signed header so it won't break the signature. Attribution-only — no functional/security impact.

Non-blocking — security (data returned is non-sensitive operational metadata; secrets correctly omitted)

  • No tenant/task scoping on the repo arg (tool-repo-config.ts, gateway_tools.py): any agent can query any onboarded repo's compute_type/model_id/build_command/lint_command. Low impact now; worth scoping (inject + validate the task's repo) or documenting the decision before P3 generalizes to N targets.
  • grantReadData broader than needed (tool-gateway.ts:113): handler does a single GetItem; consider an inline dynamodb:GetItem-only policy.
  • InvokeGateway grant resource scope unasserted in tests — confirm the L2 scopes to the gateway ARN (not *) and assert Resource, so the IAM5 suppressions can't mask a wildcard.
  • No https:// guard on ABCA_TOOL_GATEWAY_URL (gateway_tools.py): a one-line scheme check prevents SigV4 X-Amz-Security-Token from ever riding a plaintext request on misconfiguration.

Non-blocking — observability (never-abort posture itself is correct)

  • Unexpected-error path calls log_error_cw without task_id → files under agent_error/unknown instead of the task stream. Thread task_id into the closure.
  • Feature-disable paths (SDK-missing, region-unset) use log("WARN") → stdout-only, not APPLICATION_LOGS; an operator who opted in and finds the tool absent gets no dashboard signal.
  • Empty-success (_result_to_text"" with isError=False) is indistinguishable from a silent upstream drop — a WARN/sentinel would help future tools on this bridge.

Non-blocking — types / comments / tests

  • RepoConfigToolResult JSDoc overclaims "config present iff onboarded" — the onboarded:true arm's fields are all optional, so the guarantee is "field slots accessible iff onboarded." Reword.
  • _expected_gateway_errors() recomputed per call and its httpx/mcp import guards are effectively dead (both always in the container) — cache once at module load.
  • Comment gateway_tools.py:11-13: "the shape ADR-019 originally sketched" (stdio proxy) — the original ADR P1 row sketched a .mcp.json writer, not a stdio proxy. Minor rewording.
  • Test gaps: the string context form (enableToolGateway: 'true' — the actual cdk deploy --context path) is never tested (all tests use the boolean); no test that lookupRepo throwing propagates; OSError (most common transport failure) not exercised in the expected-WARN path.

Governance

  • "Closes #641" may prematurely close the umbrella issue#641 spans P1–P4. Use "Part of #641" / "Refs #641", or ensure P2–P4 follow-ups exist.

Bootstrap / CI

Bootstrap synth-coverage passes with no artifact change required: the two new CFN types are mapped in resource-action-map.ts and their create actions fall under the pre-existing bedrock-agentcore:* grant, so no BOOTSTRAP_VERSION bump / artifact regen / DEPLOYMENT_ROLES.md change is needed (correctly absent from the diff). All 4 CI checks green. Test quality is high — real botocore crypto in the SigV4 test, WARN-vs-loud classification pinned, secret-non-leakage asserted, and the ECS parity test correctly scoped to the EcsAgentClusterTaskRole.

Nice work — clean, well-tested, well-documented slice.

@mayakost
mayakost added this pull request to the merge queue Aug 12, 2026
Merged via the queue into aws-samples:main with commit d981c95 Aug 12, 2026
4 checks passed
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.

feat(registry): unify agent tools behind an AgentCore Gateway (managed MCP aggregation + dual-sided auth, any substrate)

3 participants