Skip to main content
Glama
ZLeventer

linkedin-campaign-manager-mcp

li_get_budget_pacing

Calculate budget utilization for active LinkedIn campaigns by comparing spend over a custom period against total budget. Identifies under-delivery below 80% or over-pacing above 100% for mid-flight pacing checks.

Instructions

Calculate budget utilization for active LinkedIn campaigns. Compares spend over the specified period_days window against total or estimated period budget, and returns a utilization_pct for each campaign. Useful for mid-flight pacing checks: if utilization is below 80% near the end of a month, the campaign may be under-delivering; above 100% means it is over-pacing. Accepts optional campaign_ids to limit scope; defaults to all ACTIVE campaigns in the account.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
ad_account_idNo
campaign_idsNoLimit pacing report to these campaigns. Omit to report all active campaigns in the account.
period_daysNoNumber of days to look back for spend. Should match your budget period (e.g., 30 for monthly).

Implementation Reference

  • The main handler function `getBudgetPacing` that fetches active LinkedIn campaigns (or specified campaign_ids), retrieves spend data via analytics API, and computes utilization_pct against total/daily budget.
    export async function getBudgetPacing(args: {
      ad_account_id?: string;
      campaign_ids?: string[];
      period_days?: number;
    }) {
      const account = resolveAdAccount(args.ad_account_id);
      const accountId = unwrapURN(account);
      const periodDays = args.period_days ?? 30;
    
      // Fetch campaign list + spend in parallel
      const campaignParams: Record<string, string | number> = {
        q: "search",
        pageSize: 100,
        search: "(status:(values:List(ACTIVE)))",
      };
    
      let campaigns: Array<Record<string, unknown>> = [];
      if (args.campaign_ids && args.campaign_ids.length > 0) {
        // Fetch each specified campaign
        campaigns = await Promise.all(
          args.campaign_ids.map((id) => {
            const campaignId = id.startsWith("urn:li:") ? id.split(":").pop()! : id;
            return liGet<Record<string, unknown>>(`/adAccounts/${accountId}/adCampaigns/${campaignId}`);
          })
        );
      } else {
        const res = await liGet<{ elements?: Array<Record<string, unknown>> }>(
          `/adAccounts/${accountId}/adCampaigns`,
          campaignParams
        );
        campaigns = res.elements ?? [];
      }
    
      // Fetch spend for the period
      const spendStart = `${periodDays}daysAgo`;
      const campaignUrns = campaigns.map((c) => {
        const id = c["id"] as string | undefined ?? "";
        return urn("sponsoredCampaign", id);
      });
    
      const start = resolveDate(spendStart);
      const end = resolveDate(DEFAULT_END);
      const spendUrl = buildAnalyticsUrl({
        pivot: "CAMPAIGN",
        timeGranularity: "ALL",
        start,
        end,
        fields: "costInUsd,costInLocalCurrency",
        campaignUrns: campaignUrns.filter(Boolean),
        accountUrn: undefined,
      });
    
      const spendData = await liGetRaw<AnalyticsResponse>(spendUrl);
      const spendMap = new Map<string, { usd: number; local: number }>();
      for (const row of spendData.elements ?? []) {
        const key = (row.pivotValues?.[0] ?? "") as string;
        spendMap.set(key, {
          usd: Number(row["costInUsd"] ?? 0),
          local: Number(row["costInLocalCurrency"] ?? 0),
        });
      }
    
      const pacing = campaigns.map((c) => {
        const id = c["id"] as string ?? "";
        const campaignUrn = urn("sponsoredCampaign", id);
        const spend = spendMap.get(campaignUrn) ?? { usd: 0, local: 0 };
    
        // totalBudget is a {amount, currencyCode} object
        const totalBudget = c["totalBudget"] as { amount?: string; currencyCode?: string } | undefined;
        const dailyBudget = c["dailyBudget"] as { amount?: string; currencyCode?: string } | undefined;
    
        const budgetAmount = totalBudget?.amount ? Number(totalBudget.amount) : null;
        const dailyBudgetAmount = dailyBudget?.amount ? Number(dailyBudget.amount) : null;
        const estimatedPeriodBudget = dailyBudgetAmount ? dailyBudgetAmount * periodDays : null;
        const effectiveBudget = budgetAmount ?? estimatedPeriodBudget;
    
        const utilizationPct =
          effectiveBudget && effectiveBudget > 0
            ? Math.round((spend.usd / effectiveBudget) * 10000) / 100
            : null;
    
        return {
          campaign_id: id,
          campaign_urn: campaignUrn,
          name: c["name"],
          status: c["status"],
          total_budget_usd: budgetAmount,
          daily_budget_usd: dailyBudgetAmount,
          estimated_period_budget_usd: estimatedPeriodBudget,
          spend_usd: spend.usd,
          spend_local: spend.local,
          utilization_pct: utilizationPct,
          period_days: periodDays,
        };
      });
    
      return {
        period_days: periodDays,
        ad_account_id: accountId,
        campaigns: pacing,
      };
    }
  • Zod schema defining the input parameters: ad_account_id (optional), campaign_ids (optional array of strings), period_days (optional int, default 30, min 1, max 365).
    export const getBudgetPacingSchema = {
      ad_account_id: z.string().optional(),
      campaign_ids: z
        .array(z.string())
        .optional()
        .describe("Limit pacing report to these campaigns. Omit to report all active campaigns in the account."),
      period_days: z
        .number()
        .int()
        .min(1)
        .max(365)
        .default(30)
        .describe("Number of days to look back for spend. Should match your budget period (e.g., 30 for monthly)."),
    };
  • src/index.ts:139-144 (registration)
    Registration of the 'li_get_budget_pacing' tool using server.tool() with its schema and handler.
    server.tool(
      "li_get_budget_pacing",
      "Calculate budget utilization for active LinkedIn campaigns. Compares spend over the specified period_days window against total or estimated period budget, and returns a utilization_pct for each campaign. Useful for mid-flight pacing checks: if utilization is below 80% near the end of a month, the campaign may be under-delivering; above 100% means it is over-pacing. Accepts optional campaign_ids to limit scope; defaults to all ACTIVE campaigns in the account.",
      getBudgetPacingSchema,
      async (args) => { try { return ok(await getBudgetPacing(args)); } catch (e) { return err(e); } }
    );
  • src/index.ts:21-22 (registration)
    Import of the getBudgetPacing handler and schema from analytics.ts module.
      getBudgetPacing, getBudgetPacingSchema,
    } from "./tools/analytics.js";
