iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🤖

What I Learned from Running AWS MCP Servers in the gemini-cli Sandbox

に公開
2025/07/18

What We Learned in the End

The sandbox mode is provided to enhance security and safety, but as a result, there are several situations where actions are restricted, and I found that we need to consider individual solutions. I will first write about what restrictions I encountered and how I solved them. The details of the configuration and solutions for running AWS MCP Servers are written afterward.

Writable Directories

First, the directories to which write permission is granted are limited to the "project root" and "/tmp". In other words, you cannot write directly under the user's home directory. Since user-specific settings and caches of various tools are often placed directly under the home directory, this is expected to have a significant impact.

In this case, the following directories are placed directly under the directory by default, so countermeasures were required. Git consideration is not mandatory, but if you want to save the work results remotely as well, it is necessary, so I dealt with it.

Issue Default Solution
AWS authentication settings and cache ~/.aws Pass temporary credentials obtained before sandbox startup as environment variables. This avoids the need to keep settings and caches directly under the home directory.
uvx cache[1] ~/.cache/uv Change the setting to directly under the project root. Of course, exclude it from git management.
git config --global ~/.gitconfig Avoid by setting with git config --local.

Environment Variables

The sandbox mode of gemini-cli is enabled by specifying the -s option. You can also specify the Docker image to use with --sandbox-image. However, I couldn't find any other options. This means that it is difficult to make flexible configuration changes to the launched Docker container.

In this case, "how to resolve the AWS authentication" became a headache. Several methods were considered, but after the following considerations, I decided to adopt a method of writing to a .env file and passing the credentials as environment variables.

Solution Conclusion Reason
Mount ~/.aws directory NG No startup option in gemini-cli to mount it.
Copy configuration files to Docker image NG It would be fixed to the settings of the docker build environment.
Pass credentials as environment variables to the sandbox Docker container NG No startup option in gemini-cli to specify environment variables.
Write credentials to .env file directly under project root OK Adopted because there is no other feasible solution. Of course, the .env file is excluded from git management.

Example of Implementing gemini-cli + AWS MCP Servers

From here, I will introduce example configurations for the gemini-cli sandbox that can run AWS MCP Servers. The file layout that appears in this article is as follows:

Project Root
├── .cache
│   └── uv
├── .env
├── .gemini
│   ├── sandbox.Dockerfile
│   └── settings.json
├── .git
(omitted)
│   └── refs
├── .gitignore
├── build_sandbox_image.sh
└── run_gemini_cli_sandbox.sh

Docker Image

I created a Docker image for the sandbox using Debian as the base image, rather than the official image. The reasons are as follows:

  • I wanted to know what needs to be installed and configured for it to work as a sandbox.
  • The official image had an unpleasant phenomenon related to the UID of the non-root user.

I'll discuss the details of the second point later. First, here is the Dockerfile I created. I think the comments will explain what it does.

sandbox.Dockerfile
# Use the latest Debian as the base image
FROM public.ecr.aws/docker/library/debian:bookworm-slim

# Install necessary packages, Node.js, gemini-cli, and uv, then delete unnecessary files
# Detailed comments on each process
RUN set -eux; \
    \
    # 1. Update package list and upgrade the system
    apt-get update -y; \
    apt-get upgrade -y; \
    \
    # 2. Install necessary packages (curl, ca-certificates, git)
    apt-get install -y --no-install-recommends \
        curl \
        ca-certificates \
        git; \
    \
    # 3. Install Node.js 24.x (using the official script)
    curl -fsSL https://deb.nodesource.com/setup_24.x | bash -; \
    apt-get install -y --no-install-recommends nodejs; \
    \
    # 4. Install gemini-cli globally (excluding dev dependencies)
    npm install -g --omit=dev @google/gemini-cli; \
    \
    # 5. Install uv (using official script, so curl | sh is acceptable)
    export UV_INSTALL_DIR=/usr/local/bin; \
    curl -LsSf https://astral.sh/uv/install.sh | sh; \
    \
    # 6. Remove unnecessary caches, temporary files, and npm cache to reduce image size
    apt-get clean; \
    apt-get autoremove -y; \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* ~/.npm

# Specify the uv cache directory via environment variable
ENV UV_CACHE_DIR=./.cache/uv

