Remote deployment wrapper for digital ads MCP servers. Currently supports:
- Google Ads MCP →
/googleads/mcp - Google Analytics MCP →
/analytics/mcp - Facebook Ads MCP →
/facebookads/mcp
- HTTP JSON-RPC Transport: Cloud-native HTTP-based MCP server
- Stateless Architecture: No session storage, scales to zero
- API Key Authentication: Validate clients via API keys
- Per-Request Credentials: Users provide their own platform-specific credentials (e.g., Google Ads developer tokens)
- Multi-Cloud Ready: Deploy to any container platform
- Auto-Updates: Uses git submodule to track upstream changes
This repository wraps digital ads MCP servers (such as google-ads-mcp) as git submodules and adds:
- Remote server transport (HTTP JSON-RPC)
- Authentication layer (API keys)
- Credential injection (per-request platform credentials)
- Container deployment configuration
# Clone with submodule
git clone --recurse-submodules <your-repo-url>
cd digital-ads-remote-mcp
# Build Docker image
docker build -t digital-ads-remote-mcp .
# Run locally for testing
docker run -p 8080:8080 \
-e ALLOWED_API_KEYS="test-key-1,test-key-2" \
-e GOOGLE_APPLICATION_CREDENTIALS="/path/to/credentials.json" \
-v /path/to/credentials.json:/path/to/credentials.json:ro \
digital-ads-remote-mcp# Build and push to Google Container Registry
gcloud builds submit --tag gcr.io/PROJECT_ID/digital-ads-remote-mcp
# Deploy to Cloud Run
gcloud run deploy digital-ads-remote-mcp \
--image=gcr.io/PROJECT_ID/digital-ads-remote-mcp \
--platform=managed \
--region=us-central1 \
--allow-unauthenticated \
--set-env-vars=ALLOWED_API_KEYS="key1,key2"# Build and push to ECR
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com
docker build -t digital-ads-remote-mcp .
docker tag digital-ads-remote-mcp:latest ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/digital-ads-remote-mcp:latest
docker push ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/digital-ads-remote-mcp:latest
# Deploy via AWS CLI (ensure task definition and service exist)
aws ecs update-service \
--cluster your-cluster \
--service digital-ads-remote-mcp \
--force-new-deployment# Build and push to Azure Container Registry
az acr build --registry YOUR_ACR_NAME --image digital-ads-remote-mcp:latest .
# Deploy to Azure Container Apps
az containerapp create \
--name digital-ads-remote-mcp \
--resource-group YOUR_RESOURCE_GROUP \
--environment YOUR_CONTAINER_APP_ENV \
--image YOUR_ACR_NAME.azurecr.io/digital-ads-remote-mcp:latest \
--target-port 8080 \
--ingress external \
--env-vars ALLOWED_API_KEYS="key1,key2"Environment variables can be encrypted using ECIES (Elliptic Curve Integrated Encryption Scheme) with secp256k1.
# Install eciespy (uses coincurve under the hood)
pip install eciespy
# Generate a new key pair
python3 -c "
from coincurve import PrivateKey
import secrets
sk = PrivateKey(secrets.token_bytes(32))
print(f'Private key (keep secret): {sk.secret.hex()}')
print(f'Public key (for clients): {sk.public_key.format(False).hex()}')
"
# Derive public key from an existing hex private key
python3 -c "
from coincurve import PrivateKey
private_key_hex = 'YOUR_PRIVATE_KEY_HEX'
sk = PrivateKey(bytes.fromhex(private_key_hex))
print(sk.public_key.format(False).hex())
"# Encrypt a string
python3 -c "
import ecies
import base64
import sys
public_key = 'YOUR_PUBLIC_KEY_HEX'
plaintext = sys.argv[1]
ciphertext = ecies.encrypt(public_key, plaintext.encode())
print(base64.urlsafe_b64encode(ciphertext).decode().rstrip('='))
" "your-secret-value"
# Encrypt a file
python3 -c "
import ecies
import base64
import sys
public_key = 'YOUR_PUBLIC_KEY_HEX'
with open(sys.argv[1], 'rb') as f:
content = f.read()
ciphertext = ecies.encrypt(public_key, content)
print(base64.urlsafe_b64encode(ciphertext).decode().rstrip('='))
" credentials.jsonEnvironment variables are passed via query parameters with type prefixes:
| Prefix | Description | Example |
|---|---|---|
PLAIN_ |
Plaintext value | ?PLAIN_FOO=bar → FOO=bar |
FILE_ |
Content written to temp file | ?FILE_CREDS={} → CREDS=/tmp/xxx.json |
ENC_ |
Encrypted value (base64url) | ?ENC_TOKEN=abc... → TOKEN=decrypted |
ENCFILE_ |
Encrypted content to temp file | ?ENCFILE_CREDS=abc... → CREDS=/tmp/xxx.json |
{
"mcpServers": {
"google-ads": {
"url": "https://your-service-url/googleads/mcp?api_key=your-api-key&ENC_GOOGLE_ADS_DEVELOPER_TOKEN=abc123...&PLAIN_GOOGLE_ADS_LOGIN_CUSTOMER_ID=1234567890",
"transport": "http"
},
"google-analytics": {
"url": "https://your-service-url/analytics/mcp?api_key=your-api-key&ENCFILE_GOOGLE_APPLICATION_CREDENTIALS=xyz789...",
"transport": "http"
},
"facebook-ads": {
"url": "https://your-service-url/facebookads/mcp?api_key=your-api-key&ENC_FB_ACCESS_TOKEN=abc123...",
"transport": "http"
}
}
}Google Ads MCP (/googleads/mcp):
GOOGLE_ADS_DEVELOPER_TOKEN: Your Google Ads API developer tokenGOOGLE_ADS_LOGIN_CUSTOMER_ID: (Optional) Manager customer IDGOOGLE_APPLICATION_CREDENTIALS: (Optional) Path to service account JSON (useFILE_orENCFILE_prefix)
Google Analytics MCP (/analytics/mcp):
GOOGLE_APPLICATION_CREDENTIALS: Path to service account JSON (useFILE_orENCFILE_prefix)GOOGLE_PROJECT_ID: Google Cloud project IDANALYTICS_PROPERTY_ID: (Optional) Default GA4 property ID
Facebook Ads MCP (/facebookads/mcp):
FB_ACCESS_TOKEN: Facebook User Access Token withads_readpermission (required)
The MCP servers are included as git submodules. To update:
# Update all submodules to latest version
git submodule update --remote
# Commit the updates
git add google-ads-mcp google-analytics-mcp facebook-ads-mcp
git commit -m "Update MCP submodules to latest"
# Redeploy to your platformServer-side environment variables:
ALLOWED_API_KEYS: Comma-separated list of valid API keysECIES_PRIVATE_KEY: Hex-encoded secp256k1 private key for decryptingENC_*andENCFILE_*parameters
- API Keys: Store in your platform's secret manager, rotate regularly
- Developer Tokens: Never stored server-side, passed per-request
- HTTPS Only: Ensure your platform enforces HTTPS
- Service Account: Use minimal permissions for Google Ads API access
- CORS: Configure
allow_originsin production
The server speaks the MCP Streamable HTTP transport at POST /{service}/mcp.
Session state is tracked via the Mcp-Session-Id response/request header:
- First request (
initialize) is sent without a session header. Env-injection query params (PLAIN_*,FILE_*,ENC_*,ENCFILE_*) are read here and configure the spawned stdio subprocess. - The server returns an
Mcp-Session-Idheader; all subsequent requests for that session MUST include it. DELETE /{service}/mcpwith the header closes the session.
Responses are returned as text/event-stream; the final event carries the
JSON-RPC response. Notification-only requests return 202 Accepted.
# Health check
curl https://your-service-url/health
# Initialize (captures the Mcp-Session-Id from response headers)
SID=$(curl -sD - -o /dev/null \
-X POST "https://your-service-url/googleads/mcp?api_key=your-api-key&PLAIN_GOOGLE_ADS_DEVELOPER_TOKEN=your-dev-token" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}' \
| awk 'tolower($1)=="mcp-session-id:"{print $2}' | tr -d '\r')
# Subsequent calls reuse the session id via the header
curl -X POST "https://your-service-url/googleads/mcp?api_key=your-api-key" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: $SID" \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
curl -X POST "https://your-service-url/googleads/mcp?api_key=your-api-key" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Mcp-Session-Id: $SID" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
# Close the session
curl -X DELETE "https://your-service-url/googleads/mcp?api_key=your-api-key" \
-H "Mcp-Session-Id: $SID"- 401 Unauthorized: Check
api_keyquery param matchesALLOWED_API_KEYS - 400 Invalid params: Check encryption/decryption - ensure
ECIES_PRIVATE_KEYis set - 500 Internal Error: Check container logs for MCP or API errors
Apache 2.0 / MIT (same as upstream MCP servers: google-ads-mcp, google-analytics-mcp, facebook-ads-mcp)