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
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,8 @@ app_data.db

# Don't commit the evaluation data
*.evalset.json
.adk/
.adk/


run-with-google-adk/libs/markdown
run-with-google-adk/libs/markdown-3.8.2.dist-info
85 changes: 47 additions & 38 deletions run-with-google-adk/Makefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
# Makefile for ADK Runbooks project
#

# Environment file
ifneq (,$(wildcard ./agents/google_mcp_security_agent/.env))
include ./agents/google_mcp_security_agent/.env
export
endif
# Environment file is now loaded by the env_manager.py script

# Python executable (use activated venv if available)
PYTHON := python3
Expand All @@ -24,13 +20,14 @@ MANAGE_AGENTS := $(PYTHON) scripts/manage_agents.py
ENV_FILE_PATH := agents/google_mcp_security_agent/.env
ENV_MANAGER := $(PYTHON) scripts/env_manager.py

.PHONY: help env-check env-update config-show oauth-client oauth-uri oauth-link oauth-verify oauth-setup docs agents-list agents-delete multi-agent-setup multi-agent-run multi-agent-web cookiecutter-setup cookiecutter-run cookiecutter-new-agent adk-deploy adk-redeploy agentspace-register agentspace-update agentspace-verify agentspace-delete agentspace-url cloudrun-deploy cloudrun-run cloudrun-test cloudrun-logs cloudrun-url cloudrun-delete gcloud-proxy test-agent
.PHONY: help env-setup env-check env-update config-show oauth-client oauth-uri oauth-link oauth-verify oauth-setup docs agents-list agents-delete multi-agent-setup multi-agent-run multi-agent-web cookiecutter-setup cookiecutter-run cookiecutter-new-agent adk-deploy adk-redeploy agentspace-register agentspace-update agentspace-verify agentspace-delete agentspace-url cloudrun-deploy cloudrun-run cloudrun-test cloudrun-logs cloudrun-url cloudrun-delete gcloud-proxy test-agent

# Put it first so that "make" without argument is like "make help".
help:
@echo "Available targets:"
@echo ""
@echo "Environment Management:"
@echo " env-setup - Create .env from .env.sample"
@echo " env-check - Validate required environment variables"
@echo " config-show - Display current configuration (masks secrets)"
@echo " env-update - Update environment variable"
Expand Down Expand Up @@ -93,6 +90,17 @@ help:
@echo " make agentspace-url"

# Environment Management targets
env-setup:
@echo "Creating .env file from example..."
@if [ -f "agents/google_mcp_security_agent/.env.example" ]; then \
cp agents/google_mcp_security_agent/.env.example agents/google_mcp_security_agent/.env; \
echo "✓ .env file created successfully."; \
echo "Please review and update the values in agents/google_mcp_security_agent/.env"; \
else \
echo "Error: agents/google_mcp_security_agent/.env.example not found."; \
exit 1; \
fi

env-check:
@echo "Checking environment configuration..."
@$(ENV_MANAGER) check --env-file $(ENV_FILE_PATH) --deployment $(or $(DEPLOYMENT),base)
Expand Down Expand Up @@ -244,39 +252,49 @@ endif
@echo "Check the new directory for your agent files."

