Skip to content

feat(narratives): add agent sidecar (Gemini MCP proxy)#426

Open
ddebasmita-lab wants to merge 1 commit into
datacommonsorg:mainfrom
ddebasmita-lab:narratives-agent
Open

feat(narratives): add agent sidecar (Gemini MCP proxy)#426
ddebasmita-lab wants to merge 1 commit into
datacommonsorg:mainfrom
ddebasmita-lab:narratives-agent

Conversation

@ddebasmita-lab

@ddebasmita-lab ddebasmita-lab commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Overview

Adds the agent sidecar the narratives UI depends on. Until now narratives/
shipped only the front end, which talks to /agent/chat/stream; this PR adds
that backend under narratives/agent/, mirroring the layout used in the
downstream app.

What it is

A Flask server (mcp_proxy_only.py) that proxies the chat pipeline and streams
Server-Sent Events back to the UI:

  1. MCP tool loop — queries Data Commons data tools via Gemini function calls.
  2. Knowledge-base lookup (optional) — grounded document search.
  3. Synthesis — Gemini streams the final answer, thoughts, chart config, and
    follow-up questions.

It emits the session_id, tool_call, thought, text, chart_config,
*_sources, usage, and done events the UI already consumes.

Files

  • agent/mcp_proxy_only.py — proxy + streaming chat pipeline
  • agent/requirements.txt — Python deps
  • agent/Dockerfile, agent/build.sh — container build / deploy
  • narratives/.gitignore — negation so the agent's Python stays tracked
    (the project-level *.py ignore otherwise excluded it)

Security

API keys are read at runtime from Secret Manager (GEMINI_API_KEYS_SECRET),
config.json, or environment variables — none are committed. config.json
is not included (kept out of version control, as in the source app).

Testing done

  • python3 -m py_compile agent/mcp_proxy_only.py — clean.
  • End-to-end run requires Gemini keys + an MCP endpoint (deploy-time).

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a sidecar agent for a multi-container Cloud Run service, including a Dockerfile, a build script, dependency definitions, and documentation of patches applied to the upstream agent code. Feedback on these changes suggests running the Docker container as a non-root user to improve security and prevent runtime permission errors, correcting a comment in the Dockerfile regarding the number of documented patches, and adding strict argument validation to the build script to avoid silently ignoring invalid flags.

Comment on lines +19 to +36
WORKDIR /app

# System deps: tzdata is needed for ZoneInfo on slim images.
RUN apt-get update \
&& apt-get install -y --no-install-recommends tzdata ca-certificates \
&& rm -rf /var/lib/apt/lists/*

COPY requirements.txt ./
RUN pip install -r requirements.txt

COPY mcp_proxy_only.py ./

EXPOSE 5001

# Cloud Run health probes hit the agent on PROXY_PORT; the agent is the
# sidecar container, so the ingress container's nginx reverse-proxies
# /agent/* to 127.0.0.1:${PROXY_PORT}.
CMD ["python", "-u", "mcp_proxy_only.py"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

Running the container as root poses a security risk. Additionally, if the deployment environment enforces a non-root user, the application will crash with a PermissionError when attempting to write config.json to /app (Patch 7 bootstrap).

To resolve this, create a dedicated non-root user, change the ownership of the /app directory to this user, and run the container using the USER directive.

WORKDIR /app

# System deps: tzdata is needed for ZoneInfo on slim images.
RUN apt-get update \
    && apt-get install -y --no-install-recommends tzdata ca-certificates \
    && rm -rf /var/lib/apt/lists/*

# Create a non-root user and group
RUN groupadd -g 10001 appuser \
    && useradd -u 10001 -g appuser -m -s /sbin/nologin appuser

COPY requirements.txt ./
RUN pip install -r requirements.txt

COPY mcp_proxy_only.py ./

# Ensure the non-root user owns /app to write config.json at runtime
RUN chown -R appuser:appuser /app

USER appuser

EXPOSE 5001

# Cloud Run health probes hit the agent on PROXY_PORT; the agent is the
# sidecar container, so the ingress container's nginx reverse-proxies
# /agent/* to 127.0.0.1:${PROXY_PORT}.
CMD ["python", "-u", "mcp_proxy_only.py"]

Comment thread narratives/agent/Dockerfile Outdated
@@ -0,0 +1,36 @@
# Sidecar agent for the Custom Data Commons multi-container Cloud Run service.
# Vendored from upstream additional_features/mcp_proxy_only.py (Apache-2.0)
# with six documented patches in PATCHES.md.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The comment states there are "six documented patches", but PATCHES.md actually documents eight patches. Updating this comment will prevent confusion for future maintainers.

# with eight documented patches in PATCHES.md.

Comment thread narratives/agent/build.sh
Comment on lines +39 to +44
if [[ "${1:-}" == "--push" ]]; then
echo "Pushing ${FULL_IMAGE}"
docker push "${FULL_IMAGE}"
docker push "${AR_REGION}-docker.pkg.dev/${PROJECT}/${AR_REPO}/${IMAGE_NAME}:latest"
echo "Done. Tag for tfvars: ${TAG}"
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Currently, any argument other than --push is silently ignored, which can lead to unexpected behavior (e.g., running ./build.sh --help or ./build.sh -p will build the image but silently skip pushing without warning the user).

Enforce strict argument validation to improve usability and prevent silent failures.

Suggested change
if [[ "${1:-}" == "--push" ]]; then
echo "Pushing ${FULL_IMAGE}"
docker push "${FULL_IMAGE}"
docker push "${AR_REGION}-docker.pkg.dev/${PROJECT}/${AR_REPO}/${IMAGE_NAME}:latest"
echo "Done. Tag for tfvars: ${TAG}"
fi
if [[ $# -gt 0 ]]; then
if [[ "${1}" == "--push" ]]; then
echo "Pushing ${FULL_IMAGE}"
docker push "${FULL_IMAGE}"
docker push "${AR_REGION}-docker.pkg.dev/${PROJECT}/${AR_REPO}/${IMAGE_NAME}:latest"
echo "Done. Tag for tfvars: ${TAG}"
else
echo "Error: Invalid argument '${1}'" >&2
echo "Usage: $0 [--push]" >&2
exit 1
fi
fi

Adds the backend the narratives UI talks to at /agent/chat/stream: a Flask
SSE proxy that runs the MCP tool loop, optional knowledge-base lookup, and
Gemini synthesis, streaming thoughts/text/tool-calls/chart-config back to the
client. Mirrors the agent/ layout from the downstream app.

- agent/mcp_proxy_only.py — the proxy + streaming chat pipeline
- agent/requirements.txt, Dockerfile, build.sh — deploy

API keys are read from Secret Manager / config.json / env at runtime; none are
committed. Comments describe current behavior (no change-history narration).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant