Skip to content
Merged
4 changes: 4 additions & 0 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Ensure you have **at least one** of the supported CLI harnesses installed:
* [Gemini CLI](https://geminicli.com/) (*being deprecated soon*)
* [Antigravity CLI (agy)](https://antigravity.google/)
* [Claude Code](https://github.com/anthropics/claude-code)
* [GitHub Copilot CLI](https://docs.github.com/en/copilot/how-tos/copilot-cli)

---

Expand All @@ -34,6 +35,9 @@ just install-geminicli

# Claude Code
just install-claude

# GitHub Copilot CLI
just install-copilot
```

---
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ You also need `python` and `uv` installed.
- **`investigation-entrypoint`**: Primary entrypoint for investigating production outages, orchestrating SRE response, and mitigating incidents. Start here when an incident occurs!
- **`gcp-architecture-discovery`**: Discover and map GCP infrastructure architecture including compute, networking, storage, and service dependencies.
- **`gcp-playbooks`**: Follows established SRE playbooks for GCP/GKE investigations, including infrastructure discovery and common mitigation steps.
- **`gcp-mcp-setup`**: Automates enabling services, Google Managed MCP (OneMCP) servers, generating API keys, and configuring `~/.gemini/settings.json`.
- **`gcp-mcp-setup`**: Automates enabling services, Google Managed MCP (OneMCP) servers, generating API keys, and configuring the MCP settings file for your CLI harness (Gemini, Antigravity, or Copilot).
- **`gcp-slo-management`**: Discover Monitoring Services, list existing SLOs, or create new SLOs (Availability/Latency) via the REST API.
- **`postmortem-generator`**: Creates a generated PostMortem given enough context about a resolved incident/outage.

Expand All @@ -70,7 +70,7 @@ You also need `python` and `uv` installed.
| :----------------------| :----------:| :--------------------------------------------------------:| :--------------------------------------------------------:| :--------------------------------------------------------:| :--------------------------------------------------------:|
| **Type** | Extension | [Plugin](https://antigravity.google/docs/plugins) | [Plugin](https://code.claude.com/docs/en/plugins-reference) | [Plugin](https://developers.openai.com/codex/plugins) | [Plugin](https://docs.github.com/copilot/how-tos/copilot-cli/customize-copilot/plugins-creating) |
| **Install** | ✅ | ✅ | ✅ | 🟢 | ✅ |
| **MCP Setup** | ✅ | ✅ | 🟢 | 🟢 | 🔴 |
| **MCP Setup** | ✅ | ✅ | 🟢 | 🟢 | |
| **SRE Skills** | ✅ | ✅ | 🟢 | 🟢 | ✅ |
| **GKE Investigation** | ✅ | ✅ | 🟢 | 🟢 | 🟢 |

Expand Down
7 changes: 7 additions & 0 deletions skills/gcp-mcp-setup/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

All notable changes to this skill will be documented in this file.

## [0.0.10] - 2026-07-03
### Added
- **GitHub Copilot CLI support**: `setup_onemcp.py` now accepts `--harness {gemini,antigravity,copilot}` and writes the correct MCP config format and path per harness (`~/.copilot/mcp-config.json` using Copilot's `type/url/tools` wire format).
- **Centralized harness registry**: `harness_registry.py` as the single source of truth for all harness configuration (config file paths, MCP entry builders, CLI commands). Both `setup_onemcp.py` and `verify_setup.py` import from it.
- **Typed harness config**: `harness_registry.py` exposes `HarnessName(str, Enum)`, `HarnessCommand(str, Enum)`, and a `HarnessConfig` frozen dataclass with a `.paths(scope)` method.
- **Harness-agnostic MCP verification**: `verify_setup.py` now accepts `--harness {gemini,antigravity,copilot}` and dispatches to the correct CLI command via `HarnessCommand.MCP_LIST`. Config file paths are derived from the registry.

## [0.0.9] - 2026-05-28
### Added
- **Antigravity CLI Support**: Added automated configuration support for the modern `agy` CLI and Antigravity Editor.
Expand Down
5 changes: 5 additions & 0 deletions skills/gcp-mcp-setup/EVAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
- Prompt: "What does "ConnectionPoolExhausted: Unable to acquire aconnection from the pool within the timeout." error mean?"
- Test: Should use MCP to search for this in the DKP knowledge base.

## Copilot CLI Setup

- Prompt: "Please install OneMCP on my project my-gcp-project using Copilot CLI"
- Test: Should run `setup_onemcp.py my-gcp-project --global --harness copilot` and write to `~/.copilot/mcp-config.json` using the `type/url/tools` format.

## Good Skill Structure

<!-- Note: this should be available on EVERY skill, so this is the wrong place.-->
Expand Down
25 changes: 12 additions & 13 deletions skills/gcp-mcp-setup/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
name: gcp-mcp-setup
description: 🐉 [SRE] Use when the user wants to set up Google Managed MCP (OneMCP) servers for their Gemini CLI environment. Automates enabling services, MCP servers, generating API keys, and configuring ~/.gemini/settings.json.
description: 🐉 [SRE] Use when the user wants to set up Google Managed MCP (OneMCP) servers for their CLI environment. Automates enabling services, MCP servers, generating API keys, and configuring the MCP settings file for the active harness (Gemini, Antigravity, or Copilot CLI).
metadata:
author: Riccardo
version: 0.0.9
version: 0.0.10
status: published
---

Expand All @@ -23,16 +23,15 @@ Use this skill when the user asks to "install OneMCP", "setup OneMCP", or "confi
2. **Setup Script Execution**: Run the provided script to enable APIs, MCP services, and generate the required API Key.
- By default, it installs a **Lean SRE toolset** (Logging, Monitoring, GKE, Run, Resource Manager, Developer Knowledge).
- Use the `--all` flag to include optional services (Error Reporting, Databases, Vertex AI).
- Script: `skills/gcp-mcp-setup/scripts/setup_onemcp.py <PROJECT_ID> [--local | --global] [--all] [--google-maps-key YOUR_KEY]`
3. **Configure Settings**: The script automatically writes the `mcpServers` configuration to the correct location depending on which harness/CLI you are running:
- **If your harness is Gemini CLI**: The configuration is injected directly into `.gemini/settings.json` (if `--local` is provided) or `~/.gemini/settings.json` (if `--global` is provided).
- **If your harness is Antigravity CLI (agy)**: The configuration is written to both `.gemini/antigravity/mcp_config.json` and `.gemini/config/mcp_config.json` (if `--local` is provided) or the global equivalents under `~/.gemini/` (if `--global` is provided).
- Use `--harness` to target a specific CLI harness (`gemini`, `antigravity`, or `copilot`). Defaults to `gemini+antigravity`.
- Script: `skills/gcp-mcp-setup/scripts/setup_onemcp.py <PROJECT_ID> [--local | --global] [--harness {gemini,antigravity,copilot}] [--all] [--google-maps-key YOUR_KEY]`
3. **Configure Settings**: The script automatically writes the `mcpServers` configuration to the correct location depending on the target harness:
- **Gemini CLI**: `.gemini/settings.json` (local) or `~/.gemini/settings.json` (global).
- **Antigravity CLI (agy)**: `.gemini/antigravity/mcp_config.json` and `.gemini/config/mcp_config.json` (local) or the global equivalents under `~/.gemini/`.
- **GitHub Copilot CLI**: `~/.copilot/mcp-config.json` (always global — no workspace-level config yet).
4. **Verification & Diagnostics:**
- Script: `skills/gcp-mcp-setup/scripts/verify_setup.py`
- This script checks the configured settings across both Gemini and Antigravity paths, and tests actual endpoint connectivity.
- For direct endpoint diagnostics, use the curl-based tool:
- Script: `skills/gcp-mcp-setup/scripts/test_mcp_endpoint.sh [ENDPOINT_URL]`
- Example: `./skills/gcp-mcp-setup/scripts/test_mcp_endpoint.sh https://monitoring.googleapis.com/mcp`
- Script: `skills/gcp-mcp-setup/scripts/verify_setup.py [--harness {gemini,antigravity,copilot}]`
- This script checks identity consistency (gcloud vs ADC), runs the harness-specific MCP list command, and confirms all expected google-* servers are registered.

## Examples

Expand All @@ -45,8 +44,8 @@ Note that the **gcloud CLI Identity** (how you are currently logged in via `gclo

MCP servers typically use ADC to authenticate. If these identities do not match, you may encounter permission errors (e.g., `serviceusage.serviceUsageConsumer` denied) even if `gcloud` commands work correctly.

If MCP doesn't work, yhou can poropose to run this on user's behalf: `gcloud auth application-default login --account <PROPOSED_IDENTITY_EMAIL>`
If MCP doesn't work, you can propose to run this on user's behalf: `gcloud auth application-default login --account <PROPOSED_IDENTITY_EMAIL>`

### 💡 Path Forward

If you encounter an identity mismatch:
Expand Down
93 changes: 93 additions & 0 deletions skills/gcp-mcp-setup/scripts/harness_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#!/usr/bin/env python3
"""Central harness registry for the gcp-mcp-setup skill.

Single source of truth for:
- MCP config file paths (global and local) per harness
- MCP server entry format builders per harness
- CLI commands per harness (keyed by HarnessCommand enum)

To add a new harness, add one HarnessConfig entry to HARNESS_REGISTRY below and to the HarnessName enum.
"""

from dataclasses import dataclass
from enum import Enum
from typing import Callable


class HarnessCommand(str, Enum):
"""Abstract command names supported across harnesses.
"""
MCP_LIST = "mcp_list"


class HarnessName(str, Enum):
"""Known CLI harness identifiers.
"""
GEMINI = "gemini"
ANTIGRAVITY = "antigravity"
COPILOT = "copilot"


@dataclass(frozen=True)
class HarnessConfig:
"""Typed configuration for a single CLI harness."""
global_paths: list[str]
local_paths: list[str]
builder: Callable[..., dict]
commands: dict[HarnessCommand, list[str]]

def paths(self, scope: str) -> list[str]:
"""Returns config file paths for the given scope ('global' or 'local')."""
return self.global_paths if scope == "global" else self.local_paths

def get_command(self, key: "HarnessCommand") -> list[str]:
"""Returns the command list for the given key.
Raises ValueError if the key is absent, None, or maps to an empty list.
"""
cmd = self.commands.get(key)
if not cmd:
raise ValueError(f"No command defined for '{key}' in this harness config.")
return cmd


def _gemini_mcp_format(url, *, project_id=None, api_key=None):
"""Returns an MCP server entry in the Gemini/Antigravity format."""
if api_key:
return {"httpUrl": url, "serverUrl": url, "headers": {"X-Goog-Api-Key": api_key}}
return {
"httpUrl": url,
"serverUrl": url,
"authProviderType": "google_credentials",
"oauth": {"scopes": ["https://www.googleapis.com/auth/cloud-platform"]},
"headers": {"X-goog-user-project": project_id},
}


def _copilot_mcp_format(url, *, project_id=None, api_key=None):
"""Returns an MCP server entry in the Copilot CLI format."""
headers = {"X-Goog-Api-Key": api_key} if api_key else {"X-goog-user-project": project_id}
return {"type": "http", "url": url, "headers": headers, "tools": ["*"]}


HARNESS_REGISTRY: dict[HarnessName, HarnessConfig] = {
HarnessName.GEMINI: HarnessConfig(
global_paths=["~/.gemini/settings.json"],
local_paths=[".gemini/settings.json"],
builder=_gemini_mcp_format,
commands={HarnessCommand.MCP_LIST: ["gemini", "-p", "/mcp list"]},
),
HarnessName.ANTIGRAVITY: HarnessConfig(
global_paths=["~/.gemini/antigravity/mcp_config.json", "~/.gemini/config/mcp_config.json"],
local_paths=[".gemini/antigravity/mcp_config.json", ".gemini/config/mcp_config.json"],
builder=_gemini_mcp_format,
commands={HarnessCommand.MCP_LIST: ["agy", "-p", "/mcp list"]},
),
HarnessName.COPILOT: HarnessConfig(
# Copilot CLI has no workspace-level MCP config; both scopes resolve to
# the same global path, so --local and --global behave identically.
global_paths=["~/.copilot/mcp-config.json"],
local_paths=["~/.copilot/mcp-config.json"],
builder=_copilot_mcp_format,
commands={HarnessCommand.MCP_LIST: ["copilot", "mcp", "list"]},
),
}
88 changes: 49 additions & 39 deletions skills/gcp-mcp-setup/scripts/setup_onemcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
"""
OneMCP Setup Script
This script sets the gcloud project, installs beta components, enables required services
and MCP servers.
and MCP servers.

By default, it installs a lean SRE toolset (Logging, Monitoring, GKE, Run, RM, ErrorReporting, DK).
Use --all to include databases (SQL, Spanner, Firestore, BigQuery) and Vertex AI.

Use --harness to target a specific CLI harness (gemini, antigravity, copilot).
Defaults to gemini+antigravity when omitted.

# Thanks to Romin Irani guidance: https://github.com/rominirani/google-mcp-servers/blob/main/demos/README.md
"""

Expand All @@ -32,39 +35,60 @@
import sys
import time

sys.path.insert(0, os.path.dirname(__file__))
from harness_registry import HARNESS_REGISTRY, HarnessName

# On Windows, the default encoding is cp1252 which cannot encode emojis. Use UTF-8 to avoid encoding errors.
if sys.stdout.encoding.lower() != 'utf-8':
sys.stdout.reconfigure(encoding='utf-8')
if sys.stderr.encoding.lower() != 'utf-8':
sys.stderr.reconfigure(encoding='utf-8')


def run_command(command, check=True):
print(f"Running: {' '.join(command)}")
result = subprocess.run(command, text=True, capture_output=True)
result = subprocess.run(command, text=True, encoding='utf-8', capture_output=True, shell=(sys.platform == "win32"))
if check and result.returncode != 0:
print(f"Command failed with error: {result.stderr}", file=sys.stderr)
sys.exit(result.returncode)
return result

def main():
parser = argparse.ArgumentParser(
description="Set up Google Managed MCP (OneMCP) for Gemini CLI."
description="Set up Google Managed MCP (OneMCP) for your CLI harness."
)
parser.add_argument("project_id", help="The Google Cloud Project ID to use.")

group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--local", action="store_true", help="Update the local .gemini/settings.json file.")
group.add_argument("--global", action="store_true", dest="global_config", help="Update the global ~/.gemini/settings.json file.")

group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--local", action="store_true", help="Update the local config file (workspace-scoped).")
group.add_argument("--global", action="store_true", dest="global_config", help="Update the global config file (user-scoped).")

parser.add_argument(
"--harness",
choices=[h.value for h in HarnessName],
default=None,
help="Target CLI harness to configure (gemini, antigravity, copilot). Defaults to gemini+antigravity.",
)
parser.add_argument("--all", action="store_true", help="Enable all supported OneMCP servers, including databases and Vertex AI.")
parser.add_argument("--google-maps-key", dest="google_maps_key", help="The Google Maps API Key to enable mapstools MCP.")

args = parser.parse_args()
project_id = args.project_id
scope = "local" if args.local else "global"

target_files = []
if args.local:
target_files.append((os.path.join(os.getcwd(), ".gemini", "settings.json"), "gemini"))
target_files.append((os.path.join(os.getcwd(), ".gemini", "antigravity", "mcp_config.json"), "antigravity"))
target_files.append((os.path.join(os.getcwd(), ".gemini", "config", "mcp_config.json"), "antigravity"))
# Resolve target harnesses: explicit selection or default to gemini+antigravity.
if args.harness:
selected_harnesses = [args.harness]
else:
target_files.append((os.path.expanduser("~/.gemini/settings.json"), "gemini"))
target_files.append((os.path.expanduser("~/.gemini/antigravity/mcp_config.json"), "antigravity"))
target_files.append((os.path.expanduser("~/.gemini/config/mcp_config.json"), "antigravity"))
selected_harnesses = [HarnessName.GEMINI, HarnessName.ANTIGRAVITY]

# Resolve target config files from the registry.
target_files = []
for harness in selected_harnesses:
harness_config = HARNESS_REGISTRY[harness]
for path in harness_config.paths(scope):
resolved = os.path.expanduser(path) if path.startswith("~") else os.path.join(os.getcwd(), path)
target_files.append((resolved, harness_config.builder))

# Core SRE Services (Default)
base_services = [
Expand Down Expand Up @@ -135,36 +159,23 @@ def main():
'bigquery.googleapis.com': ('google-bigquery', 'https://bigquery.googleapis.com/mcp'),
}

def build_mcp_servers(target_type):
def build_mcp_servers(builder):
servers = {}
for service in base_services:
if service == 'developerknowledge.googleapis.com':
cfg = {
'httpUrl': 'https://developerknowledge.googleapis.com/mcp',
'serverUrl': 'https://developerknowledge.googleapis.com/mcp',
'headers': {'X-Goog-Api-Key': dev_key}
}
servers['google-developer-knowledge'] = cfg
servers['google-developer-knowledge'] = builder(
'https://developerknowledge.googleapis.com/mcp', api_key=dev_key
)
elif service == 'mapstools.googleapis.com':
cfg = {
'httpUrl': 'https://mapstools.googleapis.com/mcp',
'serverUrl': 'https://mapstools.googleapis.com/mcp',
'headers': {'X-Goog-Api-Key': args.google_maps_key}
}
servers['google-maps'] = cfg
servers['google-maps'] = builder(
'https://mapstools.googleapis.com/mcp', api_key=args.google_maps_key
)
elif service in mcp_config_map:
key, url = mcp_config_map[service]
cfg = {
'httpUrl': url,
'serverUrl': url,
'authProviderType': 'google_credentials',
'oauth': {'scopes': ['https://www.googleapis.com/auth/cloud-platform']},
'headers': {'X-goog-user-project': project_id}
}
servers[key] = cfg
servers[key] = builder(url, project_id=project_id)
return servers

for config_file, target_type in target_files:
for config_file, builder in target_files:
print(f"Updating {config_file}...")
os.makedirs(os.path.dirname(config_file), exist_ok=True)

Expand All @@ -179,8 +190,7 @@ def build_mcp_servers(target_type):
if 'mcpServers' not in data:
data['mcpServers'] = {}

# Merge tailored servers
data['mcpServers'].update(build_mcp_servers(target_type))
data['mcpServers'].update(build_mcp_servers(builder))

with open(config_file, 'w') as f:
json.dump(data, f, indent=2)
Expand Down
Loading
Loading