Now, let me talk about the "unpleasant phenomenon" of the second point. First, there are the following assumptions:

  • The login user on the Linux environment used for this verification work is user1 (UID: 1000).
  • gemini-cli automatically creates a non-root user gemini in the sandbox environment. The UID of this user is recommended to be the same as the UID of the host side that executed docker run to avoid permission issues with mounted directories. The gemini-cli default is also like that. That is, gemini (UID: 1000) is created.
  • In the official image, node (UID: 1000) already exists.

As a result, what happened was a UID conflict.

/etc/passwd
(omitted)
  node:x:1000:1000::/home/node:/bin/bash
  gemini:x:1000:1000::/home/user1:/bin/bash

And here is the creepy phenomenon. It's clearly confused. whoami reflects the first one, while $HOME reflects the value of the second one. Before that, the fact that "IDs are duplicated" is unbearable.

$ whoami
node
$ echo $HOME
/home/user1

Therefore, for this verification, I adopted debian:bookworm-slim as the base image, which does not have a user with UID 1000 prepared in advance.

Container startup command in sandbox mode

When I checked the container started in sandbox mode with docker inspect, I found it was started with the following command. It seems the user is created at startup, not inside the Dockerfile.

            "Cmd": [
                "bash",
                "-c",
                "groupadd -f -g 1000 gemini && id -u gemini &>/dev/null || useradd -o -u 1000 -g 1000 -d /home/<username> -s /bin/bash gemini && su -p gemini -c 'gemini -s'"
            ],

Build Script

There is nothing particularly difficult; just a normal docker build is fine. I named the image wanileap-sandbox. Don't worry about the origin of the name.

build_sandbox_image.sh
#!/bin/bash

# --- Check for required commands ---
if ! command -v docker >/dev/null 2>&1; then
  echo "Error: docker command not found. Please install Docker." >&2
  exit 1
fi

# Exit immediately on error
set -e

# --- Build Docker image ---
# Build the Docker image for the wanileap-sandbox environment.
docker build -t wanileap-sandbox -f .gemini/sandbox.Dockerfile .gemini

Actually, there is a feature that automatically builds the image at startup using the project-specific .gemini/sandbox.Dockerfile placed under the project root when the environment variable BUILD_SANDBOX is set to 1. However, this feature has a major pitfall. When I actually tried it, I got this:

$ BUILD_SANDBOX=1 gemini -s

(omitted)

hopping into sandbox (command: docker) ...
ERROR: cannot build sandbox using installed gemini binary; run `npm link ./packages/cli` under gemini-cli repo to switch to linked binary.

I'm not sure what it's saying, but basically it seems to require that you have downloaded the gemini-cli source code, built it yourself with npm build, and then set it up to run with npm link. In other words, the easy installation method like npm install -g @google/gemini-cli won't work. Why...

Therefore, it is convenient to use the method of pre-building the Docker image and specifying it with the --sandbox-image option at startup. In a very simple form, it looks like this:

gemini -s --sandbox-image wanileap-sandbox

Startup Script

Starting gemini-cli is very simple as mentioned earlier, but this time we have the requirement to pass AWS authentication information as environment variables for AWS MCP Servers. The implementation method is to write it into the .env file directly under the project root. As with the build script, it is rich in comments, so you should understand what is being done by looking at it.

run_gemini_cli_sandbox.sh
#!/bin/bash

# Check for required commands
for cmd in aws gemini docker; do
  if ! command -v "$cmd" >/dev/null 2>&1; then
    echo "Error: $cmd command not found. Please install $cmd." >&2
    exit 1
  fi
done

# Exit immediately on error
set -e

# --- Retrieve and verify AWS SSO credentials ---
# Retrieve AWS SSO credentials and write them to the .env file.
# Note: Before running this script, execute 'aws sso login' on the host machine
#       and confirm the SSO session is active.
echo "Retrieving AWS SSO credentials..."
AWS_CREDENTIALS=$(aws configure export-credentials --format env)

# Exit with error if credential retrieval fails
if [ $? -ne 0 ]; then
  echo "Error: Failed to retrieve AWS SSO credentials. Please ensure 'aws sso login' has been executed,"
  echo "and that AWS CLI is correctly configured."
  exit 1
