Skip to main content
Glama
martechery

Google Ads MCP Server

by martechery

set_session_credentials

Establish a session with Google Ads credentials in multi-tenant mode to enable API interactions for managing campaigns, retrieving performance data, and executing queries.

Instructions

Establish a session with Google Ads credentials (multi-tenant mode only).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
session_keyYesUUID v4 session key
google_credentialsYes

Implementation Reference

  • Executes the tool logic: checks multi-tenant mode, validates session_key, establishes session with credentials via establishSession helper, optionally verifies Ads API scope, logs telemetry events, returns JSON success or structured error.
      const startTs = Date.now();
      if (process.env.ENABLE_RUNTIME_CREDENTIALS !== 'true') {
        const out = { content: [{ type: 'text', text: 'Multi-tenant mode not enabled' }] };
        logEvent('set_session_credentials', startTs, { requestId: input?.request_id, error: { code: 'ERR_NOT_ENABLED', message: 'Multi-tenant mode not enabled' } });
        return out;
      }
      try {
        validateSessionKey(String(input?.session_key || ''));
      } catch (e: any) {
        const msg = e?.message || String(e);
        logEvent('set_session_credentials', startTs, { sessionKey: input?.session_key, requestId: input?.request_id, error: { code: 'ERR_INPUT', message: String(msg) } });
        return { content: [{ type: 'text', text: `Error: ${msg}` }] };
      }
      try {
        const out = establishSession(String(input.session_key), input.google_credentials);
        // Optional: verify Ads scope if enabled
        if (process.env.VERIFY_TOKEN_SCOPE === 'true') {
          try {
            await verifyTokenScopeForSession(String(input.session_key));
          } catch (e: any) {
            const msg = String(e?.message || e);
            if (msg.startsWith('ERR_INSUFFICIENT_SCOPE')) {
              logEvent('set_session_credentials', startTs, { sessionKey: input?.session_key, requestId: input?.request_id, error: { code: 'ERR_INSUFFICIENT_SCOPE', message: 'Missing adwords scope' } });
              return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'ERR_INSUFFICIENT_SCOPE', message: 'Access token lacks required Google Ads scope (adwords). Please re-authenticate with the correct scope.' } }) }] };
            }
            logEvent('set_session_credentials', startTs, { sessionKey: input?.session_key, requestId: input?.request_id, error: { code: 'ERR_SCOPE_VERIFY_FAILED', message: msg } });
            return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'ERR_SCOPE_VERIFY_FAILED', message: msg } }) }] };
          }
        }
        const resp = { content: [{ type: 'text', text: JSON.stringify({ status: 'success', ...out }) }] };
        logEvent('set_session_credentials', startTs, { sessionKey: input?.session_key, requestId: input?.request_id });
        return resp;
      } catch (e: any) {
        const msg = e?.message || String(e);
        const code = msg.startsWith('ERR_IMMUTABLE_AUTH') ? 'ERR_IMMUTABLE_AUTH' : 'ERR_ESTABLISH';
        logEvent('set_session_credentials', startTs, { sessionKey: input?.session_key, requestId: input?.request_id, error: { code, message: String(msg) } });
        return { content: [{ type: 'text', text: JSON.stringify({ error: { code, message: msg } }) }] };
      }
    }
  • Zod schema defining input parameters: session_key (UUID) and google_credentials object containing access_token, optional refresh_token, required developer_token, and optional fields.
    export const SetSessionCredentialsZ = z.object({
      session_key: z.string().describe('UUID v4 session key'),
      google_credentials: z.object({
        access_token: z.string(),
        refresh_token: z.string().optional(),
        developer_token: z.string(),
        login_customer_id: z.string().optional(),
        quota_project_id: z.string().optional(),
        expires_at: z.number().optional(),
      }),
    });
  • Registers the tool on the MCP server via addTool(server, name, description, schema, handler), with inline anonymous handler function.
    addTool(
      server,
      'set_session_credentials',
      'Establish a session with Google Ads credentials (multi-tenant mode only).',
      SetSessionCredentialsZ,
      async (input: any) => {
        const startTs = Date.now();
        if (process.env.ENABLE_RUNTIME_CREDENTIALS !== 'true') {
          const out = { content: [{ type: 'text', text: 'Multi-tenant mode not enabled' }] };
          logEvent('set_session_credentials', startTs, { requestId: input?.request_id, error: { code: 'ERR_NOT_ENABLED', message: 'Multi-tenant mode not enabled' } });
          return out;
        }
        try {
          validateSessionKey(String(input?.session_key || ''));
        } catch (e: any) {
          const msg = e?.message || String(e);
          logEvent('set_session_credentials', startTs, { sessionKey: input?.session_key, requestId: input?.request_id, error: { code: 'ERR_INPUT', message: String(msg) } });
          return { content: [{ type: 'text', text: `Error: ${msg}` }] };
        }
        try {
          const out = establishSession(String(input.session_key), input.google_credentials);
          // Optional: verify Ads scope if enabled
          if (process.env.VERIFY_TOKEN_SCOPE === 'true') {
            try {
              await verifyTokenScopeForSession(String(input.session_key));
            } catch (e: any) {
              const msg = String(e?.message || e);
              if (msg.startsWith('ERR_INSUFFICIENT_SCOPE')) {
                logEvent('set_session_credentials', startTs, { sessionKey: input?.session_key, requestId: input?.request_id, error: { code: 'ERR_INSUFFICIENT_SCOPE', message: 'Missing adwords scope' } });
                return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'ERR_INSUFFICIENT_SCOPE', message: 'Access token lacks required Google Ads scope (adwords). Please re-authenticate with the correct scope.' } }) }] };
              }
              logEvent('set_session_credentials', startTs, { sessionKey: input?.session_key, requestId: input?.request_id, error: { code: 'ERR_SCOPE_VERIFY_FAILED', message: msg } });
              return { content: [{ type: 'text', text: JSON.stringify({ error: { code: 'ERR_SCOPE_VERIFY_FAILED', message: msg } }) }] };
            }
          }
          const resp = { content: [{ type: 'text', text: JSON.stringify({ status: 'success', ...out }) }] };
          logEvent('set_session_credentials', startTs, { sessionKey: input?.session_key, requestId: input?.request_id });
          return resp;
        } catch (e: any) {
          const msg = e?.message || String(e);
          const code = msg.startsWith('ERR_IMMUTABLE_AUTH') ? 'ERR_IMMUTABLE_AUTH' : 'ERR_ESTABLISH';
          logEvent('set_session_credentials', startTs, { sessionKey: input?.session_key, requestId: input?.request_id, error: { code, message: String(msg) } });
          return { content: [{ type: 'text', text: JSON.stringify({ error: { code, message: msg } }) }] };
        }
      }
    );
  • Key helper called by the handler: stores/replaces session credentials in a Map-based store, configures per-session customer allowlist and rate limiter, handles overwrite policy, starts idle sweeper, emits telemetry, returns TTL info.
    export function establishSession(sessionKey: string, credentials: GoogleCredential): { session_key: string; expires_in: number; overwritten?: boolean } {
      if (!isMultiTenantEnabled()) {
        throw new Error('Multi-tenant mode not enabled');
      }
      validateSessionKey(sessionKey);
      if (!credentials.developer_token) {
        throw new Error('Developer token required in multi-tenant mode');
      }
    
      const allowedIds = process.env.ALLOWED_CUSTOMER_IDS
        ? new Set(
            process.env.ALLOWED_CUSTOMER_IDS
              .split(',')
              .map((s) => s.trim())
              .filter(Boolean)
              .map((id) => formatCustomerId(id))
          )
        : undefined;
    
      const now = Date.now();
      const existed = connections.has(sessionKey);
      if (existed) {
        if (process.env.STRICT_IMMUTABLE_AUTH === 'true') {
          throw new Error('ERR_IMMUTABLE_AUTH: Authentication cannot be modified for this session');
        }
        // Non-strict: allow overwrite (for recovery) and log via caller
        const ctx = connections.get(sessionKey)!;
        ctx.credentials = credentials;
        ctx.lastActivityAt = now;
        ctx.allowedCustomerIds = allowedIds;
        // best-effort log
        try { emitMcpEvent({ timestamp: nowIso(), tool: 'session_established', session_key: sessionKey, response_time_ms: 0, overwritten: true }); } catch (e) { void e; }
      } else {
        const rl = isRateLimitingEnabled()
          ? new TokenBucket(
              parseInt(process.env.RATE_LIMIT_BURST || '20', 10),
              parseFloat(process.env.REQUESTS_PER_SECOND || '10')
            )
          : undefined;
        connections.set(sessionKey, {
          session_key: sessionKey,
          credentials,
          establishedAt: now,
          lastActivityAt: now,
          allowedCustomerIds: allowedIds,
          rateLimiter: rl,
        });
        totalSessionCount++;
        try { emitMcpEvent({ timestamp: nowIso(), tool: 'session_established', session_key: sessionKey, response_time_ms: 0, overwritten: false }); } catch (e) { void e; }
      }
      startConnectionSweeper();
    
      const ttlSec = parseInt(process.env.RUNTIME_CREDENTIAL_TTL || '3600', 10);
      return { session_key: sessionKey, expires_in: ttlSec, overwritten: existed };
    }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/martechery/mcp-google-ads-ts'

If you have feedback or need assistance with the MCP directory API, please join our Discord server