hasdata-bing-mcp 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: Something in the README is wrong
3
+ about: A tool table, a response sample or a documented behaviour does not match reality
4
+ labels: documentation
5
+ ---
6
+
7
+ **Where in the README**
8
+
9
+ Section or heading.
10
+
11
+ **What it says**
12
+
13
+ Quote the line.
14
+
15
+ **The call you made**
16
+
17
+ Tool name and arguments, or the equivalent REST URL with your key removed.
18
+
19
+ **What came back**
20
+
21
+ Trimmed response, with anything private removed.
@@ -0,0 +1,31 @@
1
+ # The tool contract is checked on a schedule as well as on push, because the upstream tool list
2
+ # can change without a single commit in this repository.
3
+ name: tool contract
4
+
5
+ on:
6
+ push:
7
+ branches: [main]
8
+ pull_request:
9
+ schedule:
10
+ - cron: '0 6 * * 1'
11
+ workflow_dispatch:
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ jobs:
17
+ contract:
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 5
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: actions/setup-node@v4
23
+ with:
24
+ node-version: '22'
25
+ # Forks cannot read repository secrets. The suite skips its live checks when the key is
26
+ # absent, so a pull request from a fork stays green instead of failing for a reason the
27
+ # contributor cannot fix.
28
+ - name: Assert the tool list still matches the README
29
+ env:
30
+ HASDATA_API_KEY: ${{ secrets.HASDATA_API_KEY }}
31
+ run: npm test
@@ -0,0 +1,75 @@
1
+ # Publishes the npm and PyPI wrapper packages on a version tag, using OIDC
2
+ # trusted publishing. No NPM_TOKEN or PYPI_TOKEN is stored anywhere: GitHub
3
+ # mints a short-lived OIDC token per run, and npmjs.org / pypi.org accept it
4
+ # because this repo + workflow are configured as trusted publishers.
5
+ #
6
+ # One-time setup, done once per package on the registries (not in this repo):
7
+ # npmjs.org -> package settings -> Trusted Publisher -> GitHub Actions,
8
+ # repo HasData/bing-mcp, workflow publish.yml
9
+ # pypi.org -> the hasdata org -> Publishing -> add a trusted publisher
10
+ # (pending publisher works before the first release),
11
+ # repo HasData/bing-mcp, workflow publish.yml
12
+ #
13
+ # The MCP registry entry (com.hasdata/bing) is NOT published here. It uses
14
+ # domain auth, which would need the namespace-wide Ed25519 key as a secret in
15
+ # every repo. That key stays off CI; the registry entry is published by hand
16
+ # when server.json changes, after the package versions below are live.
17
+ #
18
+ # Release: bump nothing by hand. Tag the commit `vX.Y.Z` and push the tag; the
19
+ # tag is the single source of the version and is written into both manifests.
20
+
21
+ name: publish
22
+
23
+ on:
24
+ push:
25
+ tags: ['v*.*.*']
26
+
27
+ permissions:
28
+ contents: read
29
+ id-token: write
30
+
31
+ jobs:
32
+ npm:
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+ # No registry-url here on purpose. With it, setup-node writes an .npmrc
37
+ # carrying _authToken=${NODE_AUTH_TOKEN}, which resolves to a placeholder
38
+ # when no token is passed. npm then authenticates with that garbage instead
39
+ # of falling back to OIDC, and a scoped package answers 404.
40
+ - uses: actions/setup-node@v4
41
+ with:
42
+ node-version: '24'
43
+ # OIDC trusted publishing landed in npm 11.5.1. Node 24 already ships a
44
+ # newer npm than that, but pinning the upgrade here keeps the job working
45
+ # if the runner image drifts back.
46
+ - name: Upgrade npm for OIDC trusted publishing
47
+ run: |
48
+ npm install -g npm@latest
49
+ npm -v
50
+ - name: Set version from the tag
51
+ run: npm version "${GITHUB_REF_NAME#v}" --no-git-tag-version --allow-same-version
52
+ - name: Publish to npm (OIDC, no token)
53
+ run: npm publish --access public
54
+
55
+ pypi:
56
+ runs-on: ubuntu-latest
57
+ steps:
58
+ - uses: actions/checkout@v4
59
+ - uses: actions/setup-python@v5
60
+ with:
61
+ python-version: '3.12'
62
+ - name: Set version from the tag
63
+ run: |
64
+ python - "${GITHUB_REF_NAME#v}" <<'PY'
65
+ import re, sys
66
+ v = sys.argv[1]
67
+ p = "pyproject.toml"
68
+ t = open(p, encoding="utf-8").read()
69
+ t = re.sub(r'(?m)^version = ".*"$', f'version = "{v}"', t, count=1)
70
+ open(p, "w", encoding="utf-8", newline="\n").write(t)
71
+ PY
72
+ - name: Build the wheel and sdist
73
+ run: pipx run build
74
+ - name: Publish to PyPI (OIDC, no token)
75
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,11 @@
1
+ node_modules/
2
+ package-lock.json
3
+ dist/
4
+ build/
5
+ *.egg-info/
6
+ __pycache__/
7
+ *.pyc
8
+ .env
9
+ .env.*
10
+ .DS_Store
11
+ *.log
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 HasData
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,351 @@
1
+ Metadata-Version: 2.5
2
+ Name: hasdata-bing-mcp
3
+ Version: 1.0.0
4
+ Summary: MCP server for Bing search through HasData's hosted API. 1,000 free credits every month.
5
+ Project-URL: Homepage, https://hasdata.com/apis/bing-search-api
6
+ Project-URL: Repository, https://github.com/HasData/bing-mcp
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Keywords: bing,hasdata,mcp,model-context-protocol,search-engine,seo,serp
10
+ Requires-Python: >=3.10
11
+ Requires-Dist: mcp-proxy>=0.12.0
12
+ Requires-Dist: mcp<2,>=1.17
13
+ Description-Content-Type: text/markdown
14
+
15
+ # Bing MCP Server
16
+
17
+ <!-- mcp-name: com.hasdata/bing -->
18
+
19
+ A hosted Model Context Protocol (MCP) server that gives Claude, Cursor, Windsurf and any other MCP client one read-only Bing tool. Run a Bing search from a chosen country, market, language and device, and get the organic results, the ad block and the Copilot answer as structured JSON, with no Azure subscription and nothing to host.
20
+
21
+ It reads the Bing results page a signed-out visitor sees.
22
+
23
+ **1,000 free credits every month, no card required**, which is 100 Bing calls at the 10-credit rate.
24
+
25
+ ```
26
+ https://mcp.hasdata.com/api/mcp?apis=bing
27
+ ```
28
+
29
+ [![Glama score](https://glama.ai/mcp/servers/HasData/bing-mcp/badges/score.svg)](https://glama.ai/mcp/servers/HasData/bing-mcp)
30
+ [![tool contract](https://github.com/HasData/bing-mcp/actions/workflows/contract.yml/badge.svg)](https://github.com/HasData/bing-mcp/actions/workflows/contract.yml)
31
+ [![MCP](https://img.shields.io/badge/MCP-remote%20%7C%20streamable%20HTTP-6366f1?style=flat-square)](https://mcp.hasdata.com/api/mcp?apis=bing)
32
+ [![Tools](https://img.shields.io/badge/tools-1-10b981?style=flat-square)](#tools)
33
+ [![npm](https://img.shields.io/npm/v/@hasdata/bing-mcp?style=flat-square&logo=npm&label=npm&color=cb3837)](https://www.npmjs.com/package/@hasdata/bing-mcp)
34
+ [![PyPI](https://img.shields.io/pypi/v/hasdata-bing-mcp?style=flat-square&logo=pypi&logoColor=white&label=PyPI&color=3775a9)](https://pypi.org/project/hasdata-bing-mcp/)
35
+ [![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](LICENSE)
36
+
37
+ ## Contents
38
+
39
+ - [What you need](#what-you-need)
40
+ - [Quick start](#quick-start)
41
+ - [Example prompts](#example-prompts)
42
+ - [Tools](#tools)
43
+ - [Errors and failure paths](#errors-and-failure-paths)
44
+ - [Pricing, free tier and limits](#pricing-free-tier-and-limits)
45
+ - [How it compares](#how-it-compares)
46
+ - [FAQ](#faq)
47
+ - [HasData links](#hasdata-links)
48
+ - [Development](#development)
49
+ - [Contributing](#contributing)
50
+ - [License](#license)
51
+
52
+ ## What you need
53
+
54
+ An MCP client and a HasData API key from the [dashboard](https://app.hasdata.com/sign-up?utm_source=github&utm_medium=syndication&utm_campaign=bing-mcp), free to create with no card, and the free tier covers about 100 calls a month at the 10-credit rate. This is a remote server, so the simplest path is a URL and an `x-api-key` header, with no container to run. A client that only speaks stdio reaches it through a thin launcher, published as `@hasdata/bing-mcp` on npm and `hasdata-bing-mcp` on PyPI, shown below.
55
+
56
+ ## Quick start
57
+
58
+ The server URL is the same for every client. We run it hands-on in Claude Code and Claude Desktop. The other blocks follow each client's own documented format for a remote server.
59
+
60
+ | Field | Value |
61
+ | :--- | :--- |
62
+ | URL | `https://mcp.hasdata.com/api/mcp?apis=bing` |
63
+ | Transport | HTTP, streamable |
64
+ | Auth header | `x-api-key: HASDATA_API_KEY` |
65
+
66
+ Clients with OAuth support can add the same URL as a connector and sign in without putting a key in a config file.
67
+
68
+ <details>
69
+ <summary><b>Claude Code</b></summary>
70
+
71
+ ```bash
72
+ claude mcp add --transport http bing "https://mcp.hasdata.com/api/mcp?apis=bing" \
73
+ --header "x-api-key: HASDATA_API_KEY"
74
+ ```
75
+
76
+ </details>
77
+
78
+ <details>
79
+ <summary><b>Claude Desktop</b></summary>
80
+
81
+ Settings, then Connectors, then Add custom connector, then paste `https://mcp.hasdata.com/api/mcp?apis=bing` and sign in.
82
+
83
+ For the config-file route, Claude Desktop loads only local (stdio) servers, so it reaches a remote server through a stdio launcher. The `@hasdata/bing-mcp` package is that launcher, and it reads the key from the environment. Add this to `claude_desktop_config.json`:
84
+
85
+ ```json
86
+ {
87
+ "mcpServers": {
88
+ "bing": {
89
+ "command": "npx",
90
+ "args": ["-y", "@hasdata/bing-mcp"],
91
+ "env": { "HASDATA_API_KEY": "YOUR_KEY" }
92
+ }
93
+ }
94
+ }
95
+ ```
96
+
97
+ For Python instead of Node, swap the launcher for the PyPI package, which `uvx` runs without a manual install:
98
+
99
+ ```json
100
+ {
101
+ "mcpServers": {
102
+ "bing": {
103
+ "command": "uvx",
104
+ "args": ["hasdata-bing-mcp"],
105
+ "env": { "HASDATA_API_KEY": "YOUR_KEY" }
106
+ }
107
+ }
108
+ }
109
+ ```
110
+
111
+ </details>
112
+
113
+ <details>
114
+ <summary><b>Cursor</b></summary>
115
+
116
+ `~/.cursor/mcp.json` for every project, or `.cursor/mcp.json` for one:
117
+
118
+ ```json
119
+ {
120
+ "mcpServers": {
121
+ "bing": {
122
+ "url": "https://mcp.hasdata.com/api/mcp?apis=bing",
123
+ "headers": { "x-api-key": "HASDATA_API_KEY" }
124
+ }
125
+ }
126
+ }
127
+ ```
128
+
129
+ </details>
130
+
131
+ <details>
132
+ <summary><b>Windsurf</b></summary>
133
+
134
+ `~/.codeium/windsurf/mcp_config.json`. Windsurf calls the field `serverUrl`, not `url`:
135
+
136
+ ```json
137
+ {
138
+ "mcpServers": {
139
+ "bing": {
140
+ "serverUrl": "https://mcp.hasdata.com/api/mcp?apis=bing",
141
+ "headers": { "x-api-key": "HASDATA_API_KEY" }
142
+ }
143
+ }
144
+ }
145
+ ```
146
+
147
+ </details>
148
+
149
+ <details>
150
+ <summary><b>VS Code</b></summary>
151
+
152
+ `.vscode/mcp.json` in the workspace:
153
+
154
+ ```json
155
+ {
156
+ "servers": {
157
+ "bing": {
158
+ "type": "http",
159
+ "url": "https://mcp.hasdata.com/api/mcp?apis=bing",
160
+ "headers": { "x-api-key": "HASDATA_API_KEY" }
161
+ }
162
+ }
163
+ }
164
+ ```
165
+
166
+ </details>
167
+
168
+ ## Example prompts
169
+
170
+ - Search Bing for python web scraping tutorials and list the top ten sources.
171
+ - Who is advertising against the query "best crm software" on Bing right now?
172
+ - What does Bing Copilot answer for this query, and which pages does it cite?
173
+ - Run this query from Germany in German and compare the results with the US ones.
174
+ - Show me pages Bing indexed for this query in the past week.
175
+ - Search this query as a mobile user and tell me whether the ranking differs from desktop.
176
+
177
+ One call answers each of these. Paging to the second and third page of results takes one more call each.
178
+
179
+ ## Tools
180
+
181
+ One tool, 10 credits per successful call.
182
+
183
+ ### Get Bing search results
184
+
185
+ [`hasdata_bing_serp_getSearchResults`](https://docs.hasdata.com/apis/bing/serp?utm_source=github&utm_medium=syndication&utm_campaign=bing-mcp)
186
+
187
+ One Bing results page.
188
+
189
+ | Parameter | Type | Required | Notes |
190
+ | :--- | :--- | :--- | :--- |
191
+ | `q` | string | yes | The search term |
192
+ | `location` | string | | Free-text origin, such as `Austin, Texas`. Resolved to coordinates |
193
+ | `lat` / `lon` | string | | GPS origin. Takes precedence over `location` |
194
+ | `mkt` | string | | Market, one of 38, such as `en-gb` or `de-de` |
195
+ | `cc` | string | | Country, one of 36, such as `gb` or `de` |
196
+ | `setLang` | string | | Interface and preferred result language, one of 39 |
197
+ | `safeSearch` | string | | `off`, `moderate` or `strict` |
198
+ | `deviceType` | string | | `desktop`, `mobile` or `tablet` |
199
+ | `filters` | string | | A Bing filter string, such as `ex1:"ez2"` for the past week |
200
+ | `first` | number | | Result offset, `1` for the first page, `11` for the second |
201
+
202
+ Returns `organicResults` always, plus `ads` and `copilotSearch` when the page carries them.
203
+
204
+ An organic result has `position`, `title`, `link`, `displayedLink`, `source` and `snippet`.
205
+
206
+ ```json
207
+ {
208
+ "position": 1,
209
+ "title": "10 Best CRM Software Of 2026 – Forbes Advisor",
210
+ "link": "https://www.forbes.com/advisor/business/software/best-crm-software/",
211
+ "displayedLink": "https://www.forbes.com › advisor › business › software › best-crm-software",
212
+ "source": "Forbes",
213
+ "snippet": "Compare top CRM options to find the best fit for your needs and budget, and explore features, pricing and support using the links below."
214
+ }
215
+ ```
216
+
217
+ `ads` appears on commercial queries and carries `position`, `blockPosition`, `title`, `link`, `source`, `displayedLink`, `description` and `sitelinks`. Six ads came back for `best crm software` and none for `weather`.
218
+
219
+ `copilotSearch` appears when Bing puts its AI answer on the page. It holds `textBlocks`, a sequence of typed blocks where a `heading` carries a `snippet` and a `list` carries an array of them, and `references`, the pages the answer cites.
220
+
221
+ ```json
222
+ {
223
+ "textBlocks": [
224
+ {
225
+ "type": "heading",
226
+ "snippet": "Top CRM software in 2026 includes Agentforce Sales, HubSpot Sales Hub, Pipedrive, Zoho CRM, and ActiveCampaign, each excelling in sales automation, pipeline management, and customer engagement."
227
+ },
228
+ { "type": "heading", "snippet": "Top CRM Options" },
229
+ { "type": "list", "list": [{ "snippet": "Agentforce Sales (formerly Salesforce Sales Cloud) – Best for highly customizable..." }] }
230
+ ],
231
+ "references": []
232
+ }
233
+ ```
234
+
235
+ ## Errors and failure paths
236
+
237
+ Plan for these rather than assuming a happy path.
238
+
239
+ **The result count is not ten and does not hold still.** The same query returned 6 organic results on one call and 14 on another, because Bing decides how much of the page to give to ads and to Copilot. Read the array length rather than assuming a page size, and do not compute a rank position by multiplying page number by ten.
240
+
241
+ **`ads` and `copilotSearch` are absent rather than empty when the page has neither.** Test for the key before you read it. A query like `weather` came back with organic results alone.
242
+
243
+ **An ad `link` is a Bing click tracker, not the advertiser's URL.** It points at `bing.com/aclk` with the destination encoded inside. Read `displayedLink` for the domain that was advertising, and treat the tracker as a click-through rather than as a page address.
244
+
245
+ **`lat` and `lon` override `location`.** Sending all three silently ignores the free text. Send one or the other.
246
+
247
+ **`mkt`, `cc` and `setLang` are three separate axes.** The market decides which Bing index answers, the country code decides the origin, and the language decides the interface and the preferred result language. Setting only one moves the results less than you expect.
248
+
249
+ **`filters` is a raw Bing filter string.** The date options are documented, `ex1:"ez1"` for the past day through `ex1:"ez3"` for the past month. For anything else, run the search in a browser and copy the parameter out of the Bing URL, because the values are Bing's rather than ours.
250
+
251
+ **Paging is by offset, and pages overlap a little.** `first: 11` is the second page. Consecutive pages shared one result out of six in our checks, which is Bing being Bing rather than a fault. Deduplicate by link when you merge pages.
252
+
253
+ Results that carry data also carry a `requestMetadata.id` worth quoting in support.
254
+
255
+ ## Pricing, free tier and limits
256
+
257
+ The Bing tool costs **10 credits per successful call**. Response size does not change the price.
258
+
259
+ The free tier is **1,000 credits every month with no card**, which is 100 Bing calls at the base rate. It renews with the billing cycle, so a low-volume agent runs on the free tier indefinitely.
260
+
261
+ Paid plans start at **$49 a month** for 200,000 credits, which is 20,000 calls. The unit price falls with volume, from **$2.45 per 1,000 calls** on the entry plan to **$1.00** on Business, **$0.84** on Growth and **$0.74** on the largest [high-volume plans](https://hasdata.com/prices?utm_source=github&utm_medium=syndication&utm_campaign=bing-mcp).
262
+
263
+ Your plan also sets concurrency. The free tier allows 1 request at a time, Startup 15, Business 30, Growth 50, and the high-volume plans run from 200 to 1,500. Retry on the 429 with a backoff in anything unattended, because an agent that sweeps a keyword list will reach the ceiling before you do.
264
+
265
+ A request that comes back non-200 is not billed. A successful call that finds nothing is still a call.
266
+
267
+ ## How it compares
268
+
269
+ Microsoft retired the Bing Search APIs on 11 August 2025, and the old endpoints now answer 410. Developers are routed to Grounding with Bing Search in Azure AI Agent Service, which is a different product rather than a renamed endpoint.
270
+
271
+ | | Grounding with Bing Search | This server |
272
+ | :--- | :--- | :--- |
273
+ | Eligibility | An Azure subscription and an agent resource | An API key |
274
+ | What you get | A grounded model answer with citations | The results page, parsed |
275
+ | Ad block | Not returned | Returned when present |
276
+ | Ranking positions | Not the point of the product | `position` on every result |
277
+ | Display requirements | Bound by the grounding terms | Yours to check |
278
+ | Cost model | Per grounding call, billed through Azure | Credits |
279
+
280
+ The row that decides it is what you are asking for. Grounding is built to feed a model an answer, and rank tracking, ad monitoring and SERP comparison need the page itself. When you want an answer rather than a ranking, the Azure route is the one Microsoft supports.
281
+
282
+ ## FAQ
283
+
284
+ ### Is there an official Bing MCP server?
285
+
286
+ Microsoft does not publish one for search results. This one is maintained by HasData and reads public Bing pages.
287
+
288
+ ### What is a Bing MCP server?
289
+
290
+ An MCP server exposes tools an AI client can call. This one turns a Bing results page into JSON an agent can reason over, without a browser or a scraping library in your stack.
291
+
292
+ ### Do I need an Azure subscription or a Bing API key?
293
+
294
+ No. The only credential is your HasData key.
295
+
296
+ ### Why do I get fewer than ten results?
297
+
298
+ Because Bing gives part of the page to ads and to the Copilot answer, and what is left is what you get. Six and fourteen both came back for the same query on different calls.
299
+
300
+ ### Does it return the Copilot answer?
301
+
302
+ When Bing shows one, yes, as `copilotSearch` with its text blocks and references. It is absent on queries where Bing does not generate an answer.
303
+
304
+ ### How do I search from another country?
305
+
306
+ Set `cc` for the country and `mkt` for the market, and add `setLang` when you also want the interface and result language to change. `location` or `lat`/`lon` narrows the origin further, to city level.
307
+
308
+ ### Can I use this together with other HasData APIs?
309
+
310
+ Yes. One key covers everything, and one endpoint serves them all through the `apis` parameter. Point a client at `?apis=bing,google_serp` to get both tool sets in one connection, or at [`mcp.hasdata.com/api/mcp`](https://docs.hasdata.com/mcp-server?utm_source=github&utm_medium=syndication&utm_campaign=bing-mcp) for the full catalogue.
311
+
312
+ ### Is HasData affiliated with Microsoft or Bing?
313
+
314
+ No. HasData is an independent service and is not affiliated with, endorsed by, or sponsored by Microsoft. Bing is a trademark of its respective owner. The tools work with publicly available data only, and you are responsible for using the results in line with Bing's terms and the law that applies to you.
315
+
316
+ ### Compliance and personal data
317
+
318
+ A results page returns titles, links and snippets from third-party sites, so what comes back depends on what you searched for. A query naming a person returns pages about that person, which makes the response personal data even though the tool has no concept of one. Keep that in mind before you store result sets from name queries, and check your own obligations.
319
+
320
+ ## HasData links
321
+
322
+ - [Bing Search API](https://hasdata.com/apis/bing-search-api?utm_source=github&utm_medium=syndication&utm_campaign=bing-mcp), the REST endpoint behind this tool
323
+ - [API documentation](https://docs.hasdata.com/apis/bing/serp?utm_source=github&utm_medium=syndication&utm_campaign=bing-mcp)
324
+ - [MCP server documentation](https://docs.hasdata.com/mcp-server?utm_source=github&utm_medium=syndication&utm_campaign=bing-mcp)
325
+ - [Pricing](https://hasdata.com/prices?utm_source=github&utm_medium=syndication&utm_campaign=bing-mcp)
326
+ - [Dashboard](https://app.hasdata.com/sign-up?utm_source=github&utm_medium=syndication&utm_campaign=bing-mcp)
327
+
328
+ Other HasData MCP servers: [Google Search](https://github.com/HasData/google-search-mcp), [DuckDuckGo](https://github.com/HasData/duckduckgo-mcp), [Google Maps](https://github.com/HasData/google-maps-mcp), [Google Trends](https://github.com/HasData/google-trends-mcp), [Google Flights](https://github.com/HasData/google-flights-mcp), [YouTube](https://github.com/HasData/youtube-mcp), [TikTok](https://github.com/HasData/tiktok-mcp), [Instagram](https://github.com/HasData/instagram-mcp), [Amazon](https://github.com/HasData/amazon-mcp), [Walmart](https://github.com/HasData/walmart-mcp), [Shopify](https://github.com/HasData/shopify-mcp), [Yelp](https://github.com/HasData/yelp-mcp), [Yellow Pages](https://github.com/HasData/yellowpages-mcp), [Zillow](https://github.com/HasData/zillow-mcp), [Redfin](https://github.com/HasData/redfin-mcp), [Airbnb](https://github.com/HasData/airbnb-mcp), [Booking.com](https://github.com/HasData/booking-mcp), [Indeed](https://github.com/HasData/indeed-mcp), [Glassdoor](https://github.com/HasData/glassdoor-mcp).
329
+
330
+ ## Development
331
+
332
+ The launcher is a thin stdio bridge to the remote server, so there is nothing to build.
333
+
334
+ ```bash
335
+ npm install
336
+ HASDATA_API_KEY=your_key_here npm test
337
+ ```
338
+
339
+ The tests in `test/` assert the tool contract, the part that can break without a commit here. They check that `?apis=bing` returns the one expected tool, that its name has not changed, that it still requires `q` and carries a description, that the targeting enums this README lists are still offered, and that the key in use is actually accepted. That last check calls the tool for real and costs 10 credits, which is the price of a canary that can fail for the right reason.
340
+
341
+ One test asserts that a live commercial query still returns organic results carrying `position` and `link`. This README documents the result count as variable, so a test that pinned a count would fail for the wrong reason, and one that only listed tools would pass while the parser returned nothing.
342
+
343
+ The contract suite also runs weekly on a schedule, because the upstream tool list can change without anyone touching this repository.
344
+
345
+ ## Contributing
346
+
347
+ A tool table, a response sample or a documented behaviour that does not match reality is worth an issue. There is a template for exactly that. Pull requests are welcome for the same, and for anything in the launcher.
348
+
349
+ ## License
350
+
351
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,337 @@
1
+ # Bing MCP Server
2
+
3
+ <!-- mcp-name: com.hasdata/bing -->
4
+
5
+ A hosted Model Context Protocol (MCP) server that gives Claude, Cursor, Windsurf and any other MCP client one read-only Bing tool. Run a Bing search from a chosen country, market, language and device, and get the organic results, the ad block and the Copilot answer as structured JSON, with no Azure subscription and nothing to host.
6
+
7
+ It reads the Bing results page a signed-out visitor sees.
8
+
9
+ **1,000 free credits every month, no card required**, which is 100 Bing calls at the 10-credit rate.
10
+
11
+ ```
12
+ https://mcp.hasdata.com/api/mcp?apis=bing
13
+ ```
14
+
15
+ [![Glama score](https://glama.ai/mcp/servers/HasData/bing-mcp/badges/score.svg)](https://glama.ai/mcp/servers/HasData/bing-mcp)
16
+ [![tool contract](https://github.com/HasData/bing-mcp/actions/workflows/contract.yml/badge.svg)](https://github.com/HasData/bing-mcp/actions/workflows/contract.yml)
17
+ [![MCP](https://img.shields.io/badge/MCP-remote%20%7C%20streamable%20HTTP-6366f1?style=flat-square)](https://mcp.hasdata.com/api/mcp?apis=bing)
18
+ [![Tools](https://img.shields.io/badge/tools-1-10b981?style=flat-square)](#tools)
19
+ [![npm](https://img.shields.io/npm/v/@hasdata/bing-mcp?style=flat-square&logo=npm&label=npm&color=cb3837)](https://www.npmjs.com/package/@hasdata/bing-mcp)
20
+ [![PyPI](https://img.shields.io/pypi/v/hasdata-bing-mcp?style=flat-square&logo=pypi&logoColor=white&label=PyPI&color=3775a9)](https://pypi.org/project/hasdata-bing-mcp/)
21
+ [![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](LICENSE)
22
+
23
+ ## Contents
24
+
25
+ - [What you need](#what-you-need)
26
+ - [Quick start](#quick-start)
27
+ - [Example prompts](#example-prompts)
28
+ - [Tools](#tools)
29
+ - [Errors and failure paths](#errors-and-failure-paths)
30
+ - [Pricing, free tier and limits](#pricing-free-tier-and-limits)
31
+ - [How it compares](#how-it-compares)
32
+ - [FAQ](#faq)
33
+ - [HasData links](#hasdata-links)
34
+ - [Development](#development)
35
+ - [Contributing](#contributing)
36
+ - [License](#license)
37
+
38
+ ## What you need
39
+
40
+ An MCP client and a HasData API key from the [dashboard](https://app.hasdata.com/sign-up?utm_source=github&utm_medium=syndication&utm_campaign=bing-mcp), free to create with no card, and the free tier covers about 100 calls a month at the 10-credit rate. This is a remote server, so the simplest path is a URL and an `x-api-key` header, with no container to run. A client that only speaks stdio reaches it through a thin launcher, published as `@hasdata/bing-mcp` on npm and `hasdata-bing-mcp` on PyPI, shown below.
41
+
42
+ ## Quick start
43
+
44
+ The server URL is the same for every client. We run it hands-on in Claude Code and Claude Desktop. The other blocks follow each client's own documented format for a remote server.
45
+
46
+ | Field | Value |
47
+ | :--- | :--- |
48
+ | URL | `https://mcp.hasdata.com/api/mcp?apis=bing` |
49
+ | Transport | HTTP, streamable |
50
+ | Auth header | `x-api-key: HASDATA_API_KEY` |
51
+
52
+ Clients with OAuth support can add the same URL as a connector and sign in without putting a key in a config file.
53
+
54
+ <details>
55
+ <summary><b>Claude Code</b></summary>
56
+
57
+ ```bash
58
+ claude mcp add --transport http bing "https://mcp.hasdata.com/api/mcp?apis=bing" \
59
+ --header "x-api-key: HASDATA_API_KEY"
60
+ ```
61
+
62
+ </details>
63
+
64
+ <details>
65
+ <summary><b>Claude Desktop</b></summary>
66
+
67
+ Settings, then Connectors, then Add custom connector, then paste `https://mcp.hasdata.com/api/mcp?apis=bing` and sign in.
68
+
69
+ For the config-file route, Claude Desktop loads only local (stdio) servers, so it reaches a remote server through a stdio launcher. The `@hasdata/bing-mcp` package is that launcher, and it reads the key from the environment. Add this to `claude_desktop_config.json`:
70
+
71
+ ```json
72
+ {
73
+ "mcpServers": {
74
+ "bing": {
75
+ "command": "npx",
76
+ "args": ["-y", "@hasdata/bing-mcp"],
77
+ "env": { "HASDATA_API_KEY": "YOUR_KEY" }
78
+ }
79
+ }
80
+ }
81
+ ```
82
+
83
+ For Python instead of Node, swap the launcher for the PyPI package, which `uvx` runs without a manual install:
84
+
85
+ ```json
86
+ {
87
+ "mcpServers": {
88
+ "bing": {
89
+ "command": "uvx",
90
+ "args": ["hasdata-bing-mcp"],
91
+ "env": { "HASDATA_API_KEY": "YOUR_KEY" }
92
+ }
93
+ }
94
+ }
95
+ ```
96
+
97
+ </details>
98
+
99
+ <details>
100
+ <summary><b>Cursor</b></summary>
101
+
102
+ `~/.cursor/mcp.json` for every project, or `.cursor/mcp.json` for one:
103
+
104
+ ```json
105
+ {
106
+ "mcpServers": {
107
+ "bing": {
108
+ "url": "https://mcp.hasdata.com/api/mcp?apis=bing",
109
+ "headers": { "x-api-key": "HASDATA_API_KEY" }
110
+ }
111
+ }
112
+ }
113
+ ```
114
+
115
+ </details>
116
+
117
+ <details>
118
+ <summary><b>Windsurf</b></summary>
119
+
120
+ `~/.codeium/windsurf/mcp_config.json`. Windsurf calls the field `serverUrl`, not `url`:
121
+
122
+ ```json
123
+ {
124
+ "mcpServers": {
125
+ "bing": {
126
+ "serverUrl": "https://mcp.hasdata.com/api/mcp?apis=bing",
127
+ "headers": { "x-api-key": "HASDATA_API_KEY" }
128
+ }
129
+ }
130
+ }
131
+ ```
132
+
133
+ </details>
134
+
135
+ <details>
136
+ <summary><b>VS Code</b></summary>
137
+
138
+ `.vscode/mcp.json` in the workspace:
139
+
140
+ ```json
141
+ {
142
+ "servers": {
143
+ "bing": {
144
+ "type": "http",
145
+ "url": "https://mcp.hasdata.com/api/mcp?apis=bing",
146
+ "headers": { "x-api-key": "HASDATA_API_KEY" }
147
+ }
148
+ }
149
+ }
150
+ ```
151
+
152
+ </details>
153
+
154
+ ## Example prompts
155
+
156
+ - Search Bing for python web scraping tutorials and list the top ten sources.
157
+ - Who is advertising against the query "best crm software" on Bing right now?
158
+ - What does Bing Copilot answer for this query, and which pages does it cite?
159
+ - Run this query from Germany in German and compare the results with the US ones.
160
+ - Show me pages Bing indexed for this query in the past week.
161
+ - Search this query as a mobile user and tell me whether the ranking differs from desktop.
162
+
163
+ One call answers each of these. Paging to the second and third page of results takes one more call each.
164
+
165
+ ## Tools
166
+
167
+ One tool, 10 credits per successful call.
168
+
169
+ ### Get Bing search results
170
+
171
+ [`hasdata_bing_serp_getSearchResults`](https://docs.hasdata.com/apis/bing/serp?utm_source=github&utm_medium=syndication&utm_campaign=bing-mcp)
172
+
173
+ One Bing results page.
174
+
175
+ | Parameter | Type | Required | Notes |
176
+ | :--- | :--- | :--- | :--- |
177
+ | `q` | string | yes | The search term |
178
+ | `location` | string | | Free-text origin, such as `Austin, Texas`. Resolved to coordinates |
179
+ | `lat` / `lon` | string | | GPS origin. Takes precedence over `location` |
180
+ | `mkt` | string | | Market, one of 38, such as `en-gb` or `de-de` |
181
+ | `cc` | string | | Country, one of 36, such as `gb` or `de` |
182
+ | `setLang` | string | | Interface and preferred result language, one of 39 |
183
+ | `safeSearch` | string | | `off`, `moderate` or `strict` |
184
+ | `deviceType` | string | | `desktop`, `mobile` or `tablet` |
185
+ | `filters` | string | | A Bing filter string, such as `ex1:"ez2"` for the past week |
186
+ | `first` | number | | Result offset, `1` for the first page, `11` for the second |
187
+
188
+ Returns `organicResults` always, plus `ads` and `copilotSearch` when the page carries them.
189
+
190
+ An organic result has `position`, `title`, `link`, `displayedLink`, `source` and `snippet`.
191
+
192
+ ```json
193
+ {
194
+ "position": 1,
195
+ "title": "10 Best CRM Software Of 2026 – Forbes Advisor",
196
+ "link": "https://www.forbes.com/advisor/business/software/best-crm-software/",
197
+ "displayedLink": "https://www.forbes.com › advisor › business › software › best-crm-software",
198
+ "source": "Forbes",
199
+ "snippet": "Compare top CRM options to find the best fit for your needs and budget, and explore features, pricing and support using the links below."
200
+ }
201
+ ```
202
+
203
+ `ads` appears on commercial queries and carries `position`, `blockPosition`, `title`, `link`, `source`, `displayedLink`, `description` and `sitelinks`. Six ads came back for `best crm software` and none for `weather`.
204
+
205
+ `copilotSearch` appears when Bing puts its AI answer on the page. It holds `textBlocks`, a sequence of typed blocks where a `heading` carries a `snippet` and a `list` carries an array of them, and `references`, the pages the answer cites.
206
+
207
+ ```json
208
+ {
209
+ "textBlocks": [
210
+ {
211
+ "type": "heading",
212
+ "snippet": "Top CRM software in 2026 includes Agentforce Sales, HubSpot Sales Hub, Pipedrive, Zoho CRM, and ActiveCampaign, each excelling in sales automation, pipeline management, and customer engagement."
213
+ },
214
+ { "type": "heading", "snippet": "Top CRM Options" },
215
+ { "type": "list", "list": [{ "snippet": "Agentforce Sales (formerly Salesforce Sales Cloud) – Best for highly customizable..." }] }
216
+ ],
217
+ "references": []
218
+ }
219
+ ```
220
+
221
+ ## Errors and failure paths
222
+
223
+ Plan for these rather than assuming a happy path.
224
+
225
+ **The result count is not ten and does not hold still.** The same query returned 6 organic results on one call and 14 on another, because Bing decides how much of the page to give to ads and to Copilot. Read the array length rather than assuming a page size, and do not compute a rank position by multiplying page number by ten.
226
+
227
+ **`ads` and `copilotSearch` are absent rather than empty when the page has neither.** Test for the key before you read it. A query like `weather` came back with organic results alone.
228
+
229
+ **An ad `link` is a Bing click tracker, not the advertiser's URL.** It points at `bing.com/aclk` with the destination encoded inside. Read `displayedLink` for the domain that was advertising, and treat the tracker as a click-through rather than as a page address.
230
+
231
+ **`lat` and `lon` override `location`.** Sending all three silently ignores the free text. Send one or the other.
232
+
233
+ **`mkt`, `cc` and `setLang` are three separate axes.** The market decides which Bing index answers, the country code decides the origin, and the language decides the interface and the preferred result language. Setting only one moves the results less than you expect.
234
+
235
+ **`filters` is a raw Bing filter string.** The date options are documented, `ex1:"ez1"` for the past day through `ex1:"ez3"` for the past month. For anything else, run the search in a browser and copy the parameter out of the Bing URL, because the values are Bing's rather than ours.
236
+
237
+ **Paging is by offset, and pages overlap a little.** `first: 11` is the second page. Consecutive pages shared one result out of six in our checks, which is Bing being Bing rather than a fault. Deduplicate by link when you merge pages.
238
+
239
+ Results that carry data also carry a `requestMetadata.id` worth quoting in support.
240
+
241
+ ## Pricing, free tier and limits
242
+
243
+ The Bing tool costs **10 credits per successful call**. Response size does not change the price.
244
+
245
+ The free tier is **1,000 credits every month with no card**, which is 100 Bing calls at the base rate. It renews with the billing cycle, so a low-volume agent runs on the free tier indefinitely.
246
+
247
+ Paid plans start at **$49 a month** for 200,000 credits, which is 20,000 calls. The unit price falls with volume, from **$2.45 per 1,000 calls** on the entry plan to **$1.00** on Business, **$0.84** on Growth and **$0.74** on the largest [high-volume plans](https://hasdata.com/prices?utm_source=github&utm_medium=syndication&utm_campaign=bing-mcp).
248
+
249
+ Your plan also sets concurrency. The free tier allows 1 request at a time, Startup 15, Business 30, Growth 50, and the high-volume plans run from 200 to 1,500. Retry on the 429 with a backoff in anything unattended, because an agent that sweeps a keyword list will reach the ceiling before you do.
250
+
251
+ A request that comes back non-200 is not billed. A successful call that finds nothing is still a call.
252
+
253
+ ## How it compares
254
+
255
+ Microsoft retired the Bing Search APIs on 11 August 2025, and the old endpoints now answer 410. Developers are routed to Grounding with Bing Search in Azure AI Agent Service, which is a different product rather than a renamed endpoint.
256
+
257
+ | | Grounding with Bing Search | This server |
258
+ | :--- | :--- | :--- |
259
+ | Eligibility | An Azure subscription and an agent resource | An API key |
260
+ | What you get | A grounded model answer with citations | The results page, parsed |
261
+ | Ad block | Not returned | Returned when present |
262
+ | Ranking positions | Not the point of the product | `position` on every result |
263
+ | Display requirements | Bound by the grounding terms | Yours to check |
264
+ | Cost model | Per grounding call, billed through Azure | Credits |
265
+
266
+ The row that decides it is what you are asking for. Grounding is built to feed a model an answer, and rank tracking, ad monitoring and SERP comparison need the page itself. When you want an answer rather than a ranking, the Azure route is the one Microsoft supports.
267
+
268
+ ## FAQ
269
+
270
+ ### Is there an official Bing MCP server?
271
+
272
+ Microsoft does not publish one for search results. This one is maintained by HasData and reads public Bing pages.
273
+
274
+ ### What is a Bing MCP server?
275
+
276
+ An MCP server exposes tools an AI client can call. This one turns a Bing results page into JSON an agent can reason over, without a browser or a scraping library in your stack.
277
+
278
+ ### Do I need an Azure subscription or a Bing API key?
279
+
280
+ No. The only credential is your HasData key.
281
+
282
+ ### Why do I get fewer than ten results?
283
+
284
+ Because Bing gives part of the page to ads and to the Copilot answer, and what is left is what you get. Six and fourteen both came back for the same query on different calls.
285
+
286
+ ### Does it return the Copilot answer?
287
+
288
+ When Bing shows one, yes, as `copilotSearch` with its text blocks and references. It is absent on queries where Bing does not generate an answer.
289
+
290
+ ### How do I search from another country?
291
+
292
+ Set `cc` for the country and `mkt` for the market, and add `setLang` when you also want the interface and result language to change. `location` or `lat`/`lon` narrows the origin further, to city level.
293
+
294
+ ### Can I use this together with other HasData APIs?
295
+
296
+ Yes. One key covers everything, and one endpoint serves them all through the `apis` parameter. Point a client at `?apis=bing,google_serp` to get both tool sets in one connection, or at [`mcp.hasdata.com/api/mcp`](https://docs.hasdata.com/mcp-server?utm_source=github&utm_medium=syndication&utm_campaign=bing-mcp) for the full catalogue.
297
+
298
+ ### Is HasData affiliated with Microsoft or Bing?
299
+
300
+ No. HasData is an independent service and is not affiliated with, endorsed by, or sponsored by Microsoft. Bing is a trademark of its respective owner. The tools work with publicly available data only, and you are responsible for using the results in line with Bing's terms and the law that applies to you.
301
+
302
+ ### Compliance and personal data
303
+
304
+ A results page returns titles, links and snippets from third-party sites, so what comes back depends on what you searched for. A query naming a person returns pages about that person, which makes the response personal data even though the tool has no concept of one. Keep that in mind before you store result sets from name queries, and check your own obligations.
305
+
306
+ ## HasData links
307
+
308
+ - [Bing Search API](https://hasdata.com/apis/bing-search-api?utm_source=github&utm_medium=syndication&utm_campaign=bing-mcp), the REST endpoint behind this tool
309
+ - [API documentation](https://docs.hasdata.com/apis/bing/serp?utm_source=github&utm_medium=syndication&utm_campaign=bing-mcp)
310
+ - [MCP server documentation](https://docs.hasdata.com/mcp-server?utm_source=github&utm_medium=syndication&utm_campaign=bing-mcp)
311
+ - [Pricing](https://hasdata.com/prices?utm_source=github&utm_medium=syndication&utm_campaign=bing-mcp)
312
+ - [Dashboard](https://app.hasdata.com/sign-up?utm_source=github&utm_medium=syndication&utm_campaign=bing-mcp)
313
+
314
+ Other HasData MCP servers: [Google Search](https://github.com/HasData/google-search-mcp), [DuckDuckGo](https://github.com/HasData/duckduckgo-mcp), [Google Maps](https://github.com/HasData/google-maps-mcp), [Google Trends](https://github.com/HasData/google-trends-mcp), [Google Flights](https://github.com/HasData/google-flights-mcp), [YouTube](https://github.com/HasData/youtube-mcp), [TikTok](https://github.com/HasData/tiktok-mcp), [Instagram](https://github.com/HasData/instagram-mcp), [Amazon](https://github.com/HasData/amazon-mcp), [Walmart](https://github.com/HasData/walmart-mcp), [Shopify](https://github.com/HasData/shopify-mcp), [Yelp](https://github.com/HasData/yelp-mcp), [Yellow Pages](https://github.com/HasData/yellowpages-mcp), [Zillow](https://github.com/HasData/zillow-mcp), [Redfin](https://github.com/HasData/redfin-mcp), [Airbnb](https://github.com/HasData/airbnb-mcp), [Booking.com](https://github.com/HasData/booking-mcp), [Indeed](https://github.com/HasData/indeed-mcp), [Glassdoor](https://github.com/HasData/glassdoor-mcp).
315
+
316
+ ## Development
317
+
318
+ The launcher is a thin stdio bridge to the remote server, so there is nothing to build.
319
+
320
+ ```bash
321
+ npm install
322
+ HASDATA_API_KEY=your_key_here npm test
323
+ ```
324
+
325
+ The tests in `test/` assert the tool contract, the part that can break without a commit here. They check that `?apis=bing` returns the one expected tool, that its name has not changed, that it still requires `q` and carries a description, that the targeting enums this README lists are still offered, and that the key in use is actually accepted. That last check calls the tool for real and costs 10 credits, which is the price of a canary that can fail for the right reason.
326
+
327
+ One test asserts that a live commercial query still returns organic results carrying `position` and `link`. This README documents the result count as variable, so a test that pinned a count would fail for the wrong reason, and one that only listed tools would pass while the parser returned nothing.
328
+
329
+ The contract suite also runs weekly on a schedule, because the upstream tool list can change without anyone touching this repository.
330
+
331
+ ## Contributing
332
+
333
+ A tool table, a response sample or a documented behaviour that does not match reality is worth an issue. There is a template for exactly that. Pull requests are welcome for the same, and for anything in the launcher.
334
+
335
+ ## License
336
+
337
+ MIT, see [LICENSE](LICENSE).
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "https://glama.ai/mcp/schemas/server.json",
3
+ "maintainers": [
4
+ "HasData",
5
+ "sergey-ermakovich"
6
+ ]
7
+ }
@@ -0,0 +1,28 @@
1
+ """Thin launcher for HasData's hosted Bing MCP server.
2
+
3
+ Connects an MCP client to the remote streamable-HTTP endpoint through mcp-proxy.
4
+ The server runs on HasData's infrastructure. This package only proxies stdio to it.
5
+ """
6
+ import os
7
+ import sys
8
+ import subprocess
9
+
10
+ URL = "https://mcp.hasdata.com/api/mcp?apis=bing"
11
+
12
+
13
+ def main() -> None:
14
+ key = os.environ.get("HASDATA_API_KEY")
15
+ if not key:
16
+ sys.stderr.write(
17
+ "HASDATA_API_KEY is not set. Create a free key at https://app.hasdata.com "
18
+ "and set HASDATA_API_KEY.\n"
19
+ )
20
+ raise SystemExit(1)
21
+ args = [
22
+ sys.executable, "-m", "mcp_proxy", URL,
23
+ "--transport=streamablehttp",
24
+ "--headers", "x-api-key", key,
25
+ ]
26
+ rc = subprocess.call(args)
27
+ # subprocess returns -N when mcp_proxy is killed by signal N; map to 128+N.
28
+ raise SystemExit(rc if rc >= 0 else 128 - rc)
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ // Thin launcher: connects an MCP client to HasData's hosted Bing MCP server
3
+ // (streamable HTTP) through the mcp-remote stdio bridge. The server runs remotely.
4
+ // This package only proxies, so nothing here scrapes anything.
5
+ import { spawn } from 'node:child_process';
6
+ import { createRequire } from 'node:module';
7
+ import { dirname, join } from 'node:path';
8
+
9
+ const URL = 'https://mcp.hasdata.com/api/mcp?apis=bing';
10
+ const key = process.env.HASDATA_API_KEY;
11
+ if (!key) {
12
+ process.stderr.write('HASDATA_API_KEY is not set. Create a free key at https://app.hasdata.com and set HASDATA_API_KEY.\n');
13
+ process.exit(1);
14
+ }
15
+ // Resolve mcp-remote's CLI from its own package.json bin, so a future layout change
16
+ // or an exports map does not break a hardcoded deep path.
17
+ const require = createRequire(import.meta.url);
18
+ const pkg = require('mcp-remote/package.json');
19
+ const proxy = join(dirname(require.resolve('mcp-remote/package.json')), pkg.bin['mcp-remote']);
20
+ const child = spawn(process.execPath, [proxy, URL, '--header', `x-api-key:${key}`], { stdio: 'inherit' });
21
+ child.on('exit', (code, signal) => {
22
+ if (signal) process.kill(process.pid, signal);
23
+ else process.exit(code ?? 0);
24
+ });
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@hasdata/bing-mcp",
3
+ "mcpName": "com.hasdata/bing",
4
+ "version": "1.0.0",
5
+ "description": "MCP server for Bing search through HasData's hosted API: organic results, the ad block where Bing shows one, and the Copilot answer, with geo, market and language targeting. No Azure subscription. 1,000 free credits every month.",
6
+ "type": "module",
7
+ "bin": {
8
+ "hasdata-bing-mcp": "index.mjs"
9
+ },
10
+ "files": [
11
+ "index.mjs",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "test": "node --test"
17
+ },
18
+ "dependencies": {
19
+ "mcp-remote": "^0.1.43"
20
+ },
21
+ "engines": {
22
+ "node": ">=18"
23
+ },
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/HasData/bing-mcp.git"
28
+ },
29
+ "homepage": "https://hasdata.com/apis/bing-search-api",
30
+ "keywords": [
31
+ "mcp",
32
+ "bing",
33
+ "bing-mcp",
34
+ "serp",
35
+ "search-engine",
36
+ "seo",
37
+ "model-context-protocol",
38
+ "hasdata",
39
+ "scraper"
40
+ ]
41
+ }
@@ -0,0 +1,24 @@
1
+ [project]
2
+ name = "hasdata-bing-mcp"
3
+ version = "1.0.0"
4
+ description = "MCP server for Bing search through HasData's hosted API. 1,000 free credits every month."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = { text = "MIT" }
8
+ keywords = ["mcp","bing","serp","search-engine","seo","model-context-protocol","hasdata"]
9
+ # mcp is pinned below 2 because mcp-proxy 0.12.0 imports request_ctx, removed in the mcp 2.x SDK.
10
+ dependencies = ["mcp-proxy>=0.12.0", "mcp>=1.17,<2"]
11
+
12
+ [project.urls]
13
+ Homepage = "https://hasdata.com/apis/bing-search-api"
14
+ Repository = "https://github.com/HasData/bing-mcp"
15
+
16
+ [project.scripts]
17
+ hasdata-bing-mcp = "hasdata_bing_mcp:main"
18
+
19
+ [build-system]
20
+ requires = ["hatchling"]
21
+ build-backend = "hatchling.build"
22
+
23
+ [tool.hatch.build.targets.wheel]
24
+ packages = ["hasdata_bing_mcp"]
@@ -0,0 +1,61 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "com.hasdata/bing",
4
+ "title": "HasData Bing",
5
+ "description": "Bing search results with the ad block and Copilot answer, geo and market targeting, as JSON.",
6
+ "version": "1.0.0",
7
+ "websiteUrl": "https://hasdata.com/apis/bing-search-api",
8
+ "repository": {
9
+ "url": "https://github.com/HasData/bing-mcp",
10
+ "source": "github"
11
+ },
12
+ "remotes": [
13
+ {
14
+ "type": "streamable-http",
15
+ "url": "https://mcp.hasdata.com/api/mcp?apis=bing",
16
+ "headers": [
17
+ {
18
+ "name": "x-api-key",
19
+ "description": "HasData API key, created at app.hasdata.com. 1,000 free credits every month, no card.",
20
+ "isRequired": true,
21
+ "isSecret": true
22
+ }
23
+ ]
24
+ }
25
+ ],
26
+ "packages": [
27
+ {
28
+ "registryType": "npm",
29
+ "identifier": "@hasdata/bing-mcp",
30
+ "version": "1.0.0",
31
+ "transport": {
32
+ "type": "stdio"
33
+ },
34
+ "environmentVariables": [
35
+ {
36
+ "name": "HASDATA_API_KEY",
37
+ "description": "HasData API key, created at app.hasdata.com. 1,000 free credits every month, no card.",
38
+ "isRequired": true,
39
+ "isSecret": true
40
+ }
41
+ ]
42
+ },
43
+ {
44
+ "registryType": "pypi",
45
+ "identifier": "hasdata-bing-mcp",
46
+ "version": "1.0.0",
47
+ "runtimeHint": "uvx",
48
+ "transport": {
49
+ "type": "stdio"
50
+ },
51
+ "environmentVariables": [
52
+ {
53
+ "name": "HASDATA_API_KEY",
54
+ "description": "HasData API key, created at app.hasdata.com. 1,000 free credits every month, no card.",
55
+ "isRequired": true,
56
+ "isSecret": true
57
+ }
58
+ ]
59
+ }
60
+ ]
61
+ }
@@ -0,0 +1,167 @@
1
+ // Tool contract test.
2
+ //
3
+ // The README promises one tool with a specific name and parameter set. The upstream list can
4
+ // change without a single commit here, and the README would start lying silently. These checks
5
+ // catch that before a user does.
6
+ //
7
+ // One live call serves two checks. Listing tools accepts any non-empty key, so a contract check
8
+ // that only lists tools stays green with a revoked or mistyped key, and it would also stay green
9
+ // if the parser returned an empty page. The same response answers both. That call costs 10
10
+ // credits, which is the price of a canary that can fail for the right reason.
11
+ //
12
+ // Run: HASDATA_API_KEY=your_key_here npm test
13
+
14
+ import { test } from 'node:test';
15
+ import assert from 'node:assert/strict';
16
+
17
+ const ENDPOINT = 'https://mcp.hasdata.com/api/mcp?apis=bing';
18
+ const KEY = process.env.HASDATA_API_KEY;
19
+ const TIMEOUT_MS = 30_000;
20
+
21
+ const TOOL = 'hasdata_bing_serp_getSearchResults';
22
+ const REQUIRED = ['q'];
23
+
24
+ // Targeting values the README lists by name.
25
+ const ENUMS = {
26
+ safeSearch: ['off', 'moderate', 'strict'],
27
+ deviceType: ['desktop', 'mobile', 'tablet'],
28
+ };
29
+
30
+ // Parameters the README documents without listing every value.
31
+ const PARAMS = ['location', 'lat', 'lon', 'mkt', 'cc', 'setLang', 'filters', 'first'];
32
+
33
+ // A streamable HTTP body arrives either as plain JSON or as server-sent events. One SSE event
34
+ // can span several data: lines, several events can share one response, and a server is free to
35
+ // send progress notifications before the answer. So collect every event and pick the message
36
+ // carrying our request id instead of trusting the first data: line.
37
+ function parseRpc(raw, id) {
38
+ const trimmed = raw.trim();
39
+ if (trimmed.startsWith('{') || trimmed.startsWith('[')) return JSON.parse(trimmed);
40
+
41
+ const messages = [];
42
+ for (const event of trimmed.split(/\r?\n\r?\n+/)) {
43
+ const data = event
44
+ .split(/\r?\n/)
45
+ .filter((l) => l.startsWith('data:'))
46
+ .map((l) => l.slice(5).replace(/^ /, ''))
47
+ .join('\n');
48
+ if (!data || data === '[DONE]') continue;
49
+ try {
50
+ messages.push(JSON.parse(data));
51
+ } catch {
52
+ // A keep-alive or a partial event is not our response.
53
+ }
54
+ }
55
+ assert.ok(messages.length, `no JSON-RPC message in the response: ${raw.slice(0, 300)}`);
56
+ const match = messages.find((m) => m.id === id);
57
+ assert.ok(match, `no message with id ${id} in the response: ${raw.slice(0, 300)}`);
58
+ return match;
59
+ }
60
+
61
+ let nextId = 1;
62
+
63
+ async function rpc(method, params = {}) {
64
+ // The CI key sits on the free plan, where concurrency is 1. When several of
65
+ // these repos are pushed at once their contract runs collide, and HasData
66
+ // answers 429 with code concurrency_limit straight away rather than queueing.
67
+ // That is a plan limit, not a broken contract, so the call is retried before
68
+ // the test gives up. A 401 still fails on the first attempt.
69
+ for (let attempt = 1; ; attempt++) {
70
+ const id = nextId++;
71
+ const res = await fetch(ENDPOINT, {
72
+ method: 'POST',
73
+ headers: {
74
+ 'x-api-key': KEY,
75
+ 'Content-Type': 'application/json',
76
+ // The server answers over streamable HTTP, so accept both a plain body and a stream.
77
+ Accept: 'application/json, text/event-stream',
78
+ },
79
+ body: JSON.stringify({ jsonrpc: '2.0', id, method, params }),
80
+ signal: AbortSignal.timeout(TIMEOUT_MS),
81
+ });
82
+ assert.equal(res.status, 200, `${method} returned ${res.status}`);
83
+ const raw = await res.text();
84
+ if (raw.includes('concurrency_limit') && attempt < 5) {
85
+ await new Promise((r) => setTimeout(r, attempt * 4000));
86
+ continue;
87
+ }
88
+ return { raw, body: parseRpc(raw, id) };
89
+ }
90
+ }
91
+
92
+ // One network round trip for every test that needs the list.
93
+ let toolsPromise;
94
+ function listTools() {
95
+ toolsPromise ??= rpc('tools/list').then(({ body }) => {
96
+ assert.ok(body.result?.tools, 'the response carried no result.tools');
97
+ return body.result.tools;
98
+ });
99
+ return toolsPromise;
100
+ }
101
+
102
+ // One paid round trip, shared by the checks that need a real answer.
103
+ let searchPromise;
104
+ function liveSearch() {
105
+ searchPromise ??= rpc('tools/call', {
106
+ name: TOOL,
107
+ arguments: { q: 'python web scraping tutorial' },
108
+ });
109
+ return searchPromise;
110
+ }
111
+
112
+ const live = { skip: KEY ? false : 'HASDATA_API_KEY is not set, skipping the live checks' };
113
+
114
+ test('apis=bing exposes the documented tool and nothing else', live, async () => {
115
+ const tools = await listTools();
116
+ const names = tools.map((t) => t.name).sort().join(', ');
117
+ assert.equal(tools.length, 1, `expected 1 tool, got ${tools.length}: ${names}`);
118
+ assert.equal(tools[0].name, TOOL, `the tool is now called ${tools[0].name}`);
119
+ });
120
+
121
+ test('the tool still requires its search term and carries a description', live, async () => {
122
+ const [tool] = await listTools();
123
+ const required = tool.inputSchema?.required ?? [];
124
+ for (const param of REQUIRED) {
125
+ assert.ok(required.includes(param), `${TOOL} should require ${param}, declares: ${required.join(', ') || 'nothing'}`);
126
+ }
127
+ assert.ok((tool.description || '').trim().length > 20, `${TOOL} has an empty or near-empty description`);
128
+ });
129
+
130
+ test('the targeting parameters the README documents are still in the schema', live, async () => {
131
+ const [tool] = await listTools();
132
+ const props = tool.inputSchema?.properties ?? {};
133
+ for (const param of PARAMS) {
134
+ assert.ok(props[param], `${TOOL} no longer accepts ${param}`);
135
+ }
136
+ for (const [param, values] of Object.entries(ENUMS)) {
137
+ const offered = props[param]?.enum ?? [];
138
+ for (const value of values) {
139
+ assert.ok(offered.includes(value), `${param} no longer accepts ${value}, offers: ${offered.join(', ') || 'no enum'}`);
140
+ }
141
+ }
142
+ });
143
+
144
+ test('the key is accepted by HasData', live, async () => {
145
+ const { raw } = await liveSearch();
146
+ assert.ok(!raw.includes('401 Unauthorized'), 'HasData rejected the key');
147
+ assert.ok(!raw.includes('"isError":true'), `the tool call failed: ${raw.slice(0, 300)}`);
148
+ });
149
+
150
+ // A tools/list check passes while the parser returns an empty page, and the README warns that
151
+ // the result count moves, so pinning a count would fail for the wrong reason. This asserts the
152
+ // shape instead: results arrive, and each one carries the fields the README documents.
153
+ test('a live search still returns parsed organic results', live, async () => {
154
+ const { body } = await liveSearch();
155
+ const text = body.result?.content?.[0]?.text ?? '';
156
+ const payload = JSON.parse(text);
157
+ const results = payload.json?.organicResults;
158
+
159
+ assert.ok(Array.isArray(results), `no organicResults array in the response: ${text.slice(0, 300)}`);
160
+ assert.ok(results.length, 'organicResults came back empty, so the page was not parsed');
161
+
162
+ for (const r of results) {
163
+ assert.equal(typeof r.position, 'number', `a result carried no numeric position: ${JSON.stringify(r).slice(0, 160)}`);
164
+ assert.ok(r.link, `a result carried no link: ${JSON.stringify(r).slice(0, 160)}`);
165
+ assert.ok(r.title, `a result carried no title: ${JSON.stringify(r).slice(0, 160)}`);
166
+ }
167
+ });