Skip to main content
Glama
wvuhskr

mcp-microsoft-ads

by wvuhskr

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault
MS_ADS_TENANTNoTenant (overrides credentials file)
MS_ADS_MAX_CPCNoBid/tCPA rail ceiling, in the account's own currency (default: 50)
MS_ADS_CLIENT_IDNoClient ID (overrides credentials file)
MS_ADS_ACCOUNT_IDNoAccount ID (overrides credentials file)
MS_ADS_AUDIT_PATHNoJSONL audit log location (default: ~/.mcp-microsoft-ads/audit.jsonl)
MS_ADS_CUSTOMER_IDNoCustomer ID (overrides credentials file)
MS_ADS_CLIENT_SECRETNoClient secret (overrides credentials file)
MS_ADS_ENABLE_WRITESNoMaster switch for all mutating tools (default: false)
MS_ADS_DEVELOPER_TOKENNoDeveloper token (overrides credentials file)
MS_ADS_CREDENTIALS_PATHNoCredentials file location (default: ~/.mcp-microsoft-ads/credentials.yaml)
MS_ADS_MAX_DAILY_BUDGETNoDaily-budget rail ceiling, in the account's own currency (default: 1000)
MS_ADS_ADVERTISER_CONFIGNoOptional advertiser settings file (default: ~/.mcp-microsoft-ads/advertiser.yaml)
MS_ADS_DRAFT_TTL_SECONDSNoHow long a draft stays valid before being refused (default: 3600)
MS_ADS_ALLOW_APPLY_RECOMMENDATIONNoSecond, separate switch required for apply_recommendation (default: false)

Instructions

Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.

This server publishes no instructions, or was last inspected before Glama recorded them.

Capabilities

Features and capabilities supported by this server

Protocol revision2025-11-25

CapabilityDetails
tools
{
  "listChanged": false
}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
update_ad_groupA

Draft an ad-group update. target_cpa sets an explicit MaxConversions tCPA on the ad group (MS Search campaigns on the development account run MaxConversions with no target — this INTRODUCES one). cpc_bid is allowed ONLY when the ad group's effective bid strategy is in the MANUAL_BIDDING allowlist (rails.py) — same fail-closed guard as update_keyword_bid; MS silently ignores fixed bids under Smart Bidding. Returns draft; apply with confirm_and_apply (policy rails re-checked at apply).

NOT live-verified through this tool itself. The underlying blank()-built UpdateAdGroups call IS live-proven (2026-07-28, Status flip on a z. ad group via pause/enable_entity); the cpc_bid / target_cpa branches have never run live.

draft_responsive_search_adB

Draft a Responsive Search Ad. 3-15 headlines, 2-4 descriptions. Ad copy claims MUST be verified against the advertiser's website before use — don't claim anything not actually present on the site (site-verified-claims policy). Blocklist (settings-configured blocked_terms) enforced on every headline/description/path. MS RSAs have no per-ad paused state on create — if isolation is needed, pause the parent ad group first; preview warns when it's Active (new ad serves once approved).

Live-verified 2026-07-28 — ad created in a z. ad group, read back with Type="ResponsiveSearch".

create_custom_audienceB

