Skip to main content
Glama
microsoft-ads-mcp

microsoft-ads-mcp

by shinypebble

microsoft-ads-mcp

CI

An MCP server for the Microsoft Advertising (Bing Ads) REST API, built for agent-led campaign management and reporting. It exposes a focused set of useful-work tools — walk the campaign tree, create and edit in place (rename, repoint Final URLs, tracking templates, status, bids), manage negative keywords, ad extensions, conversion goals/UET tags, and ZIP location targeting, run the Bulk API, and pull performance reports that are actually downloaded and parsed for you — rather than a 1:1 mirror of the API surface.

Built with FastMCP and the official Microsoft msads REST SDK (which ships OpenAPI-generated Pydantic v2 models). Managed with uv, linted/formatted with ruff, type-checked with ty.

Why REST / msads (not the legacy SOAP bingads SDK)

Microsoft is retiring the SOAP API: new features are REST-only from Oct 1, 2026, and SOAP is fully deprecated on Jan 31, 2027 (migration guide). The REST SDK msads gives typed Pydantic models, structured HTTP exceptions, and the same OAuth/ServiceClient entry points — so this server is built on it directly.

SDK quirks worth knowing

  • msads is synchronous (requests/urllib3). Tools here are therefore plain sync functions; FastMCP runs them in a worker thread, so the event loop is never blocked. We do not wrap the SDK in async.

  • msads does not declare its python-dateutil dependency, even though openapi_client imports it. We pin python-dateutil explicitly in pyproject.toml.

  • The package installs as the bingads.* (auth + ServiceClient) and openapi_client.* (models + exceptions) import namespaces — there is no top-level msads module.

Related MCP server: Microsoft Ads MCP Server

REST API reference & endpoints

Pydantic models shipped inside msads are code-generated from Microsoft's internal spec; the public surface is the per-operation Campaign Management reference on Microsoft Learn (the Python SOAP→REST migration guide is the most useful map of REST request/response shapes).

The REST service base URLs ServiceClient targets — set automatically from MICROSOFT_ADS_ENVIRONMENT — are:

Service

Production

Sandbox

Campaign Management

https://campaign.api.bingads.microsoft.com

https://campaign.api.sandbox.bingads.microsoft.com

Reporting

https://reporting.api.bingads.microsoft.com

https://reporting.api.sandbox.bingads.microsoft.com

Bulk

https://bulk.api.bingads.microsoft.com

https://bulk.api.sandbox.bingads.microsoft.com

Ad Insight

https://adinsight.api.bingads.microsoft.com

https://adinsight.api.sandbox.bingads.microsoft.com

Customer Mgmt / Billing

https://clientcenter.api.bingads.microsoft.com

https://clientcenter.api.sandbox.bingads.microsoft.com

Install

The published package runs from anywhere with uvx — no clone, no project files. Put your credentials in ~/.config/microsoft-ads/.env (the same directory the server already persists OAuth tokens to), then point your MCP client at uvx microsoft-ads-mcp:

mkdir -p ~/.config/microsoft-ads
cat > ~/.config/microsoft-ads/.env <<'EOF'
MICROSOFT_ADS_DEVELOPER_TOKEN=...
MICROSOFT_ADS_CLIENT_ID=...
# Optional once you've signed in; otherwise mint one via the auth tools (see Authentication):
# MICROSOFT_ADS_REFRESH_TOKEN=...
EOF
chmod 600 ~/.config/microsoft-ads/.env

This .env is read independently of the working directory, so the server works from any repo and no secrets live in any project file — the MCP client config carries only the non-secret operational flags (see MCP client configuration). Do the one-time sign-in once (see Authentication); the refresh token is then persisted next to this file and auto-refreshed.

From source (development)

uv sync                              # create .venv and install
cp .env.example .env                 # then set the credentials below
uv run python -m microsoft_ads_mcp   # run over stdio (default)

Configuration

Credentials and flags load from (highest priority first) real environment variables, a project-local .env, then the cwd-independent ~/.config/microsoft-ads/.env. See .env.example for the full list:

Variable

Required

Notes

MICROSOFT_ADS_DEVELOPER_TOKEN

yes

From the developer portal

MICROSOFT_ADS_CLIENT_ID

yes

OAuth app (client) id — an Azure app, or a Google Cloud OAuth client when IDENTITY_PROVIDER=google

MICROSOFT_ADS_IDENTITY_PROVIDER

no

microsoft (default) or google for Google-federated accounts

MICROSOFT_ADS_REFRESH_TOKEN

recommended

Run non-interactively; else mint one via the auth tools

MICROSOFT_ADS_CLIENT_SECRET

no

Microsoft web/confidential apps, or the Google OAuth client secret

MICROSOFT_ADS_ACCOUNT_ID / MICROSOFT_ADS_CUSTOMER_ID

no

Discovered via search_accounts if unset

MICROSOFT_ADS_ENVIRONMENT

no

production (default) or sandbox

READ_ONLY

no

true registers no write tools at all (default false)

TOOL_SEARCH

no

true collapses the catalog behind BM25 search_tools / call_tool with a few tools pinned; typed schemas and the READ_ONLY gate are preserved (default false)