fi

# Save the retrieved credentials to the .env file
cat <<EOF > .env
$AWS_CREDENTIALS
EOF

# Temporarily reflect the .env file contents as environment variables (for use within the script)
eval "$AWS_CREDENTIALS"

# Check that all required AWS credentials are present
if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ] || [ -z "$AWS_SESSION_TOKEN" ]; then
  echo "Error: Incomplete AWS credentials retrieved. Please verify your SSO session is active."
  exit 1
fi

echo "AWS credentials have been written to .env file."

# --- Launch gemini-cli sandbox ---
# Launch gemini-cli in sandbox mode using the pre-built wanileap-sandbox image.
gemini -s --sandbox-image wanileap-sandbox -y

We also specify the -y option at startup. This option sets gemini-cli to automatically proceed with processing without confirming with the user. Since we are using a sandbox, I decided to go with a bold configuration.

Note that even when the -y option is specified, there are cases where confirmation is still required from the user. I asked gemini-cli what those cases are.

╭────────────────────────────────────────────────────────────────────────────────────────────────╮
│  > In what cases does gemini-cli request user confirmation even when the -y option is specified?│
╰────────────────────────────────────────────────────────────────────────────────────────────────╯

✦ In gemini-cli, even when the -y option is specified, user confirmation is required mainly for
  destructive or irreversible operations, or operations that could have significant impact.

  This is a design decision prioritizing safety as an AI agent, to prevent unintended data loss or
  system changes.

  Specifically, the following cases can be considered:

  * Irreversible file system changes such as file deletion or overwriting: for example, when
    deleting important configuration files or a large number of files.
  * Operations that could have a significant impact on the system, such as deployments to
    production environments or infrastructure changes: because mis-deployment could lead to
    service outages.
  * Security-related configuration changes or handling of confidential information: because
    misoperation could lead to security risks.

  The -y option skips general confirmation prompts, but for operations requiring particularly
  careful judgment, it is designed to request the user's explicit approval.

gemini-cli Configuration File

Specifically, the file is .gemini/settings.json. Here we put the settings for the MCP Server we want to use. I will introduce the relevant settings. JSON does not allow comments, so I will explain the intent of each item later.

settings.json
{
    "mcpServers": {
        "awslabs.aws-documentation-mcp-server": {
            "command": "uvx",
            "args": [
                "awslabs.aws-documentation-mcp-server@latest"
            ],
            "env": {
                "FASTMCP_LOG_LEVEL": "ERROR",
                "AWS_DOCUMENTATION_PARTITION": "aws"
            },
            "disabled": false,
            "autoApprove": []
        },
(omitted ... I set various others)
        "awslabs.cost-explorer-mcp-server": {
            "command": "uvx",
            "args": [
                "awslabs.cost-explorer-mcp-server@latest"
            ],
            "env": {
                "FASTMCP_LOG_LEVEL": "ERROR"
            },
            "disabled": false,
            "autoApprove": []
        }
    },
    "fileFiltering": {
        "respectGitIgnore": true,
        "enableRecursiveFileSearch": true
    }
}

The entries named awslabs.xxx-server are the various AWS MCP Servers. awslabs.aws-documentation-mcp-server provides a search function for public documentation, so no login to an AWS account is required. On the other hand, awslabs.cost-explorer-mcp-server retrieves usage costs for a specific AWS account, so login to an AWS account is necessary.

Note that the documentation guides setting the AWS_PROFILE environment variable. However, since we adopted the method of passing temporary credentials using the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_SESSION_TOKEN environment variables, setting AWS_PROFILE is not allowed.

Finally, fileFiltering is not a setting directly related to AWS MCP Servers. However, it is related to changing the uv cache directory used by AWS MCP Servers to be directly under the project root. gemini-cli scans files under the project at startup to understand the context, but reading the contents of the .cache directory is not very meaningful. Therefore, by setting the respectGitIgnore option to true, we exclude .cache from the scan target.

Operation Verification Results

First, I ran the command to check the status of the MCP Servers and confirmed they were running properly. The earlier configuration was an excerpt, so only two were registered, but in reality I registered five.

╭──────────╮
│  > /mcp  │
╰──────────╯

