mcp-microsoft-ads
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| MS_ADS_TENANT | No | Tenant (overrides credentials file) | |
| MS_ADS_MAX_CPC | No | Bid/tCPA rail ceiling, in the account's own currency (default: 50) | |
| MS_ADS_CLIENT_ID | No | Client ID (overrides credentials file) | |
| MS_ADS_ACCOUNT_ID | No | Account ID (overrides credentials file) | |
| MS_ADS_AUDIT_PATH | No | JSONL audit log location (default: ~/.mcp-microsoft-ads/audit.jsonl) | |
| MS_ADS_CUSTOMER_ID | No | Customer ID (overrides credentials file) | |
| MS_ADS_CLIENT_SECRET | No | Client secret (overrides credentials file) | |
| MS_ADS_ENABLE_WRITES | No | Master switch for all mutating tools (default: false) | |
| MS_ADS_DEVELOPER_TOKEN | No | Developer token (overrides credentials file) | |
| MS_ADS_CREDENTIALS_PATH | No | Credentials file location (default: ~/.mcp-microsoft-ads/credentials.yaml) | |
| MS_ADS_MAX_DAILY_BUDGET | No | Daily-budget rail ceiling, in the account's own currency (default: 1000) | |
| MS_ADS_ADVERTISER_CONFIG | No | Optional advertiser settings file (default: ~/.mcp-microsoft-ads/advertiser.yaml) | |
| MS_ADS_DRAFT_TTL_SECONDS | No | How long a draft stays valid before being refused (default: 3600) | |
| MS_ADS_ALLOW_APPLY_RECOMMENDATION | No | Second, 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
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| 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:
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; |
| 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:
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
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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