C:\Users\Guest\site.exe

This site like Pulkit is a forever work in progress. Changes are ad hoc and never consistent.

C:\Users\Guest> type blog\i-open-sourced-a-bing-webmaster-mcp-server-it-broke-in-four-interesting-ways.txt

I Open-Sourced a Bing Webmaster MCP Server. It Broke in Four Interesting Ways.

28 Aug 2026

Why I built this

I run an SEO agency UR Digital, which means we live inside Bing Webmaster Tools and Google Search Console every day, across dozens of client sites. Every one of those tools is a dashboard you have to log into, click through, and manually read. That’s fine for the occasional check-in, but it’s a poor fit for the way I actually want to work now: describing what I need in plain language and having an AI agent go fetch it, cross-reference it, and summarise it for me.

That’s what MCP (Model Context Protocol) is for. It’s an open standard that lets AI assistants like Claude connect to external tools and data sources — think of it as a plug that lets an AI say “let me actually go check Bing Webmaster Tools” instead of guessing or telling you it can’t help. Anthropic and the wider ecosystem publish MCP servers for things like GitHub, Slack, and Google Drive, but there wasn’t one for Bing Webmaster Tools. So I decided to build one, open source it, and write up exactly how it went — bugs, dead ends, and all — because most tutorials skip the part where things actually break.

This post covers both sides of that: the practical “here’s how to use it” for anyone who just wants Bing data flowing into their AI workflow, and the full story of building, testing, and publishing it for anyone thinking about doing something similar.

What it actually does

The end result is @urdigital/mcp-server-bing-webmaster — a small server that exposes nine Bing Webmaster Tools operations to any MCP-compatible AI client (Claude Desktop, Claude Code, and others):

ToolWhat it does
bing_list_sitesLists every site verified under your Bing Webmaster account
bing_get_traffic_statsImpressions, clicks, and rank position over time for a site
bing_get_query_statsWhich search queries are driving traffic
bing_get_page_statsImpressions and clicks broken down by individual page
bing_get_crawl_issuesCrawl errors Bing has logged for a site
bing_get_url_infoWhat Bing knows about one specific URL
bing_submit_urlAsks Bing to (re)crawl a specific page
bing_submit_sitemapRegisters or resubmits a sitemap
bing_get_keyword_statsEstimated search volume for a keyword

Once it’s set up, you can ask Claude things like “which pages on our client’s site have crawl issues?” or “what’s our estimated search volume for this keyword?” and it goes and actually checks, live, instead of working from memory or asking you to go look it up yourself.

The practical part: how to use it

If you just want to use it, this is the whole setup.

1. Get a Bing Webmaster API key. Sign in at bing.com/webmasters, click your site, then the settings gear icon → API Access. Copy the key shown there.

2. Add it to your MCP client’s config. For Claude Desktop, that’s claude_desktop_config.json:

{
  "mcpServers": {
    "bing-webmaster": {
      "command": "npx",
      "args": ["-y", "@urdigital/mcp-server-bing-webmaster"],
      "env": { "BING_WEBMASTER_API_KEY": "your-api-key-here" }
    }
  }
}

3. Restart your client. That’s it — no build step, no install, npx fetches and runs it fresh. Ask your AI assistant a question about your Bing Webmaster data and watch it actually go check.

One thing worth knowing if you manage multiple sites under one Bing account, especially in an agency setup: your API key’s permissions are tied to your role on each individual site. If you’re a read-only user on a site (common if a client shared it with you, or another team member added it), you can pull all the reporting data you want, but write actions like submitting a URL or a sitemap will fail with a NotAuthorized error — not because anything’s broken, but because Bing genuinely doesn’t let you do it. More on how I discovered that the hard way, below.

The build: decisions that mattered

I built this as a TypeScript project using the official @modelcontextprotocol/sdk, structured as a small monorepo (multiple related packages living in one repository) rather than a single standalone package — even though right now it’s “just” the Bing Webmaster server. The reasoning: I already know I want to add WordPress and Microsoft Clarity servers next, and eventually Shopify, Squarespace, and Cloudflare Analytics. A monorepo with a shared internal package for HTTP handling and environment variable helpers means each new integration is “copy a folder, swap the API client” rather than re-solving the same plumbing every time. This is the same structural pattern used by large multi-package open-source projects.

Two design decisions in the actual server code are worth calling out, because they came from thinking about how an AI model — not a human — would actually use this:

Every tool has a detailed description, not just a name. An AI client picks which tool to call based on its description text. bing_get_keyword_stats isn’t just labeled “get keyword stats” — its description explains it returns estimated search volume, because that distinction matters for how a model should phrase results back to a user.

