From 5cbce93ec8617cf4eab5c02e3598a306899f01af Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Tue, 24 Feb 2026 23:36:52 -0500 Subject: [PATCH 1/8] fix: correct CDK template type names and prop names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CDK stack template used McpSpec (doesn't exist) instead of AgentCoreMcpSpec, and passed wrong prop names to AgentCoreMcp: - spec → mcpSpec - application → agentCoreApplication - Added missing projectName prop --- .../assets.snapshot.test.ts.snap | 22 +++++-------------- src/assets/cdk/bin/cdk.ts | 5 ----- src/assets/cdk/lib/cdk-stack.ts | 17 +++++--------- 3 files changed, 12 insertions(+), 32 deletions(-) diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap index 89ef1df5..c6171856 100644 --- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap @@ -36,7 +36,6 @@ import { AgentCoreStack } from '../lib/cdk-stack'; import { ConfigIO, type AwsDeploymentTarget } from '@aws/agentcore-cdk'; import { App, type Environment } from 'aws-cdk-lib'; import * as path from 'path'; -import * as fs from 'fs'; function toEnvironment(target: AwsDeploymentTarget): Environment { return { @@ -59,11 +58,8 @@ async function main() { // Read MCP configuration if it exists let mcpSpec; - let mcpDeployedState; try { mcpSpec = await configIO.readMcpSpec(); - const deployedState = JSON.parse(fs.readFileSync(path.join(configRoot, '.cli', 'deployed-state.json'), 'utf8')); - mcpDeployedState = deployedState?.mcp; } catch { // MCP config is optional } @@ -81,7 +77,6 @@ async function main() { new AgentCoreStack(app, stackName, { spec, mcpSpec, - mcpDeployedState, env, description: \`AgentCore stack for \${spec.name} deployed to \${target.name} (\${target.region})\`, tags: { @@ -221,8 +216,7 @@ exports[`Assets Directory Snapshots > CDK assets > cdk/cdk/lib/cdk-stack.ts shou AgentCoreApplication, AgentCoreMcp, type AgentCoreProjectSpec, - type McpSpec, - type McpDeployedState, + type AgentCoreMcpSpec, } from '@aws/agentcore-cdk'; import { CfnOutput, Stack, type StackProps } from 'aws-cdk-lib'; import { Construct } from 'constructs'; @@ -235,11 +229,7 @@ export interface AgentCoreStackProps extends StackProps { /** * The MCP specification containing gateways and servers. */ - mcpSpec?: McpSpec; - /** - * The MCP deployed state. - */ - mcpDeployedState?: McpDeployedState; + mcpSpec?: AgentCoreMcpSpec; } /** @@ -255,7 +245,7 @@ export class AgentCoreStack extends Stack { constructor(scope: Construct, id: string, props: AgentCoreStackProps) { super(scope, id, props); - const { spec, mcpSpec, mcpDeployedState } = props; + const { spec, mcpSpec } = props; // Create AgentCoreApplication with all agents this.application = new AgentCoreApplication(this, 'Application', { @@ -265,9 +255,9 @@ export class AgentCoreStack extends Stack { // Create AgentCoreMcp if there are gateways configured if (mcpSpec?.agentCoreGateways && mcpSpec.agentCoreGateways.length > 0) { new AgentCoreMcp(this, 'Mcp', { - spec: mcpSpec, - deployedState: mcpDeployedState, - application: this.application, + projectName: spec.name, + mcpSpec, + agentCoreApplication: this.application, }); } diff --git a/src/assets/cdk/bin/cdk.ts b/src/assets/cdk/bin/cdk.ts index e590b9f2..da2ed0eb 100644 --- a/src/assets/cdk/bin/cdk.ts +++ b/src/assets/cdk/bin/cdk.ts @@ -3,7 +3,6 @@ import { AgentCoreStack } from '../lib/cdk-stack'; import { ConfigIO, type AwsDeploymentTarget } from '@aws/agentcore-cdk'; import { App, type Environment } from 'aws-cdk-lib'; import * as path from 'path'; -import * as fs from 'fs'; function toEnvironment(target: AwsDeploymentTarget): Environment { return { @@ -26,11 +25,8 @@ async function main() { // Read MCP configuration if it exists let mcpSpec; - let mcpDeployedState; try { mcpSpec = await configIO.readMcpSpec(); - const deployedState = JSON.parse(fs.readFileSync(path.join(configRoot, '.cli', 'deployed-state.json'), 'utf8')); - mcpDeployedState = deployedState?.mcp; } catch { // MCP config is optional } @@ -48,7 +44,6 @@ async function main() { new AgentCoreStack(app, stackName, { spec, mcpSpec, - mcpDeployedState, env, description: `AgentCore stack for ${spec.name} deployed to ${target.name} (${target.region})`, tags: { diff --git a/src/assets/cdk/lib/cdk-stack.ts b/src/assets/cdk/lib/cdk-stack.ts index fbff1465..ca357c14 100644 --- a/src/assets/cdk/lib/cdk-stack.ts +++ b/src/assets/cdk/lib/cdk-stack.ts @@ -2,8 +2,7 @@ import { AgentCoreApplication, AgentCoreMcp, type AgentCoreProjectSpec, - type McpSpec, - type McpDeployedState, + type AgentCoreMcpSpec, } from '@aws/agentcore-cdk'; import { CfnOutput, Stack, type StackProps } from 'aws-cdk-lib'; import { Construct } from 'constructs'; @@ -16,11 +15,7 @@ export interface AgentCoreStackProps extends StackProps { /** * The MCP specification containing gateways and servers. */ - mcpSpec?: McpSpec; - /** - * The MCP deployed state. - */ - mcpDeployedState?: McpDeployedState; + mcpSpec?: AgentCoreMcpSpec; } /** @@ -36,7 +31,7 @@ export class AgentCoreStack extends Stack { constructor(scope: Construct, id: string, props: AgentCoreStackProps) { super(scope, id, props); - const { spec, mcpSpec, mcpDeployedState } = props; + const { spec, mcpSpec } = props; // Create AgentCoreApplication with all agents this.application = new AgentCoreApplication(this, 'Application', { @@ -46,9 +41,9 @@ export class AgentCoreStack extends Stack { // Create AgentCoreMcp if there are gateways configured if (mcpSpec?.agentCoreGateways && mcpSpec.agentCoreGateways.length > 0) { new AgentCoreMcp(this, 'Mcp', { - spec: mcpSpec, - deployedState: mcpDeployedState, - application: this.application, + projectName: spec.name, + mcpSpec, + agentCoreApplication: this.application, }); } From 065f7df2e0322b08e7829918f4d8243d72ed86f1 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Wed, 25 Feb 2026 02:41:46 -0500 Subject: [PATCH 2/8] fix: collect API key credential ARNs and write to deployed state before CDK synth API key credential providers were created during deploy but their ARNs were not stored in deployed state, causing CDK to fail with 'Credential not found in deployed state' for gateway targets with API key auth. - Return credentialProviderArn from create/update API key providers - Unify API key and OAuth credential ARNs into single deployed state map - Move credential setup before CDK synth so template can read ARNs - Write partial deployed state with credentials before synth --- src/cli/commands/deploy/actions.ts | 167 ++++++++++-------- .../operations/deploy/pre-deploy-identity.ts | 3 + .../identity/api-key-credential-provider.ts | 19 +- 3 files changed, 113 insertions(+), 76 deletions(-) diff --git a/src/cli/commands/deploy/actions.ts b/src/cli/commands/deploy/actions.ts index bc6b14ce..44fb1628 100644 --- a/src/cli/commands/deploy/actions.ts +++ b/src/cli/commands/deploy/actions.ts @@ -20,6 +20,7 @@ import { } from '../../operations/deploy'; import { formatTargetStatus, getGatewayTargetStatuses } from '../../operations/deploy/gateway-status'; import type { DeployResult } from './types'; +import type { DeployedState } from '../../../schema'; export interface ValidatedDeployOptions { target: string; @@ -104,70 +105,7 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise 0 ? new SecureCredentials(envCredentials) : undefined; + // Unified credentials map for deployed state (both API Key and OAuth) + const deployedCredentials: Record< + string, + { credentialProviderArn: string; clientSecretArn?: string; callbackUrl?: string } + > = {}; + if (hasIdentityApiProviders(context.projectSpec)) { startStep('Creating credentials...'); @@ -201,14 +145,19 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise = {}; if (hasIdentityOAuthProviders(context.projectSpec)) { startStep('Creating OAuth credentials...'); @@ -226,10 +175,10 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise 0) { + const existingPreSynthState = await configIO.readDeployedState().catch(() => ({targets: {}} as DeployedState)); + const targetState = existingPreSynthState.targets?.[target.name] ?? { resources: {} }; + if (!targetState.resources) targetState.resources = {}; + targetState.resources.credentials = deployedCredentials; + if (identityKmsKeyArn) targetState.resources.identityKmsKeyArn = identityKmsKeyArn; + await configIO.writeDeployedState({ + ...existingPreSynthState, + targets: { ...existingPreSynthState.targets, [target.name]: targetState }, + }); + } + + // Synthesize CloudFormation templates + startStep('Synthesize CloudFormation'); + const switchableIoHost = options.verbose ? createSwitchableIoHost() : undefined; + const synthResult = await synthesizeCdk( + context.cdkProject, + switchableIoHost ? { ioHost: switchableIoHost.ioHost } : undefined + ); + toolkitWrapper = synthResult.toolkitWrapper; + const stackNames = synthResult.stackNames; + if (stackNames.length === 0) { + endStep('error', 'No stacks found'); + logger.finalize(false); + return { success: false, error: 'No stacks found to deploy', logPath: logger.getRelativeLogPath() }; + } + const stackName = stackNames[0]!; + endStep('success'); + + // Check if bootstrap needed + startStep('Check bootstrap status'); + const bootstrapCheck = await checkBootstrapNeeded(context.awsTargets); + if (bootstrapCheck.needsBootstrap) { + if (options.autoConfirm) { + logger.log('Bootstrap needed, auto-confirming...'); + await bootstrapEnvironment(toolkitWrapper, target); + } else { + endStep('error', 'Bootstrap required'); + logger.finalize(false); + return { + success: false, + error: 'AWS environment needs bootstrapping. Run with --yes to auto-bootstrap.', + logPath: logger.getRelativeLogPath(), + }; + } + } + endStep('success'); + + // Check stack deployability + startStep('Check stack status'); + const deployabilityCheck = await checkStackDeployability(target.region, stackNames); + if (!deployabilityCheck.canDeploy) { + endStep('error', deployabilityCheck.message); + logger.finalize(false); + return { + success: false, + error: deployabilityCheck.message ?? 'Stack is not in a deployable state', + logPath: logger.getRelativeLogPath(), + }; + } + endStep('success'); + + // Plan mode: stop after synth and checks, don't deploy + if (options.plan) { + logger.finalize(true); + await toolkitWrapper.dispose(); + toolkitWrapper = null; + return { + success: true, + targetName: target.name, + stackName, + logPath: logger.getRelativeLogPath(), + }; + } + // Deploy const hasGateways = mcpSpec?.agentCoreGateways && mcpSpec.agentCoreGateways.length > 0; const deployStepName = hasGateways ? 'Deploying gateways...' : 'Deploy to AWS'; @@ -313,7 +338,7 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise { +): Promise<{ success: boolean; credentialProviderArn?: string; error?: string }> { try { await client.send( new CreateApiKeyCredentialProviderCommand({ @@ -48,11 +48,18 @@ export async function createApiKeyProvider( apiKey: apiKey, }) ); - return { success: true }; + // Create response doesn't include credentialProviderArn — fetch it + const getResponse = await client.send(new GetApiKeyCredentialProviderCommand({ name: providerName })); + return { success: true, credentialProviderArn: getResponse.credentialProviderArn }; } catch (error) { const errorName = (error as { name?: string }).name; if (errorName === 'ConflictException' || errorName === 'ResourceAlreadyExistsException') { - return { success: true }; + try { + const getResponse = await client.send(new GetApiKeyCredentialProviderCommand({ name: providerName })); + return { success: true, credentialProviderArn: getResponse.credentialProviderArn }; + } catch { + return { success: true }; + } } return { success: false, @@ -68,7 +75,7 @@ export async function updateApiKeyProvider( client: BedrockAgentCoreControlClient, providerName: string, apiKey: string -): Promise<{ success: boolean; error?: string }> { +): Promise<{ success: boolean; credentialProviderArn?: string; error?: string }> { try { await client.send( new UpdateApiKeyCredentialProviderCommand({ @@ -76,7 +83,9 @@ export async function updateApiKeyProvider( apiKey: apiKey, }) ); - return { success: true }; + // Update response doesn't include credentialProviderArn — fetch it + const getResponse = await client.send(new GetApiKeyCredentialProviderCommand({ name: providerName })); + return { success: true, credentialProviderArn: getResponse.credentialProviderArn }; } catch (error) { return { success: false, From 29c61124eebcca7b1ea0348384871e2b522af8fc Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Wed, 25 Feb 2026 02:41:58 -0500 Subject: [PATCH 3/8] fix: pass credential ARNs from deployed state to CDK gateway construct CDK template now reads deployed-state.json and extracts credential provider ARNs per target, passing them to AgentCoreMcp so gateway targets can reference outbound auth credentials. --- .../assets.snapshot.test.ts.snap | 22 ++++++++++++++++++- src/assets/cdk/bin/cdk.ts | 15 +++++++++++++ src/assets/cdk/lib/cdk-stack.ts | 7 +++++- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap index c6171856..bfe30df7 100644 --- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap @@ -36,6 +36,7 @@ import { AgentCoreStack } from '../lib/cdk-stack'; import { ConfigIO, type AwsDeploymentTarget } from '@aws/agentcore-cdk'; import { App, type Environment } from 'aws-cdk-lib'; import * as path from 'path'; +import * as fs from 'fs'; function toEnvironment(target: AwsDeploymentTarget): Environment { return { @@ -64,6 +65,14 @@ async function main() { // MCP config is optional } + // Read deployed state for credential ARNs (populated by pre-deploy identity setup) + let deployedState: Record | undefined; + try { + deployedState = JSON.parse(fs.readFileSync(path.join(configRoot, '.cli', 'deployed-state.json'), 'utf8')); + } catch { + // Deployed state may not exist on first deploy + } + if (targets.length === 0) { throw new Error('No deployment targets configured. Please define targets in agentcore/aws-targets.json'); } @@ -74,9 +83,15 @@ async function main() { const env = toEnvironment(target); const stackName = toStackName(spec.name, target.name); + // Extract credentials from deployed state for this target + const targetState = (deployedState as Record)?.targets as Record> | undefined; + const targetResources = targetState?.[target.name]?.resources as Record | undefined; + const credentials = targetResources?.credentials as Record | undefined; + new AgentCoreStack(app, stackName, { spec, mcpSpec, + credentials, env, description: \`AgentCore stack for \${spec.name} deployed to \${target.name} (\${target.region})\`, tags: { @@ -230,6 +245,10 @@ export interface AgentCoreStackProps extends StackProps { * The MCP specification containing gateways and servers. */ mcpSpec?: AgentCoreMcpSpec; + /** + * Credential provider ARNs from deployed state, keyed by credential name. + */ + credentials?: Record; } /** @@ -245,7 +264,7 @@ export class AgentCoreStack extends Stack { constructor(scope: Construct, id: string, props: AgentCoreStackProps) { super(scope, id, props); - const { spec, mcpSpec } = props; + const { spec, mcpSpec, credentials } = props; // Create AgentCoreApplication with all agents this.application = new AgentCoreApplication(this, 'Application', { @@ -258,6 +277,7 @@ export class AgentCoreStack extends Stack { projectName: spec.name, mcpSpec, agentCoreApplication: this.application, + credentials, }); } diff --git a/src/assets/cdk/bin/cdk.ts b/src/assets/cdk/bin/cdk.ts index da2ed0eb..498eca3a 100644 --- a/src/assets/cdk/bin/cdk.ts +++ b/src/assets/cdk/bin/cdk.ts @@ -3,6 +3,7 @@ import { AgentCoreStack } from '../lib/cdk-stack'; import { ConfigIO, type AwsDeploymentTarget } from '@aws/agentcore-cdk'; import { App, type Environment } from 'aws-cdk-lib'; import * as path from 'path'; +import * as fs from 'fs'; function toEnvironment(target: AwsDeploymentTarget): Environment { return { @@ -31,6 +32,14 @@ async function main() { // MCP config is optional } + // Read deployed state for credential ARNs (populated by pre-deploy identity setup) + let deployedState: Record | undefined; + try { + deployedState = JSON.parse(fs.readFileSync(path.join(configRoot, '.cli', 'deployed-state.json'), 'utf8')); + } catch { + // Deployed state may not exist on first deploy + } + if (targets.length === 0) { throw new Error('No deployment targets configured. Please define targets in agentcore/aws-targets.json'); } @@ -41,9 +50,15 @@ async function main() { const env = toEnvironment(target); const stackName = toStackName(spec.name, target.name); + // Extract credentials from deployed state for this target + const targetState = (deployedState as Record)?.targets as Record> | undefined; + const targetResources = targetState?.[target.name]?.resources as Record | undefined; + const credentials = targetResources?.credentials as Record | undefined; + new AgentCoreStack(app, stackName, { spec, mcpSpec, + credentials, env, description: `AgentCore stack for ${spec.name} deployed to ${target.name} (${target.region})`, tags: { diff --git a/src/assets/cdk/lib/cdk-stack.ts b/src/assets/cdk/lib/cdk-stack.ts index ca357c14..ecbf15b8 100644 --- a/src/assets/cdk/lib/cdk-stack.ts +++ b/src/assets/cdk/lib/cdk-stack.ts @@ -16,6 +16,10 @@ export interface AgentCoreStackProps extends StackProps { * The MCP specification containing gateways and servers. */ mcpSpec?: AgentCoreMcpSpec; + /** + * Credential provider ARNs from deployed state, keyed by credential name. + */ + credentials?: Record; } /** @@ -31,7 +35,7 @@ export class AgentCoreStack extends Stack { constructor(scope: Construct, id: string, props: AgentCoreStackProps) { super(scope, id, props); - const { spec, mcpSpec } = props; + const { spec, mcpSpec, credentials } = props; // Create AgentCoreApplication with all agents this.application = new AgentCoreApplication(this, 'Application', { @@ -44,6 +48,7 @@ export class AgentCoreStack extends Stack { projectName: spec.name, mcpSpec, agentCoreApplication: this.application, + credentials, }); } From d7f954df0f98f3058fe1d9a6ea309346ac998457 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Wed, 25 Feb 2026 03:10:47 -0500 Subject: [PATCH 4/8] fix: reorder TUI preflight to create credentials before CDK synth --- src/cli/tui/hooks/useCdkPreflight.ts | 81 ++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 15 deletions(-) diff --git a/src/cli/tui/hooks/useCdkPreflight.ts b/src/cli/tui/hooks/useCdkPreflight.ts index 785e60c9..b8be26c8 100644 --- a/src/cli/tui/hooks/useCdkPreflight.ts +++ b/src/cli/tui/hooks/useCdkPreflight.ts @@ -1,4 +1,4 @@ -import { SecureCredentials } from '../../../lib'; +import { ConfigIO, SecureCredentials } from '../../../lib'; import { AwsCredentialsError, validateAwsCredentials } from '../../aws/account'; import { type CdkToolkitWrapper, type SwitchableIoHost, createSwitchableIoHost } from '../../cdk/toolkit-lib'; import { getErrorMessage, isExpiredTokenError, isNoCredentialsError } from '../../errors'; @@ -361,6 +361,20 @@ export function useCdkPreflight(options: PreflightOptions): PreflightResult { return; } + // Check if API key providers need setup before CDK synth (CDK needs credential ARNs) + // Skip this check if skipIdentityCheck is true (e.g., plan command only synthesizes) + const needsCredentialSetup = !skipIdentityCheck && (hasIdentityApiProviders(preflightContext.projectSpec) || hasIdentityOAuthProviders(preflightContext.projectSpec)); + if (needsCredentialSetup) { + // Get all credentials for the prompt (not just missing ones) + const allCredentials = getAllCredentials(preflightContext.projectSpec); + + // Always show dialog when credentials exist + setMissingCredentials(allCredentials); + setPhase('credentials-prompt'); + isRunningRef.current = false; // Reset so identity-setup can run after user input + return; + } + // Step: Synthesize CloudFormation updateStep(STEP_SYNTH, { status: 'running' }); logger.startStep('Synthesize CloudFormation'); @@ -422,20 +436,6 @@ export function useCdkPreflight(options: PreflightOptions): PreflightResult { updateStep(STEP_STACK_STATUS, { status: 'success' }); } - // Check if API key providers need setup - always prompt user for credential source - // Skip this check if skipIdentityCheck is true (e.g., plan command only synthesizes) - const needsApiKeySetup = !skipIdentityCheck && hasIdentityApiProviders(preflightContext.projectSpec); - if (needsApiKeySetup) { - // Get all credentials for the prompt (not just missing ones) - const allCredentials = getAllCredentials(preflightContext.projectSpec); - - // Always show dialog when credentials exist - setMissingCredentials(allCredentials); - setPhase('credentials-prompt'); - isRunningRef.current = false; // Reset so identity-setup can run after user input - return; - } - // Check if bootstrap is needed const bootstrapCheck = await checkBootstrapNeeded(preflightContext.awsTargets); if (bootstrapCheck.needsBootstrap && bootstrapCheck.target) { @@ -566,6 +566,19 @@ export function useCdkPreflight(options: PreflightOptions): PreflightResult { logger.endStep('success'); setSteps(prev => prev.map((s, i) => (i === prev.length - 1 ? { ...s, status: 'success' } : s))); + // Collect API Key credential ARNs for deployed state + const deployedCredentials: Record< + string, + { credentialProviderArn: string; clientSecretArn?: string; callbackUrl?: string } + > = {}; + for (const result of identityResult.results) { + if (result.credentialProviderArn) { + deployedCredentials[result.providerName] = { + credentialProviderArn: result.credentialProviderArn, + }; + } + } + // Set up OAuth credential providers if needed if (hasIdentityOAuthProviders(context.projectSpec)) { setSteps(prev => [...prev, { label: 'Set up OAuth providers', status: 'running' }]); @@ -617,14 +630,52 @@ export function useCdkPreflight(options: PreflightOptions): PreflightResult { } } setOauthCredentials(creds); + Object.assign(deployedCredentials, creds); logger.endStep('success'); setSteps(prev => prev.map((s, i) => (i === prev.length - 1 ? { ...s, status: 'success' } : s))); } + // Write partial deployed state with credential ARNs before CDK synth + if (Object.keys(deployedCredentials).length > 0) { + const configIO = new ConfigIO(); + const target = context.awsTargets[0]; + const existingState = await configIO.readDeployedState().catch(() => ({ targets: {} } as any)); + const targetState = existingState.targets?.[target!.name] ?? { resources: {} }; + if (!targetState.resources) targetState.resources = {}; + targetState.resources.credentials = deployedCredentials; + if (identityResult.kmsKeyArn) targetState.resources.identityKmsKeyArn = identityResult.kmsKeyArn; + await configIO.writeDeployedState({ + ...existingState, + targets: { ...existingState.targets, [target!.name]: targetState }, + }); + } + // Clear runtime credentials setRuntimeCredentials(null); + // Re-synth now that credentials are in deployed state + updateStep(STEP_SYNTH, { status: 'running' }); + logger.startStep('Synthesize CloudFormation'); + try { + const synthResult = await synthesizeCdk(context.cdkProject, { + ioHost: switchableIoHost!.ioHost, + previousWrapper: wrapperRef.current, + }); + wrapperRef.current = synthResult.toolkitWrapper; + setCdkToolkitWrapper(synthResult.toolkitWrapper); + setStackNames(synthResult.stackNames); + logger.endStep('success'); + updateStep(STEP_SYNTH, { status: 'success' }); + } catch (err) { + const errorMsg = formatError(err); + logger.endStep('error', errorMsg); + updateStep(STEP_SYNTH, { status: 'error', error: logger.getFailureMessage('Synthesize CloudFormation') }); + setPhase('error'); + isRunningRef.current = false; + return; + } + // Check if bootstrap is needed const bootstrapCheck = await checkBootstrapNeeded(context.awsTargets); if (bootstrapCheck.needsBootstrap && bootstrapCheck.target) { From d2064cf4929496bac7555e4cddbcfe5d216148f8 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Wed, 25 Feb 2026 03:24:34 -0500 Subject: [PATCH 5/8] fix: fetch OAuth credential ARN via Get after create/update --- .../identity/oauth2-credential-provider.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/cli/operations/identity/oauth2-credential-provider.ts b/src/cli/operations/identity/oauth2-credential-provider.ts index e148d61f..cd037670 100644 --- a/src/cli/operations/identity/oauth2-credential-provider.ts +++ b/src/cli/operations/identity/oauth2-credential-provider.ts @@ -96,7 +96,12 @@ export async function createOAuth2Provider( ): Promise<{ success: boolean; result?: OAuth2ProviderResult; error?: string }> { try { const response = await client.send(new CreateOauth2CredentialProviderCommand(buildOAuth2Config(params))); - const result = extractResult(response); + let result = extractResult(response); + if (!result) { + // Create response may not include credentialProviderArn — fetch it + const getResult = await getOAuth2Provider(client, params.name); + result = getResult.result; + } if (!result) { return { success: false, error: 'No credential provider ARN in response' }; } @@ -146,7 +151,11 @@ export async function updateOAuth2Provider( ): Promise<{ success: boolean; result?: OAuth2ProviderResult; error?: string }> { try { const response = await client.send(new UpdateOauth2CredentialProviderCommand(buildOAuth2Config(params))); - const result = extractResult(response); + let result = extractResult(response); + if (!result) { + const getResult = await getOAuth2Provider(client, params.name); + result = getResult.result; + } if (!result) { return { success: false, error: 'No credential provider ARN in response' }; } From 7a58be926764ad8566518ee48332295e31572aeb Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Wed, 25 Feb 2026 03:54:08 -0500 Subject: [PATCH 6/8] fix: handle Mcp prefix in gateway output key parsing --- src/cli/cloudformation/outputs.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/cloudformation/outputs.ts b/src/cli/cloudformation/outputs.ts index 7e053e09..9cf5ae77 100644 --- a/src/cli/cloudformation/outputs.ts +++ b/src/cli/cloudformation/outputs.ts @@ -45,8 +45,8 @@ export function parseGatewayOutputs( const gatewayNames = Object.keys(gatewaySpecs); const gatewayIdMap = new Map(gatewayNames.map(name => [toPascalId(name), name])); - // Match pattern: Gateway{GatewayName}UrlOutput - const outputPattern = /^Gateway(.+?)UrlOutput/; + // Match patterns: Gateway{Name}{Type}Output or McpGateway{Name}{Type}Output + const outputPattern = /^(?:Mcp)?Gateway(.+?)(Id|Arn|Url)Output/; for (const [key, value] of Object.entries(outputs)) { const match = outputPattern.exec(key); From 770a7379c1dae0dc1e44debf21f1c514f7d26459 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Wed, 25 Feb 2026 11:52:14 -0500 Subject: [PATCH 7/8] fix: bump CDK version to 2.239.0 in project template --- src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap | 2 +- src/assets/cdk/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap index bfe30df7..1eb87cc5 100644 --- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap @@ -328,7 +328,7 @@ exports[`Assets Directory Snapshots > CDK assets > cdk/cdk/package.json should m }, "dependencies": { "@aws/agentcore-cdk": "^0.1.0-alpha.1", - "aws-cdk-lib": "2.234.1", + "aws-cdk-lib": "2.239.0", "constructs": "^10.0.0" } } diff --git a/src/assets/cdk/package.json b/src/assets/cdk/package.json index 77e21bd0..eb09002e 100644 --- a/src/assets/cdk/package.json +++ b/src/assets/cdk/package.json @@ -24,7 +24,7 @@ }, "dependencies": { "@aws/agentcore-cdk": "^0.1.0-alpha.1", - "aws-cdk-lib": "2.234.1", + "aws-cdk-lib": "2.239.0", "constructs": "^10.0.0" } } From 8f65e67fdcf2478a71dbd493ffc63833966c3c18 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Wed, 25 Feb 2026 14:01:22 -0500 Subject: [PATCH 8/8] fix: lint errors in deploy actions and preflight hook --- package.json | 2 +- src/cli/commands/deploy/actions.ts | 2 +- src/cli/tui/hooks/useCdkPreflight.ts | 9 +++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 1bd2478b..3179d4ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aws/agentcore", - "version": "0.3.0-preview.2.1", + "version": "0.3.0-preview.3", "description": "CLI for Amazon Bedrock AgentCore", "license": "Apache-2.0", "repository": { diff --git a/src/cli/commands/deploy/actions.ts b/src/cli/commands/deploy/actions.ts index 44fb1628..70cd4bcc 100644 --- a/src/cli/commands/deploy/actions.ts +++ b/src/cli/commands/deploy/actions.ts @@ -192,7 +192,7 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise 0) { const existingPreSynthState = await configIO.readDeployedState().catch(() => ({targets: {}} as DeployedState)); const targetState = existingPreSynthState.targets?.[target.name] ?? { resources: {} }; - if (!targetState.resources) targetState.resources = {}; + targetState.resources ??= {}; targetState.resources.credentials = deployedCredentials; if (identityKmsKeyArn) targetState.resources.identityKmsKeyArn = identityKmsKeyArn; await configIO.writeDeployedState({ diff --git a/src/cli/tui/hooks/useCdkPreflight.ts b/src/cli/tui/hooks/useCdkPreflight.ts index b8be26c8..9b95c742 100644 --- a/src/cli/tui/hooks/useCdkPreflight.ts +++ b/src/cli/tui/hooks/useCdkPreflight.ts @@ -1,4 +1,5 @@ import { ConfigIO, SecureCredentials } from '../../../lib'; +import type { DeployedState } from '../../../schema'; import { AwsCredentialsError, validateAwsCredentials } from '../../aws/account'; import { type CdkToolkitWrapper, type SwitchableIoHost, createSwitchableIoHost } from '../../cdk/toolkit-lib'; import { getErrorMessage, isExpiredTokenError, isNoCredentialsError } from '../../errors'; @@ -640,9 +641,9 @@ export function useCdkPreflight(options: PreflightOptions): PreflightResult { if (Object.keys(deployedCredentials).length > 0) { const configIO = new ConfigIO(); const target = context.awsTargets[0]; - const existingState = await configIO.readDeployedState().catch(() => ({ targets: {} } as any)); + const existingState = await configIO.readDeployedState().catch(() => ({ targets: {} } as DeployedState)); const targetState = existingState.targets?.[target!.name] ?? { resources: {} }; - if (!targetState.resources) targetState.resources = {}; + targetState.resources ??= {}; targetState.resources.credentials = deployedCredentials; if (identityResult.kmsKeyArn) targetState.resources.identityKmsKeyArn = identityResult.kmsKeyArn; await configIO.writeDeployedState({ @@ -659,7 +660,7 @@ export function useCdkPreflight(options: PreflightOptions): PreflightResult { logger.startStep('Synthesize CloudFormation'); try { const synthResult = await synthesizeCdk(context.cdkProject, { - ioHost: switchableIoHost!.ioHost, + ioHost: switchableIoHost.ioHost, previousWrapper: wrapperRef.current, }); wrapperRef.current = synthResult.toolkitWrapper; @@ -680,7 +681,7 @@ export function useCdkPreflight(options: PreflightOptions): PreflightResult { const bootstrapCheck = await checkBootstrapNeeded(context.awsTargets); if (bootstrapCheck.needsBootstrap && bootstrapCheck.target) { setBootstrapContext({ - toolkitWrapper: wrapperRef.current!, + toolkitWrapper: wrapperRef.current, target: bootstrapCheck.target, }); setPhase('bootstrap-confirm');