microsoft-ads-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@microsoft-ads-mcp show me my campaigns"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
microsoft-ads-mcp
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
msadsis 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.msadsdoes not declare itspython-dateutildependency, even thoughopenapi_clientimports it. We pinpython-dateutilexplicitly inpyproject.toml.The package installs as the
bingads.*(auth +ServiceClient) andopenapi_client.*(models + exceptions) import namespaces — there is no top-levelmsadsmodule.
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 |
|
|
Reporting |
|
|
Bulk |
|
|
Ad Insight |
|
|
Customer Mgmt / Billing |
|
|
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/.envThis .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 |
| yes | From the developer portal |
| yes | OAuth app (client) id — an Azure app, or a Google Cloud OAuth client when |
| no |
|
| recommended | Run non-interactively; else mint one via the auth tools |
| no | Microsoft web/confidential apps, or the Google OAuth client secret |
| no | Discovered via |
| no |
|
| no |
|
| no |
|
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):
Call
get_auth_url()→ open the URL, sign in.Copy the redirect URL and call
complete_auth(redirect_url).The refresh token is saved to
~/.config/microsoft-ads/tokens.json(mode0600) and reused/auto-refreshed thereafter — so you never need to add it to.envby 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) accepttracking_url_template,final_url_suffix, andurl_custom_parameters(a{key: value}map, referenced in templates as{_key}).create_ad_group/update_ad_groupalso acceptnetwork(ad distribution).create_campaign/update_campaignalso acceptbid_strategy_typeto set the campaign's inline bid strategy (EnhancedCpc,ManualCpc,MaxClicks,MaxConversions,TargetCpa,MaxConversionValue,TargetRoas) with optionalmax_cpc/target_cpa/target_roas— e.g.MaxClicks+max_cpcis Maximize Clicks with a Maximum CPC limit (distinct frombid_strategy_id, which applies a portfolio strategy; set one or the other).Account-level URL options —
set_account_url_optionssets the tracking template, Final URL suffix, andmsclkidauto-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 acceptis_call_tracking_enabled(US/UK) to turn on Microsoft call tracking so call-from-ad conversions are measured — pass it onadd_call_extension, or flip it on an existing asset withupdate_call_extension. New forwarding numbers are local (toll-free is no longer provisioned). They also acceptis_call_only(the "Show just my phone number" call-only mobile format). Sitelinks carry the twodescription1/description2lines (set both or neither); structured snippets carry aheaderfrom Microsoft's predefined list (e.g. "Brands", "Services", "Types") plus 3-10 shortvalues. Theupdate_sitelink_extension/update_structured_snippet_extensiontools 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_extensionremoves any extension type by id — it deletes the account-level object itself, not just a single campaign/ad-group association.get_ad_extensionssurfaces the currentis_call_tracking_enabled/is_call_onlyflags, sitelink descriptions, and snippet header/values.Conversion goals / UET tags —
create_conversion_goaladds a goal: anOfflineConversiongoal (keyed by MSCLKID, no UET tag) or a UET-backed web goal (Url/Event/Duration/PagesViewedPerVisit, which need atag_id). Goals are created active (a goal doesn't spend; a paused one silently fails to record).update_conversion_goaledits a goal in place: rename, setstatus, and (most launch-relevant) toggleexclude_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 setscount_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_conversionsis the bid-eligible path — filter the call-center log yourself (e.g. calls ≥60s), then upload qualifying calls by MSCLKID against anOfflineConversiongoal whose name matchesconversion_name.update_uet_tagrenames/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 unlessuse_searcher_time_zoneis 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 usereplace_ad_schedule(which removes the old criterion then adds the new one — the only safe order) rather than adding over it.update_campaignacceptstime_zoneto 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-gatedDevelopment
uv run ruff check . && uv run ruff format --check .
uv run ty check
uv run pytest -q
# or all at once:
bash scripts/ci.shMCP 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_mcpEither 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 toolsaccount_healthARead-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_sitelink_extensionA
Create a sitelink extension and optionally attach it to a campaign or ad group.
Inputs: final_url (required), display_text (required), entity_id, description1, +2 more. 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_downloadARead-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_bidsARead-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_bidsARead-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_optionsARead-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_extensionsARead-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_groupsBRead-only
List ad groups in a campaign.
Inputs: campaign_id (required). Returns: result
get_adsARead-only
List text/responsive-search ads in an ad group.
Inputs: ad_group_id (required). Returns: result
get_ad_schedulesARead-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_urlARead-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_budgetsARead-only
Per-campaign budget view (daily budget and any shared-budget id).
Inputs: no named parameters. Returns: result
get_campaignsARead-only
List Search campaigns in the configured account.
Inputs: include_deleted. Returns: result
get_conversion_goalsARead-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_adjustmentsARead-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_settingsARead-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_ideasARead-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_keywordsARead-only
List keywords in an ad group.
Inputs: ad_group_id (required). Returns: result
get_keyword_traffic_estimatesARead-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_intentARead-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_targetsBRead-only
List the location targets/exclusions on a campaign.
Inputs: campaign_id (required). Returns: result
get_negative_keywordsARead-only
List negative keywords attached to campaigns or ad groups.
Inputs: entity_ids (required), entity_type, parent_entity_id. Returns: result
get_uet_tagsARead-only
List UET tags. Pass tag_ids to fetch specific tags.
Inputs: tag_ids. Returns: result
get_website_exclusionsARead-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_codesARead-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_reportARead-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_accountsARead-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_sitelink_extensionA
Update an existing sitelink extension in place (e.g. add or edit its descriptions).
Microsoft replaces the whole sitelink on update, so display text and the final URL are always required; when you omit any field (e.g. to add only descriptions to an existing sitelink) this tool fetches the current extension and re-sends it, so a partial update is safe. Microsoft requires the two description lines together — a sitelink with description1 must also have description2.
Inputs: ad_extension_id (required), final_url, description1, description2, +1 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
Scored across 66 tools
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.
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.
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.
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
Related MCP Connectors
Microsoft Ads MCP server: 11 tools for search, PMax, Google Ads imports. Read-only by default.
Google Ads MCP server — manage campaigns, keywords, and metrics.
MCP server for querying and analyzing data from ad platforms, analytics tools, and spreadsheets
Hosted MCP server for marketing agencies: connect Google Ads, GA4, Search Console, Meta, LinkedIn and more to Claude or ChatGPT. Pull reports and make changes via OAuth login, no setup.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceProduction-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 npm4MIT
- AlicenseNot gradedqualityDmaintenanceA 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.2MIT
- AlicenseAqualityDmaintenanceAn 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.26MIT
- FlicenseNot gradedqualityDmaintenanceMCP 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.-