{
  "markdown": "# css-sota-mcp\n\nAn MCP server that answers **what CSS you can actually ship today** — from live Baseline data and\nMDN browser-compat-data, not from a model's training set.\n\nAgents are confidently wrong about browser support. They will tell you `anchor-name` is fine, or\nthat `:has()` needs a polyfill, depending on when their weights were frozen. This server replaces\nthe guess with the current answer.\n\n- **Endpoint** — `https://css-sota-mcp.lusrodri.workers.dev/mcp` (Streamable HTTP, no auth)\n- **Docs** — [css-sota-mcp.pages.dev](https://css-sota-mcp.pages.dev)\n- **Registry** — `io.github.LuSrodri/css-sota-mcp`, listed in the\n  [official MCP Registry](https://registry.modelcontextprotocol.io)\n\n## Tools\n\n| Tool | Answers | Source |\n| --- | --- | --- |\n| `search_css_features` | \"Which features exist for this, and are they safe yet?\" | webstatus.dev |\n| `whats_new` | \"What can I start using that I couldn't before?\" | webstatus.dev |\n| `get_feature` | \"Tell me everything about this one feature.\" | webstatus.dev + mdn/content |\n| `check_support` | \"Which browser versions support this exactly?\" | bundled browser-compat-data |\n| `audit_css` | \"Does this stylesheet work for my users?\" | bundled browser-compat-data |\n| `dont_make_me_think` | \"How should this UI be designed — and is this page any good?\" | bundled UX guidelines |\n\n`check_support` and `audit_css` answer with no network call at all — the data they need is compiled\ninto the Worker.\n\n### `dont_make_me_think`\n\nNamed after Steve Krug's rule: a page should be self-evident. Two modes.\n\n`mode: \"guidelines\"` returns the principles to design against — Nielsen's 10 heuristics, Hick's\nand Fitts's laws, WCAG 2.2, neurodiversity-inclusive design, motion and microinteractions\n(including when Lottie or Rive earn their bundle cost), SVG craft and animation, light-first\ntheming, lightness, responsiveness. Filter with `topic`. The knowledge base is\n[`mcp/src/data/ux-guidelines.json`](mcp/src/data/ux-guidelines.json); every principle carries its\nrationale, actionable rules and a source.\n\n`mode: \"review\"` checks HTML and CSS — or a fetched `url` — and reports what violates which\nprinciple, with the line and the evidence.\n\n**It reads source; it does not render it.** A Worker has no layout engine, so the review cannot\nmeasure computed contrast, real target sizes, or where focus actually lands. It catches what is\nvisible in the markup: missing `alt`, blocked zoom, animation with no reduced-motion path, a\nremoved focus ring, a dark-only palette, vague link text, a nav past Hick's range. A clean result\nis a floor, not a pass, and the tool says so in its own output.\n\n### `audit_css` targets\n\nTwo target styles, because they answer different questions:\n\n- **A Baseline level** — `baseline-widely`, `baseline-newly`. Asks \"is this interoperable enough to\n  ship?\", judged against `web-features`' Baseline status.\n- **An explicit browser list** — `chrome 120, safari 17.4, firefox 128`. Asks \"does this work for\n  *my* users?\", judged against per-browser versions.\n\nBrowserslist queries (`last 2 versions`, `>0.5%`) are **not** accepted. Resolving them needs usage\ndata this server does not carry, and approximating them would produce confidently wrong audits —\nexactly the failure mode the server exists to fix. The tool says so rather than guessing.\n\n## Connect\n\n```bash\nclaude mcp add --scope user --transport http css-sota https://css-sota-mcp.lusrodri.workers.dev/mcp\n```\n\n`--scope user` registers it once for every project on the machine. Leave it out and the server is\nadded to the current project only.\n\n<details>\n<summary>Claude Desktop</summary>\n\nRemote servers go in through Connectors, not through `claude_desktop_config.json` — that file only\ntakes local stdio servers. Open **Settings → Connectors → Add custom connector** and paste:\n\n```\nhttps://css-sota-mcp.lusrodri.workers.dev/mcp\n```\n\nThe endpoint is unauthenticated, so the connector asks for no client id and no secret.\n\n</details>\n\n<details>\n<summary>Cloudflare AI Playground</summary>\n\nOpen [playground.ai.cloudflare.com](https://playground.ai.cloudflare.com/), paste the endpoint into\nthe MCP server field, and connect. The six tools appear immediately.\n\n</details>\n\n<details>\n<summary>MCP Inspector</summary>\n\n```bash\nnpx @modelcontextprotocol/inspector@latest\n```\n\nSet transport to Streamable HTTP and connect to the endpoint.\n\n</details>\n\n## Limits on the hosted endpoint\n\nThe endpoint is public and unauthenticated on purpose: every tool is read-only over public\ndatasets, so there is nothing to protect from disclosure. What is worth protecting is the account's\nrequest budget and the server's standing with the upstreams it proxies.\n\n| Limit | Value | On exceeding |\n| --- | --- | --- |\n| Requests per client IP | 120 / minute, per Cloudflare location | `429` with `Retry-After: 60` |\n| Request body | 1 MB | `413` |\n| `audit_css` source | 400 000 characters | schema validation error |\n\n120/minute is sized against real usage rather than a round number: an agent working through a task\ncalls a handful of tools per turn, so a burst of twenty is unremarkable and 120 leaves room for a\nshared address running several clients.\n\nCloudflare's own guidance prefers keying rate limits on a user or tenant id rather than an IP,\nsince an IP can be shared behind NAT or a privacy relay. This endpoint has no authentication and so\nno such id; the limit is set generously enough that the trade is a fair one.\n\nIf you expect sustained traffic above this, run your own instance — the whole thing is one Worker\nand deploys in a minute.\n\n## Layout\n\n```\nmcp/       The MCP server — a Cloudflare Worker\nlanding/   Documentation site — Vite, on Cloudflare Pages\n```\n\n### What the landing page serves to crawlers\n\nThe docs site is how an agent finds this server without being told about it, so it publishes more\nthan HTML:\n\n| Path | What it is |\n| --- | --- |\n| `/robots.txt` | Open to everything, with a [Content Signals](https://contentsignals.org) line granting `search`, `ai-input` and `ai-train` |\n| `/sitemap.xml` | Generated at build time, so `lastmod` is the deploy date rather than a hand-edited lie |\n| `/llms.txt` | The endpoint, the six tools and the Baseline vocabulary, as Markdown |\n| `/llms-full.txt` | The whole reference — tools, targets, limits, data sources — in one file |\n| `/404.html` | Its presence is the point: without it Pages answers every unknown path with the home page under a `200`, which is how `/robots.txt` used to return HTML |\n| `/og.png` | The social card, 1200×630 |\n\nTwo things the page does *not* do are worth stating, because both were true until recently. The\nendpoint URL is no longer injected only by script — it is in the markup, so anything that reads the\nHTML without executing it still learns the one fact the page exists to convey. And the Worker origin\nnow carries a `Link: …; rel=\"canonical\"` header pointing at the docs site, so the two hostnames\ndescribing this server do not compete to be the one that gets cited.\n\nThe page makes **no third-party requests**. The three typefaces are served from this origin, pinned\nin `landing/public/fonts/` and declared in `landing/src/fonts.css` — latin subsets only, and one\nfile per family where upstream is variable. They came from fonts.googleapis.com until that\nstylesheet turned out to be the longest pole in the page's largest contentful paint: render-blocking,\non another host, and itself a hop to a *third* host for the files. Regenerate them with\n`landing/scripts/fetch-fonts.js`; it is deliberately not a build step, since refetching on every\nbuild would put a third party back in the critical path one level down.\n\n### How the data is put together\n\n`@mdn/browser-compat-data` unpacks to ~20 MB, far past a Worker's bundle budget. At build time\n`mcp/scripts/build-data.js` extracts the CSS slice of it plus the `web-features` catalog, drops\nevery field the server never reads, and encodes per-browser support positionally. The result is\nabout 1 MB of JSON — 120 KB gzipped — which ships inside the Worker.\n\nThe generated files are gitignored. Every build, test and deploy regenerates them, so the data\nalways matches whatever version npm resolved.\n\nTwo details worth knowing, both found the hard way:\n\n- `web-features` encodes Baseline as `\"high\"` / `\"low\"` / `false`, while api.webstatus.dev and all\n  Baseline documentation say `widely` / `newly` / `limited`. The build normalises to the latter so\n  the two halves of the server never disagree.\n- MDN reorganised its CSS reference under `Web/CSS/Reference/…`. Compat data records the slug a page\n  had when the entry was written, so building a raw GitHub path from `mdn_url` 404s. `get_feature`\n  resolves the canonical slug through MDN first, then reads the source.\n\n## Development\n\n```bash\nnpm install\n\nnpm run dev --workspace mcp        # wrangler dev on :8787\nnpm test --workspace mcp           # vitest\nnpm run typecheck                  # both workspaces\n\nnode mcp/scripts/smoke.js          # real MCP protocol call against :8787\nnode mcp/scripts/smoke.js <url>    # ...or against a deployment\n```\n\n`smoke.js` speaks the 2025-era Streamable HTTP flow — the same one the AI Playground and MCP\nInspector use — so a passing run means those clients will work too.\n\n### Deploy\n\nPushing to `main` deploys both. Cloudflare builds from this repo directly — no API token is\nstored in GitHub, and Cloudflare issues its own build credential.\n\n| Target | Product | Root | Build | Deploy |\n| --- | --- | --- | --- | --- |\n| Worker | Workers Builds | `mcp` | `npm run build:data` | `npx wrangler deploy` |\n| Landing | Pages Git integration | `landing` | `npm run build` | output `dist` |\n\nThe Worker's build command is **not** optional: `mcp/src/data/generated/` is gitignored, and\n`src/data/index.ts` imports it statically, so a build that skips it fails to bundle.\n\nBecause the two are independent products, neither waits for the other. `.github/workflows/verify.yml`\ncovers that gap — it smoke-tests the live endpoint on a schedule and on demand.\n\n#### Publishing to the registry\n\n[`server.json`](server.json) is the registry's record of this server. Because the server is remote,\nit carries a `remotes` entry pointing at the Worker rather than a `packages` one — there is no\nartifact to install, and so no package-ownership marker to place anywhere.\n\n`.github/workflows/publish-mcp.yml` republishes it on a `v*` tag:\n\n```bash\ngit tag v0.2.0 && git push origin v0.2.0\n```\n\nThe tag sets the version, so `server.json`'s own value is only a fallback for a manual\n`workflow_dispatch` run. The job authenticates with OIDC — proving it runs in this repository is\nwhat grants the `io.github.LuSrodri/*` namespace — so there is no token stored in GitHub, matching\nhow the rest of this repo deploys.\n\nIt deliberately does not run on every push. The registry record points at a URL, not at a build, so\nit stays correct across deploys; only a metadata change needs a new version.\n\n#### Previewing a pull request\n\nEvery push to a PR branch uploads a Worker version and builds the landing site, each reachable\nbefore merge:\n\n| | URL |\n| --- | --- |\n| Worker, by branch | `https://<branch-with-dashes>-css-sota-mcp.lusrodri.workers.dev/mcp` |\n| Worker, by version | `https://<version-prefix>-css-sota-mcp.lusrodri.workers.dev/mcp` |\n| Landing | `https://<deployment-id>.css-sota-mcp.pages.dev` |\n\nThe branch alias is the useful one — it stays put as you push. A branch named\n`fix/thing` becomes `fix-thing-css-sota-mcp.lusrodri.workers.dev`. Point the\n[AI Playground](https://playground.ai.cloudflare.com/) or MCP Inspector at it to try a PR's server\nfor real; `node mcp/scripts/smoke.js <url>/mcp` works against it too.\n\nA landing preview calls **its own branch's Worker**, not production. `landing/vite.config.ts`\nderives the alias from `CF_PAGES_BRANCH` at build time, so a PR touching both halves is previewed\nas a matched pair. Without this the preview would show a new front end against the old server —\ngreen preview, broken on merge. An explicit `VITE_MCP_ORIGIN` still wins, and production builds\nfall through to the default.\n\nThe alias is derived rather than looked up, so a mismatch points the demo at a URL that 404s. That\nfails visibly: the endpoint is printed on the page and the hero reports it could not reach the\nserver. The Pages build log prints the wiring on every preview build.\n\nOne caveat remains: **no automatic PR comment.** Cloudflare normally posts the preview links on the\npull request; this account cannot enable that (`12044: This account does not have access to Workers\nPreviews`). The URLs work — you construct them from the branch name.\n\nTo deploy by hand instead:\n\n```bash\nnpm run deploy --workspace mcp      # Worker\nnpm run deploy --workspace landing  # Pages\n```\n\nBoth need Cloudflare credentials — either `wrangler login`, or `CLOUDFLARE_API_TOKEN` and\n`CLOUDFLARE_ACCOUNT_ID` in the environment. Note that `wrangler login` needs a real terminal; in a\nnon-interactive shell it refuses and asks for the token variable instead.\n\n#### Account prerequisites\n\nCloudflare gates Workers behind these, and the errors only surface at deploy time:\n\n- **Workers enabled on the account.** Until the Workers & Pages dashboard has been opened once,\n  every Workers API call fails with `10034: You need to verify your email address to use Workers` —\n  which is misleading, since a verified email does not clear it. Opening the page does.\n- **A `workers.dev` subdomain**, if you want a `*.workers.dev` URL. Absent one, the API answers\n  `10007`.\n- **The Cloudflare GitHub App installed**, for Git-based deploys. Without it the repository\n  connection API answers `8000008`, regardless of account permissions.\n\n## Built with\n\n[MCP TypeScript SDK v2](https://github.com/modelcontextprotocol/typescript-sdk) ·\n[Cloudflare Workers](https://workers.cloudflare.com/) ·\n[webstatus.dev](https://webstatus.dev) ·\n[@mdn/browser-compat-data](https://github.com/mdn/browser-compat-data) ·\n[web-features](https://github.com/web-platform-dx/web-features)\n\nThe server uses `createMcpHandler`, which returns a web-standard `{ fetch }` object and serves\nrequests statelessly — so there is no Durable Object, no KV, and no session affinity. Any isolate\ncan answer any request.\n\n## License\n\nMIT\n",
  "bytes": 14265,
  "sha": "547abfbbde9a8fd41690e2827aff0f87add5d2527368229b24bd9b83b7176c53",
  "repo_slug": "lusrodri/css-sota-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_lusrodri_css_sota_mcp_acd762fd/readme"
}