Draft a remarketing list targeting site visitors (needs the account's UET tag; errors if none exists — create one in the MS Ads UI first). url_contains: substring the visited page URL must contain; defaults to the configured advertiser_domain setting when omitted — error naming both if neither is set. A whitespace-only url_contains is rejected outright (not treated as omitted).

Live-verified 2026-07-30.

add_audience_targetingA

Draft audience targeting on a campaign. audience_type disambiguates which id namespace audience_id is in — one of: RemarketingList, Custom, InMarket, Product, SimilarRemarketingList, CombinedList, CustomerList, ImpressionBasedRemarketingList, CustomSegment (WSDL AudienceType enum). bid_adjustment_pct is allowed ONLY when the campaign's effective bid strategy is in the MANUAL_BIDDING allowlist (rails.py); rejected for Smart Bidding, any other/unrecognized strategy, or when the strategy can't be determined at all (fail closed — standing no-%-adjustments-on-auto-bidding rule).

Live-verified 2026-07-30 (the AddCampaignCriterions write path, CriterionType= "Audience"); the read-side fault seen earlier is GET-only and doesn't apply here.

update_campaignA

Draft a campaign update (budget / status Active|Paused / tCPA). tCPA at campaign level is only operative on PMax — for Search use update_ad_group. Returns a draft; apply with confirm_and_apply.

NOT live-verified through this tool itself. The underlying blank()-built UpdateCampaigns call IS live-probed (2026-07-30, DailyBudget-only change), via the same pattern live-proven through status.py's ad_group Status flip (2026-07-28), not through update_ad_group itself.

draft_campaignB

Draft a new campaign — ALWAYS created Paused. campaign_type: Search | PerformanceMax. time_zone/language default to EasternTimeUSCanada/English (US-centric defaults) — override for other markets.

Live-verified 2026-07-30 (AddCampaigns create path, proven twice). NOT live-verified with non-default language values.

confirm_and_applyA

Execute a previously drafted write. The ONLY path that mutates the account.

Live-verified from 2026-07-28 onward — the only mutation path in this server; every live write in the project flowed through it.

create_conversion_actionA

Draft a conversion goal. goal_type: Event | Url | Duration (only supported values on the development account). Each goal_type REQUIRES its matching match-criteria arg — without one the goal creates "successfully" but can never record a conversion:

  • Url: url_expression (matched with UrlOperator="Contains")

  • Event: action_expression (matched with ActionOperator="Contains"; category/label expressions are out of scope here)

  • Duration: minimum_duration_seconds goal_category (ConversionGoalCategory enum) is REQUIRED live even though the WSDL marks it optional — omitting it faults "InvalidGoalCategory" (3347). Defaults to "Other", the only value live-verified valid across all three goal types (Task 25e probe); PageView also works on Url/Event but faults InvalidCategoryForGoalType (3349) on Duration. A category already claimed by an ACTIVE MS auto-created goal on the same UET tag faults SameCategoryAndTagNotAllowedForAutoGoalAndManualGoal (5667) — observed on Url goals only; the live acceptance matrix never exercised Event or Duration for this collision. The fault is keyed on category+tag rather than goal type, so it plausibly extends to Event/Duration too, but that's inference, not a tested fact — account state decides, not goal type. Lead-gen-meaningful values: SubmitLeadForm, BookAppointment, RequestQuote, Contact. NOTE: OfflineConversion goals are deliberately not created by this tool — create them in the MS Ads UI instead — because accounts that feed offline conversions from an external upload pipeline risk double-counting. ONE-WAY DOOR (Task 25f): there is no DeleteConversionGoals operation on this API (verified against the WSDL — every other entity family has a Delete*, this one doesn't) and UpdateConversionGoals(Status="Deleted") silently no-ops (partial_errors=[], goal reads back Active still) — so a created goal cannot be deleted. The only real levers are Status="Paused" and ExcludeFromBidding=True (both verified by read-back); full removal requires the MS Ads UI.

Live-verified 2026-07-30.

set_conversion_action_primary_statusB

Draft a campaign-goal association. MS models 'primary for this campaign' as a campaign conversion goal association (associate = campaign optimizes to it; disassociate = falls back to account-level goals).

Live-verified 2026-07-30, both directions.

get_entitiesA

Fetch entity state. entity: campaigns | ad_groups | keywords | ads | asset_groups | campaign_criterions. parent_id required for all but campaigns. campaign_criterions returns DayTime, Location, LocationIntent, Radius criterion types (Audience not readable via this API). NOTE: campaign BiddingScheme reads as None on MS — effective strategy lives on ad_groups (BiddingScheme.InheritedBidStrategyType).

draft_sitelinksA