Every input is validated with a schema (using Zod) before it ever reaches the API. If someone’s AI assistant tries to call bing_submit_sitemap without a valid URL, it fails immediately and clearly, rather than sending a malformed request to Bing and returning a confusing error three layers removed from the actual mistake.

I built against Bing’s JSON/HTTP REST API specifically, not the older SOAP/POX endpoints — a detail that turned out to be well-timed, since Microsoft will retire those legacy endpoints on August 31, 2026. At the time of publishing this, it is still available.

Testing against a real account: what actually broke

Code that compiles and even runs isn’t the same as code that works. I tested every single one of the nine tools against a real, live Bing Webmaster account — one used to manage dozens of client sites — and three things came up that are worth documenting.

“AvgClickPosition: -1” isn’t an error

Query and page stats both return an AvgClickPosition field. On low-traffic pages, this frequently comes back as -1 instead of a number, and my first reaction was that something was broken. It isn’t — it’s a sentinel value. AvgClickPosition is the average search-result position at the moment someone clicked. If nobody clicked (which the same response’s Clicks: 0 confirms), there’s no position to average, so Bing returns -1 rather than a real number. AvgImpressionPosition, by contrast, is always populated, because it reflects where the page ranked when it merely appeared in results, click or no click. Once you know the pattern, it’s obvious — but nothing in the API reference calls it out explicitly.

The page stats endpoint mislabels its own data

Calling GetPageStats returns objects typed "__type": "QueryStats:..." with a field literally called "Query" — except the value in that field is a page URL, not a search query. Bing’s page-level and query-level stats endpoints reuse the exact same underlying object shape, and nobody updated the type name for the page-stats case. Harmless once you know it, but confusing the first time you see a page URL sitting in a field called “Query.”

HttpStatus: 0 is a known, decade-old quirk

Calling GetUrlInfo on a real, live, recently-crawled PDF returned HttpStatus: 0 — which looks like it should mean something went wrong. It doesn’t. I found Microsoft’s own official documentation examples from as far back as 2011 showing this exact same HttpStatus: 0 on valid responses, and an active 2026 Microsoft support thread where another developer asked Microsoft’s own team to explain it, without a clear answer. It’s just a field the API has apparently never reliably populated. DiscoveryDate and LastCrawledDate — the fields that actually matter for most use cases — worked correctly.

The real bug: a permissions error that looked like a code bug

This is the one that took the longest to run down, because the first fix I tried genuinely fixed something — just not the actual problem.

Submitting a URL for crawling (bing_submit_url) failed with:

ApiError: Request to .../SubmitUrl?apikey=...&siteUrl=... failed with 400
{ ErrorCode: 14, Message: 'ERROR!!! NotAuthorized' }

My first hypothesis, based on other developers hitting the identical error message, was a code bug: the request was sending siteUrl in both the URL’s query string and the JSON body, when Bing’s API expects it only in the body for this endpoint. That was a real bug, and I fixed it. But the exact same error came back afterward.

The actual giveaway was the pattern, not the error message: every single read operation against that site — traffic stats, query stats, crawl issues, page stats, URL info — worked without a single failure. Only the write action failed. Reads succeeding while writes fail with “NotAuthorized” is a permissions signature, not a formatting one. It turned out the account being used had read-only access to that particular site (a common setup when you’re managing sites on behalf of others, or a site was shared with your account rather than owned by it). Switching the test to a site where the account had full administrator access, the exact same code worked immediately.

The lesson generalises past this one API: when you’re debugging an authorisation error, check whether some operations succeed and others don’t before assuming the whole request is malformed. A pattern of “reads work, writes fail” almost always points at a permissions boundary, not a bug in the request itself.

Publishing it: the unglamorous part

Getting the code working was maybe 60% of the actual effort. The rest was in getting it onto GitHub and npm cleanly — and this is the part most “how to build an MCP server” tutorials skip entirely, because it’s not interesting until it breaks.

Mistake one: node_modules got committed to Git. After running git init, git add ., and git commit, a routine check (git ls-files | findstr /i env, run to make sure no secrets had snuck in) revealed that hundreds of files from the project’s node_modules folder — third-party dependency code, not our own — had been committed. The cause turned out to be simple: the .gitignore file that was supposed to exclude node_modules had never actually been created in the project folder. No .gitignore, no exclusion rules, so Git dutifully tracked everything, including megabytes of dependency code that has no business living in a Git repository.

Mistake two, layered on top: the fix itself was silently broken. After creating a .gitignore file with the standard exclusion rules and re-running git status, node_modules was still showing up — not as ignored, but as “untracked.” The actual cause: the .gitignore file’s contents had leading spaces on every line, picked up from how the text had been pasted into a text editor. Git’s .gitignore syntax treats leading whitespace as part of the pattern, so a line reading node_modules/ (with three spaces before it) tells Git to ignore a folder literally named " node_modules" with a leading space — which doesn’t exist — rather than the real node_modules folder. Rewriting the file with no leading whitespace fixed it immediately. It’s a genuinely easy mistake to make and a genuinely easy one to miss, since most text editors don’t visually distinguish “three spaces” from “no spaces” at a glance.

