feat(tools): Gateway MCP targets — self-service registration, agent wiring & health UX (#419)#457
Merged
Merged
Conversation
…ervice) (#419) CreateGatewayTarget rejected our GATEWAY_IAM_ROLE targets with "IamCredentialProvider is required for mcpServer targets using IAM authentication": unlike OpenAPI/Lambda targets (which accept a bare GATEWAY_IAM_ROLE), an mcpServer (HTTP endpoint) target must supply an explicit iamCredentialProvider naming the AWS service to sign for. We were sending `[{credentialProviderType: GATEWAY_IAM_ROLE}]` with no nested provider. - New credential type NONE (public endpoint) — omits credentialProviderConfigurations entirely (the API parameter is optional). This is now the default (least-config path; the admin opts into IAM/OAuth/API-key). - GATEWAY_IAM_ROLE now carries aws_service (required) + optional aws_region, sent as credentialProvider.iamCredentialProvider{service, region?}. Model validator requires aws_service for IAM and the create/update calls omit the credential block for NONE. - Backend: MCPGatewayConfig + request/response models + GatewayTargetService. - Frontend: 'none' credential option (default), AWS service/region fields shown for the IAM case, build/restore wiring, and the discover auth mapping unchanged (gateway_iam_role → aws-iam, else none). Verified: full backend suite 3466 passed; tool-form gateway spec 6/6 (ng test). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…419 catalog tools (#419) The agent resolved the gateway from a hardcoded `/strands-agent-chatbot/dev/...` SSM param, while the admin form (#419) registered targets on the CDK gateway `/{PROJECT_PREFIX}/gateway/id` — two different gateways, so admin-registered tools never reached the agent. - New shared `gateway_identity.resolve_gateway_id`/`gateway_url_from_id`: one source of truth (explicit id → AGENTCORE_GATEWAY_ID override → SSM `/{PROJECT_PREFIX}/gateway/id`). The agent's `get_gateway_url_from_ssm` and GatewayTargetService both resolve through it, so they can't diverge. - Expand a `protocol=mcp` catalog tool (`gateway_class_search`) into the gateway's runtime per-tool ids (`gateway_<targetName>___<toolName>`) in base_agent, so the FilteredMCPClient matches them; raw RBAC gateway ids (already containing `___`) pass through unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rant, health, discovery UX (#419) Make admins able to register any same-account Lambda-URL MCP server through the form with no infra change, and surface the failures that were previously silent. Per-target Lambda grant (replaces the gateway role's standing `mcp-*` wildcard): - app-api grants the gateway role `lambda:InvokeFunctionUrl` on exactly the registered function at registration (`AddPermission` naming the role as Principal — a role-specific resource grant authorizes it same-account, no identity grant) and revokes it on delete. `gateway_lambda_grant.py` + GatewayTargetService lifecycle; new `MCPGatewayConfig.lambda_function_name` and a conditional "Lambda function name" form field. - Cross-account validation: `GetFunctionUrlConfig` confirms same-account + that the endpoint is under the function URL; otherwise a 400 tells the admin to go public or use a credential provider. - CDK: gateway role loses its Lambda-invoke wildcard (logs only); app-api gains scoped `AddPermission`/`RemovePermission`/`GetFunctionUrlConfig` + `GetGateway`. Gateway target health: `GET /admin/tools/{id}/gateway-status` (+ statusReasons on GatewayTargetInfo) and a Ready/Syncing/Failed/Missing badge on the tools list, so a FAILED target sync is visible instead of only "the agent can't see the tool". Discovery + form polish: unwrap the upstream HTTP status from MCP discovery errors (403 stays 403 with an actionable message; 401→400 to avoid the SPA logout); auto-derive awsService/awsRegion from the endpoint and recommend IAM when an AWS host is left on None. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on the original mcpServer credential-config fix (
None+ IAM service) into an end-to-end, admin-self-service path for registering external MCP servers as AgentCore Gateway targets — and surfaces the failures that were previously silent.The problem
A registered + enabled
protocol=mcptool (gateway_class_search) produced "I don't have that tool" from the agent. Investigation found three stacked blockers, none of them obvious:/strands-agent-chatbot/dev/…gateway; the admin form registered on the CDK gateway/{PROJECT_PREFIX}/gateway/id. Targets landed on a gateway the agent never connected to.AuthType=AWS_IAM; the gateway role lackedlambda:InvokeFunctionUrl(a distinct action fromInvokeFunction) and the{prefix}-mcp-*wildcard didn't match themcp-<name>-<env>naming → target stuckFAILED.<targetName>___<toolName>, but the catalog tool idgateway_class_searchmatched none of them → silent drop.What this PR does
Unify the gateway (Blocker 0) — shared
gateway_identity.resolve_gateway_id(one source of truth:AGENTCORE_GATEWAY_IDoverride → SSM/{PROJECT_PREFIX}/gateway/id); the agent andGatewayTargetServiceboth use it, so they can't diverge.Expand catalog tools (Blocker 2) —
base_agentexpands aprotocol=mcptool into the gateway's runtimegateway_<target>___<tool>ids so theFilteredMCPClientmatches; raw RBAC gateway ids pass through.Self-service per-target IAM grant (Blocker 1, done right) — instead of a standing wildcard on the gateway role (an infra change per off-convention server), app-api grants the gateway role
InvokeFunctionUrlon exactly the registered function at registration (lambda:AddPermissionnaming the role — a role-specific resource grant authorizes it same-account, no identity grant) and revokes it on delete. So an admin registers any same-account Lambda-URL MCP server through the form with no infra change, and the gateway role can invoke only what was explicitly registered.GetFunctionUrlConfigconfirms same-account + that the endpoint is under the function URL; otherwise a 400 tells the admin to make it public or use a credential provider.AddPermission/RemovePermission/GetFunctionUrlConfig+GetGateway.MCPGatewayConfig.lambda_function_name+ a conditional "Lambda function name" form field (shown only for Lambda-URL endpoints).Health surfacing —
GET /admin/tools/{id}/gateway-status(+statusReasons) and a Ready/Syncing/Failed/Missing badge on the tools list, so a failed target sync is visible instead of only appearing as a missing tool.Discovery + form polish — MCP discovery errors unwrap the real upstream status (403 stays 403 with an actionable message; 401→400 to avoid tripping the SPA logout); awsService/awsRegion auto-derive from the endpoint; IAM is recommended when an AWS host is left on
None.Security posture
The gateway's inbound SigV4 (
authorizerType=AWS_IAM) is free and unchanged. Outbound IAM is the boundary that keeps an MCP server from being a public bypass of RBAC/approval — kept, but moved from a standing wildcard to a least-privilege per-target grant. Same-account Lambda-URL targets only; cross-account / API-Gateway / custom-domain targets must be public or use a credential provider (the form says so).Testing
ng buildtype-checks.tscclean.FAILED → READYon the per-target grant alone; agent loads all 9 class-search tools end-to-end;gateway_class_searchmigrated to the new scheme.Deploy
platform.yml(CDK role changes — dev gateway is already in the end-state).backend.yml(new code).🤖 Generated with Claude Code