Skip to content

Commit 02f9181

Browse files
committed
fix: wire gateway primitives to actual CLI logic and fix remove-all
- GatewayPrimitive.registerCommands now calls validation and add/remove logic with proper JSON output instead of "coming soon" stubs - GatewayTargetPrimitive.registerCommands wired similarly, supporting both existing-endpoint and create-new flows - handleRemoveAll now resets mcp.json gateways alongside agentcore.json
1 parent 9099e5b commit 02f9181

3 files changed

Lines changed: 265 additions & 15 deletions

File tree

src/cli/commands/remove/command.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ async function handleRemoveAll(_options: RemoveAllOptions): Promise<RemoveResult
3131
credentials: [],
3232
});
3333

34+
// Reset mcp.json gateways if it exists
35+
if (configIO.configExists('mcp')) {
36+
await configIO.writeMcpSpec({ agentCoreGateways: [] });
37+
}
38+
3439
// Preserve aws-targets.json and deployed-state.json so that
3540
// a subsequent `agentcore deploy` can tear down existing stacks.
3641

src/cli/primitives/GatewayPrimitive.ts

Lines changed: 105 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
import { setEnvVar } from '../../lib';
1+
import { findConfigRoot, setEnvVar } from '../../lib';
22
import type { AgentCoreGateway, AgentCoreGatewayTarget, AgentCoreMcpSpec, GatewayAuthorizerType } from '../../schema';
33
import { AgentCoreGatewaySchema } from '../../schema';
4+
import type { AddGatewayOptions as CLIAddGatewayOptions } from '../commands/add/types';
5+
import { validateAddGatewayOptions } from '../commands/add/validate';
46
import { getErrorMessage } from '../errors';
57
import type { RemovalPreview, RemovalResult, SchemaChange } from '../operations/remove/types';
68
import type { AddGatewayConfig } from '../tui/screens/mcp/types';
79
import { BasePrimitive } from './BasePrimitive';
10+
import { SOURCE_CODE_NOTE } from './constants';
811
import { computeDefaultCredentialEnvVarName } from './credential-utils';
912
import type { AddResult, AddScreenComponent, RemovableResource } from './types';
1013
import type { Command } from '@commander-js/extra-typings';
@@ -146,11 +149,56 @@ export class GatewayPrimitive extends BasePrimitive<AddGatewayOptions, Removable
146149
.option('--discovery-url <url>', 'OIDC discovery URL (for CUSTOM_JWT)')
147150
.option('--allowed-audience <audience>', 'Comma-separated allowed audiences (for CUSTOM_JWT)')
148151
.option('--allowed-clients <clients>', 'Comma-separated allowed client IDs (for CUSTOM_JWT)')
152+
.option('--allowed-scopes <scopes>', 'Comma-separated allowed scopes (for CUSTOM_JWT)')
153+
.option('--agent-client-id <id>', 'Agent OAuth client ID')
154+
.option('--agent-client-secret <secret>', 'Agent OAuth client secret')
149155
.option('--agents <agents>', 'Comma-separated agent names')
150156
.option('--json', 'Output as JSON')
151-
.action(() => {
152-
console.error('AgentCore Gateway integration is coming soon.');
153-
process.exit(1);
157+
.action(async (rawOptions: Record<string, string | boolean | undefined>) => {
158+
const cliOptions = rawOptions as unknown as CLIAddGatewayOptions;
159+
try {
160+
if (!findConfigRoot()) {
161+
console.error('No agentcore project found. Run `agentcore create` first.');
162+
process.exit(1);
163+
}
164+
165+
const validation = validateAddGatewayOptions(cliOptions);
166+
if (!validation.valid) {
167+
if (cliOptions.json) {
168+
console.log(JSON.stringify({ success: false, error: validation.error }));
169+
} else {
170+
console.error(validation.error);
171+
}
172+
process.exit(1);
173+
}
174+
175+
const result = await this.add({
176+
name: cliOptions.name!,
177+
description: cliOptions.description,
178+
authorizerType: cliOptions.authorizerType ?? 'NONE',
179+
discoveryUrl: cliOptions.discoveryUrl,
180+
allowedAudience: cliOptions.allowedAudience,
181+
allowedClients: cliOptions.allowedClients,
182+
agents: cliOptions.agents,
183+
});
184+
185+
if (cliOptions.json) {
186+
console.log(JSON.stringify(result));
187+
} else if (result.success) {
188+
console.log(`Added gateway '${result.gatewayName}'`);
189+
} else {
190+
console.error(result.error);
191+
}
192+
193+
process.exit(result.success ? 0 : 1);
194+
} catch (error) {
195+
if (cliOptions.json) {
196+
console.log(JSON.stringify({ success: false, error: getErrorMessage(error) }));
197+
} else {
198+
console.error(`Error: ${getErrorMessage(error)}`);
199+
}
200+
process.exit(1);
201+
}
154202
});
155203

156204
removeCmd
@@ -159,9 +207,59 @@ export class GatewayPrimitive extends BasePrimitive<AddGatewayOptions, Removable
159207
.option('--name <name>', 'Name of resource to remove')
160208
.option('--force', 'Skip confirmation prompt')
161209
.option('--json', 'Output as JSON')
162-
.action(() => {
163-
console.error('AgentCore Gateway integration is coming soon.');
164-
process.exit(1);
210+
.action(async (cliOptions: { name?: string; force?: boolean; json?: boolean }) => {
211+
try {
212+
if (!findConfigRoot()) {
213+
console.error('No agentcore project found. Run `agentcore create` first.');
214+
process.exit(1);
215+
}
216+
217+
if (cliOptions.name || cliOptions.force || cliOptions.json) {
218+
if (!cliOptions.name) {
219+
console.log(JSON.stringify({ success: false, error: '--name is required' }));
220+
process.exit(1);
221+
}
222+
223+
const result = await this.remove(cliOptions.name);
224+
console.log(
225+
JSON.stringify({
226+
success: result.success,
227+
resourceType: this.kind,
228+
resourceName: cliOptions.name,
229+
message: result.success ? `Removed gateway '${cliOptions.name}'` : undefined,
230+
note: result.success ? SOURCE_CODE_NOTE : undefined,
231+
error: !result.success ? result.error : undefined,
232+
})
233+
);
234+
process.exit(result.success ? 0 : 1);
235+
} else {
236+
const [{ render }, { default: React }, { RemoveFlow }] = await Promise.all([
237+
import('ink'),
238+
import('react'),
239+
import('../tui/screens/remove'),
240+
]);
241+
const { clear, unmount } = render(
242+
React.createElement(RemoveFlow, {
243+
isInteractive: false,
244+
force: cliOptions.force,
245+
initialResourceType: this.kind,
246+
initialResourceName: cliOptions.name,
247+
onExit: () => {
248+
clear();
249+
unmount();
250+
process.exit(0);
251+
},
252+
})
253+
);
254+
}
255+
} catch (error) {
256+
if (cliOptions.json) {
257+
console.log(JSON.stringify({ success: false, error: getErrorMessage(error) }));
258+
} else {
259+
console.error(`Error: ${getErrorMessage(error)}`);
260+
}
261+
process.exit(1);
262+
}
165263
});
166264
}
167265

src/cli/primitives/GatewayTargetPrimitive.ts

Lines changed: 155 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { APP_DIR, MCP_APP_SUBDIR, requireConfigRoot } from '../../lib';
1+
import { APP_DIR, MCP_APP_SUBDIR, findConfigRoot, requireConfigRoot } from '../../lib';
22
import type {
33
AgentCoreCliMcpDefs,
44
AgentCoreGatewayTarget,
@@ -7,13 +7,16 @@ import type {
77
FilePath,
88
} from '../../schema';
99
import { AgentCoreCliMcpDefsSchema, AgentCoreGatewayTargetSchema, ToolDefinitionSchema } from '../../schema';
10+
import type { AddGatewayTargetOptions as CLIAddGatewayTargetOptions } from '../commands/add/types';
11+
import { validateAddGatewayTargetOptions } from '../commands/add/validate';
1012
import { getErrorMessage } from '../errors';
1113
import type { RemovableGatewayTarget } from '../operations/remove/remove-gateway-target';
1214
import type { RemovalPreview, RemovalResult, SchemaChange } from '../operations/remove/types';
1315
import { getTemplateToolDefinitions, renderGatewayTargetTemplate } from '../templates/GatewayTargetRenderer';
1416
import type { AddGatewayTargetConfig } from '../tui/screens/mcp/types';
1517
import { DEFAULT_HANDLER, DEFAULT_NODE_VERSION, DEFAULT_PYTHON_VERSION } from '../tui/screens/mcp/types';
1618
import { BasePrimitive } from './BasePrimitive';
19+
import { SOURCE_CODE_NOTE } from './constants';
1720
import type { AddResult, AddScreenComponent } from './types';
1821
import type { Command } from '@commander-js/extra-typings';
1922
import { existsSync } from 'fs';
@@ -233,13 +236,107 @@ export class GatewayTargetPrimitive extends BasePrimitive<AddGatewayTargetOption
233236
.description('Add a gateway target to the project')
234237
.option('--name <name>', 'Target name')
235238
.option('--description <desc>', 'Target description')
239+
.option('--type <type>', 'Target type: mcpServer or lambda')
240+
.option('--source <source>', 'Source: existing-endpoint or create-new')
241+
.option('--endpoint <url>', 'MCP server endpoint URL')
236242
.option('--language <lang>', 'Language: Python, TypeScript, Other')
237243
.option('--gateway <name>', 'Gateway name')
238-
.option('--host <host>', 'Host type: Lambda or AgentCoreRuntime')
244+
.option('--host <host>', 'Compute host: Lambda or AgentCoreRuntime')
245+
.option('--outbound-auth <type>', 'Outbound auth type: oauth, api-key, or none')
246+
.option('--credential-name <name>', 'Existing credential name for outbound auth')
247+
.option('--oauth-client-id <id>', 'OAuth client ID (creates credential inline)')
248+
.option('--oauth-client-secret <secret>', 'OAuth client secret (creates credential inline)')
249+
.option('--oauth-discovery-url <url>', 'OAuth discovery URL (creates credential inline)')
250+
.option('--oauth-scopes <scopes>', 'OAuth scopes, comma-separated')
239251
.option('--json', 'Output as JSON')
240-
.action(() => {
241-
console.error('Gateway target integration is coming soon.');
242-
process.exit(1);
252+
.action(async (rawOptions: Record<string, string | boolean | undefined>) => {
253+
const cliOptions = rawOptions as unknown as CLIAddGatewayTargetOptions;
254+
try {
255+
if (!findConfigRoot()) {
256+
console.error('No agentcore project found. Run `agentcore create` first.');
257+
process.exit(1);
258+
}
259+
260+
const validation = await validateAddGatewayTargetOptions(cliOptions);
261+
if (!validation.valid) {
262+
if (cliOptions.json) {
263+
console.log(JSON.stringify({ success: false, error: validation.error }));
264+
} else {
265+
console.error(validation.error);
266+
}
267+
process.exit(1);
268+
}
269+
270+
// Map CLI flag values to internal types
271+
const outboundAuthMap: Record<string, 'OAUTH' | 'API_KEY' | 'NONE'> = {
272+
oauth: 'OAUTH',
273+
'api-key': 'API_KEY',
274+
none: 'NONE',
275+
};
276+
277+
// Handle existing-endpoint targets differently (no code generation)
278+
if (cliOptions.source === 'existing-endpoint' && cliOptions.endpoint) {
279+
const config: AddGatewayTargetConfig = {
280+
name: cliOptions.name!,
281+
description: cliOptions.description ?? `Tool for ${cliOptions.name!}`,
282+
sourcePath: '',
283+
language: cliOptions.language ?? 'Other',
284+
host: 'AgentCoreRuntime',
285+
toolDefinition: {
286+
name: cliOptions.name!,
287+
description: cliOptions.description ?? `Tool for ${cliOptions.name!}`,
288+
inputSchema: { type: 'object' },
289+
},
290+
gateway: cliOptions.gateway,
291+
endpoint: cliOptions.endpoint,
292+
source: 'existing-endpoint',
293+
...(cliOptions.outboundAuthType
294+
? {
295+
outboundAuth: {
296+
type: outboundAuthMap[cliOptions.outboundAuthType.toLowerCase()] ?? 'NONE',
297+
credentialName: cliOptions.credentialName,
298+
},
299+
}
300+
: {}),
301+
};
302+
const result = await this.createExternalGatewayTarget(config);
303+
const output = { success: true, toolName: result.toolName, sourcePath: result.projectPath || undefined };
304+
if (cliOptions.json) {
305+
console.log(JSON.stringify(output));
306+
} else {
307+
console.log(`Added gateway target '${result.toolName}'`);
308+
}
309+
process.exit(0);
310+
}
311+
312+
const result = await this.add({
313+
name: cliOptions.name!,
314+
description: cliOptions.description,
315+
language: cliOptions.language ?? 'Python',
316+
gateway: cliOptions.gateway,
317+
host: cliOptions.host,
318+
});
319+
320+
if (cliOptions.json) {
321+
console.log(JSON.stringify(result));
322+
} else if (result.success) {
323+
console.log(`Added gateway target '${result.toolName}'`);
324+
if (result.sourcePath) {
325+
console.log(`Tool code: ${result.sourcePath}`);
326+
}
327+
} else {
328+
console.error(result.error);
329+
}
330+
331+
process.exit(result.success ? 0 : 1);
332+
} catch (error) {
333+
if (cliOptions.json) {
334+
console.log(JSON.stringify({ success: false, error: getErrorMessage(error) }));
335+
} else {
336+
console.error(`Error: ${getErrorMessage(error)}`);
337+
}
338+
process.exit(1);
339+
}
243340
});
244341

245342
removeCmd
@@ -248,9 +345,59 @@ export class GatewayTargetPrimitive extends BasePrimitive<AddGatewayTargetOption
248345
.option('--name <name>', 'Name of resource to remove')
249346
.option('--force', 'Skip confirmation prompt')
250347
.option('--json', 'Output as JSON')
251-
.action(() => {
252-
console.error('Gateway target integration is coming soon.');
253-
process.exit(1);
348+
.action(async (cliOptions: { name?: string; force?: boolean; json?: boolean }) => {
349+
try {
350+
if (!findConfigRoot()) {
351+
console.error('No agentcore project found. Run `agentcore create` first.');
352+
process.exit(1);
353+
}
354+
355+
if (cliOptions.name || cliOptions.force || cliOptions.json) {
356+
if (!cliOptions.name) {
357+
console.log(JSON.stringify({ success: false, error: '--name is required' }));
358+
process.exit(1);
359+
}
360+
361+
const result = await this.remove(cliOptions.name);
362+
console.log(
363+
JSON.stringify({
364+
success: result.success,
365+
resourceType: this.kind,
366+
resourceName: cliOptions.name,
367+
message: result.success ? `Removed gateway target '${cliOptions.name}'` : undefined,
368+
note: result.success ? SOURCE_CODE_NOTE : undefined,
369+
error: !result.success ? result.error : undefined,
370+
})
371+
);
372+
process.exit(result.success ? 0 : 1);
373+
} else {
374+
const [{ render }, { default: React }, { RemoveFlow }] = await Promise.all([
375+
import('ink'),
376+
import('react'),
377+
import('../tui/screens/remove'),
378+
]);
379+
const { clear, unmount } = render(
380+
React.createElement(RemoveFlow, {
381+
isInteractive: false,
382+
force: cliOptions.force,
383+
initialResourceType: this.kind,
384+
initialResourceName: cliOptions.name,
385+
onExit: () => {
386+
clear();
387+
unmount();
388+
process.exit(0);
389+
},
390+
})
391+
);
392+
}
393+
} catch (error) {
394+
if (cliOptions.json) {
395+
console.log(JSON.stringify({ success: false, error: getErrorMessage(error) }));
396+
} else {
397+
console.error(`Error: ${getErrorMessage(error)}`);
398+
}
399+
process.exit(1);
400+
}
254401
});
255402
}
256403

0 commit comments

Comments
 (0)