Skip to main content
Glama

Connect Ad Platform

platform_connect
Idempotent

Register a Google Ads or Meta Ads account in the AdOps workspace to enable campaign management tools. Accepts platform, name, and account ID; returns active connection.

Instructions

Register a Google Ads or Meta Ads account in the AdOps workspace so subsequent tools (campaign_list, campaign_create, ads_report) can target it. Input: platform ("google_ads"|"meta_ads"), name (display label), account_id (the external ad account id). Returns the stored connection object with a generated UUID and status="active". Safe to call with the same platform+account_id — returns the existing connection instead of erroring (idempotent).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
platformYesAd platform to connect
nameYesFriendly connection name
account_idYesPlatform-specific ad account ID (Google: 10 digits, Meta: act_XXXXXXXXX)

Implementation Reference

  • src/index.ts:193-230 (registration)
    The tool 'platform_connect' is registered with the MCP server using server.registerTool(), with inputSchema from PlatformConnectInputSchema and an async handler function.
    // ── Tool 1: platform_connect ────────────────────────────────────────
    
    server.registerTool(
      'platform_connect',
      {
        title: 'Connect Ad Platform',
        description: 'Register a Google Ads or Meta Ads account in the AdOps workspace so subsequent tools (campaign_list, campaign_create, ads_report) can target it. Input: platform ("google_ads"|"meta_ads"), name (display label), account_id (the external ad account id). Returns the stored connection object with a generated UUID and status="active". Safe to call with the same platform+account_id — returns the existing connection instead of erroring (idempotent).',
        inputSchema: PlatformConnectInputSchema,
        annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
      },
      async ({ platform, name, account_id }) => {
        try {
          const existing = await storage.getAllConnections();
          const duplicate = existing.find((c) => c.platform === platform && c.account_id === account_id);
          if (duplicate) {
            return { content: [{ type: 'text' as const, text: JSON.stringify({
              message: 'Connection already exists',
              connection: duplicate,
            }, null, 2) }] };
          }
    
          const conn = await storage.addConnection({
            id: uuidv4(),
            platform,
            name,
            account_id,
            connected_at: new Date().toISOString(),
            last_sync_at: null,
            status: 'active',
          });
    
          return { content: [{ type: 'text' as const, text: JSON.stringify({
            message: `Successfully connected ${platform} account "${name}"`,
            connection: conn,
          }, null, 2) }] };
        } catch (e) { return handleToolError(e); }
      },
    );
  • The handler function for platform_connect that checks for duplicate connections (idempotent), creates a new PlatformConnection object with a UUID, and stores it via storage.addConnection().
    async ({ platform, name, account_id }) => {
      try {
        const existing = await storage.getAllConnections();
        const duplicate = existing.find((c) => c.platform === platform && c.account_id === account_id);
        if (duplicate) {
          return { content: [{ type: 'text' as const, text: JSON.stringify({
            message: 'Connection already exists',
            connection: duplicate,
          }, null, 2) }] };
        }
    
        const conn = await storage.addConnection({
          id: uuidv4(),
          platform,
          name,
          account_id,
          connected_at: new Date().toISOString(),
          last_sync_at: null,
          status: 'active',
        });
    
        return { content: [{ type: 'text' as const, text: JSON.stringify({
          message: `Successfully connected ${platform} account "${name}"`,
          connection: conn,
        }, null, 2) }] };
      } catch (e) { return handleToolError(e); }
    },
  • PlatformConnectInputSchema defines the input validation for platform_connect: platform (google|meta), name (1-100 chars), account_id with format validation via .refine().
    export const PlatformConnectInputSchema = z.object({
      platform: PlatformSchema.describe('Ad platform to connect'),
      name: z.string().min(1).max(100).describe('Friendly connection name'),
      account_id: z.string().min(1).describe('Platform-specific ad account ID (Google: 10 digits, Meta: act_XXXXXXXXX)'),
    }).refine(
      (data) => {
        if (data.platform === 'google') return /^\d{10}$/.test(data.account_id.replace(/-/g, ''));
        if (data.platform === 'meta') return /^act_\d+$/.test(data.account_id);
        return true;
      },
      { message: 'Invalid account ID format. Google: 10 digits (no dashes). Meta: act_XXXXXXXXX.' },
    );
  • PlatformConnectionSchema defines the structure of the connection object returned/stored by platform_connect, including id, platform, name, account_id, connected_at, last_sync_at, and status.
    export const PlatformConnectionSchema = z.object({
      id: z.string().uuid(),
      platform: PlatformSchema,
      name: z.string().describe('Friendly connection name'),
      account_id: z.string().describe('Platform-specific account ID'),
      connected_at: z.string(),
      last_sync_at: z.string().nullable(),
      status: z.enum(['active', 'expired', 'error']),
    });
    export type PlatformConnection = z.infer<typeof PlatformConnectionSchema>;
  • Storage helper methods used by the platform_connect handler: getAllConnections() to check for duplicates, and addConnection() to persist the new PlatformConnection.
    async getAllConnections(): Promise<PlatformConnection[]> {
      return this.read<PlatformConnection[]>(this.connectionsPath);
    }
    
    async getConnectionById(id: string): Promise<PlatformConnection | null> {
      const conns = await this.read<PlatformConnection[]>(this.connectionsPath);
      return conns.find((c) => c.id === id) ?? null;
    }
    
    async getConnectionsByPlatform(platform: string): Promise<PlatformConnection[]> {
      const conns = await this.read<PlatformConnection[]>(this.connectionsPath);
      return conns.filter((c) => c.platform === platform);
    }
    
    async addConnection(conn: PlatformConnection): Promise<PlatformConnection> {
      return this.lock.run(async () => {
        const conns = await this.read<PlatformConnection[]>(this.connectionsPath);
        conns.push(conn);
        await this.write(this.connectionsPath, conns);
        return conn;
      });
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description discloses idempotency (returns existing connection) and the return format (connection object with UUID and status active). Annotations already provide idempotentHint=true; the description adds specific return details. No contradictions with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, front-loaded with purpose. Each sentence adds value: purpose, inputs, return, idempotency. No fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Covers purpose, inputs, return, idempotency, and prerequisite relationship for subsequent tools. Lacks error handling or permission requirements, but mild gap for a simple tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so descriptions repeat some info. However, the description says platform accepts 'google_ads'|'meta_ads', while schema enum is 'google'|'meta', causing inconsistency. This reduces clarity despite other parameter details.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool registers a Google Ads or Meta Ads account in the AdOps workspace to enable subsequent tools like campaign_list, campaign_create, and ads_report. It distinguishes the tool's role as a prerequisite from its siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains that the tool should be used to set up an ad account before using other campaign and reporting tools. It also notes idempotency, indicating it is safe to call multiple times. However, it does not explicitly mention when not to use it or alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/enzoemir1/adops-mcp'

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