Skip to content
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ Projects use JSON schema files in the `agentcore/` directory:
**Operations**

- [Local Development](docs/local-development.md) - Dev server and debugging
- [Proxy](docs/proxy.md) - Use the CLI behind an HTTP(S) proxy
- [Transaction Search](docs/transaction_search.md) - Trace + log search across agent invocations
- [Telemetry](docs/telemetry.md) - CLI usage telemetry — what's collected and how to opt out
- [TUI Harness](docs/tui-harness.md) - Programmatic TUI driver for testing
Expand Down
50 changes: 50 additions & 0 deletions docs/proxy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Using the CLI behind an HTTP proxy

The AgentCore CLI routes its AWS traffic through an HTTP(S) proxy when the standard proxy environment variables are set.
This covers every AWS SDK client the CLI creates (STS, CloudFormation, CloudWatch Logs, Bedrock, AgentCore, EC2, S3,
IAM, KMS, X-Ray, ...) and the CDK Toolkit used by `agentcore deploy`.

## Requirements

Node.js **22.21 or later** (or **24.5 or later**). Proxy support relies on Node's built-in `proxyEnv` agent option. On
older versions the CLI prints a warning and connects directly.

## Configuration

| Variable | Purpose |
| ----------------------------- | ----------------------------------------------------------------- |
| `HTTPS_PROXY` / `https_proxy` | Proxy used for HTTPS requests (all AWS endpoints) |
| `HTTP_PROXY` / `http_proxy` | Proxy used for plain HTTP requests |
| `NO_PROXY` / `no_proxy` | Comma-separated hosts or domains that bypass the proxy |
| `NODE_USE_ENV_PROXY=1` | Makes Node's global `fetch` honor the variables above (see below) |

```bash
export HTTPS_PROXY=http://proxy.example.com:3128
export HTTP_PROXY=http://proxy.example.com:3128
export NO_PROXY=localhost,127.0.0.1,.internal.example.com
export NODE_USE_ENV_PROXY=1

agentcore deploy
```

PowerShell:

```powershell
$env:HTTPS_PROXY = "http://proxy.example.com:3128"
$env:HTTP_PROXY = "http://proxy.example.com:3128"
$env:NO_PROXY = "localhost,127.0.0.1"
$env:NODE_USE_ENV_PROXY = "1"
agentcore deploy
```

Proxy credentials can be embedded in the URL (`http://user:password@proxy:3128`). They are redacted from any message the
CLI prints.

## Notes

- Some commands call AgentCore APIs with Node's global `fetch`. Node applies the proxy variables to `fetch` only when
`NODE_USE_ENV_PROXY=1` is set, so set it together with the proxy variables.
- Keep `localhost` and `127.0.0.1` in `NO_PROXY` so `agentcore dev` can reach the local agent server.
- When a proxy is configured, AWS SDK clients use HTTP/1.1. Node's proxy support does not cover HTTP/2.
- `ALL_PROXY` and SOCKS proxies are not supported.
- Tools the CLI launches as separate processes (Docker, `uv`, `npm`) use their own proxy settings.
1 change: 1 addition & 0 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
"@opentelemetry/otlp-transformer": "0.213.0",
"@opentelemetry/resources": "^2.6.1",
"@opentelemetry/sdk-metrics": "^2.6.1",
"@smithy/node-http-handler": "^4.9.13",
"@smithy/shared-ini-file-loader": "^4.4.2",
"commander": "^14.0.2",
"cross-spawn": "^7.0.6",
Expand Down
2 changes: 2 additions & 0 deletions src/cli/aws/account.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AwsCredentialsError, ValidationError } from '../../lib/errors/types.js';
import { getProxyRequestHandler } from '../../lib/utils/aws-proxy';
import type { AwsDeploymentTarget } from '../../schema';
import { getAwsLoginGuidance } from '../external-requirements/checks';
import { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts';
Expand Down Expand Up @@ -27,6 +28,7 @@ export async function detectAccount(): Promise<string | null> {
const client = new STSClient({
credentials: getCredentialProvider(),
region,
requestHandler: getProxyRequestHandler(),
});
const response = await client.send(new GetCallerIdentityCommand({}));
return response.Account ?? null;
Expand Down
2 changes: 2 additions & 0 deletions src/cli/aws/agentcore-control.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getProxyRequestHandler } from '../../lib/utils/aws-proxy';
import type { NetworkConfig } from '../../schema';
import type { EvaluationLevel, EvaluatorModelProvider } from '../../schema/schemas/primitives/evaluator';
import { resolveVpcIdFromSubnets } from '../commands/shared/vpc-utils';
Expand Down Expand Up @@ -34,6 +35,7 @@ export function createControlClient(region: string): BedrockAgentCoreControlClie
region,
credentials: getCredentialProvider(),
...(endpointOverride ? { endpoint: endpointOverride } : {}),
requestHandler: getProxyRequestHandler(),
});
}

