Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions integ-tests/add-remove-gateway.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';

async function readMcpConfig(projectPath: string) {
return JSON.parse(await readFile(join(projectPath, 'agentcore/mcp.json'), 'utf-8'));
async function readProjectConfig(projectPath: string) {
return JSON.parse(await readFile(join(projectPath, 'agentcore/agentcore.json'), 'utf-8'));
}

describe('integration: add and remove gateway with external MCP server', () => {
Expand All @@ -29,9 +29,9 @@ describe('integration: add and remove gateway with external MCP server', () => {
const json = JSON.parse(result.stdout);
expect(json.success).toBe(true);

const mcpSpec = await readMcpConfig(project.projectPath);
const mcpSpec = await readProjectConfig(project.projectPath);
const gateway = mcpSpec.agentCoreGateways?.find((g: { name: string }) => g.name === gatewayName);
expect(gateway, `Gateway "${gatewayName}" should be in mcp.json`).toBeTruthy();
expect(gateway, `Gateway "${gatewayName}" should be in agentcore.json`).toBeTruthy();
expect(gateway.authorizerType).toBe('NONE');
});

Expand All @@ -57,7 +57,7 @@ describe('integration: add and remove gateway with external MCP server', () => {
const json = JSON.parse(result.stdout);
expect(json.success).toBe(true);

const mcpSpec = await readMcpConfig(project.projectPath);
const mcpSpec = await readProjectConfig(project.projectPath);
const gateway = mcpSpec.agentCoreGateways?.find((g: { name: string }) => g.name === gatewayName);
const target = gateway?.targets?.find((t: { name: string }) => t.name === targetName);
expect(target, `Target "${targetName}" should be in gateway targets`).toBeTruthy();
Expand All @@ -70,7 +70,7 @@ describe('integration: add and remove gateway with external MCP server', () => {
const json = JSON.parse(result.stdout);
expect(json.success).toBe(true);

const mcpSpec = await readMcpConfig(project.projectPath);
const mcpSpec = await readProjectConfig(project.projectPath);
const gateway = mcpSpec.agentCoreGateways?.find((g: { name: string }) => g.name === gatewayName);
const targets = gateway?.targets ?? [];
const found = targets.find((t: { name: string }) => t.name === targetName);
Expand All @@ -84,7 +84,7 @@ describe('integration: add and remove gateway with external MCP server', () => {
const json = JSON.parse(result.stdout);
expect(json.success).toBe(true);

const mcpSpec = await readMcpConfig(project.projectPath);
const mcpSpec = await readProjectConfig(project.projectPath);
const gateways = mcpSpec.agentCoreGateways ?? [];
const found = gateways.find((g: { name: string }) => g.name === gatewayName);
expect(found, `Gateway "${gatewayName}" should be removed`).toBeFalsy();
Expand Down Expand Up @@ -161,7 +161,7 @@ describe('integration: add and remove gateway with OpenAPI schema target', () =>
expect(json.success).toBe(true);
expect(json.toolName).toBe(targetName);

const mcpSpec = await readMcpConfig(project.projectPath);
const mcpSpec = await readProjectConfig(project.projectPath);
const gateway = mcpSpec.agentCoreGateways?.find((g: { name: string }) => g.name === gatewayName);
const target = gateway?.targets?.find((t: { name: string }) => t.name === targetName);
expect(target, `Target "${targetName}" should be in gateway targets`).toBeTruthy();
Expand Down Expand Up @@ -202,7 +202,7 @@ describe('integration: add and remove gateway with OpenAPI schema target', () =>
const json = JSON.parse(result.stdout);
expect(json.success).toBe(true);

const mcpSpec = await readMcpConfig(project.projectPath);
const mcpSpec = await readProjectConfig(project.projectPath);
const gateway = mcpSpec.agentCoreGateways?.find((g: { name: string }) => g.name === gatewayName);
const targets = gateway?.targets ?? [];
const found = targets.find((t: { name: string }) => t.name === targetName);
Expand Down Expand Up @@ -271,7 +271,7 @@ describe('integration: add gateway with S3 URI schema target', () => {
expect(json.success).toBe(true);
expect(json.toolName).toBe(targetName);

const mcpSpec = await readMcpConfig(project.projectPath);
const mcpSpec = await readProjectConfig(project.projectPath);
const gateway = mcpSpec.agentCoreGateways?.find((g: { name: string }) => g.name === gatewayName);
const target = gateway?.targets?.find((t: { name: string }) => t.name === targetName);
expect(target, `Target "${targetName}" should be in gateway targets`).toBeTruthy();
Expand Down Expand Up @@ -333,7 +333,7 @@ describe('integration: add gateway with S3 URI and bucketOwnerAccountId', () =>
const json = JSON.parse(result.stdout);
expect(json.success).toBe(true);

const mcpSpec = await readMcpConfig(project.projectPath);
const mcpSpec = await readProjectConfig(project.projectPath);
const gateway = mcpSpec.agentCoreGateways?.find((g: { name: string }) => g.name === gatewayName);
const target = gateway?.targets?.find((t: { name: string }) => t.name === targetName);
expect(target.schemaSource?.s3?.uri).toBe('s3://cross-account-bucket/spec.json');
Expand Down Expand Up @@ -395,7 +395,7 @@ describe('integration: add gateway with Smithy model target', () => {
expect(json.success).toBe(true);
expect(json.toolName).toBe(targetName);

const mcpSpec = await readMcpConfig(project.projectPath);
const mcpSpec = await readProjectConfig(project.projectPath);
const gateway = mcpSpec.agentCoreGateways?.find((g: { name: string }) => g.name === gatewayName);
const target = gateway?.targets?.find((t: { name: string }) => t.name === targetName);
expect(target, `Target "${targetName}" should be in gateway targets`).toBeTruthy();
Expand All @@ -410,7 +410,7 @@ describe('integration: add gateway with Smithy model target', () => {
const json = JSON.parse(result.stdout);
expect(json.success).toBe(true);

const mcpSpec = await readMcpConfig(project.projectPath);
const mcpSpec = await readProjectConfig(project.projectPath);
const gateway = mcpSpec.agentCoreGateways?.find((g: { name: string }) => g.name === gatewayName);
const targets = gateway?.targets ?? [];
const found = targets.find((t: { name: string }) => t.name === targetName);
Expand Down
20 changes: 13 additions & 7 deletions src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,19 @@ async function main() {
const spec = await configIO.readProjectSpec();
const targets = await configIO.readAWSDeploymentTargets();

// Read MCP configuration if it exists
let mcpSpec;
try {
mcpSpec = await configIO.readMcpSpec();
} catch {
// MCP config is optional
}
// Extract MCP configuration from project spec.
// Gateway fields are stored in agentcore.json but may not yet be on the
// AgentCoreProjectSpec type from @aws/agentcore-cdk, so we read them
// dynamically and cast the resulting object.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const specAny = spec as any;
const mcpSpec = specAny.agentCoreGateways?.length
? {
agentCoreGateways: specAny.agentCoreGateways,
mcpRuntimeTools: specAny.mcpRuntimeTools,
unassignedTargets: specAny.unassignedTargets,
}
: undefined;

// Read deployed state for credential ARNs (populated by pre-deploy identity setup)
let deployedState: Record<string, unknown> | undefined;
Expand Down
20 changes: 13 additions & 7 deletions src/assets/cdk/bin/cdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,19 @@ async function main() {
const spec = await configIO.readProjectSpec();
const targets = await configIO.readAWSDeploymentTargets();

// Read MCP configuration if it exists
let mcpSpec;
try {
mcpSpec = await configIO.readMcpSpec();
} catch {
// MCP config is optional
}
// Extract MCP configuration from project spec.
// Gateway fields are stored in agentcore.json but may not yet be on the
// AgentCoreProjectSpec type from @aws/agentcore-cdk, so we read them
// dynamically and cast the resulting object.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const specAny = spec as any;
const mcpSpec = specAny.agentCoreGateways?.length
? {
agentCoreGateways: specAny.agentCoreGateways,
mcpRuntimeTools: specAny.mcpRuntimeTools,
unassignedTargets: specAny.unassignedTargets,
}
: undefined;

// Read deployed state for credential ARNs (populated by pre-deploy identity setup)
let deployedState: Record<string, unknown> | undefined;
Expand Down
10 changes: 5 additions & 5 deletions src/cli/commands/add/__tests__/add-gateway-target.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ describe('add gateway-target command', () => {
const json = JSON.parse(result.stdout);
expect(json.success).toBe(true);

// Verify in mcp.json
const mcpSpec = JSON.parse(await readFile(join(projectDir, 'agentcore/mcp.json'), 'utf-8'));
const gateway = mcpSpec.agentCoreGateways.find((g: { name: string }) => g.name === gatewayName);
// Verify in agentcore.json
const projectSpec = JSON.parse(await readFile(join(projectDir, 'agentcore/agentcore.json'), 'utf-8'));
const gateway = projectSpec.agentCoreGateways.find((g: { name: string }) => g.name === gatewayName);
const target = gateway?.targets?.find((t: { name: string }) => t.name === targetName);
expect(target, 'Target should be in gateway targets').toBeTruthy();
});
Expand Down Expand Up @@ -122,8 +122,8 @@ describe('add gateway-target command', () => {
expect(json.success).toBe(true);
expect(json.toolName).toBe(targetName);

const mcpSpec = JSON.parse(await readFile(join(projectDir, 'agentcore/mcp.json'), 'utf-8'));
const gateway = mcpSpec.agentCoreGateways.find((g: { name: string }) => g.name === gatewayName);
const projectSpec = JSON.parse(await readFile(join(projectDir, 'agentcore/agentcore.json'), 'utf-8'));
const gateway = projectSpec.agentCoreGateways.find((g: { name: string }) => g.name === gatewayName);
const target = gateway?.targets?.find((t: { name: string }) => t.name === targetName);
expect(target).toBeTruthy();
expect(target.targetType).toBe('lambdaFunctionArn');
Expand Down
25 changes: 12 additions & 13 deletions src/cli/commands/add/__tests__/add-gateway.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ describe('add gateway command', () => {
expect(json.success).toBe(true);
expect(json.gatewayName).toBe(gatewayName);

// Verify gateway in mcp.json
const mcpSpec = JSON.parse(await readFile(join(projectDir, 'agentcore/mcp.json'), 'utf-8'));
const gateway = mcpSpec.agentCoreGateways.find((g: { name: string }) => g.name === gatewayName);
expect(gateway, 'Gateway should be in mcp.json').toBeTruthy();
// Verify gateway in agentcore.json
const projectSpec = JSON.parse(await readFile(join(projectDir, 'agentcore/agentcore.json'), 'utf-8'));
const gateway = projectSpec.agentCoreGateways.find((g: { name: string }) => g.name === gatewayName);
expect(gateway, 'Gateway should be in agentcore.json').toBeTruthy();
expect(gateway.authorizerType).toBe('NONE');
});

Expand Down Expand Up @@ -105,10 +105,10 @@ describe('add gateway command', () => {
const json = JSON.parse(result.stdout);
expect(json.success).toBe(true);

// Verify JWT config in mcp.json
const mcpSpec = JSON.parse(await readFile(join(projectDir, 'agentcore/mcp.json'), 'utf-8'));
const gateway = mcpSpec.agentCoreGateways.find((g: { name: string }) => g.name === gatewayName);
expect(gateway, 'Gateway should be in mcp.json').toBeTruthy();
// Verify JWT config in agentcore.json
const projectSpec = JSON.parse(await readFile(join(projectDir, 'agentcore/agentcore.json'), 'utf-8'));
const gateway = projectSpec.agentCoreGateways.find((g: { name: string }) => g.name === gatewayName);
expect(gateway, 'Gateway should be in agentcore.json').toBeTruthy();
expect(gateway.authorizerType).toBe('CUSTOM_JWT');
expect(gateway.authorizerConfiguration?.customJwtAuthorizer, 'Should have JWT config').toBeTruthy();
});
Expand Down Expand Up @@ -180,15 +180,14 @@ describe('add gateway command', () => {
const json = JSON.parse(result.stdout);
expect(json.success).toBe(true);

// Verify allowedScopes in mcp.json
const mcpSpec = JSON.parse(await readFile(join(projectDir, 'agentcore/mcp.json'), 'utf-8'));
const gateway = mcpSpec.agentCoreGateways.find((g: { name: string }) => g.name === gatewayName);
expect(gateway, 'Gateway should be in mcp.json').toBeTruthy();
// Verify allowedScopes in agentcore.json
const projectSpec = JSON.parse(await readFile(join(projectDir, 'agentcore/agentcore.json'), 'utf-8'));
const gateway = projectSpec.agentCoreGateways.find((g: { name: string }) => g.name === gatewayName);
expect(gateway, 'Gateway should be in agentcore.json').toBeTruthy();
expect(gateway.authorizerType).toBe('CUSTOM_JWT');
expect(gateway.authorizerConfiguration?.customJwtAuthorizer?.allowedScopes).toEqual(['scope1', 'scope2']);

// Verify managed OAuth credential in agentcore.json
const projectSpec = JSON.parse(await readFile(join(projectDir, 'agentcore/agentcore.json'), 'utf-8'));
const credential = projectSpec.credentials.find((c: { name: string }) => c.name === `${gatewayName}-oauth`);
expect(credential, 'Managed OAuth credential should exist').toBeTruthy();
expect(credential.type).toBe('OAuthCredentialProvider');
Expand Down
11 changes: 6 additions & 5 deletions src/cli/commands/add/__tests__/validate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

const mockReadProjectSpec = vi.fn();
const mockConfigExists = vi.fn().mockReturnValue(true);
const mockReadMcpSpec = vi.fn();

vi.mock('../../../../lib/index.js', () => ({
ConfigIO: class {
readProjectSpec = mockReadProjectSpec;
configExists = mockConfigExists;
readMcpSpec = mockReadMcpSpec;
},
findConfigRoot: vi.fn().mockReturnValue('/mock/project/agentcore'),
}));
Expand Down Expand Up @@ -313,7 +311,7 @@ describe('validate', () => {
describe('validateAddGatewayTargetOptions', () => {
beforeEach(() => {
// By default, mock that the gateway from validGatewayTargetOptions exists
mockReadMcpSpec.mockResolvedValue({ agentCoreGateways: [{ name: 'my-gateway' }] });
mockReadProjectSpec.mockResolvedValue({ agentCoreGateways: [{ name: 'my-gateway' }] });
});

// AC15: Required fields validated
Expand All @@ -332,15 +330,15 @@ describe('validate', () => {
});

it('returns error when no gateways exist', async () => {
mockReadMcpSpec.mockResolvedValue({ agentCoreGateways: [] });
mockReadProjectSpec.mockResolvedValue({ agentCoreGateways: [] });
const result = await validateAddGatewayTargetOptions({ ...validGatewayTargetOptions });
expect(result.valid).toBe(false);
expect(result.error).toContain('No gateways found');
expect(result.error).toContain('agentcore add gateway');
});

it('returns error when specified gateway does not exist', async () => {
mockReadMcpSpec.mockResolvedValue({ agentCoreGateways: [{ name: 'other-gateway' }] });
mockReadProjectSpec.mockResolvedValue({ agentCoreGateways: [{ name: 'other-gateway' }] });
const result = await validateAddGatewayTargetOptions({ ...validGatewayTargetOptions });
expect(result.valid).toBe(false);
expect(result.error).toContain('Gateway "my-gateway" not found');
Expand Down Expand Up @@ -446,6 +444,7 @@ describe('validate', () => {
// AC21: credential validation through outbound auth
it('returns error when credential not found', async () => {
mockReadProjectSpec.mockResolvedValue({
agentCoreGateways: [{ name: 'my-gateway' }],
credentials: [{ name: 'existing-cred', type: 'ApiKey' }],
});

Expand All @@ -464,6 +463,7 @@ describe('validate', () => {

it('returns error when no credentials configured', async () => {
mockReadProjectSpec.mockResolvedValue({
agentCoreGateways: [{ name: 'my-gateway' }],
credentials: [],
});

Expand All @@ -482,6 +482,7 @@ describe('validate', () => {

it('passes when credential exists', async () => {
mockReadProjectSpec.mockResolvedValue({
agentCoreGateways: [{ name: 'my-gateway' }],
credentials: [{ name: 'valid-cred', type: 'ApiKey' }],
});

Expand Down
6 changes: 2 additions & 4 deletions src/cli/commands/add/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,10 +335,8 @@ export async function validateAddGatewayTargetOptions(options: AddGatewayTargetO
const gatewayConfigIO = new ConfigIO();
let existingGateways: string[] = [];
try {
if (gatewayConfigIO.configExists('mcp')) {
const mcpSpec = await gatewayConfigIO.readMcpSpec();
existingGateways = mcpSpec.agentCoreGateways.map(g => g.name);
}
const project = await gatewayConfigIO.readProjectSpec();
existingGateways = project.agentCoreGateways.map(g => g.name);
} catch {
// If we can't read the config, treat as no gateways
}
Expand Down
1 change: 1 addition & 0 deletions src/cli/commands/create/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ function createDefaultProjectSpec(projectName: string): AgentCoreProjectSpec {
credentials: [],
evaluators: [],
onlineEvalConfigs: [],
agentCoreGateways: [],
policyEngines: [],
};
}
Expand Down
12 changes: 6 additions & 6 deletions src/cli/commands/deploy/actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ConfigIO, SecureCredentials } from '../../../lib';
import type { DeployedState } from '../../../schema';
import type { AgentCoreMcpSpec, DeployedState } from '../../../schema';
import { validateAwsCredentials } from '../../aws/account';
import { createSwitchableIoHost } from '../../cdk/toolkit-lib';
import {
Expand Down Expand Up @@ -81,13 +81,13 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise<Dep
}
endStep('success');

// Read MCP spec for gateway information
let mcpSpec;
// Read project spec for gateway information (used later for deploy step name and outputs)
let mcpSpec: Pick<AgentCoreMcpSpec, 'agentCoreGateways'> | null = null;
try {
mcpSpec = await configIO.readMcpSpec();
const projectSpec = await configIO.readProjectSpec();
mcpSpec = { agentCoreGateways: projectSpec.agentCoreGateways };
} catch {
// No mcp.json or invalid — no gateways
mcpSpec = null;
// Project read failed — no gateways
}

// Preflight: validate project
Expand Down
4 changes: 4 additions & 0 deletions src/cli/commands/logs/__tests__/action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ describe('resolveAgentContext', () => {
credentials: [],
evaluators: [],
onlineEvalConfigs: [],
agentCoreGateways: [],
policyEngines: [],
},
deployedState: {
Expand Down Expand Up @@ -119,6 +120,7 @@ describe('resolveAgentContext', () => {
credentials: [],
evaluators: [],
onlineEvalConfigs: [],
agentCoreGateways: [],
policyEngines: [],
},
});
Expand Down Expand Up @@ -160,6 +162,7 @@ describe('resolveAgentContext', () => {
credentials: [],
evaluators: [],
onlineEvalConfigs: [],
agentCoreGateways: [],
policyEngines: [],
},
deployedState: {
Expand Down Expand Up @@ -209,6 +212,7 @@ describe('resolveAgentContext', () => {
credentials: [],
evaluators: [],
onlineEvalConfigs: [],
agentCoreGateways: [],
policyEngines: [],
},
});
Expand Down
Loading
Loading