Skip to content

Commit e0cafef

Browse files
authored
fix: use project-scoped credential naming consistently (#111)
1 parent 18a11fe commit e0cafef

13 files changed

Lines changed: 120 additions & 72 deletions

File tree

src/assets/cdk/bin/cdk.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env node
22
import { AgentCoreStack } from '../lib/cdk-stack';
3-
import { ConfigIO, type AgentCoreMcpSpec, type AwsDeploymentTarget } from '@aws/agentcore-l3-cdk-constructs';
3+
import { ConfigIO, type AwsDeploymentTarget } from '@aws/agentcore-l3-cdk-constructs';
44
import { App, type Environment } from 'aws-cdk-lib';
55
import * as path from 'path';
66

@@ -23,12 +23,6 @@ async function main() {
2323
const spec = await configIO.readProjectSpec();
2424
const targets = await configIO.readAWSDeploymentTargets();
2525

26-
// Read MCP spec if it exists (stored separately in mcp.json)
27-
let mcpSpec: AgentCoreMcpSpec | undefined;
28-
if (configIO.configExists('mcp')) {
29-
mcpSpec = await configIO.readMcpSpec();
30-
}
31-
3226
if (targets.length === 0) {
3327
throw new Error('No deployment targets configured. Please define targets in agentcore/aws-targets.json');
3428
}
@@ -41,7 +35,6 @@ async function main() {
4135

4236
new AgentCoreStack(app, stackName, {
4337
spec,
44-
mcpSpec,
4538
env,
4639
description: `AgentCore stack for ${spec.name} deployed to ${target.name} (${target.region})`,
4740
tags: {

src/assets/cdk/lib/cdk-stack.ts

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
import {
2-
AgentCoreApplication,
3-
AgentCoreMcp,
4-
type AgentCoreProjectSpec,
5-
type AgentCoreMcpSpec,
6-
} from '@aws/agentcore-l3-cdk-constructs';
1+
import { AgentCoreApplication, type AgentCoreProjectSpec } from '@aws/agentcore-l3-cdk-constructs';
72
import { CfnOutput, Stack, type StackProps } from 'aws-cdk-lib';
83
import { Construct } from 'constructs';
94

@@ -12,12 +7,6 @@ export interface AgentCoreStackProps extends StackProps {
127
* The AgentCore project specification containing agents, memories, and credentials.
138
*/
149
spec: AgentCoreProjectSpec;
15-
16-
/**
17-
* Optional MCP specification for gateways and runtime tools.
18-
* MCP is stored separately in mcp.json.
19-
*/
20-
mcpSpec?: AgentCoreMcpSpec;
2110
}
2211

2312
/**
@@ -30,28 +19,16 @@ export class AgentCoreStack extends Stack {
3019
/** The AgentCore application containing all agent environments */
3120
public readonly application: AgentCoreApplication;
3221

33-
/** The MCP construct if MCP is configured */
34-
public readonly mcp?: AgentCoreMcp;
35-
3622
constructor(scope: Construct, id: string, props: AgentCoreStackProps) {
3723
super(scope, id, props);
3824

39-
const { spec, mcpSpec } = props;
25+
const { spec } = props;
4026

4127
// Create AgentCoreApplication with all agents
4228
this.application = new AgentCoreApplication(this, 'Application', {
4329
spec,
4430
});
4531

46-
// Instantiate AgentCoreMcp if MCP spec is provided
47-
if (mcpSpec) {
48-
this.mcp = new AgentCoreMcp(this, 'Mcp', {
49-
projectName: spec.name,
50-
mcpSpec,
51-
agentCoreApplication: this.application,
52-
});
53-
}
54-
5532
// Stack-level output
5633
new CfnOutput(this, 'StackNameOutput', {
5734
description: 'Name of the CloudFormation Stack',

src/cli/commands/add/actions.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ export async function handleAddAgent(options: ValidatedAddAgentOptions): Promise
110110

111111
async function handleCreatePath(options: ValidatedAddAgentOptions, configBaseDir: string): Promise<AddAgentResult> {
112112
const projectRoot = dirname(configBaseDir);
113+
const configIO = new ConfigIO({ baseDir: configBaseDir });
114+
const project = await configIO.readProjectSpec();
113115

114116
const generateConfig = {
115117
projectName: options.name,
@@ -121,7 +123,8 @@ async function handleCreatePath(options: ValidatedAddAgentOptions, configBaseDir
121123

122124
const agentPath = join(projectRoot, APP_DIR, options.name);
123125

124-
const renderConfig = mapGenerateConfigToRenderConfig(generateConfig);
126+
// Pass actual project name for credential naming in templates
127+
const renderConfig = mapGenerateConfigToRenderConfig(generateConfig, project.name);
125128
const renderer = createRenderer(renderConfig);
126129
await renderer.render({ outputDir: projectRoot });
127130

@@ -132,7 +135,9 @@ async function handleCreatePath(options: ValidatedAddAgentOptions, configBaseDir
132135
}
133136

134137
if (options.apiKey && options.modelProvider !== 'Bedrock') {
135-
const envVarName = computeDefaultCredentialEnvVarName(options.modelProvider);
138+
// Use project-scoped credential name: {projectName}{modelProvider}
139+
const credentialName = `${project.name}${options.modelProvider}`;
140+
const envVarName = computeDefaultCredentialEnvVarName(credentialName);
136141
await setEnvVar(envVarName, options.apiKey, configBaseDir);
137142
}
138143

@@ -167,7 +172,9 @@ async function handleByoPath(
167172
await configIO.writeProjectSpec(project);
168173

169174
if (options.apiKey && options.modelProvider !== 'Bedrock') {
170-
const envVarName = computeDefaultCredentialEnvVarName(options.modelProvider);
175+
// Use project-scoped credential name: {projectName}{modelProvider}
176+
const credentialName = `${project.name}${options.modelProvider}`;
177+
const envVarName = computeDefaultCredentialEnvVarName(credentialName);
171178
await setEnvVar(envVarName, options.apiKey, configBaseDir);
172179
}
173180

src/cli/commands/create/action.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { getErrorMessage } from '../../errors';
1212
import { checkCreateDependencies } from '../../external-requirements';
1313
import { initGitRepo, setupPythonProject, writeEnvFile, writeGitignore } from '../../operations';
1414
import { mapGenerateConfigToRenderConfig, writeAgentToProject } from '../../operations/agent/generate';
15+
import { computeDefaultCredentialEnvVarName } from '../../operations/identity/create-identity';
1516
import { CDKRenderer, createRenderer } from '../../templates';
1617
import type { CreateResult } from './types';
1718
import { mkdir } from 'fs/promises';
@@ -143,24 +144,28 @@ export async function createProjectWithAgent(options: CreateWithAgentOptions): P
143144

144145
try {
145146
// Build GenerateConfig for agent creation
147+
// Note: In this context, agent name = project name since we're creating a project with a single agent
148+
const agentName = name;
146149
const generateConfig = {
147-
projectName: name,
150+
projectName: agentName,
148151
sdk: framework,
149152
modelProvider,
150153
apiKey,
151154
memory,
152155
language,
153156
};
154157

155-
// Generate agent code
156-
const renderConfig = mapGenerateConfigToRenderConfig(generateConfig);
158+
// Generate agent code - pass actual project name for credential naming
159+
const renderConfig = mapGenerateConfigToRenderConfig(generateConfig, name);
157160
const renderer = createRenderer(renderConfig);
158161
await renderer.render({ outputDir: projectRoot });
159162
await writeAgentToProject(generateConfig, { configBaseDir });
160163

161164
// Store API key for non-Bedrock providers
162165
if (apiKey && modelProvider !== 'Bedrock') {
163-
const envVarName = `AGENTCORE_IDENTITY_${modelProvider.toUpperCase()}`;
166+
// Use project-scoped credential name: {projectName}{modelProvider}
167+
const credentialName = `${name}${modelProvider}`;
168+
const envVarName = computeDefaultCredentialEnvVarName(credentialName);
164169
await setEnvVar(envVarName, apiKey, configBaseDir);
165170
}
166171

src/cli/operations/agent/generate/schema-mapper.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ import type {
88
MemoryStrategy,
99
ModelProvider,
1010
} from '../../../../schema';
11-
import type { AgentRenderConfig } from '../../../templates/types';
11+
import type { AgentRenderConfig, IdentityProviderRenderConfig } from '../../../templates/types';
1212
import {
1313
DEFAULT_MEMORY_EXPIRY_DAYS,
1414
DEFAULT_NETWORK_MODE,
1515
DEFAULT_PYTHON_ENTRYPOINT,
1616
DEFAULT_PYTHON_VERSION,
1717
} from '../../../tui/screens/generate/defaults';
1818
import type { GenerateConfig, MemoryOption } from '../../../tui/screens/generate/types';
19+
import { computeDefaultCredentialEnvVarName } from '../../identity/create-identity';
1920

2021
/**
2122
* Result of mapping GenerateConfig to v2 schema.
@@ -28,10 +29,11 @@ export interface GenerateConfigMappingResult {
2829
}
2930

3031
/**
31-
* Compute the qualified credential name for AWS resources.
32+
* Compute the credential name for a model provider.
33+
* Scoped to project (not agent) to avoid conflicts across projects.
3234
* Format: {projectName}{providerName}
3335
*/
34-
function computeQualifiedCredentialName(projectName: string, providerName: string): string {
36+
function computeCredentialName(projectName: string, providerName: string): string {
3537
return `${projectName}${providerName}`;
3638
}
3739

@@ -79,7 +81,7 @@ export function mapModelProviderToCredentials(modelProvider: ModelProvider, proj
7981
return [
8082
{
8183
type: 'ApiKeyCredentialProvider',
82-
name: computeQualifiedCredentialName(projectName, modelProvider),
84+
name: computeCredentialName(projectName, modelProvider),
8385
},
8486
];
8587
}
@@ -112,16 +114,41 @@ export function mapGenerateConfigToResources(config: GenerateConfig): GenerateCo
112114
};
113115
}
114116

117+
/**
118+
* Maps model provider to identity providers for template rendering.
119+
*/
120+
function mapModelProviderToIdentityProviders(
121+
modelProvider: ModelProvider,
122+
projectName: string
123+
): IdentityProviderRenderConfig[] {
124+
if (modelProvider === 'Bedrock') {
125+
return [];
126+
}
127+
128+
const credentialName = computeCredentialName(projectName, modelProvider);
129+
return [
130+
{
131+
name: credentialName,
132+
envVarName: computeDefaultCredentialEnvVarName(credentialName),
133+
},
134+
];
135+
}
136+
115137
/**
116138
* Maps GenerateConfig to AgentRenderConfig for template rendering.
139+
* @param config - Generate config (note: config.projectName is actually the agent name)
140+
* @param actualProjectName - Optional actual project name for credential naming (defaults to config.projectName)
117141
*/
118-
export function mapGenerateConfigToRenderConfig(config: GenerateConfig): AgentRenderConfig {
142+
export function mapGenerateConfigToRenderConfig(config: GenerateConfig, actualProjectName?: string): AgentRenderConfig {
143+
// Use actualProjectName for credential naming, fallback to config.projectName (agent name) for standalone generate
144+
const projectNameForCredentials = actualProjectName ?? config.projectName;
119145
return {
120146
name: config.projectName,
121147
sdkFramework: config.sdk,
122148
targetLanguage: config.language,
123149
modelProvider: config.modelProvider,
124150
hasMemory: config.memory !== 'none',
125151
hasIdentity: config.modelProvider !== 'Bedrock',
152+
identityProviders: mapModelProviderToIdentityProviders(config.modelProvider, projectNameForCredentials),
126153
};
127154
}

src/cli/operations/agent/generate/write-agent-to-project.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { AgentCoreProjectSpec } from '../../../../schema';
33
import { SCHEMA_VERSION } from '../../../constants';
44
import { AgentAlreadyExistsError } from '../../../errors';
55
import type { GenerateConfig } from '../../../tui/screens/generate/types';
6-
import { mapGenerateConfigToResources } from './schema-mapper';
6+
import { mapGenerateConfigToAgent, mapGenerateInputToMemories, mapModelProviderToCredentials } from './schema-mapper';
77

88
export interface WriteAgentOptions {
99
configBaseDir?: string;
@@ -20,26 +20,35 @@ export interface WriteAgentOptions {
2020
export async function writeAgentToProject(config: GenerateConfig, options?: WriteAgentOptions): Promise<void> {
2121
const configBaseDir = options?.configBaseDir ?? requireConfigRoot();
2222
const configIO = new ConfigIO({ baseDir: configBaseDir });
23-
const { agent, memories, credentials } = mapGenerateConfigToResources(config);
23+
24+
// Map agent config to resources
25+
// Note: config.projectName is actually the agent name (GenerateConfig naming is confusing)
26+
const agentName = config.projectName;
27+
const agent = mapGenerateConfigToAgent(config);
28+
const memories = mapGenerateInputToMemories(config.memory, agentName);
2429

2530
if (configIO.configExists('project')) {
2631
const project = await configIO.readProjectSpec();
2732

2833
// Check for duplicate agent name
29-
if (project.agents.some(a => a.name === config.projectName)) {
30-
throw new AgentAlreadyExistsError(config.projectName);
34+
if (project.agents.some(a => a.name === agentName)) {
35+
throw new AgentAlreadyExistsError(agentName);
3136
}
3237

38+
// Use actual project name for credential naming (not agent name)
39+
const credentials = mapModelProviderToCredentials(config.modelProvider, project.name);
40+
3341
// Add resources to project
3442
project.agents.push(agent);
3543
project.memories.push(...memories);
3644
project.credentials.push(...credentials);
3745

3846
await configIO.writeProjectSpec(project);
3947
} else {
40-
// Create new project
48+
// Create new project - use agent name as project name (fallback for standalone generate)
49+
const credentials = mapModelProviderToCredentials(config.modelProvider, agentName);
4150
const project: AgentCoreProjectSpec = {
42-
name: config.projectName,
51+
name: agentName,
4352
version: SCHEMA_VERSION,
4453
agents: [agent],
4554
memories,

src/cli/operations/identity/create-identity.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,23 +35,30 @@ export async function getAllCredentialNames(): Promise<string[]> {
3535
/**
3636
* Create a credential resource and add it to the project.
3737
* Also writes the API key to the .env file.
38+
*
39+
* If the credential already exists (e.g., created during agent generation),
40+
* just updates the API key in the .env file.
3841
*/
3942
export async function createCredential(config: CreateCredentialConfig): Promise<Credential> {
4043
const configIO = new ConfigIO();
4144
const project = await configIO.readProjectSpec();
4245

43-
// Check for duplicate
44-
if (project.credentials.some(c => c.name === config.name)) {
45-
throw new Error(`Credential "${config.name}" already exists.`);
46-
}
47-
48-
const credential: Credential = {
49-
type: 'ApiKeyCredentialProvider',
50-
name: config.name,
51-
};
46+
// Check if credential already exists
47+
const existingCredential = project.credentials.find(c => c.name === config.name);
5248

53-
project.credentials.push(credential);
54-
await configIO.writeProjectSpec(project);
49+
let credential: Credential;
50+
if (existingCredential) {
51+
// updates credentital
52+
credential = existingCredential;
53+
} else {
54+
// Create new credential entry
55+
credential = {
56+
type: 'ApiKeyCredentialProvider',
57+
name: config.name,
58+
};
59+
project.credentials.push(credential);
60+
await configIO.writeProjectSpec(project);
61+
}
5562

5663
// Write API key to .env file
5764
const envVarName = computeDefaultCredentialEnvVarName(config.name);

src/cli/templates/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import type { ModelProvider, SDKFramework, TargetLanguage } from '../../schema';
22

3+
/**
4+
* Identity provider info for template rendering.
5+
*/
6+
export interface IdentityProviderRenderConfig {
7+
name: string;
8+
envVarName: string;
9+
}
10+
311
/**
412
* Configuration needed by template renderers.
513
* This is separate from the v2 Agent schema which only stores runtime config.
@@ -11,4 +19,6 @@ export interface AgentRenderConfig {
1119
modelProvider: ModelProvider;
1220
hasMemory: boolean;
1321
hasIdentity: boolean;
22+
/** Identity providers for template rendering (maps to credentials in schema) */
23+
identityProviders: IdentityProviderRenderConfig[];
1424
}

src/cli/tui/screens/agent/useAddAgent.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,14 @@ async function handleCreatePath(
132132
): Promise<AddAgentCreateResult | AddAgentError> {
133133
// configBaseDir is the agentcore/ directory, project root is its parent
134134
const projectRoot = dirname(configBaseDir);
135+
const configIO = new ConfigIO({ baseDir: configBaseDir });
136+
const project = await configIO.readProjectSpec();
135137

136138
const generateConfig = mapAddAgentConfigToGenerateConfig(config);
137139
const agentPath = join(projectRoot, config.name);
138140

139-
// Generate agent files
140-
const renderConfig = mapGenerateConfigToRenderConfig(generateConfig);
141+
// Generate agent files - pass actual project name for credential naming
142+
const renderConfig = mapGenerateConfigToRenderConfig(generateConfig, project.name);
141143
const renderer = createRenderer(renderConfig);
142144
await renderer.render({ outputDir: projectRoot });
143145

@@ -152,7 +154,9 @@ async function handleCreatePath(
152154

153155
// Write API key to agentcore/.env for non-Bedrock providers
154156
if (config.apiKey && config.modelProvider !== 'Bedrock') {
155-
const envVarName = computeDefaultCredentialEnvVarName(config.modelProvider);
157+
// Use project-scoped credential name: {projectName}{modelProvider}
158+
const credentialName = `${project.name}${config.modelProvider}`;
159+
const envVarName = computeDefaultCredentialEnvVarName(credentialName);
156160
await setEnvVar(envVarName, config.apiKey, configBaseDir);
157161
}
158162

@@ -188,7 +192,9 @@ async function handleByoPath(
188192

189193
// Write API key to agentcore/.env for non-Bedrock providers
190194
if (config.apiKey && config.modelProvider !== 'Bedrock') {
191-
const envVarName = computeDefaultCredentialEnvVarName(config.modelProvider);
195+
// Use project-scoped credential name: {projectName}{modelProvider}
196+
const credentialName = `${project.name}${config.modelProvider}`;
197+
const envVarName = computeDefaultCredentialEnvVarName(credentialName);
192198
await setEnvVar(envVarName, config.apiKey, configBaseDir);
193199
}
194200

0 commit comments

Comments
 (0)