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
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-read-connector-gkzcxxkc5e",
"targetId": "Z3FQ0H8JCK",
"createdAt": {
"$date": "2026-08-05T20:20:45.351Z"
},
"updatedAt": {
"$date": "2026-08-05T20:20:51.210Z"
},
"status": "READY",
"name": "agentcore-cli-gateway-read-connector-openai",
"targetConfiguration": {
"inference": {
"connector": {
"source": {
"connectorId": "openai"
}
}
}
},
"credentialProviderConfigurations": [
{
"credentialProviderType": "API_KEY",
"credentialProvider": {
"apiKeyCredentialProvider": {
"providerArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:token-vault/default/apikeycredentialprovider/DONOTDELETEe2eOAI",
"credentialParameterName": "Authorization",
"credentialPrefix": "Bearer",
"credentialLocation": "HEADER"
}
}
}
],
"description": "AgentCore CLI persistent READY Gateway Connector Target fixture",
"lastSynchronizedAt": {
"$date": "2026-08-05T20:20:51.000Z"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"items": [
{
"targetId": "Z3FQ0H8JCK",
"name": "agentcore-cli-gateway-read-connector-openai",
"status": "READY",
"createdAt": {
"$date": "2026-08-05T20:20:45.351Z"
},
"updatedAt": {
"$date": "2026-08-05T20:20:51.210Z"
},
"description": "AgentCore CLI persistent READY Gateway Connector Target fixture",
"lastSynchronizedAt": {
"$date": "2026-08-05T20:20:51.000Z"
},
"targetType": "CONNECTOR"
}
]
}
32 changes: 32 additions & 0 deletions src/handlers/gateway/__fixtures__/connector-get.golden.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"gatewayArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:gateway/agentcore-cli-gateway-read-connector-gkzcxxkc5e",
"targetId": "Z3FQ0H8JCK",
"createdAt": "2026-08-05T20:20:45.351Z",
"updatedAt": "2026-08-05T20:20:51.210Z",
"status": "READY",
"name": "agentcore-cli-gateway-read-connector-openai",
"targetConfiguration": {
"inference": {
"connector": {
"source": {
"connectorId": "openai"
}
}
}
},
"credentialProviderConfigurations": [
{
"credentialProviderType": "API_KEY",
"credentialProvider": {
"apiKeyCredentialProvider": {
"providerArn": "arn:aws:bedrock-agentcore:us-east-1:685197708687:token-vault/default/apikeycredentialprovider/DONOTDELETEe2eOAI",
"credentialParameterName": "Authorization",
"credentialPrefix": "Bearer",
"credentialLocation": "HEADER"
}
}
}
],
"description": "AgentCore CLI persistent READY Gateway Connector Target fixture",
"lastSynchronizedAt": "2026-08-05T20:20:51.000Z"
}
14 changes: 14 additions & 0 deletions src/handlers/gateway/__fixtures__/connector-list.golden.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"items": [
{
"targetId": "Z3FQ0H8JCK",
"name": "agentcore-cli-gateway-read-connector-openai",
"status": "READY",
"createdAt": "2026-08-05T20:20:45.351Z",
"updatedAt": "2026-08-05T20:20:51.210Z",
"description": "AgentCore CLI persistent READY Gateway Connector Target fixture",
"lastSynchronizedAt": "2026-08-05T20:20:51.000Z",
"targetType": "CONNECTOR"
}
]
}
42 changes: 42 additions & 0 deletions src/handlers/gateway/connector/get/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { TargetConfiguration } from "@aws-sdk/client-bedrock-agentcore-control";
import z from "zod";
import { InputValidationError } from "../../../../errors";
import { createHandler, flag } from "../../../../router";
import { JsonRendererKey } from "../../../../tui";
import type { Core } from "../../../types";
import { coreOptsFromCtx } from "../../../utils";

function isConnectorTarget(configuration: TargetConfiguration | undefined): boolean {
return (
configuration?.mcp?.connector !== undefined || configuration?.inference?.connector !== undefined
);
}

export const createGetGatewayConnectorHandler = (core: Core) =>
createHandler({
name: "get",
description: "get a connector configured for an AgentCore Gateway",
flags: [
flag("gateway-id", "the ID of the Gateway", z.string().optional()),
flag("id", "the ID of the connector-backed Gateway Target", z.string().optional()),
],
handle: async (ctx, flags) => {
if (!flags["gateway-id"]) {
throw new InputValidationError("required option '--gateway-id <gateway-id>' not specified");
}
if (!flags.id) {
throw new InputValidationError("required option '--id <id>' not specified");
}

const target = await core.gateway.getGatewayTarget(
flags["gateway-id"],
flags.id,
coreOptsFromCtx(ctx),
);
if (!isConnectorTarget(target.targetConfiguration)) {
throw new InputValidationError(`Gateway Target "${flags.id}" is not connector-backed`);
}

ctx.require(JsonRendererKey).renderJson(target);
},
});
13 changes: 13 additions & 0 deletions src/handlers/gateway/connector/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { AppIO } from "../../../io";
import { Router } from "../../../router";
import { createHelpDefault } from "../../help";
import type { Core } from "../../types";
import { createGetGatewayConnectorHandler } from "./get";
import { createListGatewayConnectorsHandler } from "./list";