# ADK deployment targets
adk-deploy: env-validate-agent-engine
adk-deploy:
@# Use agents/google_mcp_security_agent directory if AGENT_DIR not specified
$(eval AGENT_DIR := $(or $(AGENT_DIR),agents/google_mcp_security_agent))
@echo "Deploying agent from directory: $(AGENT_DIR)"
@if [ ! -d "$(AGENT_DIR)" ]; then \
echo "Error: Directory $(AGENT_DIR) does not exist"; \
exit 1; \
fi
@# Install markdown
@echo "Installing markdown..."
@pip install markdown
@# Copy libs to agent directory for deployment
@echo "Copying libs to $(AGENT_DIR)..."
@cp -R libs "$(AGENT_DIR)/"
@# Use environment variables with proper fallbacks
$(eval PROJECT := $(or $(PROJECT),$(GOOGLE_CLOUD_PROJECT)))
$(eval REGION := $(or $(REGION),$(GOOGLE_CLOUD_LOCATION),us-central1))
$(eval PROJECT := $(or $(PROJECT),$(shell grep "^GOOGLE_CLOUD_PROJECT=" $(ENV_FILE_PATH) | cut -d= -f2)))
$(eval REGION := $(or $(REGION),$(shell grep "^GOOGLE_CLOUD_LOCATION=" $(ENV_FILE_PATH) | cut -d= -f2),us-central1))
$(eval DISPLAY_NAME := $(or $(DISPLAY_NAME),$(AGENT_DISPLAY_NAME),"Google Security Agent"))
$(eval STAGING_BUCKET := $(or $(STAGING_BUCKET),$(GCS_STAGING_BUCKET)))
@# Generate staging bucket if not provided
@if [ -z "$(STAGING_BUCKET)" ]; then \
$(eval STAGING_BUCKET := gs://agent-deploy-$(PROJECT)-$(shell date +%Y%m%d-%H%M%S)); \
echo "Generated staging bucket: $(STAGING_BUCKET)"; \
fi
@echo "Deployment configuration:"
@echo " PROJECT: $(PROJECT)"
@echo " REGION: $(REGION)"
@echo " DISPLAY_NAME: $(DISPLAY_NAME)"
@echo " STAGING_BUCKET: $(STAGING_BUCKET)"
@echo " AGENT_DIR: $(AGENT_DIR)"
@echo ""
@echo "Deploying agent to Agent Engine..."
@BUCKET_TO_USE='$(STAGING_BUCKET)'; \
if [ -z "$$BUCKET_TO_USE" ]; then \
BUCKET_TO_USE="gs://agent-deploy-$(PROJECT)-$(shell date +%Y%m%d-%H%M%S)"; \
echo "Generated staging bucket: $$BUCKET_TO_USE"; \
fi; \
echo "Deployment configuration:"; \
echo " PROJECT: $(PROJECT)"; \
echo " REGION: $(REGION)"; \
echo " DISPLAY_NAME: $(DISPLAY_NAME)"; \
echo " STAGING_BUCKET: $$BUCKET_TO_USE"; \
echo " AGENT_DIR: $(AGENT_DIR)"; \
echo ""; \
echo "Deploying agent to Agent Engine..."; \
adk deploy agent_engine \
--project $(PROJECT) \
--region $(REGION) \
--staging_bucket '$(STAGING_BUCKET)' \
--staging_bucket "$$BUCKET_TO_USE" \
--display_name '$(DISPLAY_NAME)' \
$(if $(TRACE),--trace_to_cloud) \
$(AGENT_DIR)
$(AGENT_DIR);
@# Clean up copied libs
@echo "Cleaning up copied libs from $(AGENT_DIR)..."
@rm -rf "$(AGENT_DIR)/libs"
@echo ""
@echo "✓ Agent deployed successfully!"
@echo ""
Expand Down Expand Up @@ -311,22 +329,13 @@ agentspace-url:
# Cloud Run deployment targets
cloudrun-deploy: env-check
@echo "Deploying agent to Cloud Run..."
@# Validate required environment variables
@if [ -z "$(GOOGLE_CLOUD_PROJECT)" ]; then \
echo "Error: GOOGLE_CLOUD_PROJECT not set in .env"; \
exit 1; \
fi
@if [ -z "$(GOOGLE_CLOUD_LOCATION)" ]; then \
echo "Error: GOOGLE_CLOUD_LOCATION not set in .env"; \
exit 1; \
fi
@echo "Deployment configuration:"
@echo " PROJECT: $(GOOGLE_CLOUD_PROJECT)"
@echo " REGION: $(GOOGLE_CLOUD_LOCATION)"
@echo " PROJECT: $$(grep GOOGLE_CLOUD_PROJECT $(ENV_FILE_PATH) | cut -d= -f2)"
@echo " REGION: $$(grep GOOGLE_CLOUD_LOCATION $(ENV_FILE_PATH) | cut -d= -f2)"
@echo " SERVICE: mcp-security-agent-service"
@echo ""
@# Run the deployment script from parent directory
@cd .. && bash ./run-with-google-adk/scripts/cloudrun_deploy_run.sh deploy
@# Run the deployment script
@bash ./scripts/cloudrun_deploy_run.sh deploy
@echo ""
@echo "✓ Cloud Run deployment complete!"
@echo ""
Expand Down Expand Up @@ -359,8 +368,8 @@ cloudrun-test:

cloudrun-logs:
@echo "Viewing Cloud Run logs..."
$(eval PROJECT := $(or $(PROJECT),$(GOOGLE_CLOUD_PROJECT)))
$(eval REGION := $(or $(REGION),$(GOOGLE_CLOUD_LOCATION),us-central1))
$(eval PROJECT := $(or $(PROJECT),$(shell grep "^GOOGLE_CLOUD_PROJECT=" $(ENV_FILE_PATH) | cut -d= -f2)))
$(eval REGION := $(or $(REGION),$(shell grep "^GOOGLE_CLOUD_LOCATION=" $(ENV_FILE_PATH) | cut -d= -f2),us-central1))
$(eval SERVICE := mcp-security-agent-service)
gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=$(SERVICE)" \
--project $(PROJECT) \
Expand Down
15 changes: 3 additions & 12 deletions run-with-google-adk/agents/google_mcp_security_agent/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,13 @@ GOOGLE_API_KEY=NOT_SET
# Model selection
# Gemini API models: https://ai.google.dev/gemini-api/docs/models#model-variations
# Vertex AI models: https://cloud.google.com/vertex-ai/generative-ai/docs/models
GOOGLE_MODEL=gemini-2.0-flash
GOOGLE_MODEL=gemini-2.5-flash

# Default prompt (use single quotes)
DEFAULT_PROMPT='Help user investigate security issues using Google Secops SIEM, SOAR, Security Command Center(SCC) and Google Threat Intel Tools. All authentication actions are automatically approved. If the query is about a SOAR case try to provide a backlink to the user. A backlink is formed by adding /cases/<case id> to this URL when present in field ui_base_link of your input. If the user asks with only ? or are you there? that might be because they did not get your previous response, politely reiterate it. Try to respond in markdown whenever possible.

You also have access tools to perform following file operations - store_file, list_files and get_file_link

store_file - store files on the disk by sending file_name taken from user and markdown string as input - do not reformat the input, it is already markdown.
list_files - Requires no input. Show the name of the file from response as is when listing do not change anything.
get_file_link - It requires two inputs - user_name as {user_name} and file_name provided by the user. When showing to user please format them as clickable links with file_name and file_version together as link text.

The current user name is {user_name}
'
DEFAULT_PROMPT='Help user investigate security issues using Google Secops SIEM, SOAR, Security Command Center(SCC) and Google Threat Intel Tools. All authentication actions are automatically approved. If the query is about a SOAR case try to provide a backlink to the user. A backlink is formed by adding /cases/<case id> to this URL when present in field ui_base_link of your input. If the user asks with only ? or are you there? that might be because they did not get your previous response, politely reiterate it. Try to respond in markdown whenever possible. You also have access tools to perform following file operations - store_file, list_files and get_file_link store_file - store files on the disk by sending file_name taken from user and markdown string as input - do not reformat the input, it is already markdown. list_files - Requires no input. Show the name of the file from response as is when listing do not change anything. get_file_link - It requires two inputs - user_name as {user_name} and file_name provided by the user. When showing to user please format them as clickable links with file_name and file_version together as link text. The current user name is {user_name}'

# Initial timeout for loading tools and dependencies
STDIO_PARAM_TIMEOUT=60.0
STDIO_PARAM_TIMEOUT=6000.0

# Set to true for production environments to reduce log volume
MINIMAL_LOGGING=false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
libs/
82 changes: 71 additions & 11 deletions run-with-google-adk/agents/google_mcp_security_agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
from pathlib import Path
from typing import List, Optional, TextIO

# Add current directory to Python path for local libs
current_dir = Path(__file__).parent
if (current_dir / "libs").exists():
sys.path.insert(0, str(current_dir))

from google.adk.agents.llm_agent import LlmAgent
from google.adk.tools.mcp_tool.mcp_toolset import (
StdioConnectionParams,
Expand All @@ -34,8 +39,15 @@
logging.getLogger().setLevel(logging.ERROR)

# Define base directory and server directory
BASE_DIR = Path(__file__).resolve().parents[2] # Root of run-with-google-adk
SERVER_DIR = BASE_DIR / "server"
# In Cloud Run, the structure is different - server is at /app/server
if os.path.exists("/app/server"):
# Running in Cloud Run container
BASE_DIR = Path("/app")
SERVER_DIR = BASE_DIR / "server"
else:
# Running locally
BASE_DIR = Path(__file__).resolve().parents[2] # Root of run-with-google-adk
SERVER_DIR = BASE_DIR.parent / "server" # Server is in parent directory


def _create_mcp_toolset(
Expand All @@ -47,18 +59,39 @@ def _create_mcp_toolset(
extra_args: Optional[List[str]] = None,
) -> Optional[MCPToolSetWithSchemaAccess]:
"""Helper function to create and configure an MCPToolSet."""
if os.environ.get(f"LOAD_{server_name.upper()}_MCP", "false").lower() != "true":
# Map server names to environment variable names
env_var_mapping = {
"scc": "LOAD_SCC_MCP",
"secops/secops_mcp": "LOAD_SECOPS_MCP",
"gti/gti_mcp": "LOAD_GTI_MCP",
"secops-soar/secops_soar_mcp": "LOAD_SECOPS_SOAR_MCP"
}

load_var = env_var_mapping.get(server_name, f"LOAD_{server_name.upper().replace('/', '_').replace('-', '_')}_MCP")
load_value = os.environ.get(load_var, "false")
logging.info(f"Checking {load_var}: {load_value}")

if load_value.lower() != "true":
logging.info(f"Skipping {server_name} - not enabled")
return None

server_path = SERVER_DIR / server_name
logging.info(f"Looking for server at: {server_path}")
if not server_path.exists():
logging.error(f"Server directory not found: {server_path}")
logging.error(f"SERVER_DIR is: {SERVER_DIR}")
logging.error(f"Contents of parent: {list(SERVER_DIR.parent.iterdir()) if SERVER_DIR.parent.exists() else 'parent does not exist'}")
return None

args = ["--directory", str(server_path), "run"]
if env_file_path.exists():
args.extend(["--env-file", str(env_file_path)])
args.append("server.py")

# Different servers have different entry points
if server_name == "scc":
args.append("scc_mcp.py")
else:
args.append("server.py")
if extra_args:
args.extend(extra_args)

Expand All @@ -76,7 +109,24 @@ def get_all_tools() -> List[MCPToolSetWithSchemaAccess]:
"""Get Tools from All MCP servers."""
logging.info("Attempting to connect to MCP servers...")
timeout = float(os.environ.get("STDIO_PARAM_TIMEOUT", "60.0"))
env_file_path = BASE_DIR / "agents" / "google_mcp_security_agent" / ".env"

# Try different paths for the .env file
possible_env_paths = [
BASE_DIR / "agents" / "google_mcp_security_agent" / ".env",
Path("/tmp/.env"), # Cloud Run creates env file here
Path(".env"),
]

env_file_path = None
for path in possible_env_paths:
if path.exists():
env_file_path = path
logging.info(f"Using env file at: {env_file_path}")
break

if not env_file_path:
logging.warning("No .env file found, using environment variables only")
env_file_path = Path("/tmp/.env") # Use a dummy path

# Required temporarily for https://github.com/google/adk-python/issues/1024
errlog_ae: Optional[TextIO] = sys.stderr
Expand Down Expand Up @@ -106,19 +156,29 @@ def get_all_tools() -> List[MCPToolSetWithSchemaAccess]:
),
]

logging.info("MCP Toolsets created successfully.")
return [ts for ts in toolsets if ts is not None]
valid_toolsets = [ts for ts in toolsets if ts is not None]
logging.info(f"MCP Toolsets created successfully. Found {len(valid_toolsets)} valid toolsets.")
return valid_toolsets


def create_agent() -> LlmAgent:
"""Create and configure the LlmAgent."""
tools = get_all_tools()
logging.info(f"Got {len(tools)} MCP toolsets from get_all_tools()")
tools.extend([store_file, get_file_link, list_files])
logging.info(f"Total tools after adding file tools: {len(tools)}")

# Get model and instruction with defaults
model = os.environ.get("GOOGLE_MODEL", "gemini-2.5-flash")
instruction = os.environ.get(
"DEFAULT_PROMPT",
"Help user investigate security issues using Google Secops SIEM, SOAR, Security Command Center(SCC) and Google Threat Intel Tools."
)

return LlmAgent(
model=os.environ.get("GOOGLE_MODEL"),
model=model,
name="google_mcp_security_agent",
instruction=os.environ.get("DEFAULT_PROMPT"),
instruction=instruction,
tools=tools,
before_model_callback=bmc_trim_llm_request,
before_agent_callback=bac_setup_state_variable,
Expand All @@ -128,10 +188,10 @@ def create_agent() -> LlmAgent:

def main() -> None:
"""Main execution function."""
global root_agent
root_agent = create_agent()
logging.info("Agent created successfully.")


if __name__ == "__main__":
main()

root_agent = create_agent()
6 changes: 5 additions & 1 deletion run-with-google-adk/scripts/cloudrun_deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@
# limitations under the License.

import os
import sys

# Add the run-with-google-adk directory to Python path for libs imports
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "run-with-google-adk"))

import uvicorn
from fastapi import FastAPI
from google.adk.cli.fast_api import get_fast_api_app

# Get the directory where main.py is located
AGENT_DIR = os.path.dirname(os.path.abspath(__file__))+"/run-with-google-adk"
AGENT_DIR = os.path.dirname(os.path.abspath(__file__))+"/run-with-google-adk/agents"
# Example session DB URL (e.g., SQLite)
SESSION_SERVICE_URI = None
if os.environ.get("SESSION_SERVICE","in_memory") == "db":
Expand Down
Loading