Behavior4/5

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

No annotations provided, but the description discloses the core behavior: compares spend over period_days, returns utilization_pct, operates on active campaigns. It is a read-only analytic, so safety is implied. No contradictions.

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: first defines purpose, second explains logic, third provides usage examples. Every sentence is essential and front-loaded. No unnecessary details.

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

Completeness5/5

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

Given no output schema, the description explains the return value (utilization_pct per campaign) and the underlying logic. It covers inputs, behavior, and output adequately for a simple analytic tool. The complexity is low, and the description is self-contained.

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

Parameters4/5

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

Schema covers 67% of parameters with descriptions; the description adds context for period_days (should match budget period) and campaign_ids (defaults to all active). For ad_account_id, which lacks schema description, the description does not cover it, but it's a standard parameter. The description adds value beyond schema.

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 calculates budget utilization for active LinkedIn campaigns, specifying it compares spend and returns utilization percentage. This distinguishes it from sibling tools like li_get_campaign_performance, which provides general performance metrics.

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?

Provides explicit guidance on when to use: mid-flight pacing checks with thresholds (below 80% under-delivering, above 100% over-pacing). Also explains optional campaign_ids and default scope. Could be improved by mentioning alternatives like li_get_campaign_performance for other metrics.

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/ZLeventer/linkedin-campaign-manager-mcp'

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