Draft sitelinks on a campaign. sitelinks: [{text, url, description1?, description2?}]. Copy claims must be site-verified (the advertiser's website).

Live-verified 2026-07-30.

On a mid-apply failure (association raises after the library add landed) the error carries every landed ID (extension_ids) — reconcile account state before retrying (a retry re-runs ALL steps, including re-adding the extensions).

create_calloutsB

Draft callout extensions (max 25 chars each).

Live-verified 2026-07-30.

On a mid-apply failure (association raises after the library add landed) the error carries every landed ID (extension_ids) — reconcile account state before retrying (a retry re-runs ALL steps, including re-adding the extensions).

create_structured_snippetsB

Draft a structured snippet (header must be an MS-supported header, e.g. 'Services'; 3+ values required).

Live-verified 2026-07-30.

On a mid-apply failure (association raises after the library add landed) the error carries every landed ID (extension_ids) — reconcile account state before retrying (a retry re-runs ALL steps, including re-adding the extension).

remove_extensionA

Draft removal: disassociate from campaign, then delete from library.

Live-verified 2026-07-30.

If the disassociation itself (step 1) comes back with a batch error, nothing landed — the apply aborts and the delete is never attempted (aborted=... in the result), nothing to reconcile.

On a mid-apply failure (delete raises after disassociation landed) the error carries what landed (disassociated=True, deleted=False) — reconcile account state before retrying (a retry re-runs ALL steps, including re-disassociating).

search_geo_targetsA

Search targetable/excludable locations by name (MS ships a file, not a query API; cached locally 30 days). Returns location ids for geo write tools.

exclude_geo_targetB

Draft a location EXCLUSION on a campaign. location_id from search_geo_targets.

Live-verified 2026-07-30.

remove_geo_targetA

Draft removal of a campaign criterion by id, via DeleteCampaignCriterions with CriterionType="Targets" — the Targets group covers Location, DayTime, Radius, and other campaign-criterion types, so this deletes whatever criterion id is passed, not Location only. ids from get_entities('campaign_criterions', parent_id=campaign_id).

Live-verified 2026-07-30.

health_checkA

Verify auth chain: token refresh + Customer Management GetUser round-trip.

list_accountsA

List ALL advertiser accounts visible to the authenticated user (paged SearchAccounts under the hood; truncated is true only if the 1000-account safety cap was hit — raise MAX_PAGES if that ever happens for real).

get_account_infoB

Authenticated user + roles + configured account/customer ids.

discover_keywordsB

Keyword ideas from seed keywords and/or a landing-page URL (Ad Insight GetKeywordIdeas). Scoped to the location/language/network configured via advertiser settings' keyword_research block (defaults: US/English/Owned&Operated+Syndicated).

get_keyword_forecastsB

Traffic estimates (clicks/impressions/CPC/cost) for candidate keywords at a given max CPC (defaults to the account's MS_ADS_MAX_CPC cap). Scoped to the location/language/network configured via advertiser settings (see discover_keywords).

list_recommendationsB

Account recommendations (MS Ad Insight GetRecommendations). recommendation_type is passed through as-is to MS — live probing found the API rejects a null type but every documented RecommendationType enum value also faulted (607, account/feature gating, not a code issue); pass whatever type MS has enabled for this account.

NOT live-verified — 607-gated on the development account, as above.

apply_recommendationA

Draft applying an MS recommendation (mutates the account — rails apply).

NOT live-verified — every RecommendationType faults 607 InvalidOpportunityType on the development account (account-level gating). Fakes only.

Also gated by MS_ADS_ALLOW_APPLY_RECOMMENDATION (rails.py), in addition to the global MS_ADS_ENABLE_WRITES — its monetary effect can't be bounded by the budget/bid caps, so it needs its own opt-in on top of the general write gate.

dismiss_recommendationB

Draft dismissing an MS recommendation.

NOT live-verified — same 607 gate as apply_recommendation. Fakes only.

draft_keywordsA

Draft new keywords under an ad group — created Paused. keywords: [{text, match_type: Broad|Phrase|Exact, bid: optional float}]. Blocklist (settings-configured blocked_terms) + bid cap (MS_ADS_MAX_CPC) enforced.

Live-verified 2026-07-28 (one paused keyword added to a z. ad group).

update_keyword_bidA

Draft a keyword CPC bid change (MS_ADS_MAX_CPC cap enforced). Allowed ONLY when the ad group's effective bid strategy is in the MANUAL_BIDDING allowlist (rails.py). Pre-rejected for Smart Bidding: UpdateKeywords silently ignores Bid there instead of erroring (live-verified — empty PartialErrors, bid unchanged on read-back), so this must be caught before the call, not after. Also rejected, fail closed, for any other unrecognized strategy or when the effective strategy can't be determined at all.

NOT live-verified for a landed bid change — the manual-bidding write path has never run live (no manual-bidding ad group exists on the development account).

remove_keywordsC

Draft keyword deletion (irreversible — pause_entity is the reversible lever).

Live-verified 2026-07-28.

upload_image_assetA

Draft an image upload to the account media library (for image extensions / PMax asset groups). Returns media id on apply. PNG/JPEG.

media_type is the image's aspect ratio, one of:

  • Image1x1: square (e.g. 1200x1200)

  • Image191x100: 1.91:1 landscape (e.g. 1200x628)

  • GenericImage

  • Image4x1

Live-verified 2026-07-30, after fixing two live faults (MediaType is an aspect-ratio label; the AddMedia response comes back unwrapped — both noted above). AddMedia also dedupes byte-identical images: re-uploading returns the existing media id.

get_negative_keywordsA

Campaign-level negatives + ALL shared negative keyword lists (with items and campaign associations). Always call before recommending/adding negatives.

add_negative_keywordsA

Draft negatives. scope: campaign | ad_group | shared_list. keywords: [{text, match_type: Phrase|Exact}]. Checks existing negatives first and skips duplicates (check-existing-lists-first rule). Blocklist NOT applied — negating a settings-blocked term is desired. scope='ad_group' REQUIRES campaign_id (the parent campaign) — MS's read API hard-faults for AdGroup scope without it.

Live-verified 2026-07-28 at ad_group scope, including the duplicate-check read; campaign scope not separately smoked.

remove_negative_keywordsA

Draft negative removal by id. scope: campaign | ad_group | shared_list. keyword_ids come from get_negative_keywords. scope='ad_group' REQUIRES campaign_id for consistency with add_negative_keywords — validation only, DeleteNegativeKeywordsFromEntities has no parent field per WSDL.

Live-verified 2026-07-28 at ad_group scope; campaign scope not separately smoked.

create_pmax_campaignA

Draft a Performance Max campaign + first asset group — created PAUSED. time_zone defaults to EasternTimeUSCanada (US-centric default) — override for other markets. target_cpa is required by default (require_pmax_target_cpa setting, default true) — never create PMax without a target; removing one later explodes impressions (hard rule observed on the owner's Google Ads account). Set require_pmax_target_cpa false in advertiser settings to allow a no-target MaxConversions campaign instead. Images are REQUIRED (upload via upload_image_asset first). Each image_media_ids entry must already be in the account media library with MediaType Image1x1 or Image191x100; GenericImage, Image4x1, unknown types, and absent ids raise. Aborts the asset-group add if the campaign add fails. Returns a draft; apply with confirm_and_apply.

Live-verified 2026-07-30 — with an explicit target_cpa: campaign + asset group created and read back with both images carrying the correct per-image SubType; ImageAsset.Id = media id write-proven. NOT live-verified — the require_pmax_target_cpa=false / no-target_cpa path: only exercised against fakes, never run against the live API through this tool. NOT live-verified with non-default time_zone values.

On a mid-apply failure (asset group add raises after the campaign landed) the error carries every landed ID (campaign_ids) — reconcile account state before retrying (a retry re-runs ALL steps, including re-creating the campaign).

create_portfolio_bidding_strategyA

Draft a portfolio bid strategy (account library, shared across campaigns). strategy_type: MaxConversions | MaxClicks | TargetImpressionShare. Returns a draft; apply with confirm_and_apply.

Live-verified 2026-07-30.

list_extensionsA

Ad extensions. campaign_id=None: full account library (sitelinks, callouts, structured snippets...). campaign_id set: only extensions associated with that campaign.

get_conversion_actionsD

Conversion goals (nil ids returns all goals of the given types).

get_policy_issuesA

Disapproved ads + keywords in an ad group (editorial review failures).

remove_entityA

Draft PERMANENT deletion of campaign | ad_group | keyword | ad. Irreversible — prefer pause_entity. ad_group needs parent_id=campaign id; keyword/ad need parent_id=ad group id.

Live-verified: ad branch 2026-07-28, campaign/keyword branches 2026-07-30. NOT live-verified: ad_group branch (fixed by analogy only).

get_campaign_performanceC

Campaign performance (impressions/clicks/spend/conversions/CPA).

get_ad_performanceD

Per-ad performance.

get_keyword_performanceC

Per-keyword performance incl. QualityScore.

get_geo_performanceD

Geographic performance.

get_search_termsD

Search query report.

run_reportA

Arbitrary report (replaces Google run_gaql for metrics pulls). report_type = exact v13 request type name e.g. 'AgeGenderAudienceReportRequest'.

aggregation: "Daily" (default) works with any column set. "Summary" is only valid when columns exclude TimePeriod — the live API raises InvalidTimePeriodColumnForSummaryReport otherwise. days must be 7, 14, or 30.

start_date/end_date (YYYY-MM-DD, both or neither) use an explicit custom date range instead of days — no length restriction. Live-verified 2026-08-14 (106-day AccountPerformance custom-range pull).

set_campaign_scheduleA

REPLACE a campaign's ad schedule. week = 7 dicts {day, from_hour, to_hour} (0-23 hour clock; to_hour is the hour service stops, e.g. to_hour=22 serves until 22:00). MUST cover all 7 days — a campaign with any DayTime rows serves only inside them, so a partial week silently narrows delivery. Deletes existing DayTime criterions then adds the new set. Returns a draft; apply with confirm_and_apply.

Live-verified 2026-07-30 including the REPLACE delete path (7 rows deleted/7 added, read-back verified).

On a mid-apply failure (add or the verify readback raises after the existing schedule was deleted) the error carries every landed ID (deleted_criterion_ids, added_criterion_ids) — reconcile account state before retrying (a retry re-runs ALL steps, including re-deleting whatever the previous attempt left).

pause_entityB

Draft a pause. entity_type: campaign | ad_group | keyword | ad. keyword/ad need parent_id = ad group id; ad_group needs parent_id = campaign id.

Live-verified 2026-07-28: ad_group branch (Paused→Active→Paused on a z. ad group, read-back verified). NOT live-verified: campaign/ad/keyword branches (fixed by analogy only).

enable_entityC

Draft an enable (Status=Active). Same shape as pause_entity.

Live-verified 2026-07-28: ad_group branch (same probe as pause_entity). NOT live-verified: campaign/ad/keyword branches (fixed by analogy only).

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

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/wvuhskr/mcp-microsoft-ads'

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