gam-mcp
by UnopndJude
README.md
# gam-mcp
> A security-first MCP server for **Google Ad Manager** — least-privilege by default, gated writes, and a path to per-user OAuth that existing GAM MCP servers don't offer.
*(한국어 설명은 아래에 있습니다 / Korean version below.)*
---
## English
### What this is
`gam-mcp` is a self-hosted [Model Context Protocol](https://modelcontextprotocol.io) server that lets an AI client (Claude Code, Claude Desktop, etc.) read and — with explicit confirmation — write **Google Ad Manager** data through the [Ad Manager API (Beta) REST](https://developers.google.com/ad-manager/api/beta).
It is built for an enterprise posture where billions in ad spend are at stake: the service-account key and access tokens stay **inside the server process** and are never exposed to the model, reads and writes are separated by OAuth scope, and every mutation requires a deliberate two-step confirmation.
### Why another GAM MCP server? (the gap)
Ad Manager MCP servers already exist, but they share one architectural choice that doesn't fit a security-sensitive, multi-user org:
| Project | Auth model | Transport | Gap for enterprise use |
|---|---|---|---|
| [MatiousCorp/google-ad-manager-mcp](https://github.com/MatiousCorp/google-ad-manager-mcp) | **Single shared service account** (`GAM_CREDENTIALS_PATH`), HTTP bearer token | stdio + HTTP | One "master key" for the whole network; every action attributes to the service account, so **per-user accountability is lost** |
| [OrbiAds/Orbiads-GAM-MCP](https://github.com/OrbiAds/Orbiads-GAM-MCP) | Vendor-hosted managed proxy | Hosted HTTP | Credentials are **entrusted to a third-party vendor** |
| [googleads/google-ads-mcp](https://github.com/googleads/google-ads-mcp) (official) | **Per-user OAuth**, on-behalf-of | stdio + Cloud Run | For **Google Ads (advertiser)**, not **Google Ad Manager (publisher)** — different product/API |
In short: the per-user OAuth *on-behalf-of* pattern — where each call runs against the caller's own GAM permissions and stays fully auditable — **exists for Google Ads but not for Google Ad Manager.** Every off-the-shelf GAM MCP server is a shared-service-account ("master key") design.
**`gam-mcp` fills that gap.** It starts as a least-privilege single-user server and is deliberately structured (transport- and auth-agnostic) to grow into per-user OAuth on-behalf-of for central deployment — the model the existing GAM servers don't provide.
### Security model
| Layer | Control | Where |
|---|---|---|
| 1 | **Least-privilege token** — the read-only OAuth scope (`admanager.readonly`) is requested when `GAM_READ_ONLY=true`, so a bug can't mutate GAM at Google's side. | `src/gam/client.ts` |
| 2 | **Read-only kill-switch** — write tools aren't even registered. | `src/index.ts` |
| 3 | **Network allowlist** — every tool re-checks the network code against `GAM_NETWORK_ALLOWLIST`. | `src/config.ts` |
| 4 | **Propose → confirm gate** — `propose_*` returns a short-lived, single-use token plus a before/after preview; nothing mutates until `confirm_write`. | `src/security.ts` |
| 5 | **Append-only audit log** — every call is recorded with redacted args before the result returns. | `src/audit.ts` |
The service-account key and access tokens live in this process only and are never placed in a tool result. Claude Code's own permission prompt gates each tool call as a separate, outer layer.
### Authentication modes
The client is auth-agnostic (`AuthStrategy`), so the same tools work under any of:
- **`service_account`** — one shared identity. Simplest for a local, single-operator setup. This is the "master key" model every off-the-shelf GAM MCP server uses.
- **`user_oauth`** — **per-user identity.** Each operator runs `npm run login` once to authorize with their own Google account; the server then calls GAM **as that user**, so their own GAM role bounds every action and the audit trail attributes to a real person. Works over stdio today. Only a refresh token (never an access token) is cached, at `GAM_TOKEN_STORE`.
- **OBO (roadmap)** — central multi-tenant on-behalf-of, where an incoming authenticated user maps to their stored per-user credential. Requires the HTTP transport; the `AuthStrategy` interface is the drop-in point.
Set the mode with `GAM_AUTH_MODE`. For `user_oauth`:
```bash
GAM_AUTH_MODE=user_oauth
GAM_OAUTH_CLIENT_ID=xxxx.apps.googleusercontent.com
GAM_OAUTH_CLIENT_SECRET=xxxx
npm run build && npm run login # opens a browser, caches the refresh token
```
### Tools
**Read** (always on): `get_network`, `list_orders`, `get_order`, `list_line_items`, `get_line_item`, `run_report`.
**Write** (only when `GAM_READ_ONLY=false`): `propose_update_line_item` → `confirm_write`.
### Prerequisites (one-time, in the GAM + GCP consoles)
Shared, both modes:
1. **Enable the API** — GAM → Admin → Global settings → Network settings → turn on **API access**. Note your **network code** (the number in `admanager.google.com/<code>#home`).
2. **GCP project** — enable the *Google Ad Manager API*.
Then, depending on `GAM_AUTH_MODE`:
- **`service_account`** — create a service account, download a JSON key (never commit it), and add the service-account email as a GAM user with a least-privilege role (start read-only).
- **`user_oauth`** — create an **OAuth 2.0 Client ID (type: Desktop app)** and configure the consent screen. No key file and no extra GAM user needed — each operator just has to already be a GAM user. Run `npm run login` to authorize.
### Install
```bash
npm install
cp .env.example .env # fill in credentials path + network code(s)
npm run build
```
### Configure (`.env`)
```bash
GOOGLE_APPLICATION_CREDENTIALS=/absolute/path/to/service-account.key.json
GAM_NETWORK_ALLOWLIST=1234567
GAM_READ_ONLY=true # flip to false only after validating reads
```
### Register with Claude Code / Claude Desktop
```json
{
"mcpServers": {
"gam": {
"command": "node",
"args": ["/absolute/path/to/gam-mcp/dist/index.js"],
"env": {
"GOOGLE_APPLICATION_CREDENTIALS": "/absolute/path/to/service-account.key.json",
"GAM_NETWORK_ALLOWLIST": "1234567",
"GAM_READ_ONLY": "true"
}
}
}
}
```
### API surface & gaps
Uses the **Ad Manager API (Beta) REST** (`https://admanager.googleapis.com/v1`). Not every mutating operation exists in Beta yet — some remain SOAP-only. Where a write endpoint isn't live, `confirm_write` surfaces the GAM error verbatim; a SOAP fallback is future work.
### Roadmap
- [x] Per-user OAuth identity for local/stdio (`GAM_AUTH_MODE=user_oauth`)
- [ ] Central multi-tenant on-behalf-of (needs HTTP transport + IdP)
- [ ] Streamable HTTP transport
- [ ] SOAP fallback for operations not yet in Beta REST
- [ ] Rate limiting and budget/impression caps on writes
---
## 한국어
### 개요
`gam-mcp`는 AI 클라이언트(Claude Code, Claude Desktop 등)가 [Ad Manager API (Beta) REST](https://developers.google.com/ad-manager/api/beta)를 통해 **Google Ad Manager** 데이터를 읽고, 명시적 확인을 거쳐 쓰도록 해주는 self-hosted [MCP](https://modelcontextprotocol.io) 서버입니다.
수십억 규모의 광고 집행을 다루는 보안 민감 환경을 전제로 설계했습니다 — 서비스 계정 키와 액세스 토큰은 **서버 프로세스 안에만** 존재하며 모델에 노출되지 않고, 읽기/쓰기는 OAuth scope로 분리되며, 모든 변경은 2단계 확인을 거쳐야 합니다.
### 왜 또 다른 GAM MCP 서버인가? (채우는 공백)
Ad Manager용 MCP 서버는 이미 있지만, 전부 보안 민감·다중 사용자 조직에 맞지 않는 하나의 설계를 공유합니다:
| 프로젝트 | 인증 모델 | 전송 | 엔터프라이즈 관점의 공백 |
|---|---|---|---|
| [MatiousCorp/google-ad-manager-mcp](https://github.com/MatiousCorp/google-ad-manager-mcp) | **단일 공유 서비스 계정** + HTTP bearer | stdio + HTTP | 네트워크 전체가 하나의 "마스터 키" → 모든 액션이 서비스 계정에 귀속되어 **사용자별 추적성 상실** |
| [OrbiAds/Orbiads-GAM-MCP](https://github.com/OrbiAds/Orbiads-GAM-MCP) | 벤더 호스팅 관리형 프록시 | 호스티드 HTTP | 자격증명을 **외부 벤더에 위탁** |
| [googleads/google-ads-mcp](https://github.com/googleads/google-ads-mcp) (공식) | **per-user OAuth**, on-behalf-of | stdio + Cloud Run | **Google Ads(광고주)** 용, **Ad Manager(퍼블리셔)** 아님 — 다른 제품/API |
요약하면, 각 호출이 호출자 본인의 GAM 권한으로 실행되고 완전히 감사 가능한 **per-user OAuth on-behalf-of** 패턴은 **Google Ads에는 있지만 Google Ad Manager에는 없습니다.** 기성 GAM MCP 서버는 전부 공유 서비스 계정("마스터 키") 방식입니다.
**`gam-mcp`가 그 공백을 메웁니다.** 최소 권한 단일 사용자 서버로 시작하되, 중앙 배포용 per-user OAuth on-behalf-of로 확장할 수 있도록 transport·auth-agnostic하게 설계했습니다 — 기존 GAM 서버가 제공하지 않는 모델입니다.
### 보안 모델
| 계층 | 통제 | 위치 |
|---|---|---|
| 1 | **최소 권한 토큰** — `GAM_READ_ONLY=true`면 읽기 전용 scope(`admanager.readonly`)만 발급 → 버그가 있어도 Google 쪽에서 변경 불가 | `src/gam/client.ts` |
| 2 | **읽기 전용 kill-switch** — 쓰기 도구를 아예 등록하지 않음 | `src/index.ts` |
| 3 | **네트워크 allowlist** — 모든 도구가 네트워크 코드를 재검증 | `src/config.ts` |
| 4 | **propose → confirm 게이트** — `propose_*`는 단회성·만료 토큰 + before/after 미리보기만 반환, `confirm_write` 전엔 변경 없음 | `src/security.ts` |
| 5 | **append-only 감사 로그** — 모든 호출을 redaction 후 기록 | `src/audit.ts` |
키·토큰은 서버 프로세스에만 존재하고 도구 결과에 절대 담기지 않습니다. Claude Code 자체 permission 프롬프트가 별도의 바깥 계층으로 각 호출을 게이트합니다.
### 인증 모드
클라이언트가 auth-agnostic(`AuthStrategy`)이라 아래 어느 방식이든 같은 도구가 동작합니다:
- **`service_account`** — 단일 공유 신원. 로컬 단일 운영자에게 가장 단순. 기성 GAM MCP가 모두 쓰는 "마스터 키" 모델.
- **`user_oauth`** — **사용자별 신원.** 각 운영자가 `npm run login`으로 자기 Google 계정을 1회 인증하면, 서버가 **그 사용자로서** GAM을 호출 → 본인의 GAM role이 모든 액션의 한도가 되고 감사 로그가 실제 사람에게 귀속됩니다. 지금 stdio에서 동작하며, refresh token만(액세스 토큰은 절대 저장 안 함) `GAM_TOKEN_STORE`에 캐시됩니다.
- **OBO (로드맵)** — 중앙 멀티테넌트 on-behalf-of. 인증된 사용자를 그의 저장된 자격증명에 매핑. HTTP transport 필요하며, `AuthStrategy` 인터페이스가 그 확장 지점입니다.
`GAM_AUTH_MODE`로 선택. `user_oauth` 예:
```bash
GAM_AUTH_MODE=user_oauth
GAM_OAUTH_CLIENT_ID=xxxx.apps.googleusercontent.com
GAM_OAUTH_CLIENT_SECRET=xxxx
npm run build && npm run login # 브라우저 인증 → refresh token 캐시
```
### 도구
**읽기**(상시): `get_network`, `list_orders`, `get_order`, `list_line_items`, `get_line_item`, `run_report`.
**쓰기**(`GAM_READ_ONLY=false`일 때만): `propose_update_line_item` → `confirm_write`.
### 사전 준비 (GAM + GCP 콘솔, 1회)
공통 (두 모드 모두):
1. **API 활성화** — GAM → Admin → Global settings → Network settings → **API access** 켜기. **네트워크 코드**(`admanager.google.com/<코드>#home`의 숫자) 확인.
2. **GCP 프로젝트** — *Google Ad Manager API* 활성화.
그다음 `GAM_AUTH_MODE`에 따라:
- **`service_account`** — 서비스 계정 생성 → JSON 키 다운로드(커밋 금지) → 서비스 계정 이메일을 GAM 사용자로 추가하고 최소 권한 role 부여(읽기 전용으로 시작).
- **`user_oauth`** — **OAuth 2.0 Client ID(유형: 데스크톱 앱)** 생성 + consent 화면 설정. 키 파일·추가 GAM 사용자 불필요 — 각 운영자가 이미 GAM 사용자이기만 하면 됨. `npm run login`으로 인증.
### 설치 · 설정 · 등록
위 English 섹션의 명령/설정과 동일합니다.
### API 범위와 한계
**Ad Manager API (Beta) REST**(`https://admanager.googleapis.com/v1`)를 사용합니다. 일부 변경 작업은 아직 Beta에 없고 SOAP 전용입니다. REST 엔드포인트가 없는 경우 `confirm_write`가 GAM 오류를 그대로 전달하며, SOAP 폴백은 향후 과제입니다.
---
## License
MIT
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues