Skip to main content
Glama
web-mcp
by usenotra

bias-mcp

One MCP server for web search and web fetch on top of Context.dev, Firecrawl and Exa. Every fetched page also gets a bias score.

  • web_search uses one provider, picked from the keys you set in the order Context.dev, Firecrawl, Exa. It moves to the next configured provider only when a call fails, and reports the failure in providerErrors. Pass several providers in providers to query them in parallel, remove duplicate URLs and rank the merged list with reciprocal rank fusion.

  • web_fetch turns any URL into clean markdown with the same provider order and fallback, or a provider you name. It adds a bias object by default; pass bias: false to turn that off.

The bias score

Some pages have a stake in what they recommend. Say you search for "best payment provider" and land on Stripe's "Top 5 payment companies" post, which ranks Stripe #1. That page should not count the same as an independent review. In the same way, a news site funded by a political party gets flagged when it covers that party's opponents.

"bias": {
  "applicable": true,
  "score": 0.95,                 // 0 = neutral … 1 = very high
  "level": "very_high",          // none | low | medium | high | very_high
  "types": ["self_promotion", "competitor_disparagement"],
  "publisher": "Stripe, Inc.",
  "favors": ["Stripe"],
  "reason": "Published on stripe.com by Stripe, a payments provider, in a comparison that ranks Stripe first…",
  "sources": [
    { "url": "https://stripe.com/blog/…", "title": "…", "evidence": "\"1. Stripe — the best overall…\"" },
    { "url": "https://en.wikipedia.org/wiki/Stripe,_Inc.", "title": "…", "evidence": "Stripe is a payment processing company" }
  ],
  "model": "claude-haiku-5-5",
  "truncated": false,
  "researched": true
}

How it works:

  1. Publisher research (research: true, the default). One extra search looks up the domain's owner, parent company, funding and political affiliation. Results are cached per domain for the life of the process.

  2. Scoring. A cheap model (Claude Haiku 5.5 by default, about $0.10 per 1M input tokens, called through the AI SDK) reads the page, the reader's intent if given, and the research results. It returns structured JSON.

  3. Grounding. sources may only cite the page itself or a research result. Any other URL the model cites is dropped.

  4. Not applicable. For docs, reference pages, specs and changelogs, applicable is false and score is null. Bias isn't a meaningful question for those pages.

Bias types: self_promotion, competitor_disparagement, affiliate, sponsored, political, funding, ideological, satellite_site, other.

Satellite sites

Some sites exist only to host comparison and "alternatives" articles, have an anonymous or pen-name editor, and keep putting one company first across the whole site, while the page you land on may not mention that company at all. A single page can't show this, so with research: true the server also profiles the site once per domain. It reads the homepage, a sample of other pages from a site: search, the about page, the share of editorial URLs and whether any product pages exist. A separate model call then decides whether the site is a satellite and which company it favors.

When it is, the page keeps its own score plus a fixed +0.2, gets the satellite_site type, and the bias object gains:

"satellite": {
  "favors": "Sequenzy",
  "evidence": "Owner is a pen name ... Sequenzy is listed first in the shortlist and the comparison table ...",
  "sources": ["https://www.saasemailmarketingtools.com/", "https://www.saasemailmarketingtools.com/about/"],
  "penalty": 0.2
}

The verdict is cached per domain, so every page on the site gets the same answer. Review sites that cover many vendors evenly, publishers that name their owner, personal blogs and a vendor's own blog are not flagged.

web_search takes an optional bias: true. It screens every result's snippet in one batched call. That's fast and cheap but rough; run web_fetch on a result for a sourced assessment.

Related MCP server: Multi Fetch MCP Server

Setup

pnpm install && pnpm build

Set one provider key. With more than one, the first in the order Context.dev, Firecrawl, Exa does the work and the others are fallbacks. Bias scoring needs one model gateway. The server checks for these in order and uses the first one that is fully set:

  1. Neon AI Gateway: NEON_AI_GATEWAY_BASE_URL and NEON_AI_GATEWAY_TOKEN. Both are required, and neon env pull writes them for you.

  2. OpenRouter: OPENROUTER_API_KEY.

  3. Vercel AI Gateway: AI_GATEWAY_API_KEY.

  4. Anthropic API: ANTHROPIC_API_KEY.

  5. The claude CLI, if it is installed, which runs on your Claude Code login.