Refresh tokens are persisted to ~/.config/microsoft-ads/tokens.json, created with 0600 permissions (owner read/write only).

Authentication

If you have no refresh token yet, mint one once (interactive):

  1. Call get_auth_url() → open the URL, sign in.

  2. Copy the redirect URL and call complete_auth(redirect_url).

  3. The refresh token is saved to ~/.config/microsoft-ads/tokens.json (mode 0600) and reused/auto-refreshed thereafter — so you never need to add it to .env by hand.

MCP client configuration

Recommended — run the published package with uvx, credentials in ~/.config/microsoft-ads/.env (see Install). The config carries only the non-secret flags:

{
  "mcpServers": {
    "microsoft-ads": {
      "command": "uvx",
      "args": ["microsoft-ads-mcp"],
      "env": {
        "READ_ONLY": "false",
        "TOOL_SEARCH": "true"
      }
    }
  }
}

Alternatively, run from a source checkout (development) and/or pass credentials inline instead of via .env:

{
  "mcpServers": {
    "microsoft-ads": {
      "type": "stdio",
      "command": "uv",
      "args": ["run", "--directory", "${CLAUDE_PROJECT_DIR:-.}", "python", "-m", "microsoft_ads_mcp"],
      "env": {
        "MICROSOFT_ADS_DEVELOPER_TOKEN": "...",
        "MICROSOFT_ADS_CLIENT_ID": "...",
        "MICROSOFT_ADS_REFRESH_TOKEN": "...",
        "READ_ONLY": "false"
      }
    }
  }
}

Tools

Call account_health first to validate credentials and learn whether writes are enabled. It returns a discriminated auth_state (ok / no_token / token_expired / token_rejected / dev_token_missing / account_inactive) and needs_interactive_auth, so a client can branch deterministically instead of pattern-matching an error string.

Auth — get_auth_url, complete_auth (one-time interactive sign-in; see below).

Read — account_health, search_accounts, set_active_account (switch which account calls hit), get_campaigns, get_ad_groups, get_keywords, get_ads (includes the RSA copy: headlines / descriptions / paths), get_budgets, get_negative_keywords, get_ad_extensions, get_conversion_goals, get_uet_tags, get_location_targets, get_location_intent (presence vs. search-interest targeting), get_ad_schedules (dayparting windows plus the campaign time zone they run in), get_device_bid_adjustments (per-device modifiers — Computers / Smartphones / Tablets), resolve_postal_codes (ZIP → Microsoft LocationId), bulk_download, get_account_url_options. get_campaigns also surfaces each campaign's time_zone, start_date, languages, bid_strategy_type (plus its stored max_cpc / target_cpa / target_roas when the scheme carries them), and ad_schedule_use_searcher_time_zone. get_ad_groups surfaces each ad group's network (ad distribution: the entire Microsoft Advertising Network vs. Microsoft sites and select traffic only). The hierarchy reads (get_campaigns, get_ad_groups, get_ads, get_keywords) also surface each entity's URL tracking — tracking_url_template, final_url_suffix, and url_custom_parameters. A null template at a level usually means it inherits the account-level default, which get_account_url_options returns (tracking template, Final URL suffix, and msclkid_auto_tagging_enabled — the Microsoft Click ID that drives attribution). Confirm these before activating paused campaigns rather than assuming the per-campaign blanks mean "untracked". get_ads and get_keywords also surface editorial_status — the ad-review state (Active / Inactive / ActiveLimited / Disapproved), separate from the Active/Paused status — so you can tell whether an Active ad or keyword is actually approved to serve (the first thing to check on zero impressions). get_conversion_goals reports each goal's exclude_from_bidding — the inverse of the UI's "Include in conversions" checkbox, i.e. whether the goal feeds automated bidding (ECPC / tCPA) — plus count_type, conversion_window_in_minutes, goal_category, and the revenue model; confirm a goal is included before relying on it to steer spend.

Reporting — run_performance_report (submit → poll → download → parse, returns rows), covering campaign / keyword / search-query / geographic reports. Supports a predefined date_range or a custom start_date/end_date, and scoping to a single campaign_id / ad_group_id / account_id.

Keyword research (Ad Insight / Keyword Planner; read-only, registered even in READ_ONLY mode) — estimate_keyword_bids returns the estimated first-page (or mainline) bid per keyword (estimated_min_bid) with the modeled CPC/CTR/clicks/impressions/cost it buys; get_keyword_ideas discovers keywords from seed phrases and/or a landing-page URL with monthly search volume, a suggested bid, and a competition bucket (defaults to English / United States); and get_keyword_traffic_estimates projects weekly clicks / impressions / cost / position for keywords at a given max CPC. check_first_page_bids(ad_group_id, campaign_id) joins an ad group's live keyword bids to these estimates and flags the keywords bidding below their first-page bid (the "Below first page bid" delivery state), each with its current_bid, estimated_first_page_bid, and shortfall. Every value is a modeled estimate and may be null where Microsoft has no data.