Expand Down
4 changes: 4 additions & 0 deletions src/cli/aws/agentcore.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getProxyRequestHandler } from '../../lib/utils/aws-proxy';
import { parseJsonRpcResponse } from '../../lib/utils/json-rpc';
import { getCredentialProvider } from './account';
import { parseAguiSSEStream } from './agui-parser';
Expand Down Expand Up @@ -30,6 +31,7 @@ function createAgentCoreClient(region: string, headers?: Record<string, string>)
region,
credentials: getCredentialProvider(),
endpoint: dataPlaneEndpoint(region),
requestHandler: getProxyRequestHandler(),
});

if (headers && Object.keys(headers).length > 0) {
Expand Down Expand Up @@ -533,6 +535,7 @@ export async function evaluate(options: EvaluateOptions): Promise<EvaluateResult
const client = new BedrockAgentCoreClient({
region: options.region,
credentials: getCredentialProvider(),
requestHandler: getProxyRequestHandler(),
});

const evaluationTarget = options.targetSpanIds
Expand Down Expand Up @@ -1147,6 +1150,7 @@ export async function stopRuntimeSession(options: StopRuntimeSessionOptions): Pr
const client = new BedrockAgentCoreClient({
region: options.region,
credentials: getCredentialProvider(),
requestHandler: getProxyRequestHandler(),
});

const command = new StopRuntimeSessionCommand({
Expand Down
7 changes: 6 additions & 1 deletion src/cli/aws/bedrock-agent.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getProxyRequestHandler } from '../../lib/utils/aws-proxy';
import { getCredentialProvider } from './account';
import {
BedrockAgentClient,
Expand All @@ -19,7 +20,11 @@ import {
* the CLI is a one-shot process and connection reuse provides marginal benefit.
*/
function makeClient(region: string): BedrockAgentClient {
return new BedrockAgentClient({ region, credentials: getCredentialProvider() });
return new BedrockAgentClient({
region,
credentials: getCredentialProvider(),
requestHandler: getProxyRequestHandler(),
});
}

function isNotFound(err: unknown): boolean {
Expand Down
11 changes: 8 additions & 3 deletions src/cli/aws/bedrock-import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* AWS SDK v3 wrapper for fetching Bedrock Agent configurations.
* Port of the starter toolkit's agent_info.py.
*/
import { getProxyRequestHandler } from '../../lib/utils/aws-proxy';
import { getCredentialProvider } from './account';
import type {
ActionGroupInfo,
Expand Down Expand Up @@ -29,15 +30,19 @@ import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
import yaml from 'js-yaml';

function createBedrockAgentClient(region: string): BedrockAgentClient {
return new BedrockAgentClient({ credentials: getCredentialProvider(), region });
return new BedrockAgentClient({
credentials: getCredentialProvider(),
region,
requestHandler: getProxyRequestHandler(),
});
}

function createBedrockClient(region: string): BedrockClient {
return new BedrockClient({ credentials: getCredentialProvider(), region });
return new BedrockClient({ credentials: getCredentialProvider(), region, requestHandler: getProxyRequestHandler() });
}

function createS3Client(region: string): S3Client {
return new S3Client({ credentials: getCredentialProvider(), region });
return new S3Client({ credentials: getCredentialProvider(), region, requestHandler: getProxyRequestHandler() });
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/cli/aws/bedrock.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getProxyRequestHandler } from '../../lib/utils/aws-proxy';
import { getCredentialProvider } from './account';
import { BedrockRuntimeClient, InvokeModelCommand } from '@aws-sdk/client-bedrock-runtime';

Expand All @@ -17,6 +18,7 @@ export async function invokeBedrockSync(options: BedrockInvokeOptions): Promise<
const client = new BedrockRuntimeClient({
region: options.region,
credentials: getCredentialProvider(),
requestHandler: getProxyRequestHandler(),
});

const command = new InvokeModelCommand({
Expand Down
3 changes: 3 additions & 0 deletions src/cli/aws/cloudwatch.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getProxyRequestHandler } from '../../lib/utils/aws-proxy';
import { DEFAULT_ENDPOINT_NAME } from '../constants';
import { getCredentialProvider } from './account';
import { arnPrefix } from './partition';
Expand Down Expand Up @@ -53,6 +54,7 @@ export async function* streamLogs(options: StreamLogsOptions): AsyncGenerator<Lo
const client = new CloudWatchLogsClient({
region,
credentials: getCredentialProvider(),
requestHandler: getProxyRequestHandler(),
});

const command = new StartLiveTailCommand({
Expand Down Expand Up @@ -114,6 +116,7 @@ export async function* searchLogs(options: SearchLogsOptions): AsyncGenerator<Lo
const client = new CloudWatchLogsClient({
region,
credentials: getCredentialProvider(),
requestHandler: getProxyRequestHandler(),
});

let nextToken: string | undefined;
Expand Down
3 changes: 3 additions & 0 deletions src/cli/aws/policy-generation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getProxyRequestHandler } from '../../lib/utils/aws-proxy';
import { getCredentialProvider } from './account';
import {
BedrockAgentCoreControlClient,
Expand Down Expand Up @@ -36,6 +37,7 @@ export async function startPolicyGeneration(
const client = new BedrockAgentCoreControlClient({
region: options.region,
credentials: getCredentialProvider(),
requestHandler: getProxyRequestHandler(),
});

const command = new StartPolicyGenerationCommand({
Expand All @@ -60,6 +62,7 @@ export async function getPolicyGeneration(options: GetPolicyGenerationOptions):
const client = new BedrockAgentCoreControlClient({
region: options.region,
credentials: getCredentialProvider(),
requestHandler: getProxyRequestHandler(),
});

// Use the SDK waiter to poll until generation completes
Expand Down
11 changes: 8 additions & 3 deletions src/cli/aws/transaction-search.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AccessDeniedError } from '../../lib';
import type { Result } from '../../lib/result';
import { getProxyRequestHandler } from '../../lib/utils/aws-proxy';
import { getErrorMessage, isAccessDeniedError } from '../errors';
import { getCredentialProvider } from './account';
import { arnPrefix } from './partition';
Expand Down Expand Up @@ -36,7 +37,11 @@ export async function enableTransactionSearch(

// Step 1: Enable Application Signals (creates service-linked role, idempotent)
try {
const appSignalsClient = new ApplicationSignalsClient({ region, credentials });
const appSignalsClient = new ApplicationSignalsClient({
region,
credentials,
requestHandler: getProxyRequestHandler(),
});
await appSignalsClient.send(new StartDiscoveryCommand({}));
} catch (err: unknown) {
const message = getErrorMessage(err);
Expand All @@ -53,7 +58,7 @@ export async function enableTransactionSearch(

// Step 2: Create CloudWatch Logs resource policy for X-Ray (if needed)
try {
const logsClient = new CloudWatchLogsClient({ region, credentials });
const logsClient = new CloudWatchLogsClient({ region, credentials, requestHandler: getProxyRequestHandler() });
const policiesResult = await logsClient.send(new DescribeResourcePoliciesCommand({}));
const hasPolicy = policiesResult.resourcePolicies?.some(p => p.policyName === RESOURCE_POLICY_NAME);

Expand Down Expand Up @@ -95,7 +100,7 @@ export async function enableTransactionSearch(
};
}

const xrayClient = new XRayClient({ region, credentials });
const xrayClient = new XRayClient({ region, credentials, requestHandler: getProxyRequestHandler() });

// Step 3: Set trace segment destination to CloudWatchLogs
try {
Expand Down
9 changes: 7 additions & 2 deletions src/cli/cdk/toolkit-lib/wrapper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CONFIG_DIR } from '../../../lib';
import { getProxyHttpsAgent } from '../../../lib/utils/aws-proxy';
import { CDK_APP_ENTRY, CDK_PROJECT_DIR } from '../../constants';
import { isChangesetInProgressError } from '../../errors';
import type { CdkToolkitWrapperOptions, DeployOptions, DestroyOptions, DiffOptions, ListOptions } from './types';
Expand Down Expand Up @@ -97,9 +98,13 @@ export class CdkToolkitWrapper {
// Use explicit profile and region, fall back to env vars per AWS SDK precedence
const profile = this.options.profile ?? process.env.AWS_PROFILE;
const region = this.options.region ?? process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION;
const baseCredentials =
profile || region ? BaseCredentials.awsCliCompatible({ profile, defaultRegion: region }) : undefined;
// Route the toolkit's own SDK calls (CloudFormation, S3/ECR asset publishing, ...) through HTTPS_PROXY.
const proxyAgent = getProxyHttpsAgent();
const sdkConfig =
profile || region
? { baseCredentials: BaseCredentials.awsCliCompatible({ profile, defaultRegion: region }) }
baseCredentials || proxyAgent
? { baseCredentials, httpOptions: proxyAgent ? { agent: proxyAgent } : undefined }
: undefined;

this.toolkit = new Toolkit({
Expand Down
7 changes: 6 additions & 1 deletion src/cli/cloudformation/bootstrap.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getProxyRequestHandler } from '../../lib/utils/aws-proxy';
import { getCredentialProvider } from '../aws';
import { CloudFormationClient, DescribeStacksCommand } from '@aws-sdk/client-cloudformation';

Expand All @@ -13,7 +14,11 @@ export interface BootstrapStatus {
* Check if an AWS environment is bootstrapped by looking for the CDKToolkit stack.
*/
export async function checkBootstrapStatus(region: string): Promise<BootstrapStatus> {
const cfn = new CloudFormationClient({ region, credentials: getCredentialProvider() });
const cfn = new CloudFormationClient({
region,
credentials: getCredentialProvider(),
requestHandler: getProxyRequestHandler(),
});

try {
const resp = await cfn.send(new DescribeStacksCommand({ StackName: CDK_TOOLKIT_STACK_NAME }));
Expand Down
7 changes: 6 additions & 1 deletion src/cli/cloudformation/outputs.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getProxyRequestHandler } from '../../lib/utils/aws-proxy';
import type {
AgentCoreDeployedState,
CapacityProviderDeployedState,
Expand Down Expand Up @@ -33,7 +34,11 @@ export function omitPaymentAuthorizationOutputs(outputs: StackOutputs): StackOut
* Fetch CloudFormation stack outputs.
*/
export async function getStackOutputs(region: string, stackName: string): Promise<StackOutputs> {
const cfn = new CloudFormationClient({ region, credentials: getCredentialProvider() });
const cfn = new CloudFormationClient({
region,
credentials: getCredentialProvider(),
requestHandler: getProxyRequestHandler(),
});
const resp = await cfn.send(new DescribeStacksCommand({ StackName: stackName }));
const stack = resp.Stacks?.[0];
if (!stack) {
Expand Down
7 changes: 6 additions & 1 deletion src/cli/cloudformation/stack-discovery.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getProxyRequestHandler } from '../../lib/utils/aws-proxy';
import { getCredentialProvider } from '../aws';
import { GetResourcesCommand, ResourceGroupsTaggingAPIClient } from '@aws-sdk/client-resource-groups-tagging-api';

Expand Down Expand Up @@ -42,7 +43,11 @@ function parseStackNameFromArn(arn: string): string | null {
* Handles pagination to retrieve all matching stacks.
*/
export async function discoverStacksByProject(region: string, projectName: string): Promise<DiscoveredStack[]> {
const tagging = new ResourceGroupsTaggingAPIClient({ region, credentials: getCredentialProvider() });
const tagging = new ResourceGroupsTaggingAPIClient({
region,
credentials: getCredentialProvider(),
requestHandler: getProxyRequestHandler(),
});
const stacks: DiscoveredStack[] = [];
let paginationToken: string | undefined;

Expand Down
7 changes: 6 additions & 1 deletion src/cli/cloudformation/stack-status.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getProxyRequestHandler } from '../../lib/utils/aws-proxy';
import { getCredentialProvider } from '../aws';
import { CloudFormationClient, DescribeStacksCommand } from '@aws-sdk/client-cloudformation';

Expand Down Expand Up @@ -51,7 +52,11 @@ export interface StackStatusResult {
* - If the stack is in a failed state, deployment may require manual intervention
*/
export async function checkStackStatus(region: string, stackName: string): Promise<StackStatusResult> {
const cfn = new CloudFormationClient({ region, credentials: getCredentialProvider() });
const cfn = new CloudFormationClient({
region,
credentials: getCredentialProvider(),
requestHandler: getProxyRequestHandler(),
});

try {
const resp = await cfn.send(new DescribeStacksCommand({ StackName: stackName }));
Expand Down
13 changes: 11 additions & 2 deletions src/cli/commands/import/phase1-update.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getProxyRequestHandler } from '../../../lib/utils/aws-proxy';
import { getCredentialProvider } from '../../aws/account';
import type { CfnTemplate } from './template-utils';
import { filterCompanionOnlyTemplate } from './template-utils';
Expand Down Expand Up @@ -34,7 +35,11 @@ export interface Phase1Result {
export async function executePhase1(options: Phase1Options): Promise<Phase1Result> {
const { region, stackName, synthTemplate, onProgress } = options;

const cfn = new CloudFormationClient({ region, credentials: getCredentialProvider() });
const cfn = new CloudFormationClient({
region,
credentials: getCredentialProvider(),
requestHandler: getProxyRequestHandler(),
});

// Filter template to companion-only
const companionTemplate = filterCompanionOnlyTemplate(synthTemplate);
Expand Down Expand Up @@ -125,7 +130,11 @@ export async function executePhase1(options: Phase1Options): Promise<Phase1Resul
* Get the currently deployed CloudFormation template.
*/
export async function getDeployedTemplate(region: string, stackName: string): Promise<CfnTemplate | null> {
const cfn = new CloudFormationClient({ region, credentials: getCredentialProvider() });
const cfn = new CloudFormationClient({
region,
credentials: getCredentialProvider(),
requestHandler: getProxyRequestHandler(),
});

try {
const response = await cfn.send(
Expand Down
Loading
Loading