Both were fixed with git rm -r --cached node_modules (which untracks a folder from Git without touching the actual files on disk) followed by a clean commit — but it’s a good reminder to check git status carefully after your very first commit on any new project, before pushing anything anywhere.

The last snag: registry propagation lag. After successfully running pnpm publish --access public for both the shared internal package and the actual Bing Webmaster server — both showing clean success output — installing the freshly published package with npx -y @urdigital/mcp-server-bing-webmaster from a completely different folder returned a 404, as if the package didn’t exist. The npm package page on the website loaded it perfectly. The raw registry API, queried anonymously, returned it perfectly. But the authenticated path — which is what your logged-in npm/npx CLI always uses — was still catching up. This is a known characteristic of npm’s registry infrastructure: a brand-new scope’s very first published package can take several minutes (occasionally longer) to propagate fully across every access path, even though it’s already live and visible elsewhere. The fix was simply patience — waiting roughly ten minutes and retrying confirmed everything was actually fine the whole time.

What I’d tell someone doing this for the first time

  • Test against a real account before you trust the code. Every one of the four issues above — the sentinel values, the mislabeled fields, the actual bug, and the permissions gotcha — only surfaced by running real tool calls against a real, messy, multi-site account. A server that compiles cleanly and passes a syntax check can still be completely wrong about how an external API actually behaves in practice.
  • A pattern across multiple calls tells you more than any single error message. The permissions issue wasn’t solved by reading the error text more carefully — it was solved by noticing that reads succeeded and writes didn’t, across several different tool calls.
  • Check git status immediately after your first commit, not after your tenth. A missing or broken .gitignore is invisible until you look, and the earlier you catch it, the less history you have to clean up.
  • If a freshly published package 404s right after publishing, don’t assume the publish failed. Check the package’s website page and the raw registry API directly before concluding something’s broken — propagation lag is common and easy to mistake for a real error.

Why a monorepo, specifically, and not three separate repos

It’s worth explaining this decision a bit more, because it’s the kind of thing that’s easy to get wrong at the start and expensive to fix later. The alternative to a monorepo — a completely separate GitHub repository and npm package for every single integration — sounds simpler on paper. In practice, it means every new integration starts from zero: its own repo setup, its own .gitignore, its own build config, its own copy-pasted HTTP client code slightly diverging from the last one over time.

A monorepo with one shared internal package (@urdigital/mcp-server-shared, in this case) holding the HTTP request logic and environment variable handling means that logic gets written once and reused everywhere. When the Bing Webmaster server needed retry-with-backoff behavior for rate-limited requests, that lived in the shared package — so the WordPress and Clarity servers, once built, inherit it automatically rather than needing the same fix applied three separate times in three separate places.

The tradeoff is that publishing gets one extra step: shared internal packages have to be published to npm in the correct order, since a package depending on another package that doesn’t exist yet on the registry will fail to install for anyone else. That’s a small, one-time cost against the ongoing cost of maintaining near-duplicate code across separate repos indefinitely.

A note on the account structure this was tested against

It’s worth being specific about what “tested against a real account” actually meant here, because it shaped which bugs surfaced. This wasn’t a single personal website — it was an agency-style Bing Webmaster account with visibility into dozens of separate client domains, a mix of fully verified and pending-verification sites, and a mix of full-administrator and read-only access levels across different sites. That’s a meaningfully messier and more realistic test environment than a single demo site would have been, and it’s exactly why the permissions bug surfaced at all — a single-site personal account, where you’re always the owner, would never have hit it.

If you’re building or testing your own integration against an API like this, the lesson generalises: test against the messiest real account you have access to, not the cleanest one. Edge cases hide in the mess.

What’s next

Nine tools cover the core of what most SEO workflows need from Bing Webmaster Tools, but there’s more of the API left to wire up: link and backlink data, full content submission (not just URL submission), submission quota checks, and page removal requests. WordPress and Microsoft Clarity servers are next in the same monorepo, followed by Shopify, Squarespace, and Cloudflare Analytics.

The whole thing is open source and MIT-licensed. If you manage Bing Webmaster Tools for client sites and want your AI assistant to actually go check the data instead of guessing, it’s a two-minute setup:

npx -y @urdigital/mcp-server-bing-webmaster

Source code: github.com/urdigitalau/mcp-integrations

Package: npmjs.com/package/@urdigital/mcp-server-bing-webmaster

I Open-Sourced a Bing Webmaster MCP Server. It Broke in Four Interesting Ways. | Pulkit Agrawal