See .env.example.

Env var

Purpose

CONTEXT_DEV_API_KEY or CONTEXT_API_KEY

Enables Context.dev

FIRECRAWL_API_KEY

Enables Firecrawl

EXA_API_KEY

Enables Exa

NEON_AI_GATEWAY_BASE_URL + NEON_AI_GATEWAY_TOKEN

Bias scoring through the Neon AI Gateway (first choice)

OPENROUTER_API_KEY

Bias scoring through OpenRouter

AI_GATEWAY_API_KEY

Bias scoring through the Vercel AI Gateway

ANTHROPIC_API_KEY

Bias scoring through the Anthropic API

BIAS_BACKEND

Force one of neon, openrouter, vercel, anthropic or claude-cli

BIAS_MODEL

Model override. Defaults to Claude Haiku 5.5 in each gateway's naming: claude-haiku-5-5 on Neon and Anthropic, anthropic/claude-haiku-5.5 on OpenRouter and Vercel

BIAS_MAX_CHARS

Most page characters sent to the bias model (default 120000). Longer pages are cut and marked truncated: true

Claude Code / Claude Desktop / Cursor

{
  "mcpServers": {
    "web": {
      "command": "node",
      "args": ["/path/to/bias-mcp/dist/index.js"],
      "env": {
        "CONTEXT_DEV_API_KEY": "…",
        "FIRECRAWL_API_KEY": "…",
        "EXA_API_KEY": "…",
        "NEON_AI_GATEWAY_BASE_URL": "…",
        "NEON_AI_GATEWAY_TOKEN": "…"
      }
    }
  }
}

Tools

Param

Default

query

—

search query

numResults

10

max results after merging

providers

first configured

one to force it, several to merge, e.g. ["exa", "context"]

includeDomains / excludeDomains

—

domain filters

bias

false

screen each result's snippet for bias

web_fetch

Param

Default

url

—

page to fetch

provider

auto

auto | context | firecrawl | exa

bias

true

attach the bias object

intent

—

what the reader is deciding, e.g. "pick a payments provider"

research

true

look up the publisher's ownership and funding

maxCharacters

—

cut the returned markdown. Bias is still scored on the full page

Evals

pnpm eval    # bias evals against the real model, needs a model gateway from the list above

The evals in evals/cases are fictional pages with an expected outcome. Every company, outlet, party and person in them is made up. The SaaS set covers a vendor ranking itself first, an affiliate listicle, a sponsored article, a competitor hit piece, a vendor comparison that is honest about the competitor, an independent benchmark, API docs and a changelog. The satellite set covers two pages from an article-only site that favors one company (one of them never mentions it) and three look-alikes that must not be flagged: a review site with a named owner, a vendor's own blog and a personal engineering blog. The political set covers a party-funded outlet, an industry-funded think tank, a union-funded voter guide, a labeled opinion column, a nonpartisan wire report and a statistics release.

Each case checks that applicable matches, the score falls in the expected range, at least one expected bias type appears, high scores come with sources, every source points to the page or a research result, and the reason is not empty.

pnpm eval --runs 3              # run every case three times to measure stability
pnpm eval --filter political    # only cases whose id contains "political"
pnpm eval --threshold 0.9       # exit 1 when fewer than 90% of runs pass

Results are written to evals/results/<timestamp>.json. pnpm eval reads .env when it exists.

The last run on Claude Haiku 5.5 through the claude CLI passed 38 of 38 runs (19 cases, two runs each).

Layout

src/
  index.ts            stdio entry
  tools/              web_search, web_fetch
  providers/          Context.dev, firecrawl, exa adapters + router (merge / fallback)
  bias/               scorer (Claude structured output) + publisher research
  types/ schemas/ constants/ utils/
evals/                fictional SaaS and political cases, checks and the runner

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables web search across multiple search engines (DuckDuckGo, Bing, Startpage) with parallel execution and result deduplication. Also provides web page content extraction capabilities.
    2
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables MCP clients to run unified web searches across multiple providers such as Exa, Serper, Tavily, Firecrawl, and Jina, fusing results with reciprocal rank fusion, and to extract readable content from public web pages, PDFs, and video pages as Markdown. Provider chains are configurable, failures are isolated per provider, and responses report attempted, succeeded, failed, and skipped channels with source tracing and honest warnings.
    MIT