Write (only when READ_ONLY=false) — new campaigns / ad groups / ads are created PAUSED.

  • Campaigns, ad groups, ads, keywords — create_campaign, update_campaign, update_campaign_status, create_ad_group, update_ad_group, create_responsive_search_ad, update_responsive_search_ad, add_keywords, update_keyword, delete_campaign, delete_ad_group, delete_ad, delete_keyword. Create/update at every level (campaign, ad group, ad, keyword) accept tracking_url_template, final_url_suffix, and url_custom_parameters (a {key: value} map, referenced in templates as {_key}). create_ad_group / update_ad_group also accept network (ad distribution). create_campaign / update_campaign also accept bid_strategy_type to set the campaign's inline bid strategy (EnhancedCpc, ManualCpc, MaxClicks, MaxConversions, TargetCpa, MaxConversionValue, TargetRoas) with optional max_cpc / target_cpa / target_roas — e.g. MaxClicks + max_cpc is Maximize Clicks with a Maximum CPC limit (distinct from bid_strategy_id, which applies a portfolio strategy; set one or the other).

  • Account-level URL options — set_account_url_options sets the tracking template, Final URL suffix, and msclkid auto-tagging once for the whole account (every campaign inherits them) — the cleanest single-point lever for an account-wide tracking/rebrand change.

  • Negative keywords — add_negative_keywords, remove_negative_keywords (campaign or ad-group scope).

  • Ad extensions — add_call_extension, update_call_extension, add_callout_extension, add_sitelink_extension, update_sitelink_extension, add_structured_snippet_extension, update_structured_snippet_extension, delete_ad_extension. Call extensions accept is_call_tracking_enabled (US/UK) to turn on Microsoft call tracking so call-from-ad conversions are measured — pass it on add_call_extension, or flip it on an existing asset with update_call_extension. New forwarding numbers are local (toll-free is no longer provisioned). They also accept is_call_only (the "Show just my phone number" call-only mobile format). Sitelinks carry the two description1 / description2 lines (set both or neither); structured snippets carry a header from Microsoft's predefined list (e.g. "Brands", "Services", "Types") plus 3-10 short values. The update_sitelink_extension / update_structured_snippet_extension tools edit those in place (e.g. add descriptions to an existing sitelink), re-sending the replace-required fields for you so a partial edit is safe. delete_ad_extension removes any extension type by id — it deletes the account-level object itself, not just a single campaign/ad-group association. get_ad_extensions surfaces the current is_call_tracking_enabled / is_call_only flags, sitelink descriptions, and snippet header/values.

  • Conversion goals / UET tags — create_conversion_goal adds a goal: an OfflineConversion goal (keyed by MSCLKID, no UET tag) or a UET-backed web goal (Url / Event / Duration / PagesViewedPerVisit, which need a tag_id). Goals are created active (a goal doesn't spend; a paused one silently fails to record). update_conversion_goal edits a goal in place: rename, set status, and (most launch-relevant) toggle exclude_from_bidding — the inverse of the UI's "Include in conversions" checkbox, the single switch for whether a goal feeds automated bidding (ECPC / tCPA). Also sets count_type, conversion_window_in_minutes, and the revenue model (revenue_type / revenue_value / revenue_currency_code). For phone calls there is no native "calls from ads" goal: apply_offline_conversions is the bid-eligible path — filter the call-center log yourself (e.g. calls ≥60s), then upload qualifying calls by MSCLKID against an OfflineConversion goal whose name matches conversion_name. update_uet_tag renames/redescribes a tag.

  • Location (ZIP/geo) targeting — add_location_targets, remove_location_targets, set_location_intent (presence — PeopleIn — vs. search-interest targeting; one criterion per campaign, updated in place).

  • Ad scheduling (dayparting) — add_ad_schedules, remove_ad_schedules, replace_ad_schedule (day + time windows at 15-minute granularity; times run in the campaign time zone unless use_searcher_time_zone is set). Windows are additive, but a same-day window may not overlap an existing one (the API rejects it), so to change or extend a window use replace_ad_schedule (which removes the old criterion then adds the new one — the only safe order) rather than adding over it. update_campaign accepts time_zone to set the zone those schedules run in.

  • Device bid adjustments — set_device_bid_adjustment(campaign_id, device, bid_adjustment) sets a per-device modifier (-100 to 900 percent; -100 excludes the device). Microsoft calls mobile Smartphones (there is no "Mobile"); "Computers" is desktop/laptop. Device criterions are created as a set, so the first call also creates the other two at a neutral 0.

  • Bulk API — bulk_upload.

The update_* tools patch in place: only the fields you pass change. Prefer them over recreate-and-pause when an entity already exists.

Tool discovery (TOOL_SEARCH)

With TOOL_SEARCH=true, the server lists only a few pinned orientation tools (account_health, search_accounts, get_campaigns, run_performance_report, plus the auth tools) alongside two synthetic tools: search_tools(query) (BM25 over names, descriptions, and parameters) and call_tool(name, arguments). The rest of the catalog is discovered on demand instead of loaded upfront — useful as the tool count grows. Hidden tools keep their full typed schemas, and because search runs through the normal pipeline, the READ_ONLY gate still applies: write tools aren't registered in read-only mode, so they're neither listed nor discoverable. This is FastMCP's stable BM25SearchTransform — no code execution, no sandbox.

Architecture

src/microsoft_ads_mcp/
  config.py            # pydantic-settings; all env config
  server.py            # builds FastMCP, lifespan-manages the client, registers tools
  api/
    auth.py            # OAuth flow + hardened token store
    client.py          # wraps msads ServiceClient(s); the single dispatch point
    errors.py          # translate openapi_client exceptions -> MsAdsApiError
  domain/
    entities.py        # lean Pydantic summary/report models for tool outputs
  services/
    accounts.py        # user/account reads (CustomerManagementService)
    account_properties.py  # account-level URL options (CampaignManagementService AccountProperties)
    campaigns.py       # hierarchy + list reads
    mutations.py       # create/update/delete for campaigns, ad groups, ads, keywords
    negatives.py       # negative-keyword add/list/remove
    extensions.py      # ad extensions (call/callout/sitelink/structured snippet)
    conversions.py     # conversion goals + UET tags
    criteria.py        # location (ZIP/geo) targeting via campaign criterions
    geo.py             # ZIP -> LocationId resolution (cached geo-locations file)
    bulk.py            # Bulk API upload/download (submit/poll)
    reporting.py       # submit/poll/download/parse
    insights.py        # Ad Insight keyword research (bid/idea/traffic estimates)
  tools/
    health.py read_tools.py write_tools.py reporting_tools.py insight_tools.py auth_tools.py  # READ_ONLY-gated

Development

uv run ruff check . && uv run ruff format --check .
uv run ty check
uv run pytest -q
# or all at once:
bash scripts/ci.sh

MCP Inspector

The MCP Inspector is a browser UI for calling the server's tools by hand — the fastest way to exercise a tool while iterating locally. FastMCP ships an integration that launches it (with auto-reload on file changes):

# Run the package as a module (-m) so its relative imports resolve; --with-editable .
# installs this package into the Inspector's ephemeral env.
uv run fastmcp dev inspector microsoft_ads_mcp -m --with-editable .

This prints a http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=... URL — open it, connect, and call account_health first. To test the exact python -m entrypoint an MCP client uses, run the standalone Inspector against the real command instead:

npx @modelcontextprotocol/inspector uv run python -m microsoft_ads_mcp

Either way, credentials load from .env. Write tools only appear when READ_ONLY=false — set it in .env, or (for the standalone Inspector) in its env panel before connecting.

License

MIT — see LICENSE.

Available Tools

66 tools
account_healthA
Read-only

Validate credentials and report the environment and write mode.

Call this first. read_only tells you whether write tools are available this session; environment is production or sandbox. auth_state discriminates why auth failed (e.g. no_token vs token_rejected); branch on needs_interactive_auth rather than the message string.

Inputs: no named parameters. Returns: Result of the first call an agent should make: validates auth and reports mode.…

add_ad_schedulesA

Add ad-schedule (dayparting) windows to a campaign.

Each window restricts serving to one day and time range and is additive (a campaign with no schedule serves all hours). Hours are 0-24; minutes are 15-minute granularity, so only 0/15/30/45 are valid (e.g. 09:15-16:45 -> from_hour 9, from_minute 15, to_hour 16, to_minute 45). Times run in the campaign time zone (see get_campaigns / get_ad_schedules) unless you pass use_searcher_time_zone=true. Read existing windows with get_ad_schedules first to avoid duplicates.

A new window for a day may NOT overlap an existing window on that same day -- the API rejects the add (returned as ok=false with a partial error, not a crash). To change or extend a window, remove_ad_schedules the old criterion first and then add the new one, in that order (adding the overlapping window first fails); or use replace_ad_schedule, which does the remove-then-add for a single window in one call.

Inputs: schedules (required), campaign_id (required), use_searcher_time_zone. Returns: Generic result for create/update tools.

add_call_extensionA

Create a call extension and optionally attach it to a campaign or ad group.

Inputs: phone_number (required), entity_id, country_code, is_call_only, +2 more. Returns: Generic result for create/update tools.

add_callout_extensionA

Create a callout extension and optionally attach it to a campaign or ad group.

Inputs: text (required), entity_id, association_type. Returns: Generic result for create/update tools.

add_keywordsB

Add keywords (Active) to an ad group.

Inputs: keywords (required), ad_group_id (required), match_type, default_bid, +3 more. Returns: Generic result for create/update tools.

add_location_targetsA

Target (or exclude) Microsoft LocationIds on a campaign.

Resolve ZIPs to LocationIds first with resolve_postal_codes.

Inputs: campaign_id (required), location_ids (required), exclude, bid_adjustment. Returns: Generic result for create/update tools.

add_negative_keywordsB

Attach negative keywords to a campaign or ad group.

Inputs: keywords (required), entity_id (required), match_type, entity_type. Returns: Generic result for create/update tools.

add_structured_snippet_extensionA

Create a structured snippet extension and optionally attach it to a campaign/ad group.

A structured snippet is a header followed by a short list of values (e.g. header "Brands" with values ["Acme", "Globex", "Initech"]).

Inputs: header (required), values (required), entity_id, association_type. Returns: Generic result for create/update tools.

add_website_exclusionsA

Block websites / mobile-app ids on a campaign so ads won't serve there.

Additive: new sites are merged with the campaign's existing exclusions (read-modify-write), so this never clobbers what's already blocked. Pass bare domains or paths (e.g. "example.com", "example.com/section") or mobile-app ids; a leading http(s):// is stripped for you. Microsoft sites (e.g. MSN.com) can't be excluded and there's a ~2500-site cap per campaign -- such rejections come back in partial_errors. View with get_website_exclusions.

Inputs: urls (required), campaign_id (required). Returns: Generic result for create/update tools.

apply_offline_conversionsA

Import offline conversions against an OfflineConversion goal, keyed by MSCLKID.

This is the bid-eligible path for phone calls: create an OfflineConversion goal with create_conversion_goal, apply your own qualifying filter to the call-center log (e.g. keep calls >=60s), then upload one record per qualifying call. Each record is attributed to the click that drove it and counted under the goal whose name matches conversion_name. Returns per-record errors in partial_errors (the API returns no ids).

Note: wait ~2 hours after creating an OfflineConversion goal before uploading — earlier uploads are rejected with "OfflineConversionNotAcceptedForGoal" (expected, not a bug).

Inputs: conversions_to_apply (required). Returns: Generic result for create/update tools.

bulk_downloadA
Read-only

Export the account to a Bulk file; returns the result file URL when ready.

Submits a Bulk download and polls to completion (the file can be large, so the URL is returned rather than the contents).

Inputs: entities. Returns: object

bulk_uploadB

Apply ready-made Bulk CSV rows to the account; polls to completion.

Inputs: entity_records (required). Returns: object

check_first_page_bidsA
Read-only

Flag keywords whose bid is below the estimated first-page bid ("Below first page bid").

This is the API-driven version of the delivery state the UI shows as "Below first page bid". For each keyword in the ad group it looks up the Keyword Planner first-page bid estimate (at the keyword's own match type) and compares it against the keyword's effective bid -- the keyword's own bid, or the ad group's default bid when the keyword has none. The result lists the under-bid keywords first (largest shortfall first) with each one's current_bid, estimated_first_page_bid, and bid_source, plus counts. Use it when diagnosing low impressions, or before activating a campaign, to find keywords that won't reach the first page at their current bid.

Inputs: ad_group_id (required), campaign_id (required), language, location_ids, +1 more. Returns: An ad group's keywords checked against their first-page bid estimates. ``keywor…

complete_authA

Finish sign-in: exchange the browser's redirect URL for a saved refresh token.

Call this with the URL the user pasted back after completing get_auth_url.

Inputs: redirect_url (required). Returns: result

create_ad_groupA

Create an ad group (PAUSED) in a campaign.

Inputs: name (required), campaign_id (required), cpc_bid, network, +4 more. Returns: Generic result for create/update tools.

create_campaignA

Create a Search campaign (PAUSED by default for safety).

Inputs: name (required), daily_budget (required), max_cpc, target_cpa, +6 more. Returns: Generic result for create/update tools.

create_conversion_goalA

Create a conversion goal. Goals are created Active (a goal does not spend; a paused goal silently fails to record conversions) — pass status="Paused" to override.

Conversion goals CANNOT be deleted (Microsoft has no delete API) — they can only be paused or renamed, so name them deliberately.

Microsoft has no native "calls from ads" goal. The bid-eligible path for phone calls is an "OfflineConversion" goal fed by apply_offline_conversions (keyed by MSCLKID) — create that goal here, then upload qualifying calls with apply_offline_conversions.

Inputs: name (required), goal_type (required), value, status, +19 more. Returns: Generic result for create/update tools.

create_responsive_search_adB

Create a Responsive Search Ad (PAUSED).

Inputs: final_url (required), headlines (required), ad_group_id (required), descriptions (required), +5 more. Returns: Generic result for create/update tools.

delete_adB

Delete one or more ads by id.

Inputs: ad_ids (required), ad_group_id (required). Returns: Generic result for create/update tools.

delete_ad_extensionA

Delete account-level ad extensions by id.

Removes the extension objects entirely (not just their campaign/ad-group associations).

Inputs: ad_extension_ids (required). Returns: Generic result for create/update tools.

delete_ad_groupB

Delete one or more ad groups by id.

Inputs: campaign_id (required), ad_group_ids (required). Returns: Generic result for create/update tools.

delete_campaignB

Delete one or more campaigns by id.

Inputs: campaign_ids (required). Returns: Generic result for create/update tools.

delete_keywordB

Delete one or more keywords by id.

Inputs: ad_group_id (required), keyword_ids (required). Returns: Generic result for create/update tools.

estimate_keyword_bidsA
Read-only

Estimate the bid to reach the first page (or mainline) for keywords -- the "estimated first page bid" from Keyword Planner.

For each keyword, returns one estimate per match type: estimated_min_bid (the headline first-page/mainline bid) plus modeled average CPC, CTR, and weekly clicks/impressions/cost ranges. Estimates are account-scoped and may be null where Microsoft has no data.

Inputs: keywords (required), language, match_types, location_ids, +2 more. Returns: result

get_account_url_optionsA
Read-only

Read the account-level URL tracking / tracking template that every campaign inherits.

This is the account-level URL options: the tracking template (tracking URL template), Final URL suffix, msclkid auto-tagging, and parallel tracking that per-campaign, ad-group, ad, and keyword values inherit. Those per-entity values are typically blank and inherit from here, so this answers "where is the tracking template set?" and how UTM / msclkid click tracking is configured -- check this (not just the entity hierarchy) to confirm how clicks are tracked. msclkid_auto_tagging_enabled is what appends the Microsoft Click ID (msclkid) for conversion attribution. Confirm these before activating paused campaigns. To see the resolved (effective) value for a specific campaign or ad group and which level set it, see get_effective_url_settings; to change these, see set_account_url_options.

Inputs: no named parameters. Returns: Account-level URL tracking: the defaults every campaign inherits unless it over…

get_ad_extensionsA
Read-only

List ad extensions in the account (call, callout, sitelink, etc.).

By default this enumerates all association scopes (Account, Campaign, AdGroup) and merges them, so extensions attached at the campaign or ad-group level are included — most extensions are associated to a campaign, not the account, so scoping to Account alone often looks empty. Pass an explicit association_type to narrow to one scope.

Inputs: extension_types, association_type. Returns: result

get_ad_groupsB
Read-only

List ad groups in a campaign.

Inputs: campaign_id (required). Returns: result

get_adsA
Read-only

List text/responsive-search ads in an ad group.

Inputs: ad_group_id (required). Returns: result

get_ad_schedulesA
Read-only

Read a campaign's ad-schedule (dayparting) windows and their time-zone context.

Returns each window (day, from_hour/from_minute, to_hour/to_minute, bid_adjustment) plus the campaign's time_zone and use_searcher_time_zone flag. When use_searcher_time_zone is false, the hours run in the campaign time_zone; remove a window with remove_ad_schedules using its criterion_id.

Inputs: campaign_id (required). Returns: A campaign's full ad-schedule view: the windows plus the time-zone context they…

get_auth_urlA
Read-only

Begin one-time sign-in: returns an OAuth sign-in URL to give the user.

The URL targets the account's identity provider (Microsoft by default, or Google for Google-federated accounts). Present it to the user as a clickable sign-in link, and ask them to sign in with the account that manages the ad account. After they sign in, the browser lands on a near-blank page whose address-bar URL contains a code= value; have them paste that full URL back, then call complete_auth with it.

Only needed once, when no refresh token is configured. The minted token is persisted and auto-refreshed thereafter, so the user never has to repeat this.

Inputs: no named parameters. Returns: result

get_budgetsA
Read-only

Per-campaign budget view (daily budget and any shared-budget id).

Inputs: no named parameters. Returns: result

get_campaignsA
Read-only

List Search campaigns in the configured account.

Inputs: include_deleted. Returns: result

get_conversion_goalsA
Read-only

List conversion goals (all types). Pass goal_ids to fetch specific goals.

Each goal reports exclude_from_bidding — the inverse of the UI's "Include in conversions" checkbox and the single switch for whether the goal steers automated bidding: false means it counts in the Conversions column and ECPC/tCPA bid math, true means it only shows under All conversions. Also surfaces count_type (All/Unique), conversion_window_in_minutes, goal_category, and the revenue model (revenue_type / revenue_value / revenue_currency_code). Confirm exclude_from_bidding is false before relying on a goal to drive spend; flip it with update_conversion_goal.

Inputs: goal_ids. Returns: result

get_device_bid_adjustmentsA
Read-only

Read a campaign's device bid adjustments (Computers / Smartphones / Tablets).

Each row's bid_adjustment is a percent modifier (-100 to 900; -100 excludes the device). An empty list means no device modifier is set, so every device serves at the base bid. Note Microsoft calls mobile "Smartphones". Set one with set_device_bid_adjustment.

Inputs: campaign_id (required). Returns: result

get_effective_url_settingsA
Read-only

Resolve the effective URL tracking (tracking template, Final URL suffix, URL custom parameters) for a campaign or ad group, and which level set each.

URL tracking follows Microsoft's inherited / override order (keyword > ad > ad group > campaign > account): a value set at a deeper level wins, otherwise it inherits from its parent, ultimately the account-level URL options. So a campaign or ad group can show a null tracking template while the account template is what actually applies. This walks that inheritance chain and returns the effective tracking_url_template / final_url_suffix / url_custom_parameters plus a *_source ("ad_group", "campaign", or "account") for each, so you do not have to manually cross-reference get_account_url_options with the per-entity reads. Also surfaces msclkid_auto_tagging_enabled. Use it to answer "what tracking template actually applies here, and where is it set?" before activating a campaign.

Inputs: campaign_id (required), ad_group_id. Returns: The URL tracking that actually applies to an entity, plus the level that set ea…

get_keyword_ideasA
Read-only

Discover keyword ideas from seed phrases and/or a landing-page URL (Keyword Planner).

Each idea reports avg_monthly_searches (+ the monthly history), a rough suggested_bid, and a competition bucket (Low/Medium/High). Provide at least one of keywords or url.

Inputs: url, network, keywords, language, +3 more. Returns: result

get_keywordsA
Read-only

List keywords in an ad group.

Inputs: ad_group_id (required). Returns: result

get_keyword_traffic_estimatesA
Read-only

Estimate weekly traffic (clicks/impressions/cost/position) for keywords at a given bid.

Each keyword's estimate is a min..max bracket at the supplied max_cpc and match type -- useful to gauge search volume and likely spend before launching. (estimate_keyword_bids also returns weekly clicks/impressions/cost, so reach for this when you specifically want the traffic at a bid you choose rather than at the first-page suggested bid.)

Inputs: max_cpc (required), keywords (required), network, language, +2 more. Returns: result

get_location_intentA
Read-only

Read a campaign's location-intent setting (presence vs. broader reach).

Returns the single LocationIntent criterion's intent_option: PeopleIn (only people physically in the targeted locations) or PeopleInOrSearchingForOrViewingPages (people in, searching for, or viewing pages about them; Microsoft's default). Legacy campaigns may still report the deprecated PeopleSearchingForOrViewingPages.

Inputs: campaign_id (required). Returns: result

get_location_targetsB
Read-only

List the location targets/exclusions on a campaign.

Inputs: campaign_id (required). Returns: result

get_negative_keywordsA
Read-only

List negative keywords attached to campaigns or ad groups.

Inputs: entity_ids (required), entity_type, parent_entity_id. Returns: result

get_uet_tagsA
Read-only

List UET tags. Pass tag_ids to fetch specific tags.

Inputs: tag_ids. Returns: result

get_website_exclusionsA
Read-only

List the website / mobile-app-id exclusions (negative sites) blocked on a campaign.

These prevent ads from serving on the listed sites/apps. Add or remove them with add_website_exclusions / remove_website_exclusions.

Inputs: campaign_id (required). Returns: The website / mobile-app-id exclusions (negative sites) blocked on a campaign.…

remove_ad_schedulesA

Remove ad-schedule (dayparting) windows from a campaign by criterion id.

Inputs: campaign_id (required), criterion_ids (required). Returns: Generic result for create/update tools.

remove_location_targetsA

Remove location targets/exclusions from a campaign by criterion id.

Inputs: campaign_id (required), criterion_ids (required). Returns: Generic result for create/update tools.

remove_negative_keywordsA

Remove negative keywords from a campaign or ad group, identified by id.

Resolve ids first with get_negative_keywords (deletion is by id, not text).

Inputs: entity_id (required), keyword_ids (required), entity_type. Returns: Generic result for create/update tools.

remove_website_exclusionsA

Unblock previously excluded websites / app ids on a campaign (matched by URL).

Read-modify-write: the named sites are filtered out and the rest are re-set, so other exclusions are retained. Removal is by URL (case-insensitive), not by id. List the current exclusions first with get_website_exclusions.

Inputs: urls (required), campaign_id (required). Returns: Generic result for create/update tools.

replace_ad_scheduleA

Replace one ad-schedule (dayparting) window: remove the old criterion, add a new one.

The API rejects adding a window that overlaps an existing same-day window, so an in-place edit is impossible; this does the only safe sequence -- remove the old criterion, then add new_window -- in one call. Get the criterion_id from get_ad_schedules. If the remove fails, nothing changes; if the add fails after the remove, the result says so (the old window is gone, so that slot is briefly uncovered until you re-add it).

Inputs: new_window (required), campaign_id (required), criterion_id (required), use_searcher_time_zone. Returns: Generic result for create/update tools.

resolve_postal_codesA
Read-only

Resolve ZIP / postal codes to Microsoft LocationIds (for location targeting).

Downloads and caches Microsoft's geo-locations file on first use.

Inputs: postal_codes (required), language_locale. Returns: result

run_performance_reportA
Read-only

Run a performance report end-to-end and return the parsed rows.

Unlike a raw submit, this submits the report, polls until it is ready, downloads the CSV, and parses it — so the rows come back inline.

Inputs: columns, end_date, account_id, date_range, +4 more. Returns: A completed performance report: its columns and parsed rows.

search_accountsA
Read-only

List every Microsoft Advertising account reachable by the authenticated user.

Inputs: no named parameters. Returns: result

set_account_url_optionsA

Set account-level URL options / tracking template (applies account-wide; every campaign inherits them).

The cleanest way to apply a tracking template (tracking URL template), Final URL suffix, msclkid auto-tagging, or parallel tracking across the whole account at once -- the inherited URL settings -- instead of editing each campaign/ad/keyword. Only the fields you pass change. Read the current values first with get_account_url_options, and use get_effective_url_settings to confirm what a given campaign/ad group resolves to afterward.

Inputs: final_url_suffix, tracking_url_template, ad_click_parallel_tracking, msclkid_auto_tagging_enabled. Returns: Generic result for create/update tools.

set_active_accountA

Switch which account subsequent tool calls read from and write to (this session only).

Use search_accounts to find ids. The OAuth credential is unchanged; this only rescopes calls. Confirm the target with account_health afterwards before any write.

Inputs: account_id (required), customer_id. Returns: object

set_device_bid_adjustmentA

Set a campaign's bid adjustment for one device (e.g. a mobile modifier).

Microsoft calls mobile "Smartphones" (there is no "Mobile"); "Computers" is desktop/laptop. Device criterions are created as a set, so the first time you set any device this also creates the other two at a neutral 0. Read the current values first with get_device_bid_adjustments.

Inputs: device (required), campaign_id (required), bid_adjustment (required). Returns: Generic result for create/update tools.

set_location_intentA

Set who sees a campaign's ads relative to its targeted locations (location intent).

Updates the campaign's single LocationIntent criterion in place (created by Microsoft with a default of "PeopleInOrSearchingForOrViewingPages"). Read the current value first with get_location_intent.

Inputs: campaign_id (required), intent_option (required). Returns: Generic result for create/update tools.

update_ad_groupA

Update an existing ad group in place. Only the fields you pass change.

Inputs: ad_group_id (required), campaign_id (required), name, status, +5 more. Returns: Generic result for create/update tools.

update_call_extensionA

Update an existing call extension in place (e.g. the brand's phone number or tracking).

Microsoft replaces the whole call extension on update, so phone number and country code are always required; when you omit them (e.g. to flip only is_call_tracking_enabled) this tool fetches the current extension and re-sends them, so a single-field toggle is safe.

Inputs: ad_extension_id (required), country_code, is_call_only, phone_number, +1 more. Returns: Generic result for create/update tools.

update_campaignA

Update an existing campaign in place. Only the fields you pass change.

Inputs: campaign_id (required), name, status, max_cpc, +9 more. Returns: Generic result for create/update tools.

update_campaign_statusB

Set a campaign Active or Paused.

Inputs: status (required), campaign_id (required). Returns: Generic result for create/update tools.

update_conversion_goalA

Update a conversion goal in place. Only the fields you pass change.

The key bidding lever is exclude_from_bidding — the inverse of the web UI's "Include in conversions" checkbox. exclude_from_bidding=false keeps the goal in the Conversions column and in automated-bidding math (ECPC / tCPA); true drops it from both (it still reports under All conversions). Confirm this is false before relying on a goal to steer spend. Read the current values first with get_conversion_goals.

Inputs: goal_id (required), name, status, count_type, +5 more. Returns: Generic result for create/update tools.

update_keywordA

Update an existing keyword in place. Only the fields you pass change.

Inputs: keyword_id (required), ad_group_id (required), bid, status, +5 more. Returns: Generic result for create/update tools.

update_responsive_search_adA

Update an existing Responsive Search Ad in place. Only the fields you pass change.

Use this to repoint a Final URL or refresh copy without recreating the ad.

Inputs: ad_id (required), ad_group_id (required), path1, path2, +7 more. Returns: Generic result for create/update tools.

update_structured_snippet_extensionA

Update an existing structured snippet extension in place (edit its header or values).

Microsoft replaces the whole snippet on update, so the header and values are always required; when you omit either (e.g. to change only the values) this tool fetches the current extension and re-sends the other, so a partial update is safe.

Inputs: ad_extension_id (required), header, values. Returns: Generic result for create/update tools.

update_uet_tagB

Update a UET tag's name and/or description in place.

Inputs: tag_id (required), name, description. Returns: Generic result for create/update tools.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.8/5.0

    Scored across 66 tools

    Disambiguation4/5

    Most tools have distinct purposes, with clear CRUD patterns for each entity. A few tools like get_effective_url_settings and get_account_url_options are closely related but well-differentiated in descriptions. Overall, an agent can reliably distinguish tools.

    Naming Consistency5/5

    All tool names follow a consistent verb_noun pattern using snake_case. Examples include search_accounts, get_campaigns, create_campaign, delete_campaign, add_negative_keywords. No mixing of conventions.

    Tool Count3/5

    66 tools is on the higher end, but given the comprehensive coverage of Microsoft Advertising (campaigns, ad groups, keywords, ads, extensions, conversion goals, reports, etc.), the count is borderline. It may be slightly heavy but not excessive.

    Completeness4/5

    The tool set covers a wide range of operations: account management, CRUD for campaigns, ad groups, keywords, ads, extensions, conversion goals, location targeting, ad schedules, device adjustments, reports, and bulk operations. Minor gaps exist (e.g., no single entity getters), but core workflows are well-supported.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      B
      maintenance
      Production-grade MCP server for Microsoft Advertising (Bing Ads) API. Enables Claude to manage Bing/Microsoft Ads accounts with full campaign, ad group, keyword, and performance analysis capabilities.
      374 npm
      4
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      A Model Context Protocol (MCP) server for Microsoft Advertising (Bing Ads / DuckDuckGo Ads). This server enables AI assistants to create, manage, and report on Microsoft Advertising campaigns programmatically.
      2
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      An MCP server for the Microsoft Advertising (Bing Ads) API. Gives an MCP client live read and management access to your Microsoft Advertising accounts — reporting, campaign structure, and full write operations.
      26
      MIT
    • F
      license
      Not graded
      quality
      D
      maintenance
      MCP server enabling AI assistants to manage Microsoft Advertising (Bing Ads) campaigns, including campaigns, ad groups, keywords, and ad extensions via a clean set of tools.
      -