ℹ Configured MCP servers:
 
  🟢 awslabs.core-mcp-server - Ready (1 tools)
    - prompt_understanding

  🟢 awslabs.aws-documentation-mcp-server - Ready (3 tools)
    - read_documentation
    - search_documentation
    - recommend

  🟢 awslabs.cdk-mcp-server - Ready (7 tools)
    - CDKGeneralGuidance
    - ExplainCDKNagRule
    - CheckCDKNagSuppressions
    - GenerateBedrockAgentSchema
    - GetAwsSolutionsConstructPattern
    - SearchGenAICDKConstructs
    - LambdaLayerDocumentationProvider

  🟢 awslabs.cost-analysis-mcp-server - Ready (6 tools)
    - analyze_cdk_project
    - analyze_terraform_project
    - get_pricing_from_web
    - get_pricing_from_api
    - get_bedrock_patterns
    - generate_cost_report

  🟢 awslabs.cost-explorer-mcp-server - Ready (7 tools)
    - get_today_date
    - get_dimension_values
    - get_tag_values
    - get_cost_forecast
    - get_cost_and_usage_comparisons
    - get_cost_comparison_drivers
    - get_cost_and_usage

Finally, I asked a question that could not be answered without the help of MCP Servers, and confirmed that they properly contributed.

╭─────────────────────────────────────────────────────────────────────────────╮
│  > Investigate the trend of AWS usage costs over the past year and        │
│    suggest improvements if any.                                           │
╰─────────────────────────────────────────────────────────────────────────────╯

╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
 │ ✔  get_today_date (awslabs.cost-explorer-mcp-server MCP Server) {}                                                                                      │
 │                                                                                                                                                         │
 │    {                                                                                                                                                    │
 │      "today_date_UTC": "2025-07-02",                                                                                                                    │
 │      "current_month": "2025-07"                                                                                                                         │
 │    }                                                                                                                                                    │
 ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ I will investigate the AWS usage cost trends over the past year and suggest improvements.

  First, we need to specify a time period to retrieve the cost data for the past year. Today is July 2, 2025, so we will retrieve data from "July 1, 2024 to June 30, 2025" as the past year.

  Next, we decide the granularity and grouping of the data.
   * Granularity: Use MONTHLY to see monthly trends.
   * Group by: Group by SERVICE to see which services account for costs.
   * Metric: Use UnblendedCost.

  Based on this information, I will use the get_cost_and_usage tool to retrieve the data.

  After retrieving the data, I will analyze the trends and suggest general improvements. More detailed suggestions can be provided based on the content of the retrieved data.

  So, let's first retrieve the monthly cost data by service for the past year.
 ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
 │ ✔  get_cost_and_usage (awslabs.cost-explorer-mcp-server MCP Server) {"group_by":{"Key":"SERVICE","Type":"DIMENSION"},"date_range":{"start_date":"2024-07-01","end… │
 │                                                                                                                                                                    │
 │    ... first 193 lines hidden ...                                                                                                                                  │
(omitted)

 │        }                                                                                                                                                           │
 │      }                                                                                                                                                             │
 │    }                                                                                                                                                               │
 ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
✦ Successfully retrieved the AWS usage cost data for the past year.

  Analysis of usage cost trends over the past year

  From the retrieved data, the following trends are observed:

(omitted: amounts are confidential)

  These improvement suggestions are general, but the optimal methods vary depending on the specific usage situation and architecture. We recommend first checking the usage details of S3 and Bedrock in detail, and then starting from there.

We successfully confirmed that AWS MCP Servers can connect to an AWS account and provide information.

Bonus

Note that all of this implementation was done by gemini-cli or GitHub Copilot. There were not a few cases where non-functional code or code that did not act as intended was generated, but after several rounds of generation and feedback, I think I finally got what I wanted.

It was a task of thinking up and typing prompts repeatedly, but in reality, it felt like reviewing code written by generative AI and giving feedback to the generative AI. In the future, those who have unwavering requirements like "I want to create this!" and can give specific, clear instructions and feedback to generative AI to approach their own vision of the completed image might be the ones who can survive while coexisting with generative AI.

脚注
  1. uv is a Python package manager used for dependency management of MCP servers. ↩︎

Discussion