export function createGatewayConnectorHandler(core: Core, io: AppIO): Router {
return new Router("connector", "inspect connectors configured for an AgentCore Gateway")
.default(createHelpDefault(io))
.handler(createGetGatewayConnectorHandler(core))
.handler(createListGatewayConnectorsHandler(core));
}
35 changes: 35 additions & 0 deletions src/handlers/gateway/connector/list/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { TargetType } from "@aws-sdk/client-bedrock-agentcore-control";
import z from "zod";
import { InputValidationError } from "../../../../errors";
import { createHandler, flag } from "../../../../router";
import { JsonRendererKey } from "../../../../tui";
import type { Core } from "../../../types";
import { coreOptsFromCtx } from "../../../utils";

export const createListGatewayConnectorsHandler = (core: Core) =>
createHandler({
name: "list",
description: "list connectors configured for an AgentCore Gateway",
flags: [
flag("gateway-id", "the ID of the Gateway", z.string().optional()),
flag("next-token", "pagination token returned by a previous request", z.string().optional()),
flag("max-results", "maximum number of items to return", z.number().optional()),
],
handle: async (ctx, flags) => {
if (!flags["gateway-id"]) {
throw new InputValidationError("required option '--gateway-id <gateway-id>' not specified");
}

const response = await core.gateway.listGatewayTargets(
flags["gateway-id"],
flags["next-token"],
flags["max-results"],
coreOptsFromCtx(ctx),
);

ctx.require(JsonRendererKey).renderJson({
...response,
items: response.items?.filter((target) => target.targetType === TargetType.CONNECTOR),
});
},
});
33 changes: 28 additions & 5 deletions src/handlers/gateway/gateway.fixture.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@ import {
import { createRootHandler } from "../index";

const REGION = "us-west-2";
const CONNECTOR_REGION = "us-east-1";
const FIXTURES = join(import.meta.dir, "__fixtures__");
const GATEWAY_ID = "agentcore-cli-gateway-read-fixture-a-l6opkbe2kd";
const TARGET_ID = "KALJACI9HO";
const RULE_GATEWAY_ID = "agentcore-cli-gateway-read-rule-fixture-lhpid2reoy";
const RULE_ID = "d396c3f4-4591-41b3-a4d5-816e03c32419";
const CONNECTOR_GATEWAY_ID = "agentcore-cli-gateway-read-connector-gkzcxxkc5e";
const CONNECTOR_ID = "Z3FQ0H8JCK";

// Account 685197708687 owns the persistent read-only fixture graph:
// two listable Gateways, two MCP Targets under GATEWAY_ID, and two Rules under
// RULE_GATEWAY_ID. Record with:
// The e2e-test profile owns the persistent read-only fixture graph:
// listable Gateways and Targets in REGION, plus one READY connector Target under
// CONNECTOR_GATEWAY_ID in CONNECTOR_REGION. Record with:
// AWS_PROFILE=e2e-test RECORD=1 bun test src/handlers/gateway/gateway.fixture.test.tsx
function createFixtureCore(): CoreClient {
const { createControlClient, createDataClient, createIamClient } = fixtureFactories(FIXTURES);
Expand All @@ -31,14 +34,14 @@ function createFixtureCore(): CoreClient {
});
}

