nulljosh

Sidewise

Community nulljosh
Updated

MCP server + JSON API for headlines from 17 news outlets, with left/center/right bias tags and blindspot detection

Sidewise

version license GitHub

Headlines from 16 feeds across 14 newsrooms, spanning the political spectrum, withleft/center/right bias tags and blindspot detection: stories covered by only one side.Free, unauthenticated, no rate limit, no account.

Two feeds from the same newsroom count as one voice, so a single publisher running an opinionsection alongside its main feed cannot fake corroboration or bury a blindspot.

Four ways in: a web reader, native iPhone/iPad/Mac apps, a JSON API, and an MCP server.

Apps

SwiftUI, one codebase for iOS and macOS, in ios/. Reads the same public API — no account, notracking, saved stories and the feed cache stay on device.

cd ios && xcodegen generate
xcodebuild -scheme Sidewise-iOS -destination 'generic/platform=iOS Simulator' build

MCP

claude mcp add --transport http sidewise https://news.heyitsmejosh.com/mcp
Tool Params Returns
get_news view, outlet, bias, developing, q, limit Current headlines, flat or clustered by story
get_blindspots limit Only stories covered by a single political side
compare_coverage q (required), limit One story as each side headlines it, plus the words unique to each
get_feed_health Which feeds answered, and whether the data served is complete or a stale fallback

get_feed_health is worth calling before you treat an empty or one-sided result as real: anoutage and a quiet news day look identical otherwise.

Stateless streamable HTTP, no auth. Works with any MCP client — Claude Desktop, Claude Code, Cursor.

API

GET https://news.heyitsmejosh.com/api/stories — every parameter is optional.

Param Values Default
view latest, stories, both both
outlet any outlet name, e.g. Hacker News all
bias left, center, right all
blindspot true off
developing true — three or more newsrooms in the last 90 minutes off
compare true — attach the side-by-side breakdown to each story off
q substring match on headline and summary text none
limit 1–200 60 clusters / 120 headlines

Two more endpoints sit alongside it. GET /api/health reports every feed's status and answers503 when more than half are down, so it can be pointed at a monitor as-is. GET /api/sourceslists each feed with its bias, resolved side and parent newsroom.

curl 'https://news.heyitsmejosh.com/api/stories?view=stories&blindspot=true'
curl 'https://news.heyitsmejosh.com/api/stories?view=latest&outlet=Hacker%20News&limit=10'
{
  "updated": 1754700000000,
  "stories": [
    {
      "title": "Fed holds rates steady",
      "blindspot": false,
      "sources": [
        { "title": "Fed holds rates steady", "link": "https://…", "outlet": "NPR", "bias": -1 },
        { "title": "Fed refuses to cut rates", "link": "https://…", "outlet": "Fox News", "bias": 2 }
      ]
    }
  ],
  "latest": [
    { "title": "Fed holds rates steady", "link": "https://…", "outlet": "NPR", "bias": -1, "ts": 1754699000000 }
  ]
}

CORS open to all origins. Feeds are re-pulled at most every 2 minutes; responses themselves are no-store (see below). ts is epoch ms, or 0 when the feed published no date (those sort to the bottom). Full spec: openapi.yaml · orientation for agents: llms.txt.

How it works

One Cloudflare Worker (worker.js) polls every RSS feed in src/feeds.js and serves the page,the API and MCP off a single pooled pull:

  • latest — flat reverse-chronological feed across all sources.
  • stories — headlines clustered by title-keyword overlap, each source tagged left/center/right, one-sided clusters flagged blindspot.

Only the feed pull is cached, under a constant key. Filtering happens per-request in shape() downstream of it, and responses go out no-store — the zone's CDN cache ignores query strings, so caching them would serve one caller's ?outlet= to everyone. Nothing is refetched either way; only the cheap filtering runs again.

Clusters are filtered after clustering, and the flat feed before its 120-item cap. Both orderings matter: narrowing the input first would destroy the cross-outlet comparison, and filtering after the cap would hide low-volume outlets.

architecture

Bias scores

Each source carries a score from -2 (left) to +2 (right); 0 is center, or non-political for tech outlets like Hacker News and Daring Fireball. These are hand-assigned in FEEDS, not a third-party rating — treat them as a rough lean.

CBC · The Guardian · NPR · BBC · Global News · National Post · Fox News · NY Post · Daily Wire · Hacker News · Daring Fireball · NBC News · Wall Street Journal · NY Post Opinion · Vancouver Sun · The Province

Add one by appending [outlet, bias, url] to FEEDS at the top of worker.js. Any RSS 2.0 or Atom feed works.

Develop

npm test         # 95 checks, no network
npm run feeds    # check every feed for freshness, not just a 200
npm run deploy   # wrangler deploy

npm run feeds is the one to run after touching FEEDS. It checks recency, which is theonly way to catch a zombie feed: an endpoint that still answers 200 with well-formed XML whosenewest item is two years old. CNN did exactly that for three years, and an item-count checknever noticed.

Cloudflare Workers + Workers Static Assets — one deploy serves the page, /api/stories, and /mcp.

Tests

test/ covers the four modules in src/ with no network and no Worker runtime:

File What it holds down
parse.test.mjs Entity decoding, double-escaped summaries, CDATA and Atom shapes, and the rejection of javascript: and data: links
stories.test.mjs Clustering, the newsroom-not-feed counting rule behind blindspots, the developing window, and filter-after-cluster ordering
load.test.mjs Feed failure reporting, timeouts, the degraded/stale fallback, and the rule that a bad pull never overwrites the last good one
worker.test.mjs Query parsing and clamping, and that everything under /api/ answers JSON with CORS, including 404s, 405s and 500s
mcp.test.mjs JSON-RPC framing, malformed input, and each tool's contract

Security: see SECURITY.md.

License

MIT 2026, Joshua Trommel. Headlines and links belong to their publishers — Sidewise stores nothing and links out to the original article.

Whitepaper

Technical whitepaper

API and agent tools

REST (/api/*), the POST /mcp JSON-RPC server, and in-page WebMCP tools on the reader(public/webmcp.js) all expose the same four operations, kept in sync and tested againsteach other. See docs/API.md.

MCP Server · Populars

MCP Server · New

    ocm-mcp-server

    🛡️ ocm-mcp-server

    An MCP server that lets AI agents operate a multi-cluster Kubernetes fleet through an Open Cluster Management hub, with policy, approval, and audit between the model and your clusters.

    Community ocm-mcp-server
    M4F-S

    Gomaa 🧠

    Gomaa — Autonomous Agent Memory OS. Persistent memory system for AI agents with Obsidian vault integration, hybrid RRF search, knowledge graphs, security gates, and MCP server.

    Community M4F-S
    chatmcp

    3802

    directory for Awesome MCP Servers

    Community chatmcp
    Morningstar202604

    AgentSeed

    Anti-hallucination gate for AI coding agents — 8 MCP tools catch invented APIs (17 languages), fake "all tests pass" claims, and slopsquatting packages before they ship. Zero-dependency Agent Plugins 1.0.0 plugin (Skill + MCP server + CLI + CI gate) for Claude Code, Cursor, VS Code, Copilot.

    Community Morningstar202604
    skarn-security

    Skarn guard: agent plugins

    Skarn plugins for Claude Code, Codex CLI, Gemini CLI, Grok Build, and Antigravity: audit skills, guard hooks, and MCP declarations that find leaked secrets and credentials in AI coding sessions, locally and redacted.

    Community skarn-security