async function run(args: string[]): Promise<string> {
async function run(args: string[], region = REGION): Promise<string> {
const io = testIO();
const root = createRootHandler(createFixtureCore(), {
io: io.io,
logger: createSilentLogger(),
globalConfigAccessor: new TestGlobalConfigAccessor(),
});
await root.route(["node", "agentcore", ...args, "--region", REGION]);
await root.route(["node", "agentcore", ...args, "--region", region]);
return io.stdout();
}

Expand Down Expand Up @@ -112,6 +115,26 @@ describe("Gateway fixture-backed reads", () => {
expect(JSON.parse(page2).items).toHaveLength(1);
});

test("gets a Gateway Connector", async () => {
const stdout = await run(
["gateway", "connector", "get", "--gateway-id", CONNECTOR_GATEWAY_ID, "--id", CONNECTOR_ID],
CONNECTOR_REGION,
);
matchGolden(FIXTURES, "connector-get.golden.json", stdout);
expect(JSON.parse(stdout).targetId).toBe(CONNECTOR_ID);
});

test("lists Gateway Connectors", async () => {
const stdout = await run(
["gateway", "connector", "list", "--gateway-id", CONNECTOR_GATEWAY_ID],
CONNECTOR_REGION,
);
matchGolden(FIXTURES, "connector-list.golden.json", stdout);
expect(JSON.parse(stdout).items.map(({ targetId }: { targetId: string }) => targetId)).toEqual([
CONNECTOR_ID,
]);
});

test("gets a Gateway Rule", async () => {
const stdout = await run([
"gateway",
Expand Down
90 changes: 79 additions & 11 deletions src/handlers/gateway/gateway.test.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { describe, expect, test } from "bun:test";
import type {
GatewayRuleDetail,
GatewaySummary,
GetGatewayResponse,
GetGatewayRuleResponse,
GetGatewayTargetResponse,
ListGatewayRulesResponse,
ListGatewaysResponse,
ListGatewayTargetsResponse,
TargetSummary,
import {
TargetType,
type GatewayRuleDetail,
type GatewaySummary,
type GetGatewayResponse,
type GetGatewayRuleResponse,
type GetGatewayTargetResponse,
type ListGatewayRulesResponse,
type ListGatewaysResponse,
type ListGatewayTargetsResponse,
type TargetSummary,
} from "@aws-sdk/client-bedrock-agentcore-control";
import {
createSilentLogger,
Expand Down Expand Up @@ -66,20 +67,23 @@ describe("gateway command hierarchy", () => {
});
const gateway = root.children().find((child) => child.name() === "gateway");
const target = gateway?.children().find((child) => child.name() === "target");
const connector = gateway?.children().find((child) => child.name() === "connector");
const rule = gateway?.children().find((child) => child.name() === "rule");

expect(gateway?.flags().map((flag) => flag.name)).not.toContain("interactive");
expect(gateway?.children().map((child) => child.name())).toEqual([
"get",
"list",
"target",
"connector",
"rule",
]);
expect(target?.children().map((child) => child.name())).toEqual(["get", "list"]);
expect(connector?.children().map((child) => child.name())).toEqual(["get", "list"]);
expect(rule?.children().map((child) => child.name())).toEqual(["get", "list"]);
});

test.each(["gateway", "gateway target", "gateway rule"])(
test.each(["gateway", "gateway target", "gateway connector", "gateway rule"])(
"prints help for bare `%s` without a Core call",
async (command) => {
const { core, stdout } = await run(command.split(" "));
Expand Down Expand Up @@ -210,6 +214,67 @@ describe("gateway reads", () => {
]);
});

test.each([
Comment thread
aidandaly24 marked this conversation as resolved.
["MCP", { mcp: { connector: { source: { connectorId: "web-search" } } } }],
["inference", { inference: { connector: { source: { connectorId: "openai" } } } }],
] as const)("gets a configured %s Connector", async (_kind, targetConfiguration) => {
const core = new TestCoreClient();
core.gateway.setGetTargetResponse({ ...targetResponse, targetConfiguration });

const result = await run(
["gateway", "connector", "get", "--gateway-id", GATEWAY_ID, "--id", TARGET_ID],
core,
);

expect(result.core.gateway.calls).toEqual([
{
method: "getGatewayTarget",
args: [GATEWAY_ID, TARGET_ID, { region: REGION }],
},
]);
expect(JSON.parse(result.stdout)).toEqual({ ...targetResponse, targetConfiguration });
});

test("lists only configured Connectors and preserves the service token", async () => {
const response: ListGatewayTargetsResponse = {
items: [
{ targetId: TARGET_ID, targetType: TargetType.CONNECTOR } as TargetSummary,
{ targetId: "target-2", targetType: TargetType.MCP_SERVER } as TargetSummary,
],
nextToken: "target-page-2",
};
const core = new TestCoreClient();
core.gateway.setListTargetsResponse(response);

const result = await run(
["gateway", "connector", "list", "--gateway-id", GATEWAY_ID, "--max-results", "2"],
core,
);

expect(JSON.parse(result.stdout)).toEqual({
items: [response.items![0]],
nextToken: response.nextToken,
});
expect(result.core.gateway.calls).toEqual([
{
method: "listGatewayTargets",
args: [GATEWAY_ID, undefined, 2, { region: REGION }],
},
]);
});

test("rejects a non-Connector Target from connector get", async () => {
const core = new TestCoreClient();
core.gateway.setGetTargetResponse({
...targetResponse,
targetConfiguration: { mcp: { mcpServer: { endpoint: "https://example.test/mcp" } } },
});

await expect(
run(["gateway", "connector", "get", "--gateway-id", GATEWAY_ID, "--id", TARGET_ID], core),
).rejects.toThrow(`Gateway Target "${TARGET_ID}" is not connector-backed`);
});

test("gets a Gateway Rule with qualified selectors", async () => {
const core = new TestCoreClient();
core.gateway.setGetRuleResponse(ruleResponse);
Expand Down Expand Up @@ -279,6 +344,9 @@ describe("gateway validation and errors", () => {
["Target get parent", ["gateway", "target", "get"], /--gateway-id/],
["Target get child", ["gateway", "target", "get", "--gateway-id", GATEWAY_ID], /--target-id/],
["Target list", ["gateway", "target", "list"], /--gateway-id/],
["Connector get parent", ["gateway", "connector", "get"], /--gateway-id/],
["Connector get child", ["gateway", "connector", "get", "--gateway-id", GATEWAY_ID], /--id/],
["Connector list", ["gateway", "connector", "list"], /--gateway-id/],
["Rule get parent", ["gateway", "rule", "get"], /--gateway-id/],
["Rule get child", ["gateway", "rule", "get", "--gateway-id", GATEWAY_ID], /--rule-id/],
["Rule list", ["gateway", "rule", "list"], /--gateway-id/],
Expand Down
Loading
Loading